From 530f30479473e945890779245d9e5854013290b7 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 10 Oct 2022 15:12:27 +0100 Subject: [PATCH 001/136] 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 002/136] 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) From 677358ab5e9b84f32ff586e6ddb87f74f29a9c76 Mon Sep 17 00:00:00 2001 From: Joe Dean <51745709+jpdean@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:56:30 +0000 Subject: [PATCH 003/136] Allow multiple subdomain data (#120) * Append all data Co-authored-by: Chris Richardson --- test/test_measures.py | 42 ++++++++++++++++++++++-------------------- ufl/form.py | 12 ++++-------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/test/test_measures.py b/test/test_measures.py index ff5c4433f..395464d6b 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -109,26 +109,26 @@ def test_foo(): # domain=None, # subdomain_id="everywhere", # metadata=None) - assert dx.ufl_domain() == None + assert dx.ufl_domain() is None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to "everywhere", still no domain set dxe = dx() - assert dxe.ufl_domain() == None + assert dxe.ufl_domain() is None assert dxe.subdomain_id() == "everywhere" # Set subdomain_id to 5, still no domain set dx5 = dx(5) - assert dx5.ufl_domain() == None + assert dx5.ufl_domain() is None assert dx5.subdomain_id() == 5 # Check that original dx is untouched - assert dx.ufl_domain() == None + assert dx.ufl_domain() is None assert dx.subdomain_id() == "everywhere" # Set subdomain_id to (2,3), still no domain set dx23 = dx((2, 3)) - assert dx23.ufl_domain() == None + assert dx23.ufl_domain() is None assert dx23.subdomain_id(), (2 == 3) # Map metadata to metadata, ffc interprets as before @@ -136,7 +136,7 @@ def test_foo(): # assert dxm.metadata() == {"dummy":123} assert dxm.metadata() == {"dummy": 123} # Deprecated, TODO: Remove - assert dxm.ufl_domain() == None + assert dxm.ufl_domain() is None assert dxm.subdomain_id() == "everywhere" # dxm = dx(metadata={"dummy":123}) @@ -144,7 +144,7 @@ def test_foo(): dxm = dx(metadata={"dummy": 123}) assert dxm.metadata() == {"dummy": 123} - assert dxm.ufl_domain() == None + assert dxm.ufl_domain() is None assert dxm.subdomain_id() == "everywhere" dxi = dx(metadata={"quadrature_degree": 3}) @@ -168,9 +168,9 @@ def test_foo(): dSd = dS[interior_facet_domains] # Current behaviour: no domain created, measure domain data is a single # object not a full dict - assert dxd.ufl_domain() == None - assert dsd.ufl_domain() == None - assert dSd.ufl_domain() == None + assert dxd.ufl_domain() is None + assert dsd.ufl_domain() is None + assert dSd.ufl_domain() is None assert dxd.subdomain_data() is cell_domains assert dsd.subdomain_data() is exterior_facet_domains assert dSd.subdomain_data() is interior_facet_domains @@ -185,23 +185,25 @@ def test_foo(): domain, = Mx.ufl_domains() assert domain.ufl_id() == mydomain.ufl_id() assert domain.ufl_cargo() == mymesh - assert Mx.subdomain_data()[mydomain]["cell"] == cell_domains + assert len(Mx.subdomain_data()[mydomain]["cell"]) == 1 + assert Mx.subdomain_data()[mydomain]["cell"][0] == cell_domains domain, = Ms.ufl_domains() assert domain.ufl_cargo() == mymesh - assert Ms.subdomain_data()[mydomain][ - "exterior_facet"] == exterior_facet_domains + assert len(Ms.subdomain_data()[mydomain]["exterior_facet"]) == 1 + assert Ms.subdomain_data()[mydomain]["exterior_facet"][0] == exterior_facet_domains domain, = MS.ufl_domains() assert domain.ufl_cargo() == mymesh - assert MS.subdomain_data()[mydomain][ - "interior_facet"] == interior_facet_domains + assert len(MS.subdomain_data()[mydomain]["interior_facet"]) == 1 + assert MS.subdomain_data()[mydomain]["interior_facet"][0] == interior_facet_domains # Test joining of these domains in a single form domain, = M.ufl_domains() assert domain.ufl_cargo() == mymesh - assert M.subdomain_data()[mydomain]["cell"] == cell_domains - assert M.subdomain_data()[mydomain][ - "exterior_facet"] == exterior_facet_domains - assert M.subdomain_data()[mydomain][ - "interior_facet"] == interior_facet_domains + assert len(M.subdomain_data()[mydomain]["cell"]) == 1 + assert M.subdomain_data()[mydomain]["cell"][0] == cell_domains + assert len(M.subdomain_data()[mydomain]["exterior_facet"]) == 1 + assert M.subdomain_data()[mydomain]["exterior_facet"][0] == exterior_facet_domains + assert len(M.subdomain_data()[mydomain]["interior_facet"]) == 1 + assert M.subdomain_data()[mydomain]["interior_facet"][0] == interior_facet_domains diff --git a/ufl/form.py b/ufl/form.py index 9f2bd17cb..52ea2680e 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -440,14 +440,10 @@ def _analyze_subdomain_data(self): sd = integral.subdomain_data() # Collect subdomain data - data = subdomain_data[domain].get(it) - if data is None: - subdomain_data[domain][it] = sd - elif sd is not None: - if data.ufl_id() != sd.ufl_id(): - error( - "Integrals in form have different subdomain_data objects." - ) + if subdomain_data[domain].get(it) is None: + subdomain_data[domain][it] = [sd] + else: + subdomain_data[domain][it].append(sd) self._subdomain_data = subdomain_data def _analyze_form_arguments(self): From fd773880195b39327f652a47a892d89f7ef200cc Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Tue, 8 Nov 2022 13:55:29 +0000 Subject: [PATCH 004/136] tsfc testing on ufl PRs (#130) * Test tsfc with any ufl changes * rm tab * remove -n auto * Only test PRs * Try updating to v3 --- .github/workflows/fenicsx-tests.yml | 12 ++++---- .github/workflows/pythonapp.yml | 10 +++---- .github/workflows/tsfc-tests.yml | 46 +++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/tsfc-tests.yml diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 103a2b31c..9c19999cd 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -11,15 +11,15 @@ jobs: ffcx-tests: name: Run FFCx tests runs-on: ubuntu-latest - + env: CC: gcc-10 CXX: g++-10 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.9 @@ -36,7 +36,7 @@ jobs: python3 -m pip install git+https://github.com/FEniCS/basix.git - name: Clone FFCx - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: ./ffcx repository: FEniCS/ffcx @@ -67,7 +67,7 @@ jobs: OMPI_MCA_hwloc_base_binding_policy: none steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install dependencies (Python) run: | python3 -m pip install --upgrade pip @@ -82,7 +82,7 @@ jobs: python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: ./dolfinx repository: FEniCS/dolfinx diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 4f8017f56..bb9845ecc 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -23,9 +23,9 @@ jobs: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Lint with flake8 @@ -46,14 +46,14 @@ jobs: env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: coveralls - + - name: Build documentation run: | cd doc/sphinx make html - name: Upload documentation artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: doc-${{ matrix.os }}-${{ matrix.python-version }} path: doc/sphinx/build/html/ @@ -62,7 +62,7 @@ jobs: - name: Checkout FEniCS/docs if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: "FEniCS/docs" path: "docs" diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml new file mode 100644 index 000000000..601422623 --- /dev/null +++ b/.github/workflows/tsfc-tests.yml @@ -0,0 +1,46 @@ +# This workflow will install tsfc and run its unit tests + +name: tsfc integration + +on: + pull_request: + branches: + - main + +jobs: + tsfc-tests: + name: Run TSFC tests + runs-on: ubuntu-latest + + env: + CC: gcc-10 + CXX: g++-10 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - name: Install UFL + run: | + pip3 install . + + - name: Clone tsfc + uses: actions/checkout@v3 + with: + path: ./tsfc + repository: firedrakeproject/tsfc + ref: master + - name: Install tsfc + run: | + cd tsfc + pip install -r requirements-ext.txt + pip install git+https://github.com/coneoproject/COFFEE.git#egg=coffee + pip install git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat + pip install git+https://github.com/FInAT/FInAT.git#egg=finat + pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy + pip install .[ci] + - name: Run tsfc unit tests + run: python3 -m pytest tsfc/tests From 2b69e80d332b2cb22bb3be74080e340a4ec9c2d3 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 14 Nov 2022 08:56:25 +0000 Subject: [PATCH 005/136] Remove deprecated things (#131) * remove deprecated elements * turn on deprecation warnings * remove deprecated rule * import * deprecate more stuff * remove imports * update tests * max_value * update test * undeprecate * one more --- demo/FunctionOperators.py | 6 +++--- test/test_measures.py | 11 +++-------- test/test_simplify.py | 4 ++-- ufl/__init__.py | 11 ++++------- ufl/algorithms/ad.py | 8 +------- ufl/algorithms/formfiles.py | 8 ++------ ufl/argument.py | 12 +++--------- ufl/core/expr.py | 17 +++++++---------- ufl/finiteelement/__init__.py | 4 ---- ufl/finiteelement/facetelement.py | 16 ---------------- ufl/finiteelement/interiorelement.py | 16 ---------------- ufl/measure.py | 23 ++--------------------- ufl/operators.py | 10 ---------- 13 files changed, 27 insertions(+), 119 deletions(-) delete mode 100644 ufl/finiteelement/facetelement.py delete mode 100644 ufl/finiteelement/interiorelement.py diff --git a/demo/FunctionOperators.py b/demo/FunctionOperators.py index 60b1ae016..4b9bb4a93 100644 --- a/demo/FunctionOperators.py +++ b/demo/FunctionOperators.py @@ -16,8 +16,8 @@ # along with UFL. If not, see . # # Test form for operators on Coefficients. -from ufl import (Coefficient, FiniteElement, Max, TestFunction, TrialFunction, - dot, dx, grad, sqrt, triangle) +from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, + dot, dx, grad, sqrt, triangle, max_value) element = FiniteElement("Lagrange", triangle, 1) @@ -26,4 +26,4 @@ f = Coefficient(element) g = Coefficient(element) -a = sqrt(1 / Max(1 / f, -1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx +a = sqrt(1 / max_value(1 / f, -1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx diff --git a/test/test_measures.py b/test/test_measures.py index 395464d6b..28852b4a4 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -158,14 +158,9 @@ def test_foo(): exterior_facet_domains = MockMeshFunction(2, mesh) interior_facet_domains = MockMeshFunction(3, mesh) - assert dx[cell_domains] == dx(subdomain_data=cell_domains) - assert dx[cell_domains] != dx - assert dx[cell_domains] != dx[exterior_facet_domains] - - # Test definition of a custom measure with legacy bracket syntax - dxd = dx[cell_domains] - dsd = ds[exterior_facet_domains] - dSd = dS[interior_facet_domains] + dxd = dx(subdomain_data=cell_domains) + dsd = ds(subdomain_data=exterior_facet_domains) + dSd = dS(subdomain_data=interior_facet_domains) # Current behaviour: no domain created, measure domain data is a single # object not a full dict assert dxd.ufl_domain() is None diff --git a/test/test_simplify.py b/test/test_simplify.py index 48dbfffe9..4bc9bd444 100755 --- a/test/test_simplify.py +++ b/test/test_simplify.py @@ -110,9 +110,9 @@ def test_mathfunctions(self): assert math.exp(i) == exp(i) assert math.log(i) == ln(i) # TODO: Implement automatic simplification of conditionals? - assert i == float(Max(i, i-1)) + assert i == float(max_value(i, i-1)) # TODO: Implement automatic simplification of conditionals? - assert i == float(Min(i, i+1)) + assert i == float(min_value(i, i+1)) def test_indexing(self): diff --git a/ufl/__init__.py b/ufl/__init__.py index 575417829..2132a5ae1 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -87,8 +87,6 @@ - HDivElement - HCurlElement - BrokenElement - - FacetElement - - InteriorElement * Function spaces:: @@ -281,8 +279,7 @@ from ufl.finiteelement import FiniteElementBase, FiniteElement, \ MixedElement, VectorElement, TensorElement, EnrichedElement, \ NodalEnrichedElement, RestrictedElement, TensorProductElement, \ - HDivElement, HCurlElement, BrokenElement, \ - FacetElement, InteriorElement, WithMapping + HDivElement, HCurlElement, BrokenElement, WithMapping # Hook to extend predefined element families from ufl.finiteelement.elementlist import register_element, show_elements # , ufl_elements @@ -328,7 +325,7 @@ cosh, sinh, tanh, \ bessel_J, bessel_Y, bessel_I, bessel_K, \ eq, ne, le, ge, lt, gt, And, Or, Not, \ - conditional, sign, max_value, min_value, Max, Min, \ + conditional, sign, max_value, min_value, \ variable, diff, \ Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, \ jump, avg, cell_avg, facet_avg, \ @@ -383,7 +380,7 @@ 'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement', 'NodalEnrichedElement', 'RestrictedElement', 'TensorProductElement', 'HDivElement', 'HCurlElement', - 'BrokenElement', 'FacetElement', 'InteriorElement', "WithMapping", + 'BrokenElement', "WithMapping", 'register_element', 'show_elements', 'FunctionSpace', 'MixedFunctionSpace', 'Argument', 'TestFunction', 'TrialFunction', @@ -405,7 +402,7 @@ 'cosh', 'sinh', 'tanh', 'bessel_J', 'bessel_Y', 'bessel_I', 'bessel_K', 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', - 'conditional', 'sign', 'max_value', 'min_value', 'Max', 'Min', + 'conditional', 'sign', 'max_value', 'min_value', 'variable', 'diff', 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', 'jump', 'avg', 'cell_avg', 'facet_avg', diff --git a/ufl/algorithms/ad.py b/ufl/algorithms/ad.py index 003e27a76..faaa8e615 100644 --- a/ufl/algorithms/ad.py +++ b/ufl/algorithms/ad.py @@ -9,12 +9,11 @@ # # Modified by Anders Logg, 2009. -from ufl.log import warning from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives -def expand_derivatives(form, **kwargs): +def expand_derivatives(form): """Expand all derivatives of expr. In the returned expression g which is mathematically @@ -22,11 +21,6 @@ def expand_derivatives(form, **kwargs): or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ - # For a deprecation period (I see that dolfin-adjoint passes some - # args here) - if kwargs: - warning("Deprecation: expand_derivatives no longer takes any keyword arguments") - # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index e091b1cee..2a67df93d 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -13,7 +13,7 @@ import io import os import re -from ufl.log import error, warning +from ufl.log import error from ufl.utils.sorting import sorted_by_key from ufl.form import Form from ufl.finiteelement import FiniteElementBase @@ -148,11 +148,7 @@ def get_form(name): error("Expecting 'elements' to be a list of FiniteElementBase instances.") # Get list of exported coefficients - # TODO: Temporarily letting 'coefficients' override 'functions', - # but allow 'functions' for compatibility - functions = namespace.get("functions", []) - if functions: - warning("Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients.") + functions = [] ufd.coefficients = namespace.get("coefficients", functions) # Validate types diff --git a/ufl/argument.py b/ufl/argument.py index 230371a1a..27f0a4de4 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -73,15 +73,11 @@ def ufl_function_space(self): return self._ufl_function_space def ufl_domain(self): - "Deprecated, please use .ufl_function_space().ufl_domain() instead." - # TODO: deprecate("Argument.ufl_domain() is deprecated, please - # use .ufl_function_space().ufl_domain() instead.") + """Return the UFL domain.""" return self._ufl_function_space.ufl_domain() def ufl_element(self): - "Deprecated, please use .ufl_function_space().ufl_element() instead." - # TODO: deprecate("Argument.ufl_domain() is deprecated, please - # use .ufl_function_space().ufl_element() instead.") + """Return The UFL element.""" return self._ufl_function_space.ufl_element() def number(self): @@ -100,9 +96,7 @@ def is_cellwise_constant(self): return False def ufl_domains(self): - "Deprecated, please use .ufl_function_space().ufl_domains() instead." - # TODO: deprecate("Argument.ufl_domains() is deprecated, - # please use .ufl_function_space().ufl_domains() instead.") + """Return UFL domains.""" return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 66304e2af..97d8181b8 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -19,7 +19,7 @@ # Modified by Anders Logg, 2008 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error +from ufl.log import error, deprecate # --- The base object for all UFL expression tree nodes --- @@ -291,13 +291,17 @@ def _ufl_expr_reconstruct_(self, *operands): # --- Functions for geometric properties of expression --- - def ufl_domains(self): # TODO: Deprecate this and use extract_domains(expr) + def ufl_domains(self): "Return all domains this expression is defined on." + deprecate("Expr.ufl_domains() is deprecated, please " + "use extract_domains(expr) instead.") from ufl.domain import extract_domains return extract_domains(self) - def ufl_domain(self): # TODO: Deprecate this and use extract_unique_domain(expr) + def ufl_domain(self): "Return the single unique domain this expression is defined on, or throw an error." + deprecate("Expr.ufl_domain() is deprecated, please " + "use extract_unique_domain(expr) instead.") from ufl.domain import extract_unique_domain return extract_unique_domain(self) @@ -414,13 +418,6 @@ def __round__(self, n=None): val = NotImplemented return val - # --- Deprecated functions - - def geometric_dimension(self): - "Return the geometric dimension this expression lives in." - from ufl.domain import find_geometric_dimension - return find_geometric_dimension(self) - # Initializing traits here because Expr is not defined in the class # declaration diff --git a/ufl/finiteelement/__init__.py b/ufl/finiteelement/__init__.py index f7e8b7836..9a24584ad 100644 --- a/ufl/finiteelement/__init__.py +++ b/ufl/finiteelement/__init__.py @@ -24,8 +24,6 @@ from ufl.finiteelement.tensorproductelement import TensorProductElement from ufl.finiteelement.hdivcurl import HDivElement, HCurlElement, WithMapping from ufl.finiteelement.brokenelement import BrokenElement -from ufl.finiteelement.facetelement import FacetElement -from ufl.finiteelement.interiorelement import InteriorElement # Export list for ufl.classes __all_classes__ = [ @@ -41,7 +39,5 @@ "HDivElement", "HCurlElement", "BrokenElement", - "FacetElement", - "InteriorElement", "WithMapping" ] diff --git a/ufl/finiteelement/facetelement.py b/ufl/finiteelement/facetelement.py deleted file mode 100644 index 40b3db9f2..000000000 --- a/ufl/finiteelement/facetelement.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2017 Miklós Homolya -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from ufl.finiteelement.restrictedelement import RestrictedElement -from ufl.log import deprecate - - -def FacetElement(element): - """Constructs the restriction of a finite element to the facets of the - cell.""" - deprecate('FacetElement(element) is deprecated, please use element["facet"] instead.') - return RestrictedElement(element, restriction_domain="facet") diff --git a/ufl/finiteelement/interiorelement.py b/ufl/finiteelement/interiorelement.py deleted file mode 100644 index cd1ee239f..000000000 --- a/ufl/finiteelement/interiorelement.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (C) 2017 Miklós Homolya -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from ufl.finiteelement.restrictedelement import RestrictedElement -from ufl.log import deprecate - - -def InteriorElement(element): - """Constructs the restriction of a finite element to the interior of - the cell.""" - deprecate('InteriorElement(element) is deprecated, please use element["interior"] instead.') - return RestrictedElement(element, restriction_domain="interior") diff --git a/ufl/measure.py b/ufl/measure.py index cf3f6be59..923ab14a0 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -14,7 +14,7 @@ from itertools import chain -from ufl.log import error, deprecate +from ufl.log import error from ufl.core.expr import Expr from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl @@ -241,15 +241,9 @@ def subdomain_data(self): # (subdomain_id, metadata) for backwards compatibility, because # some tutorials write e.g. dx(0, {...}) to set metadata. def __call__(self, subdomain_id=None, metadata=None, domain=None, - subdomain_data=None, degree=None, scheme=None, rule=None): + subdomain_data=None, degree=None, scheme=None): """Reconfigure measure with new domain specification or metadata.""" - # Deprecation of 'rule' in favour of 'scheme' - if rule is not None: - deprecate("Measure argument 'rule' has been renamed to 'scheme'.") - assert scheme is None or scheme == rule - scheme = rule - # Let syntax dx() mean integral over everywhere all_args = (subdomain_id, metadata, domain, subdomain_data, degree, scheme) @@ -283,19 +277,6 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, metadata=metadata, subdomain_data=subdomain_data) - def __getitem__(self, data): - """This operator supports legacy syntax in python dolfin programs. - - The old documentation reads: Return a new Measure for same - integration type with an attached context for interpreting - domain ids. By default this new Measure integrates over - everywhere, but it can be restricted with a domain id as - usual. Example: dx = dx[boundaries]; L = f*v*dx + g*v+dx(1). - - """ - deprecate("Notation dx[meshfunction] is deprecated. Please use dx(subdomain_data=meshfunction) instead.") - return self(subdomain_data=data) - def __str__(self): global integral_type_to_measure_name name = integral_type_to_measure_name[self._integral_type] diff --git a/ufl/operators.py b/ufl/operators.py index 7bf10b85a..256beaa84 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -567,16 +567,6 @@ def min_value(x, y): return MinValue(x, y) -def Max(x, y): # TODO: Deprecate this notation? - "UFL operator: Take the maximum of *x* and *y*." - return max_value(x, y) - - -def Min(x, y): # TODO: Deprecate this notation? - "UFL operator: Take the minimum of *x* and *y*." - return min_value(x, y) - - # --- Math functions --- def _mathfunction(f, cls): From 4fd9a80f35ac1b24f7685fa08155ea02a20eea19 Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Tue, 22 Nov 2022 11:09:00 +0000 Subject: [PATCH 006/136] Use Python warnings (#133) * Use Python warnings * Remove deprecate too --- test/test_scratch.py | 5 +-- ufl/__init__.py | 4 +-- ufl/algorithms/apply_derivatives.py | 5 +-- ufl/algorithms/apply_geometry_lowering.py | 13 ++++---- ufl/algorithms/estimate_degrees.py | 6 ++-- ufl/algorithms/formtransformations.py | 5 +-- ufl/conditional.py | 8 +++-- ufl/core/expr.py | 12 ++++--- ufl/core/terminal.py | 6 ++-- ufl/finiteelement/elementlist.py | 7 ++-- ufl/form.py | 5 +-- ufl/log.py | 39 +++++------------------ ufl/mathfunctions.py | 7 ++-- ufl/operators.py | 5 +-- ufl/precedence.py | 4 +-- ufl/utils/sorting.py | 4 +-- 16 files changed, 64 insertions(+), 71 deletions(-) diff --git a/test/test_scratch.py b/test/test_scratch.py index 2b6673a31..df8c177f8 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -8,10 +8,11 @@ """ import pytest +import warnings # This imports everything external code will see from ufl from ufl import * -from ufl.log import error, warning +from ufl.log import error from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor # TODO: Import only what you need from classes and algorithms: @@ -149,7 +150,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): if self._cd._data: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... - warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) + warnings.warn("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): diff --git a/ufl/__init__.py b/ufl/__init__.py index 2132a5ae1..0517989fc 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -258,7 +258,7 @@ # Output control from ufl.log import get_handler, get_logger, set_handler, set_level, add_logfile, \ - UFLException, DEBUG, INFO, WARNING, ERROR, CRITICAL + UFLException, DEBUG, INFO, ERROR, CRITICAL # Types for geometric quantities @@ -366,7 +366,7 @@ __all__ = [ 'product', 'get_handler', 'get_logger', 'set_handler', 'set_level', 'add_logfile', - 'UFLException', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL', + 'UFLException', 'DEBUG', 'INFO', 'ERROR', 'CRITICAL', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'HInf', 'HEin', 'HDivDiv', diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index c4123f3cc..ac94867ad 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -7,9 +7,10 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import warnings from collections import defaultdict -from ufl.log import error, warning +from ufl.log import error from ufl.core.expr import ufl_err_str from ufl.core.terminal import Terminal @@ -1013,7 +1014,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # TODO: Make it possible to silence this message # in particular? It may be good to have for # debugging... - warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) + warnings.warn("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 32c2ebae3..ef265f69d 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -11,10 +11,11 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import warnings from functools import reduce from itertools import combinations -from ufl.log import error, warning +from ufl.log import error from ufl.core.multiindex import Index, indices from ufl.corealg.multifunction import MultiFunction, memoized_handler @@ -189,7 +190,7 @@ def cell_volume(self, o): if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler - warning("Only know how to compute the cell volume of an affine cell.") + warnings.warn("Only know how to compute the cell volume of an affine cell.") return o r = self.jacobian_determinant(JacobianDeterminant(domain)) @@ -206,7 +207,7 @@ def facet_area(self, o): if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler - warning("Only know how to compute the facet area of an affine cell.") + warnings.warn("Only know how to compute the facet area of an affine cell.") return o # Area of "facet" of interval (i.e. "area" of a vertex) is defined as 1.0 @@ -273,7 +274,7 @@ def _reduce_cell_edge_length(self, o, reduction_op): if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler - warning("Only know how to compute cell edge lengths of P1 or Q1 cell.") + warnings.warn("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o elif domain.ufl_cell().cellname() == "interval": @@ -297,7 +298,7 @@ def cell_diameter(self, o): if not domain.ufl_coordinate_element().degree() in {1, (1, 1)}: # Don't lower bendy cells, instead leave it to form compiler - warning("Only know how to compute cell diameter of P1 or Q1 cell.") + warnings.warn("Only know how to compute cell diameter of P1 or Q1 cell.") return o elif domain.is_piecewise_linear_simplex_domain(): @@ -331,7 +332,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): elif not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler - warning("Only know how to compute facet edge lengths of P1 or Q1 cell.") + warnings.warn("Only know how to compute facet edge lengths of P1 or Q1 cell.") return o else: diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 115af6b48..6030909ac 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -10,7 +10,9 @@ # Modified by Anders Logg, 2009-2010 # Modified by Jan Blechta, 2012 -from ufl.log import warning, error +import warnings + +from ufl.log import error from ufl.form import Form from ufl.integral import Integral from ufl.algorithms.multifunction import MultiFunction @@ -117,7 +119,7 @@ def _not_handled(self, v, *args): def expr(self, v, *ops): "For most operators we take the max degree of its operands." - warning("Missing degree estimation handler for type %s" % v._ufl_class_.__name__) + warnings.warn("Missing degree estimation handler for type %s" % v._ufl_class_.__name__) return self._add_degrees(v, *ops) # Utility types with no degree concept diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 984986de1..d11961ca3 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -12,8 +12,9 @@ # Modified by Garth N. Wells, 2010. # Modified by Marie E. Rognes, 2010. +import warnings -from ufl.log import error, warning, debug +from ufl.log import error, debug # All classes: from ufl.core.expr import ufl_err_str @@ -316,7 +317,7 @@ def compute_form_with_arity(form, arity, arguments=None): error("compute_form_with_arity cannot handle parts.") if len(arguments) < arity: - warning("Form has no parts with arity %d." % arity) + warnings.warn("Form has no parts with arity %d." % arity) return 0 * form # Assuming that the form is not a sum of terms diff --git a/ufl/conditional.py b/ufl/conditional.py index b02761478..2cb8fab96 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -7,7 +7,9 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import warning, error +import warnings + +from ufl.log import error from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator @@ -265,7 +267,7 @@ def evaluate(self, x, mapping, component, index_values): try: res = min(a, b) except ValueError: - warning('Value error in evaluation of min() of %s and %s.' % self.ufl_operands) + warnings.warn('Value error in evaluation of min() of %s and %s.' % self.ufl_operands) raise return res @@ -290,7 +292,7 @@ def evaluate(self, x, mapping, component, index_values): try: res = max(a, b) except ValueError: - warning('Value error in evaluation of max() of %s and %s.' % self.ufl_operands) + warnings.warn('Value error in evaluation of max() of %s and %s.' % self.ufl_operands) raise return res diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 97d8181b8..e6c355603 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -19,11 +19,13 @@ # Modified by Anders Logg, 2008 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error, deprecate +import warnings +from ufl.log import error # --- The base object for all UFL expression tree nodes --- + class Expr(object): """Base class for all UFL expression types. @@ -293,15 +295,15 @@ def _ufl_expr_reconstruct_(self, *operands): def ufl_domains(self): "Return all domains this expression is defined on." - deprecate("Expr.ufl_domains() is deprecated, please " - "use extract_domains(expr) instead.") + warnings.warn("Expr.ufl_domains() is deprecated, please " + "use extract_domains(expr) instead.", DeprecationWarning) from ufl.domain import extract_domains return extract_domains(self) def ufl_domain(self): "Return the single unique domain this expression is defined on, or throw an error." - deprecate("Expr.ufl_domain() is deprecated, please " - "use extract_unique_domain(expr) instead.") + warnings.warn("Expr.ufl_domain() is deprecated, please " + "use extract_unique_domain(expr) instead.", DeprecationWarning) from ufl.domain import extract_unique_domain return extract_unique_domain(self) diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 490bdbeba..5b0840268 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -11,7 +11,9 @@ # Modified by Anders Logg, 2008 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error, warning +import warnings + +from ufl.log import error from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type @@ -59,7 +61,7 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): if hasattr(self, 'ufl_evaluate'): return self.ufl_evaluate(x, component, derivatives) # Take component if any - warning("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) + warnings.warn("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) f = self if component: f = f[component] diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index e45fc7813..f5ed2265b 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -13,9 +13,10 @@ # Modified by Lizao Li , 2015, 2016 # Modified by Massimiliano Leoni, 2016 +import warnings from numpy import asarray -from ufl.log import warning, error +from ufl.log import error from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf from ufl.utils.formatting import istr from ufl.cell import Cell, TensorProductCell @@ -436,11 +437,11 @@ def canonical_element_description(family, cell, order, form_degree): family = "Q" elif family == "Discontinuous Lagrange": if order >= 1: - warning("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) + warnings.warn("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) family = "DQ" elif family == "Discontinuous Lagrange L2": if order >= 1: - warning("Discontinuous Lagrange L2 element requested on %s, creating DQ L2 element." % cell.cellname()) + warnings.warn("Discontinuous Lagrange L2 element requested on %s, creating DQ L2 element." % cell.cellname()) family = "DQ L2" # Validate cellname if a valid cell is specified diff --git a/ufl/form.py b/ufl/form.py index 52ea2680e..838a93a1b 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -11,10 +11,11 @@ # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. +import warnings from itertools import chain from collections import defaultdict -from ufl.log import error, warning +from ufl.log import error from ufl.domain import sort_domains from ufl.integral import Integral from ufl.checks import is_scalar_constant_expression @@ -378,7 +379,7 @@ def __call__(self, *args, **kwargs): if f in coeffs: repdict[f] = coefficients[f] else: - warning("Coefficient %s is not in form." % ufl_err_str(f)) + warnings.warn("Coefficient %s is not in form." % ufl_err_str(f)) if repdict: from ufl.formoperators import replace return replace(self, repdict) diff --git a/ufl/log.py b/ufl/log.py index 7a7da9e4b..3379e8741 100644 --- a/ufl/log.py +++ b/ufl/log.py @@ -14,22 +14,19 @@ import sys import types import logging -from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL # noqa: F401 +import warnings +from logging import DEBUG, INFO, ERROR, CRITICAL # noqa: F401 -log_functions = ["log", "debug", "info", "deprecate", "warning", "error", +log_functions = ["log", "debug", "info", "error", "begin", "end", "set_level", "push_level", "pop_level", "set_indent", "add_indent", "set_handler", "get_handler", "get_logger", "add_logfile", "set_prefix", - "info_red", "info_green", "info_blue", - "warning_red", "warning_green", "warning_blue"] + "info_red", "info_green", "info_blue"] __all__ = log_functions + ["Logger", "log_functions"] +\ - ["DEBUG", "INFO", "DEPRECATE", "WARNING", "ERROR", "CRITICAL"] - - -DEPRECATE = (INFO + WARNING) // 2 + ["DEBUG", "INFO", "ERROR", "CRITICAL"] # This is used to override emit() in StreamHandler for printing @@ -63,7 +60,7 @@ def __init__(self, name, exception_type=Exception): # Set up handler h = logging.StreamHandler(sys.stdout) - h.setLevel(WARNING) + h.setLevel(ERROR) # Override emit() in handler for indentation h.emit = types.MethodType(emit, h) self._handler = h @@ -90,7 +87,7 @@ def add_logfile(self, filename=None, mode="a", level=DEBUG): if filename is None: filename = "%s.log" % self._name if filename in self._logfiles: - self.warning("Adding logfile %s multiple times." % filename) + warnings.warn("Adding logfile %s multiple times." % filename) return h = logging.FileHandler(filename, mode) h.emit = types.MethodType(emit, h) @@ -132,26 +129,6 @@ def info_blue(self, *message): "Write info message in blue." self.log(INFO, BLUE % self._format_raw(*message)) - def deprecate(self, *message): - "Write deprecation message." - self.log(DEPRECATE, RED % self._format_raw(*message)) - - def warning(self, *message): - "Write warning message." - self._log.warning(self._format(*message)) - - def warning_red(self, *message): - "Write warning message in red." - self._log.warning(RED % self._format(*message)) - - def warning_green(self, *message): - "Write warning message in green." - self._log.warning(GREEN % self._format(*message)) - - def warning_blue(self, *message): - "Write warning message in blue." - self._log.warning(BLUE % self._format(*message)) - def error(self, *message): "Write error message and raise an exception." self._log.error(*message) @@ -242,4 +219,4 @@ class UFLValueError(UFLException): for foo in log_functions: exec("%s = ufl_logger.%s" % (foo, foo)) -set_level(DEPRECATE) # noqa +set_level(ERROR) # noqa diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 3d3c877c0..a53ff4e91 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -13,8 +13,9 @@ import math import cmath import numbers +import warnings -from ufl.log import warning, error +from ufl.log import error from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type from ufl.constantvalue import is_true_ufl_scalar, Zero, RealValue, FloatValue, IntValue, ComplexValue, ConstantValue, as_ufl @@ -63,7 +64,7 @@ def evaluate(self, x, mapping, component, index_values): else: res = getattr(cmath, self._name)(a) except ValueError: - warning('Value error in evaluation of function %s with argument %s.' % (self._name, a)) + warnings.warn('Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res @@ -289,7 +290,7 @@ def evaluate(self, x, mapping, component, index_values): except TypeError: error('Atan2 does not support complex numbers.') except ValueError: - warning('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) + warnings.warn('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) raise return res diff --git a/ufl/operators.py b/ufl/operators.py index 256beaa84..786561289 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -13,9 +13,10 @@ # Modified by Kristian B. Oelgaard, 2011 # Modified by Massimiliano Leoni, 2016. +import warnings import operator -from ufl.log import error, warning +from ufl.log import error from ufl.form import Form from ufl.constantvalue import Zero, RealValue, ComplexValue, as_ufl from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv @@ -450,7 +451,7 @@ def jump(v, n=None): else: return dot(v('+'), n('+')) + dot(v('-'), n('-')) else: - warning("Returning zero from jump of expression without a domain. This may be erroneous if a dolfin.Expression is involved.") + warnings.warn("Returning zero from jump of expression without a domain. This may be erroneous if a dolfin.Expression is involved.") # FIXME: Is this right? If v has no domain, it doesn't depend # on anything spatially variable or any form arguments, and # thus the jump is zero. In other words, I'm assuming that "v diff --git a/ufl/precedence.py b/ufl/precedence.py index b5ca6c052..aee5c1537 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -7,7 +7,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import warning +import warnings # FIXME: This code is crap... @@ -102,7 +102,7 @@ def assign_precedences(precedence_list): if missing: msg = "Missing precedence levels for classes:\n" +\ "\n".join(' %s' % c for c in sorted(missing)) - warning(msg) + warnings.warn(msg) """ diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index fe73fe890..14cb9e33c 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -7,7 +7,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import warning +import warnings def topological_sorting(nodes, edges): @@ -91,7 +91,7 @@ def canonicalize_metadata(metadata): elif isinstance(value, (int, float, str)) or value is None: value = str(value) else: - warning("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__)) + warnings.warn("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__)) value = str(value) newvalues.append(value) From 26548e9fff49aa9c0adea47f5402fd3c16d62a6d Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 20 Dec 2022 14:36:25 +0000 Subject: [PATCH 007/136] Fix FEniCSx integration action (#134) * change * remove comment * petsc arch --- .github/workflows/fenicsx-tests.yml | 1 + README.rst | 1 + setup.cfg | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 9c19999cd..7aaf42d7f 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -15,6 +15,7 @@ jobs: env: CC: gcc-10 CXX: g++-10 + PETSC_ARCH: linux-gnu-real-64 steps: - uses: actions/checkout@v3 diff --git a/README.rst b/README.rst index 15f1446de..0d43d1e08 100644 --- a/README.rst +++ b/README.rst @@ -40,3 +40,4 @@ License You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . + diff --git a/setup.cfg b/setup.cfg index 77beb85f6..559dbe31a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,7 +59,7 @@ ci = [flake8] ignore = E501, W504, - E741 # ambiguous variable name + E741 builtins = ufl exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test From 64c846af95e5a59f4c5c3348b362a489a7b761a0 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 20 Dec 2022 17:24:00 +0000 Subject: [PATCH 008/136] Restore comment in format that flake8 supports (#136) * change * remove comment * petsc arch * restore comment --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 559dbe31a..1a805468f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,7 @@ ci = [flake8] ignore = E501, W504, + # ambiguous variable name E741 builtins = ufl exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test From 6d84d0ee2439b78c915638986ea37184f89f580b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 4 Jan 2023 15:12:33 +0000 Subject: [PATCH 009/136] Remove usage of deprecated functions from the source code and testing (#139) * Make sure that no deprecation warnings are hit by tests, i.e. `python3 -m pytest -W error::DeprecationWarning -xvs .` passes * More extractions * Add deprecation warning as error in integration test * Point to relevant branches * Remove asserts * Add deprecation as error on ffcx as well * Remove warning == error (due to cffi relying on distutils). Instead add in hard asserts to catch all dolfinx/ffcx errors. Remove hard asserts once these have been fixed * Fix CI * Remove asserts to make TSFC compatible * Sort imports * Revert FFCx and DOLFINx branches * , * E741 is not pydocstyle * Fill in empty docstring (!) Co-authored-by: Garth N. Wells Co-authored-by: Matthew Scroggs --- setup.cfg | 4 +- test/test_derivative.py | 23 ++++---- ufl/algorithms/apply_derivatives.py | 59 ++++++++----------- ufl/algorithms/apply_function_pullbacks.py | 20 +++---- ufl/algorithms/apply_geometry_lowering.py | 68 ++++++++++------------ ufl/algorithms/apply_restrictions.py | 10 ++-- ufl/algorithms/checks.py | 3 +- ufl/algorithms/estimate_degrees.py | 14 ++--- ufl/cell.py | 14 +---- ufl/checks.py | 3 +- ufl/differentiation.py | 26 ++++----- ufl/domain.py | 47 +++++++-------- ufl/geometry.py | 24 +++----- ufl/index_combination_utils.py | 3 +- 14 files changed, 135 insertions(+), 183 deletions(-) diff --git a/setup.cfg b/setup.cfg index 1a805468f..bdcf22ec1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,8 +68,8 @@ exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test # Work on removing these ignores ignore = D100,D101,D102,D103,D104,D105,D107, D200,D202, - D203, # this error should be disabled + # the skipping of D203 should be removed + D203, D204,D205,D208,D209,D210,D212,D213, D300,D301, D400,D401,D402,D404,D415,D416 - E741 # Variable names l, O, I, ... diff --git a/test/test_derivative.py b/test/test_derivative.py index c9ce779bf..0259f066e 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -1,20 +1,20 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2009-02-17 -- 2009-02-17" -import pytest -import math from itertools import chain +import pytest + from ufl import * -from ufl.classes import Indexed, MultiIndex, ReferenceGrad -from ufl.constantvalue import as_ufl -from ufl.algorithms import expand_indices, strip_variables, post_traversal, compute_form_data +from ufl.algorithms import (compute_form_data, expand_indices, post_traversal, + strip_variables) +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering -from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.classes import Indexed, MultiIndex, ReferenceGrad +from ufl.constantvalue import as_ufl +from ufl.domain import extract_unique_domain + def assertEqualBySampling(actual, expected): ad = compute_form_data(actual*dx) @@ -43,9 +43,8 @@ def make_value(c): amapping = dict((c, make_value(c)) for c in chain(ad.original_form.coefficients(), ad.original_form.arguments())) bmapping = dict((c, make_value(c)) for c in chain(bd.original_form.coefficients(), bd.original_form.arguments())) - - acell = actual.ufl_domain().ufl_cell() - bcell = expected.ufl_domain().ufl_cell() + acell = extract_unique_domain(actual).ufl_cell() + bcell = extract_unique_domain(expected).ufl_cell() assert acell == bcell if acell.geometric_dimension() == 1: x = (0.3,) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index ac94867ad..b18d77f69 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module contains the apply_derivatives algorithm which computes the derivatives of a form of expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -9,39 +8,31 @@ import warnings from collections import defaultdict - -from ufl.log import error - -from ufl.core.expr import ufl_err_str -from ufl.core.terminal import Terminal -from ufl.core.multiindex import MultiIndex, FixedIndex, indices - -from ufl.tensors import as_tensor, as_scalar, as_scalars, unit_indexed_tensor, unwrap_list_tensor - -from ufl.classes import ConstantValue, Identity, Zero, FloatValue -from ufl.classes import Coefficient, FormArgument, ReferenceValue -from ufl.classes import Grad, ReferenceGrad, Variable -from ufl.classes import Indexed, ListTensor, ComponentTensor -from ufl.classes import ExprList, ExprMapping -from ufl.classes import Product, Sum, IndexSum -from ufl.classes import Conj, Real, Imag -from ufl.classes import JacobianInverse -from ufl.classes import SpatialCoordinate - -from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar -from ufl.operators import (conditional, sign, - sqrt, exp, ln, cos, sin, cosh, sinh, - bessel_J, bessel_Y, bessel_I, bessel_K, - cell_avg, facet_avg) - from math import pi -from ufl.corealg.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dag from ufl.algorithms.map_integrands import map_integrand_dags - from ufl.checks import is_cellwise_constant +from ufl.classes import (Coefficient, ComponentTensor, Conj, ConstantValue, + ExprList, ExprMapping, FloatValue, FormArgument, Grad, + Identity, Imag, Indexed, IndexSum, JacobianInverse, + ListTensor, Product, Real, ReferenceGrad, + ReferenceValue, SpatialCoordinate, Sum, Variable, + Zero) +from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar +from ufl.core.expr import ufl_err_str +from ufl.core.multiindex import FixedIndex, MultiIndex, indices +from ufl.core.terminal import Terminal +from ufl.corealg.map_dag import map_expr_dag +from ufl.corealg.multifunction import MultiFunction from ufl.differentiation import CoordinateDerivative +from ufl.domain import extract_unique_domain +from ufl.log import error +from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, + conditional, cos, cosh, exp, facet_avg, ln, sign, + sin, sinh, sqrt) +from ufl.tensors import (as_scalar, as_scalars, as_tensor, unit_indexed_tensor, + unwrap_list_tensor) + # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset @@ -499,7 +490,7 @@ def geometric_quantity(self, o): if is_cellwise_constant(o): return self.independent_terminal(o) else: - domain = o.ufl_domain() + domain = extract_unique_domain(o) K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) return Do @@ -523,7 +514,7 @@ def spatial_coordinate(self, o): def cell_coordinate(self, o): "dX/dx = inv(dx/dX) = inv(J) = K" # FIXME: Is this true for manifolds? What about orientation? - return JacobianInverse(o.ufl_domain()) + return JacobianInverse(extract_unique_domain(o)) # --- Specialized rules for form arguments @@ -552,7 +543,7 @@ def reference_value(self, o): if not f._ufl_is_terminal_: error("ReferenceValue can only wrap a terminal") - domain = f.ufl_domain() + domain = extract_unique_domain(f) K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) return Do @@ -564,7 +555,7 @@ def reference_grad(self, o): valid_operand = f._ufl_is_in_reference_frame_ or isinstance(f, (JacobianInverse, SpatialCoordinate)) if not valid_operand: error("ReferenceGrad can only wrap a reference frame type!") - domain = f.ufl_domain() + domain = extract_unique_domain(f) K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) return Do @@ -1223,7 +1214,7 @@ def apply_grads(f): def jacobian(self, o): # d (grad_X(x))/d x => grad_X(Argument(x.function_space()) for (w, v) in zip(self._w, self._v): - if o.ufl_domain() == w.ufl_domain() and isinstance(v.ufl_operands[0], FormArgument): + if extract_unique_domain(o) == extract_unique_domain(w) and isinstance(v.ufl_operands[0], FormArgument): return ReferenceGrad(v) return self.independent_terminal(o) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index 737fba334..dd33fa5ea 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -6,23 +5,20 @@ # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Lizao Li , 2016 -from itertools import chain, accumulate, repeat +from itertools import accumulate, chain, repeat -from ufl.log import error +import numpy +from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.classes import (Jacobian, JacobianDeterminant, JacobianInverse, + ReferenceValue) from ufl.core.multiindex import indices from ufl.corealg.multifunction import MultiFunction, memoized_handler -from ufl.algorithms.map_integrands import map_integrand_dags - -from ufl.classes import (ReferenceValue, - Jacobian, JacobianInverse, JacobianDeterminant) - +from ufl.domain import extract_unique_domain +from ufl.log import error from ufl.tensors import as_tensor, as_vector from ufl.utils.sequences import product -import numpy def sub_elements_with_mappings(element): @@ -70,7 +66,7 @@ def apply_known_single_pullback(r, element): # Coefficient/Argument (in the case of mixed elements, see below # in apply_single_function_pullbacks), to which we cannot apply ReferenceValue mapping = element.mapping() - domain = r.ufl_domain() + domain = extract_unique_domain(r) if mapping == "physical": return r elif mapping == "identity": diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index ef265f69d..bd44124c5 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithm for lowering abstractions of geometric types. This means replacing high-level types with expressions @@ -15,32 +14,25 @@ from functools import reduce from itertools import combinations -from ufl.log import error - +from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, + CellOrientation, CellOrigin, CellVertices, CellVolume, + Expr, FacetEdgeVectors, FacetJacobian, + FacetJacobianDeterminant, FloatValue, Form, Integral, + Jacobian, JacobianDeterminant, JacobianInverse, + MaxCellEdgeLength, ReferenceCellVolume, + ReferenceFacetVolume, ReferenceGrad, ReferenceNormal, + SpatialCoordinate) +from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr from ufl.core.multiindex import Index, indices -from ufl.corealg.multifunction import MultiFunction, memoized_handler from ufl.corealg.map_dag import map_expr_dag -from ufl.measure import custom_integral_types, point_integral_types - -from ufl.classes import (Expr, Form, Integral, - ReferenceGrad, - Jacobian, JacobianInverse, JacobianDeterminant, - CellOrientation, CellOrigin, CellCoordinate, - FacetJacobian, FacetJacobianDeterminant, - CellFacetJacobian, - MaxCellEdgeLength, - CellEdgeVectors, FacetEdgeVectors, CellVertices, - ReferenceNormal, - ReferenceCellVolume, ReferenceFacetVolume, CellVolume, - SpatialCoordinate, - FloatValue) +from ufl.corealg.multifunction import MultiFunction, memoized_handler # FacetJacobianInverse, # FacetOrientation, QuadratureWeight, - +from ufl.domain import extract_unique_domain +from ufl.log import error +from ufl.measure import custom_integral_types, point_integral_types +from ufl.operators import conj, max_value, min_value, real, sqrt from ufl.tensors import as_tensor, as_vector -from ufl.operators import sqrt, max_value, min_value, conj, real - -from ufl.compound_expressions import determinant_expr, cross_expr, inverse_expr class GeometryLoweringApplier(MultiFunction): @@ -60,7 +52,7 @@ def terminal(self, t): def jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if domain.ufl_coordinate_element().mapping() != "identity": error("Piola mapped coordinates are not implemented.") # Note: No longer supporting domain.coordinates(), always @@ -83,7 +75,7 @@ def jacobian_inverse(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) J = self.jacobian(Jacobian(domain)) # TODO: This could in principle use # preserve_types[JacobianDeterminant] with minor refactoring: @@ -95,7 +87,7 @@ def jacobian_determinant(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) J = self.jacobian(Jacobian(domain)) detJ = determinant_expr(J) @@ -113,7 +105,7 @@ def facet_jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) J = self.jacobian(Jacobian(domain)) RFJ = CellFacetJacobian(domain) i, j, k = indices(3) @@ -124,7 +116,7 @@ def facet_jacobian_inverse(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) FJ = self.facet_jacobian(FacetJacobian(domain)) # This could in principle use # preserve_types[JacobianDeterminant] with minor refactoring: @@ -135,7 +127,7 @@ def facet_jacobian_determinant(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) FJ = self.facet_jacobian(FacetJacobian(domain)) detFJ = determinant_expr(FJ) @@ -153,7 +145,7 @@ def spatial_coordinate(self, o): "Fall through to coordinate field of domain if it exists." if self._preserve_types[o._ufl_typecode_]: return o - if o.ufl_domain().ufl_coordinate_element().mapping() != "identity": + if extract_unique_domain(o).ufl_coordinate_element().mapping() != "identity": error("Piola mapped coordinates are not implemented.") # No longer supporting domain.coordinates(), always preserving # SpatialCoordinate object. @@ -165,7 +157,7 @@ def cell_coordinate(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) K = self.jacobian_inverse(JacobianInverse(domain)) x = self.spatial_coordinate(SpatialCoordinate(domain)) x0 = CellOrigin(domain) @@ -186,7 +178,7 @@ def cell_volume(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler @@ -202,7 +194,7 @@ def facet_area(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) tdim = domain.topological_dimension() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to @@ -223,7 +215,7 @@ def circumradius(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if not domain.is_piecewise_linear_simplex_domain(): error("Circumradius only makes sense for affine simplex cells") @@ -270,7 +262,7 @@ def _reduce_cell_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler @@ -294,7 +286,7 @@ def cell_diameter(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if not domain.ufl_coordinate_element().degree() in {1, (1, 1)}: # Don't lower bendy cells, instead leave it to form compiler @@ -325,7 +317,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) if domain.ufl_cell().topological_dimension() < 3: error("Facet edge lengths only make sense for topological dimension >= 3.") @@ -348,7 +340,7 @@ def cell_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) gdim = domain.geometric_dimension() tdim = domain.topological_dimension() @@ -380,7 +372,7 @@ def facet_normal(self, o): if self._preserve_types[o._ufl_typecode_]: return o - domain = o.ufl_domain() + domain = extract_unique_domain(o) tdim = domain.topological_dimension() if tdim == 1: diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 1ab07d72d..2f2745e1d 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module contains the apply_restrictions algorithm which propagates restrictions in a form towards the terminals.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -8,11 +7,12 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error +from ufl.algorithms.map_integrands import map_integrand_dags from ufl.classes import Restricted -from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag -from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.corealg.multifunction import MultiFunction +from ufl.domain import extract_unique_domain +from ufl.log import error from ufl.measure import integral_type_to_measure_name from ufl.sobolevspace import H1 @@ -136,7 +136,7 @@ def coefficient(self, o): return self._require_restriction(o) def facet_normal(self, o): - D = o.ufl_domain() + D = extract_unique_domain(o) e = D.ufl_coordinate_element() f = e.family() d = e.degree() diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index 9c2c7a8f9..b65fe8642 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -23,6 +23,7 @@ from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import traverse_unique_terminals from ufl.algorithms.check_restrictions import check_restrictions +from ufl.domain import extract_unique_domain def validate_form(form): # TODO: Can we make this return a list of errors instead of raising exception? @@ -43,7 +44,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste # errors.append("Form is not multilinear in arguments.") # FIXME DOMAIN: Add check for consistency between domains somehow - domains = set(t.ufl_domain() + domains = set(extract_unique_domain(t) for e in iter_expressions(form) for t in traverse_unique_terminals(e)) - {None} if not domains: diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 6030909ac..c0896740c 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithms for estimating polynomial degrees of expressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg @@ -12,13 +11,14 @@ import warnings -from ufl.log import error -from ufl.form import Form -from ufl.integral import Integral from ufl.algorithms.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dags from ufl.checks import is_cellwise_constant from ufl.constantvalue import IntValue +from ufl.corealg.map_dag import map_expr_dags +from ufl.domain import extract_unique_domain +from ufl.form import Form +from ufl.integral import Integral +from ufl.log import error class IrreducibleInt(int): @@ -50,11 +50,11 @@ def geometric_quantity(self, v): return 0 else: # As a heuristic, just returning domain degree to bump up degree somewhat - return v.ufl_domain().ufl_coordinate_element().degree() + return extract_unique_domain(v).ufl_coordinate_element().degree() def spatial_coordinate(self, v): "A coordinate provides additional degrees depending on coordinate field of domain." - return v.ufl_domain().ufl_coordinate_element().degree() + return extract_unique_domain(v).ufl_coordinate_element().degree() def cell_coordinate(self, v): "A coordinate provides one additional degree." diff --git a/ufl/cell.py b/ufl/cell.py index 2f5c95905..c423a94f8 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- "Types for representing a cell." # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -6,20 +5,13 @@ # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg, 2009. -# Modified by Kristian B. Oelgaard, 2009 -# Modified by Marie E. Rognes 2012 -# Modified by Andrew T. T. McRae, 2014 -# Modified by Massimiliano Leoni, 2016 -import numbers import functools +import numbers import ufl.cell -from ufl.log import error from ufl.core.ufl_type import attach_operators_from_hash_data - +from ufl.log import error # Export list for ufl.classes __all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"] @@ -313,4 +305,4 @@ def as_cell(cell): elif isinstance(cell, tuple): return TensorProductCell(cell) else: - error("Invalid cell %s." % cell) + raise ValueError(f"Invalid cell {cell}.") diff --git a/ufl/checks.py b/ufl/checks.py index 0b2ac7337..a3cad0e8b 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Utility functions for checking properties of expressions.""" # Copyright (C) 2013-2016 Martin Sandve Alnæs @@ -45,8 +44,8 @@ def is_globally_constant(expr): # negatives are possible. # from ufl.argument import Argument # from ufl.coefficient import Coefficient - from ufl.geometry import GeometricQuantity from ufl.core.terminal import FormArgument + from ufl.geometry import GeometricQuantity for e in traverse_unique_terminals(expr): # Return False if any single terminal is not constant if e._ufl_is_literal_: diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 49c7c432b..049dd2685 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -1,31 +1,27 @@ -# -*- coding: utf-8 -*- -"Differential operators." +"""Differential operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg, 2009. -from ufl.log import error +from ufl.checks import is_cellwise_constant +from ufl.coefficient import Coefficient +from ufl.constantvalue import Zero from ufl.core.expr import Expr -from ufl.core.terminal import Terminal from ufl.core.operator import Operator +from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type - +from ufl.domain import extract_unique_domain, find_geometric_dimension from ufl.exprcontainers import ExprList, ExprMapping -from ufl.constantvalue import Zero -from ufl.coefficient import Coefficient -from ufl.variable import Variable +from ufl.log import error from ufl.precedence import parstr -from ufl.domain import find_geometric_dimension -from ufl.checks import is_cellwise_constant - +from ufl.variable import Variable # --- Basic differentiation objects --- + @ufl_type(is_abstract=True, is_differential=True) class Derivative(Operator): @@ -182,14 +178,14 @@ class ReferenceGrad(CompoundDerivative): def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): - dim = f.ufl_domain().topological_dimension() + dim = extract_unique_domain(f).topological_dimension() return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): CompoundDerivative.__init__(self, (f,)) - self._dim = f.ufl_domain().topological_dimension() + self._dim = extract_unique_domain(f).topological_dimension() 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 cd3d329dd..4f95a26f0 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -1,26 +1,19 @@ -# -*- coding: utf-8 -*- -"Types for representing a geometric domain." +"""Types for representing a geometric domain.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg, 2009. -# Modified by Kristian B. Oelgaard, 2009 -# Modified by Marie E. Rognes 2012 -# Modified by Cecile Daversin-Catty, 2018 import numbers -from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.cell import AbstractCell, TensorProductCell, as_cell from ufl.core.ufl_id import attach_ufl_id +from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.corealg.traversal import traverse_unique_terminals -from ufl.log import error -from ufl.cell import as_cell, AbstractCell, TensorProductCell from ufl.finiteelement.tensorproductelement import TensorProductElement - +from ufl.log import error # Export list for ufl.classes __all_classes__ = ["AbstractDomain", "Mesh", "MeshView", "TensorProductMesh"] @@ -274,18 +267,19 @@ def as_domain(domain): if isinstance(domain, AbstractDomain): # Modern UFL files and dolfin behaviour return domain - elif hasattr(domain, "ufl_domain"): - # If we get a dolfin.Mesh, it can provide us a corresponding - # ufl.Mesh. This would be unnecessary if dolfin.Mesh could - # subclass ufl.Mesh. - return domain.ufl_domain() - else: - # Legacy UFL files - # TODO: Make this conversion in the relevant constructors - # closer to the user interface? - # TODO: Make this configurable to be an error from the dolfin side? - cell = as_cell(domain) - return default_domain(cell) + + try: + return extract_unique_domain(domain) + except AttributeError: + try: + # Legacy UFL files + # TODO: Make this conversion in the relevant constructors + # closer to the user interface? + # TODO: Make this configurable to be an error from the dolfin side? + cell = as_cell(domain) + return default_domain(cell) + except ValueError: + return domain.ufl_domain() def sort_domains(domains): @@ -361,10 +355,9 @@ def find_geometric_dimension(expr): "Find the geometric dimension of an expression." gdims = set() for t in traverse_unique_terminals(expr): - if hasattr(t, "ufl_domain"): - domain = t.ufl_domain() - if domain is not None: - gdims.add(domain.geometric_dimension()) + domain = extract_unique_domain(t) + if domain is not None: + gdims.add(domain.geometric_dimension()) if hasattr(t, "ufl_element"): element = t.ufl_element() if element is not None: diff --git a/ufl/geometry.py b/ufl/geometry.py index 1d4741950..b456d302d 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -1,21 +1,15 @@ -# -*- coding: utf-8 -*- -"Types for representing symbolic expressions for geometric quantities." +"""Types for representing symbolic expressions for geometric quantities.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg, 2009. -# Modified by Kristian B. Oelgaard, 2009 -# Modified by Marie E. Rognes 2012 -# Modified by Massimiliano Leoni, 2016 -from ufl.log import error -from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal -from ufl.domain import as_domain +from ufl.core.ufl_type import ufl_type +from ufl.domain import as_domain, extract_unique_domain +from ufl.log import error """ @@ -372,7 +366,7 @@ def __init__(self, domain): @property def ufl_shape(self): - cell = self.ufl_domain().ufl_cell() + cell = extract_unique_domain(self).ufl_cell() ne = cell.num_edges() t = cell.topological_dimension() return (ne, t) @@ -397,7 +391,7 @@ def __init__(self, domain): @property def ufl_shape(self): - cell = self.ufl_domain().ufl_cell() + cell = extract_unique_domain(self).ufl_cell() facet_types = cell.facet_types() # Raise exception for cells with more than one facet type e.g. prisms @@ -425,7 +419,7 @@ def __init__(self, domain): @property def ufl_shape(self): - cell = self.ufl_domain().ufl_cell() + cell = extract_unique_domain(self).ufl_cell() nv = cell.num_vertices() g = cell.geometric_dimension() return (nv, g) @@ -450,7 +444,7 @@ def __init__(self, domain): @property def ufl_shape(self): - cell = self.ufl_domain().ufl_cell() + cell = extract_unique_domain(self).ufl_cell() ne = cell.num_edges() g = cell.geometric_dimension() return (ne, g) @@ -475,7 +469,7 @@ def __init__(self, domain): @property def ufl_shape(self): - cell = self.ufl_domain().ufl_cell() + cell = extract_unique_domain(self).ufl_cell() facet_types = cell.facet_types() # Raise exception for cells with more than one facet type e.g. prisms diff --git a/ufl/index_combination_utils.py b/ufl/index_combination_utils.py index 166badb98..c3bd5bf9f 100644 --- a/ufl/index_combination_utils.py +++ b/ufl/index_combination_utils.py @@ -84,8 +84,7 @@ def merge_unique_indices(afi, afid, bfi, bfid): def remove_indices(fi, fid, rfi): - """ - """ + """Remove indices.""" if not rfi: return fi, fid From f7620393a2fb80eed79cde4f66f4e66f1fb92373 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 4 Jan 2023 16:00:17 +0000 Subject: [PATCH 010/136] Replace `not isinstance(element, MixedElement)` with `element.num_sub_elements == 0` (#122) * Use num_sub_elements instead of isinstance(, MixedElement) * unused import --- ufl/split_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 1f3d067ea..1b4439145 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -11,7 +11,7 @@ from ufl.log import error from ufl.utils.sequences import product -from ufl.finiteelement import MixedElement, TensorElement +from ufl.finiteelement import TensorElement from ufl.tensors import as_vector, as_matrix, ListTensor from ufl.indexed import Indexed from ufl.permutation import compute_indices @@ -51,7 +51,7 @@ def split(v): # Special case: simple element, just return function in a tuple element = v.ufl_element() - if not isinstance(element, MixedElement): + if element.num_sub_elements == 0: assert end is None return (v,) From 8382ed00b98b42e30b2cb3f3aaa05a51eb0b77b1 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 6 Jan 2023 09:28:09 +0000 Subject: [PATCH 011/136] Pass more variants around (#141) * add variant for MixedElement (#129) * Allow VectorElement to take a variant argument. (#17) Co-authored-by: Patrick Farrell Co-authored-by: Pablo Brubeck Co-authored-by: Patrick E. Farrell Co-authored-by: Patrick Farrell --- ufl/finiteelement/mixedelement.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index e80743fb9..d6715ae11 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -234,6 +234,13 @@ def degree(self, component=None): def reconstruct(self, **kwargs): return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()]) + def variant(self): + try: + variant, = {e.variant() for e in self.sub_elements()} + return variant + except ValueError: + return None + def __str__(self): "Format as string for pretty printing." tmp = ", ".join(str(element) for element in self._sub_elements) From c58156809f91f6107ed2d1704a236ea9b0a7178b Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Mon, 9 Jan 2023 11:22:28 +0000 Subject: [PATCH 012/136] Simplify coeff and constant naming (#142) --- ufl/coefficient.py | 6 +----- ufl/constant.py | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/ufl/coefficient.py b/ufl/coefficient.py index d73677612..dd6936cab 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -88,11 +88,7 @@ def _ufl_signature_data_(self, renumbering): return ("Coefficient", count, fsdata) def __str__(self): - count = str(self._count) - if len(count) == 1: - return "w_%s" % count - else: - return "w_{%s}" % count + return f"w_{self._count}" def __repr__(self): return self._repr diff --git a/ufl/constant.py b/ufl/constant.py index 83a0247fd..f2359cc7c 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -48,11 +48,7 @@ def is_cellwise_constant(self): return True def __str__(self): - count = str(self._count) - if len(count) == 1: - return "c_%s" % count - else: - return "c_{%s}" % count + return f"c_{self._count}" def __repr__(self): return self._repr From b9d3e974289cccc0df48701470a1b0a2f294dc47 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 11 Jan 2023 13:30:57 +0000 Subject: [PATCH 013/136] Dual spaces (#143) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Add dual space function to mixed function space * Add Dual Space class and mixed tests * Change name of dual space function * Add basic implementation of isprimal/dual * update dual tests * Corrections in functionspace and duals * Initial implementation of Cofunction * Initial Implementation of cofunction * lint * Export new classes * Add test for equality of cofunctions * Improved implementation of isdual/isprimal * rename duals test file * Comments * Add new method to coefficient * Add new to argument and tests * Lint * Update to fit docstyle * add newarguments so that pickle works * Add Matrix Class * Changes to Matrix Class * Add Matrix to init * Add BaseForm * Add form sum to init file * change inheritance structure and add baseform * Add baseform to matrix * Add some operation tests * Add comment about FormArgument * Fix signature tests * Tidy and add assertions to tests * Starting to implement matrix adjoint * Change approach of Adjoint Implementation * Remove unnecessary functions from BaseForm * Change implementation of new to fix tests * Update adjoint * Initial Action implementation and tests * Lint * Check Action arguments * Add alternative Action to formoperators * lint * Rework action * lint * Add errors to action + test public api * Modify compute_energy_norm to alternative def * Add hashing to baseform instances * Add some comments and lint * Hash in matrix * Add test for action on action * remove eq in form as redundant * lint + amend tests * Fix Issue with adjoint * small change to arguments method of adjoint class * lint * lint * more style * cleanup Action * cleanup Action * cleanup adjoint * use correct action * sanitise argument creation * Shut up Flake8 W503 is no longer PEP8 best practice. E129 causes spurious problems on long `if` statements. * clean up Matrix * Clean up duals * Clean up functionspace * Remove unneeded signature from Matrix * cleanup form * Check trivial case for Action and Adjoint * Update possible right term for Action * Take into account various BaseForm objects in expand_derivatives * Add BaseForm in __init__ * Add BaseForm in as_ufl * Update action * Update Cofunction and Coargument for when they take in a primal space * Add equals to FormSum * Add __eq__ to Action * Add __eq__ to Adjoint * Add __eq__ and __hash__ to Coargument * Fix typos * Refactor analysis.py * Add BaseFormDerivative * Fix for arguments, coefficients and function spaces * Fix hash for coefficients, arguments and function spaces + some more equality fixes * Draft BaseForm diff * Draft: Refactor UFL types using UFLType * Fix __eq__ for Coargument * Add UFLType handler as the default handler + Move UFLType to ufl_type.py * Add ufl_operands and _ufl_compute_hash to BaseForm objects for MultiFunction traversal * Add Matrix/Cofunction/Coargument differentiation + Add some ufl_type handlers * Push arguments analysis through BaseFormDerivative * Add Action differentiation * Add tests for BaseForm differentiation * Add Adjoint(Adjoint(.)) = Id * Fix Action differentiation * Matrix derivative is always 0 (since we can't differentiate wrt a Matrix) * Update _handle_derivative_arguments * Fix _analyze_form_arguments for FormSum * Update FormSum and tests * Fix ExprList * Add Adjoint differentiation * Add handlers for Action/Adjoint/FormSum in map_integrands * Use pytest.raises * Change __eq__ to equals for dual objects * Update traversal.py * Replace expr handler by ufl_type in Replacer * Refactor UFL type system * Address comments from the PR * Update arguments analysis for FormSum * Fix lint * Extend Action distribution over ufl.Sum * Add test for Action distributivity wrt FormSum and Sum * Enable Matrix on the rhs of Action * Add ZeroBaseForm * Add tests for ZeroBaseForm * Update author info * Fix Cofunction's argument * Update expand_derivatives * Clean way of getting action arguments and check function spaces * Rename _get_action_arguments * Fix ZeroBaseForm simplification for BaseForm * Swap ZeroBaseForm's arguments for adjoint * Check arguments when summing a ZeroBaseForm * Fix argument contraction with CoefficientDerivative * Clean up * Fix __str__ for Action/Adjoint * Add/Fix comments * Fix typo * Update warnings * Clean up docstrings + fix flake8 Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: India Marsden Co-authored-by: David Ham Co-authored-by: nbouziani Co-authored-by: nbouziani <48448063+nbouziani@users.noreply.github.com> --- setup.cfg | 4 + test/test_algorithms.py | 2 +- test/test_duals.py | 319 ++++++++++++++++++++ test/test_equals.py | 28 +- test/test_form.py | 26 ++ test/test_mixed_function_space.py | 14 + ufl/__init__.py | 22 +- ufl/action.py | 164 ++++++++++ ufl/adjoint.py | 92 ++++++ ufl/algorithms/ad.py | 17 +- ufl/algorithms/analysis.py | 54 ++-- ufl/algorithms/apply_algebra_lowering.py | 2 +- ufl/algorithms/apply_derivatives.py | 28 +- ufl/algorithms/formtransformations.py | 4 +- ufl/algorithms/map_integrands.py | 24 +- ufl/algorithms/replace.py | 2 +- ufl/algorithms/transformer.py | 13 +- ufl/algorithms/traversal.py | 11 +- ufl/argument.py | 109 ++++++- ufl/coefficient.py | 110 ++++++- ufl/constantvalue.py | 3 +- ufl/core/expr.py | 40 +-- ufl/core/terminal.py | 2 +- ufl/core/ufl_type.py | 87 ++++-- ufl/corealg/multifunction.py | 14 +- ufl/differentiation.py | 20 ++ ufl/duals.py | 27 ++ ufl/exprcontainers.py | 7 +- ufl/form.py | 364 ++++++++++++++++++++++- ufl/formoperators.py | 51 +++- ufl/functionspace.py | 114 ++++++- ufl/matrix.py | 99 ++++++ 32 files changed, 1712 insertions(+), 161 deletions(-) create mode 100644 test/test_duals.py create mode 100644 ufl/action.py create mode 100644 ufl/adjoint.py create mode 100644 ufl/duals.py create mode 100644 ufl/matrix.py diff --git a/setup.cfg b/setup.cfg index bdcf22ec1..77f81636d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,6 +59,10 @@ ci = [flake8] ignore = E501, W504, + # Line break before operator, no longer PEP8. + W503, + # Indentation, can trigger for valid code. + E129, # ambiguous variable name E741 builtins = ufl diff --git a/test/test_algorithms.py b/test/test_algorithms.py index b5484def3..c68bb15c7 100755 --- a/test/test_algorithms.py +++ b/test/test_algorithms.py @@ -9,7 +9,7 @@ import pytest from pprint import * -from ufl import (FiniteElement, TestFunction, TrialFunction, triangle, +from ufl import (FiniteElement, TestFunction, TrialFunction, Matrix, triangle, div, grad, Argument, dx, adjoint, Coefficient, FacetNormal, inner, dot, ds) from ufl.algorithms import (extract_arguments, expand_derivatives, diff --git a/test/test_duals.py b/test/test_duals.py new file mode 100644 index 000000000..8fdf357bb --- /dev/null +++ b/test/test_duals.py @@ -0,0 +1,319 @@ +#!/usr/bin/env py.test +# -*- coding: utf-8 -*- + +from ufl import FiniteElement, FunctionSpace, MixedFunctionSpace, \ + Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument,\ + TestFunction, TrialFunction, Adjoint, Action, \ + action, adjoint, derivative, tetrahedron, triangle, interval, dx +from ufl.constantvalue import Zero +from ufl.form import ZeroBaseForm + +__authors__ = "India Marsden" +__date__ = "2020-12-28 -- 2020-12-28" + +import pytest + +from ufl.domain import default_domain +from ufl.duals import is_primal, is_dual +from ufl.algorithms.ad import expand_derivatives + + +def test_mixed_functionspace(self): + # Domains + domain_3d = default_domain(tetrahedron) + domain_2d = default_domain(triangle) + domain_1d = default_domain(interval) + # Finite elements + f_1d = FiniteElement("CG", interval, 1) + f_2d = FiniteElement("CG", triangle, 1) + f_3d = FiniteElement("CG", tetrahedron, 1) + # Function spaces + V_3d = FunctionSpace(domain_3d, f_3d) + V_2d = FunctionSpace(domain_2d, f_2d) + V_1d = FunctionSpace(domain_1d, f_1d) + + # MixedFunctionSpace = V_3d x V_2d x V_1d + V = MixedFunctionSpace(V_3d, V_2d, V_1d) + # Check sub spaces + assert is_primal(V_3d) + assert is_primal(V_2d) + assert is_primal(V_1d) + assert is_primal(V) + + # Get dual of V_3 + V_dual = V_3d.dual() + + # Test dual functions on MixedFunctionSpace = V_dual x V_2d x V_1d + V = MixedFunctionSpace(V_dual, V_2d, V_1d) + V_mixed_dual = MixedFunctionSpace(V_dual, V_2d.dual(), V_1d.dual()) + + assert is_dual(V_dual) + assert not is_dual(V) + assert is_dual(V_mixed_dual) + + +def test_dual_coefficients(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + V_dual = V.dual() + + v = Coefficient(V, count=1) + u = Coefficient(V_dual, count=1) + w = Cofunction(V_dual) + + assert is_primal(v) + assert not is_dual(v) + + assert is_dual(u) + assert not is_primal(u) + + assert is_dual(w) + assert not is_primal(w) + + with pytest.raises(ValueError): + x = Cofunction(V) + + +def test_dual_arguments(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + V_dual = V.dual() + + v = Argument(V, 1) + u = Argument(V_dual, 2) + w = Coargument(V_dual, 3) + + assert is_primal(v) + assert not is_dual(v) + + assert is_dual(u) + assert not is_primal(u) + + assert is_dual(w) + assert not is_primal(w) + + with pytest.raises(ValueError): + x = Coargument(V, 4) + + +def test_addition(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + f_2d_2 = FiniteElement("CG", triangle, 2) + V2 = FunctionSpace(domain_2d, f_2d_2) + V_dual = V.dual() + + u = TrialFunction(V) + v = TestFunction(V) + + # linear 1-form + L = v * dx + a = Cofunction(V_dual) + res = L + a + assert isinstance(res, FormSum) + assert res + + L = u * v * dx + a = Matrix(V, V) + res = L + a + assert isinstance(res, FormSum) + assert res + + # Check BaseForm._add__ simplification + res += ZeroBaseForm((v, u)) + assert res == a + L + # Check Form._add__ simplification + L += ZeroBaseForm((v,)) + assert L == u * v * dx + # Check BaseForm._add__ simplification + res = ZeroBaseForm((v, u)) + res += a + assert res == a + # Check __neg__ + res = L + res -= ZeroBaseForm((v,)) + assert res == L + + with pytest.raises(ValueError): + # Raise error for incompatible arguments + v2 = TestFunction(V2) + res = L + ZeroBaseForm((v2, u)) + + +def test_scalar_mult(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + V_dual = V.dual() + + # linear 1-form + a = Cofunction(V_dual) + res = 2 * a + assert isinstance(res, FormSum) + assert res + + a = Matrix(V, V) + res = 2 * a + assert isinstance(res, FormSum) + assert res + + +def test_adjoint(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + a = Matrix(V, V) + + adj = adjoint(a) + res = 2 * adj + assert isinstance(res, FormSum) + assert res + + res = adjoint(2 * a) + assert isinstance(res, FormSum) + assert isinstance(res.components()[0], Adjoint) + + # Adjoint(Adjoint(.)) = Id + assert adjoint(adj) == a + + +def test_action(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + domain_1d = default_domain(interval) + f_1d = FiniteElement("CG", interval, 1) + U = FunctionSpace(domain_1d, f_1d) + + a = Matrix(V, U) + b = Matrix(V, U.dual()) + u = Coefficient(U) + u_a = Argument(U, 0) + v = Coefficient(V) + ustar = Cofunction(U.dual()) + u_form = u_a * dx + + res = action(a, u) + assert res + assert len(res.arguments()) < len(a.arguments()) + assert isinstance(res, Action) + + repeat = action(res, v) + assert repeat + assert len(repeat.arguments()) < len(res.arguments()) + + res = action(2 * a, u) + assert isinstance(res, FormSum) + assert isinstance(res.components()[0], Action) + + res = action(b, u_form) + assert res + assert len(res.arguments()) < len(b.arguments()) + + with pytest.raises(TypeError): + res = action(a, v) + + with pytest.raises(TypeError): + res = action(a, ustar) + + b2 = Matrix(V, U.dual()) + ustar2 = Cofunction(U.dual()) + # Check Action left-distributivity with FormSum + res = action(b, ustar + ustar2) + assert res == Action(b, ustar) + Action(b, ustar2) + # Check Action right-distributivity with FormSum + res = action(b + b2, ustar) + assert res == Action(b, ustar) + Action(b2, ustar) + + a2 = Matrix(V, U) + u2 = Coefficient(U) + u3 = Coefficient(U) + # Check Action left-distributivity with Sum + # Add 3 Coefficients to check composition of Sum works fine since u + u2 + u3 => Sum(u, Sum(u2, u3)) + res = action(a, u + u2 + u3) + assert res == Action(a, u3) + Action(a, u) + Action(a, u2) + # Check Action right-distributivity with Sum + res = action(a + a2, u) + assert res == Action(a, u) + Action(a2, u) + + +def test_differentiation(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + domain_1d = default_domain(interval) + f_1d = FiniteElement("CG", interval, 1) + U = FunctionSpace(domain_1d, f_1d) + + u = Coefficient(U) + v = Argument(U, 0) + vstar = Argument(U.dual(), 0) + + # -- Cofunction -- # + w = Cofunction(U.dual()) + dwdu = expand_derivatives(derivative(w, u)) + assert isinstance(dwdu, ZeroBaseForm) + assert dwdu.arguments() == (Argument(u.ufl_function_space(), 0),) + # Check compatibility with int/float + assert dwdu == 0 + + dwdw = expand_derivatives(derivative(w, w, vstar)) + assert dwdw == vstar + + dudw = expand_derivatives(derivative(u, w)) + # du/dw is a ufl.Zero and not a ZeroBaseForm + # as we are not differentiating a BaseForm + assert isinstance(dudw, Zero) + assert dudw == 0 + + # -- Coargument -- # + dvstardu = expand_derivatives(derivative(vstar, u)) + assert isinstance(dvstardu, ZeroBaseForm) + assert dvstardu.arguments() == vstar.arguments() + (Argument(u.ufl_function_space(), 1),) + # Check compatibility with int/float + assert dvstardu == 0 + + # -- Matrix -- # + M = Matrix(V, U) + dMdu = expand_derivatives(derivative(M, u)) + assert isinstance(dMdu, ZeroBaseForm) + assert dMdu.arguments() == M.arguments() + (Argument(u.ufl_function_space(), 2),) + # Check compatibility with int/float + assert dMdu == 0 + + # -- Action -- # + Ac = Action(M, u) + dAcdu = expand_derivatives(derivative(Ac, u)) + + # Action(dM/du, u) + Action(M, du/du) = Action(M, uhat) since dM/du = 0. + # Multiply by 1 to get a FormSum (type compatibility). + assert dAcdu == 1 * Action(M, v) + + # -- Adjoint -- # + Ad = Adjoint(M) + dAddu = expand_derivatives(derivative(Ad, u)) + # Push differentiation through Adjoint + assert dAddu == 0 + + # -- Form sum -- # + Fs = M + Ac + dFsdu = expand_derivatives(derivative(Fs, u)) + # Distribute differentiation over FormSum components + assert dFsdu == 1 * Action(M, v) + + +def test_zero_base_form_mult(): + domain_2d = default_domain(triangle) + f_2d = FiniteElement("CG", triangle, 1) + V = FunctionSpace(domain_2d, f_2d) + v = Argument(V, 0) + Z = ZeroBaseForm((v, v)) + + u = Coefficient(V) + + Zu = Z * u + assert Zu == action(Z, u) + assert action(Zu, u) == ZeroBaseForm(()) diff --git a/test/test_equals.py b/test/test_equals.py index abc9d0b55..fd9522ead 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -22,7 +22,7 @@ def test_comparison_of_coefficients(): u2 = Coefficient(U, count=2) u2b = Coefficient(Ub, count=2) - # Itentical objects + # Identical objects assert v1 == v1 assert u2 == u2 @@ -36,6 +36,32 @@ def test_comparison_of_coefficients(): assert not v1 == u1 assert not v2 == u2 +def test_comparison_of_cofunctions(): + V = FiniteElement("CG", triangle, 1) + U = FiniteElement("CG", triangle, 2) + Ub = FiniteElement("CG", triangle, 2) + v1 = Cofunction(V, count=1) + v1b = Cofunction(V, count=1) + v2 = Cofunction(V, count=2) + u1 = Cofunction(U, count=1) + u2 = Cofunction(U, count=2) + u2b = Cofunction(Ub, count=2) + + # Identical objects + assert v1 == v1 + assert u2 == u2 + + # Equal but distinct objects + assert v1 == v1b + assert u2 == u2b + + # Different objects + assert not v1 == v2 + assert not u1 == u2 + assert not v1 == u1 + assert not v2 == u2 + + def test_comparison_of_products(): V = FiniteElement("CG", triangle, 1) diff --git a/test/test_form.py b/test/test_form.py index 10141d51a..71f4a2a20 100755 --- a/test/test_form.py +++ b/test/test_form.py @@ -3,6 +3,7 @@ import pytest from ufl import * +from ufl.form import BaseForm @pytest.fixture @@ -134,3 +135,28 @@ def test_form_call(): a = u*v*dx M = eval("(a @ f) @ g") assert M == g*f*dx + +def test_formsum(mass): + V = FiniteElement("CG", triangle, 1) + v = Cofunction(V) + + assert(v + mass) + assert(mass + v) + assert(isinstance((mass+v), FormSum)) + + assert(len((mass + v + v).components()) == 3) + # Variational forms are summed appropriately + assert(len((mass + v + mass).components()) == 2) + + assert(v - mass) + assert(mass - v) + assert(isinstance((mass+v), FormSum)) + + assert(-v) + assert(isinstance(-v, BaseForm)) + assert((-v).weights()[0] == -1) + + assert(2 * v) + assert(isinstance(2 * v, BaseForm)) + assert((2 * v).weights()[0] == 2) + diff --git a/test/test_mixed_function_space.py b/test/test_mixed_function_space.py index 34d445c84..4cb5abb3a 100644 --- a/test/test_mixed_function_space.py +++ b/test/test_mixed_function_space.py @@ -78,3 +78,17 @@ def test_mixed_functionspace(self): assert ( extract_blocks(f,0) == f_3 ) assert ( extract_blocks(f,1) == f_2 ) assert ( extract_blocks(f,2) == f_1 ) + + # Test dual space method + V_dual = V.dual() + assert( V_dual.num_sub_spaces() == 3 ) + assert( V_dual.ufl_sub_space(0) == V_3d.dual() ) + assert( V_dual.ufl_sub_space(1) == V_2d.dual() ) + assert( V_dual.ufl_sub_space(2) == V_1d.dual() ) + + V_dual = V.dual(*[0,2]) + assert( V_dual.num_sub_spaces() == 3 ) + assert( V_dual.ufl_sub_space(0) == V_3d.dual() ) + assert( V_dual.ufl_sub_space(1) == V_2d ) + assert( V_dual.ufl_sub_space(2) == V_1d.dual() ) + diff --git a/ufl/__init__.py b/ufl/__init__.py index 0517989fc..7bb84df1c 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -288,13 +288,22 @@ from ufl.functionspace import FunctionSpace, MixedFunctionSpace # Arguments -from ufl.argument import Argument, TestFunction, TrialFunction, \ +from ufl.argument import Argument, Coargument, TestFunction, TrialFunction, \ Arguments, TestFunctions, TrialFunctions # Coefficients -from ufl.coefficient import Coefficient, Coefficients +from ufl.coefficient import Coefficient, Cofunction, Coefficients from ufl.constant import Constant, VectorConstant, TensorConstant +# Matrices +from ufl.matrix import Matrix + +# Adjoints +from ufl.adjoint import Adjoint + +# Actions +from ufl.action import Action + # Split function from ufl.split_functions import split @@ -335,7 +344,7 @@ from ufl.measure import Measure, register_integral_type, integral_types, custom_integral_types # Form class -from ufl.form import Form, replace_integral_domains +from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm, replace_integral_domains # Integral classes from ufl.integral import Integral @@ -383,9 +392,10 @@ 'BrokenElement', "WithMapping", 'register_element', 'show_elements', 'FunctionSpace', 'MixedFunctionSpace', - 'Argument', 'TestFunction', 'TrialFunction', + 'Argument','Coargument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', - 'Coefficient', 'Coefficients', + 'Coefficient', 'Cofunction', 'Coefficients', + 'Matrix', 'Adjoint', 'Action', 'Constant', 'VectorConstant', 'TensorConstant', 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', @@ -407,7 +417,7 @@ 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', 'jump', 'avg', 'cell_avg', 'facet_avg', 'elem_mult', 'elem_div', 'elem_pow', 'elem_op', - 'Form', + 'Form','FormSum', 'ZeroBaseForm', 'Integral', 'Measure', 'register_integral_type', 'integral_types', 'custom_integral_types', 'replace', 'replace_integral_domains', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'extract_blocks', 'system', 'functional', 'adjoint', 'sensitivity_rhs', diff --git a/ufl/action.py b/ufl/action.py new file mode 100644 index 000000000..7b32b7bf9 --- /dev/null +++ b/ufl/action.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +"""This module defines the Action class.""" + +# Copyright (C) 2021 India Marsden +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2021-2022. + +from ufl.form import BaseForm, FormSum, Form, ZeroBaseForm +from ufl.core.ufl_type import ufl_type +from ufl.algebra import Sum +from ufl.argument import Argument +from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction +from ufl.differentiation import CoefficientDerivative +from ufl.matrix import Matrix + +# --- The Action class represents the action of a numerical object that needs +# to be computed at assembly time --- + + +@ufl_type() +class Action(BaseForm): + """UFL base form type: respresents the action of an object on another. + For example: + res = Ax + A would be the first argument, left and x would be the second argument, + right. + + Action objects will result when the action of an assembled object + (e.g. a Matrix) is taken. This delays the evaluation of the action until + assembly occurs. + """ + + __slots__ = ( + "_left", + "_right", + "ufl_operands", + "_repr", + "_arguments", + "_hash") + + def __getnewargs__(self): + return (self._left, self._right) + + def __new__(cls, *args, **kw): + left, right = args + + # Check trivial case + if left == 0 or right == 0: + # Check compatibility of function spaces + _check_function_spaces(left, right) + # Still need to work out the ZeroBaseForm arguments. + new_arguments = _get_action_form_arguments(left, right) + return ZeroBaseForm(new_arguments) + + if isinstance(left, (FormSum, Sum)): + # Action distributes over sums on the LHS + return FormSum(*[(Action(component, right), 1) + for component in left.ufl_operands]) + if isinstance(right, (FormSum, Sum)): + # Action also distributes over sums on the RHS + return FormSum(*[(Action(left, component), 1) + for component in right.ufl_operands]) + + return super(Action, cls).__new__(cls) + + def __init__(self, left, right): + BaseForm.__init__(self) + + self._left = left + self._right = right + self.ufl_operands = (self._left, self._right) + + # Check compatibility of function spaces + _check_function_spaces(left, right) + + self._repr = "Action(%s, %s)" % (repr(self._left), repr(self._right)) + self._hash = None + + def ufl_function_spaces(self): + "Get the tuple of function spaces of the underlying form" + if isinstance(self._right, Form): + return self._left.ufl_function_spaces()[:-1] \ + + self._right.ufl_function_spaces()[1:] + elif isinstance(self._right, Coefficient): + return self._left.ufl_function_spaces()[:-1] + + def left(self): + return self._left + + def right(self): + return self._right + + def _analyze_form_arguments(self): + """Compute the Arguments of this Action. + + The highest number Argument of the left operand and the lowest number + Argument of the right operand are consumed by the action. + """ + self._arguments = _get_action_form_arguments(self._left, self._right) + + def equals(self, other): + if type(other) is not Action: + return False + if self is other: + return True + return (self._left == other._left and self._right == other._right) + + def __str__(self): + return "Action(%s, %s)" % (str(self._left), str(self._right)) + + def __repr__(self): + return self._repr + + def __hash__(self): + "Hash code for use in dicts " + if self._hash is None: + self._hash = hash(("Action", + hash(self._right), + hash(self._left))) + return self._hash + + +def _check_function_spaces(left, right): + """Check if the function spaces of left and right match.""" + + if isinstance(right, CoefficientDerivative): + # Action differentiation pushes differentiation through + # right as a consequence of Leibniz formula. + right, *_ = right.ufl_operands + + if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)): + if (left.arguments()[-1].ufl_function_space().dual() + != right.arguments()[0].ufl_function_space()): + + raise TypeError("Incompatible function spaces in Action") + elif isinstance(right, (Coefficient, Cofunction, Argument)): + if (left.arguments()[-1].ufl_function_space() + != right.ufl_function_space()): + + raise TypeError("Incompatible function spaces in Action") + else: + raise TypeError("Incompatible argument in Action: %s" % type(right)) + + +def _get_action_form_arguments(left, right): + """Perform argument contraction to work out the arguments of Action""" + + if isinstance(right, CoefficientDerivative): + # Action differentiation pushes differentiation through + # right as a consequence of Leibniz formula. + right, *_ = right.ufl_operands + + if isinstance(right, BaseForm): + return left.arguments()[:-1] + right.arguments()[1:] + elif isinstance(right, BaseCoefficient): + return left.arguments()[:-1] + elif isinstance(right, Argument): + return left.arguments()[:-1] + (right,) + else: + raise TypeError diff --git a/ufl/adjoint.py b/ufl/adjoint.py new file mode 100644 index 000000000..8129596de --- /dev/null +++ b/ufl/adjoint.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +"""This module defines the Adjoint class.""" + +# Copyright (C) 2021 India Marsden +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2021-2022. + +from ufl.form import BaseForm, FormSum, ZeroBaseForm +from ufl.core.ufl_type import ufl_type +# --- The Adjoint class represents the adjoint of a numerical object that +# needs to be computed at assembly time --- + + +@ufl_type() +class Adjoint(BaseForm): + """UFL base form type: represents the adjoint of an object. + + Adjoint objects will result when the adjoint of an assembled object + (e.g. a Matrix) is taken. This delays the evaluation of the adjoint until + assembly occurs. + """ + + __slots__ = ( + "_form", + "_repr", + "_arguments", + "ufl_operands", + "_hash") + + def __getnewargs__(self): + return (self._form) + + def __new__(cls, *args, **kw): + form = args[0] + # Check trivial case: This is not a ufl.Zero but a ZeroBaseForm! + if form == 0: + # Swap the arguments + return ZeroBaseForm(form.arguments()[::-1]) + + if isinstance(form, Adjoint): + return form._form + elif isinstance(form, FormSum): + # Adjoint distributes over sums + return FormSum(*[(Adjoint(component), 1) + for component in form.components()]) + + return super(Adjoint, cls).__new__(cls) + + def __init__(self, form): + BaseForm.__init__(self) + + if len(form.arguments()) != 2: + raise ValueError("Can only take Adjoint of a 2-form.") + + self._form = form + self.ufl_operands = (self._form,) + self._hash = None + self._repr = "Adjoint(%s)" % repr(self._form) + + def ufl_function_spaces(self): + "Get the tuple of function spaces of the underlying form" + return self._form.ufl_function_spaces() + + def form(self): + return self._form + + def _analyze_form_arguments(self): + """The arguments of adjoint are the reverse of the form arguments.""" + self._arguments = self._form.arguments()[::-1] + + def equals(self, other): + if type(other) is not Adjoint: + return False + if self is other: + return True + return (self._form == other._form) + + def __str__(self): + return "Adjoint(%s)" % str(self._form) + + def __repr__(self): + return self._repr + + def __hash__(self): + """Hash code for use in dicts.""" + if self._hash is None: + self._hash = hash(("Adjoint", hash(self._form))) + return self._hash diff --git a/ufl/algorithms/ad.py b/ufl/algorithms/ad.py index faaa8e615..4755070ae 100644 --- a/ufl/algorithms/ad.py +++ b/ufl/algorithms/ad.py @@ -9,11 +9,14 @@ # # Modified by Anders Logg, 2009. +import warnings + +from ufl.adjoint import Adjoint from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives -def expand_derivatives(form): +def expand_derivatives(form, **kwargs): """Expand all derivatives of expr. In the returned expression g which is mathematically @@ -21,6 +24,18 @@ def expand_derivatives(form): or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ + # For a deprecation period (I see that dolfin-adjoint passes some + # args here) + if kwargs: + warnings("Deprecation: expand_derivatives no longer takes any keyword arguments") + + if isinstance(form, Adjoint): + dform = expand_derivatives(form._form) + if dform == 0: + return dform + # Adjoint is taken on a 3-form which can't happen + raise NotImplementedError('Adjoint derivative is not supported.') + # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index 4d9265769..ab74c1582 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -15,10 +15,11 @@ from ufl.log import error from ufl.utils.sorting import sorted_by_count, topological_sorting -from ufl.core.terminal import Terminal, FormArgument -from ufl.argument import Argument -from ufl.coefficient import Coefficient +from ufl.core.terminal import Terminal +from ufl.argument import BaseArgument +from ufl.coefficient import BaseCoefficient from ufl.constant import Constant +from ufl.form import BaseForm, Form from ufl.algorithms.traversal import iter_expressions from ufl.corealg.traversal import unique_pre_traversal, traverse_unique_terminals @@ -45,29 +46,40 @@ def unique_tuple(objects): def __unused__extract_classes(a): """Build a set of all unique Expr subclasses used in a. - The argument a can be a Form, Integral or Expr.""" + The argument a can be a BaseForm, Integral or Expr.""" return set(o._ufl_class_ for e in iter_expressions(a) for o in unique_pre_traversal(e)) -def extract_type(a, ufl_type): - """Build a set of all objects of class ufl_type found in a. - The argument a can be a Form, Integral or Expr.""" - if issubclass(ufl_type, Terminal): +def extract_type(a, ufl_types): + """Build a set of all objects found in a whose class is in ufl_types. + The argument a can be a BaseForm, Integral or Expr.""" + + if not isinstance(ufl_types, (list, tuple)): + ufl_types = (ufl_types,) + + # BaseForms that aren't forms only have arguments + if isinstance(a, BaseForm) and not isinstance(a, Form): + if any(issubclass(t, BaseArgument) for t in ufl_types): + return set(a.arguments()) + else: + return set() + + if all(issubclass(t, Terminal) for t in ufl_types): # Optimization return set(o for e in iter_expressions(a) for o in traverse_unique_terminals(e) - if isinstance(o, ufl_type)) + if any(isinstance(o, t) for t in ufl_types)) else: return set(o for e in iter_expressions(a) for o in unique_pre_traversal(e) - if isinstance(o, ufl_type)) + if any(isinstance(o, t) for t in ufl_types)) def has_type(a, ufl_type): """Return if an object of class ufl_type can be found in a. - The argument a can be a Form, Integral or Expr.""" + The argument a can be a BaseForm, Integral or Expr.""" if issubclass(ufl_type, Terminal): # Optimization traversal = traverse_unique_terminals @@ -78,7 +90,7 @@ def has_type(a, ufl_type): def has_exact_type(a, ufl_type): """Return if an object of class ufl_type can be found in a. - The argument a can be a Form, Integral or Expr.""" + The argument a can be a BaseForm, Integral or Expr.""" tc = ufl_type._ufl_typecode_ if issubclass(ufl_type, Terminal): # Optimization @@ -90,14 +102,14 @@ def has_exact_type(a, ufl_type): def extract_arguments(a): """Build a sorted list of all arguments in a, - which can be a Form, Integral or Expr.""" - return _sorted_by_number_and_part(extract_type(a, Argument)) + which can be a BaseForm, Integral or Expr.""" + return _sorted_by_number_and_part(extract_type(a, BaseArgument)) def extract_coefficients(a): """Build a sorted list of all coefficients in a, - which can be a Form, Integral or Expr.""" - return sorted_by_count(extract_type(a, Coefficient)) + which can be a BaseForm, Integral or Expr.""" + return sorted_by_count(extract_type(a, BaseCoefficient)) def extract_constants(a): @@ -107,15 +119,15 @@ def extract_constants(a): def extract_arguments_and_coefficients(a): """Build two sorted lists of all arguments and coefficients - in a, which can be a Form, Integral or Expr.""" + in a, which can be BaseForm, Integral or Expr.""" # This function is faster than extract_arguments + extract_coefficients # for large forms, and has more validation built in. - # Extract lists of all form argument instances - terminals = extract_type(a, FormArgument) - arguments = [f for f in terminals if isinstance(f, Argument)] - coefficients = [f for f in terminals if isinstance(f, Coefficient)] + # Extract lists of all BaseArgument and BaseCoefficient instances + base_coeff_and_args = extract_type(a, (BaseArgument, BaseCoefficient)) + arguments = [f for f in base_coeff_and_args if isinstance(f, BaseArgument)] + coefficients = [f for f in base_coeff_and_args if isinstance(f, BaseCoefficient)] # Build number,part: instance mappings, should be one to one bfnp = dict((f, (f.number(), f.part())) for f in arguments) diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index 24fc2fbb6..66846fb4e 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -29,7 +29,7 @@ class LowerCompoundAlgebra(MultiFunction): def __init__(self): MultiFunction.__init__(self) - expr = MultiFunction.reuse_if_untouched + ufl_type = MultiFunction.reuse_if_untouched # ------------ Compound tensor operators diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index b18d77f69..e6cdaade3 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -33,6 +33,7 @@ from ufl.tensors import (as_scalar, as_scalars, as_tensor, unit_indexed_tensor, unwrap_list_tensor) +from ufl.form import ZeroBaseForm # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset @@ -1037,6 +1038,31 @@ def coordinate_derivative(self, o): o = o.ufl_operands return CoordinateDerivative(map_expr_dag(self, o[0]), o[1], o[2], o[3]) + # -- Handlers for BaseForm objects -- # + + def cofunction(self, o): + # Same rule than for Coefficient except that we use a Coargument. + # The coargument is already attached to the class (self._v) + # which `self.coefficient` relies on. + dc = self.coefficient(o) + if dc == 0: + # Convert ufl.Zero into ZeroBaseForm + return ZeroBaseForm(self._v) + return dc + + def coargument(self, o): + # Same rule than for Argument (da/dw == 0). + dc = self.argument(o) + if dc == 0: + # Convert ufl.Zero into ZeroBaseForm + return ZeroBaseForm(o.arguments() + self._v) + return dc + + def matrix(self, M): + # Matrix rule: D_w[v](M) = v if M == w else 0 + # We can't differentiate wrt a matrix so always return zero in the appropriate space + return ZeroBaseForm(M.arguments() + self._v) + class DerivativeRuleDispatcher(MultiFunction): def __init__(self): @@ -1051,7 +1077,7 @@ def terminal(self, o): def derivative(self, o): error("Missing derivative handler for {0}.".format(type(o).__name__)) - expr = MultiFunction.reuse_if_untouched + ufl_type = MultiFunction.reuse_if_untouched def grad(self, o, f): rules = GradRuleset(o.ufl_shape[-1]) diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index d11961ca3..765b5163b 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -428,6 +428,8 @@ def compute_energy_norm(form, coefficient): Arguments, and one additional Coefficient at the end if no coefficient has been provided. """ + from ufl.formoperators import action # Delayed import to avoid circularity + arguments = form.arguments() parts = [arg.part() for arg in arguments] @@ -447,7 +449,7 @@ def compute_energy_norm(form, coefficient): if coefficient.ufl_function_space() != U: error("Trying to compute action of form on a " "coefficient in an incompatible element space.") - return replace(form, {u: coefficient, v: coefficient}) + return action(action(form, coefficient), coefficient) def compute_form_adjoint(form, reordered_arguments=None): diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 0846218f4..728b35f47 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -15,7 +15,9 @@ from ufl.core.expr import Expr from ufl.corealg.map_dag import map_expr_dag from ufl.integral import Integral -from ufl.form import Form +from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm +from ufl.action import Action +from ufl.adjoint import Adjoint from ufl.constantvalue import Zero @@ -35,7 +37,25 @@ def map_integrands(function, form, only_integral_type=None): return itg.reconstruct(function(itg.integrand())) else: return itg - elif isinstance(form, Expr): + elif isinstance(form, FormSum): + mapped_components = [map_integrands(function, component, only_integral_type) + for component in form.components()] + nonzero_components = [(component, 1) for component in mapped_components + # Catch ufl.Zero and ZeroBaseForm + if component != 0] + return FormSum(*nonzero_components) + elif isinstance(form, Adjoint): + # Zeros are caught inside `Adjoint.__new__` + return Adjoint(map_integrands(function, form._form, only_integral_type)) + elif isinstance(form, Action): + left = map_integrands(function, form._left, only_integral_type) + right = map_integrands(function, form._right, only_integral_type) + # Zeros are caught inside `Action.__new__` + return Action(left, right) + elif isinstance(form, ZeroBaseForm): + arguments = tuple(map_integrands(function, arg, only_integral_type) for arg in form._arguments) + return ZeroBaseForm(arguments) + elif isinstance(form, (Expr, BaseForm)): integrand = form return function(integrand) else: diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 39c4bd9e3..e3a8fb815 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -24,7 +24,7 @@ def __init__(self, mapping): if not all(k.ufl_shape == v.ufl_shape for k, v in mapping.items()): error("Replacement expressions must have the same shape as what they replace.") - def expr(self, o, *args): + def ufl_type(self, o, *args): try: return self.mapping[o] except KeyError: diff --git a/ufl/algorithms/transformer.py b/ufl/algorithms/transformer.py index 7d4f9a87a..5587aff35 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -16,6 +16,7 @@ from ufl.algorithms.map_integrands import map_integrands from ufl.classes import Variable, all_ufl_classes +from ufl.core.ufl_type import UFLType from ufl.log import error @@ -50,7 +51,13 @@ def __init__(self, variable_cache=None): for c in classobject.mro(): # Register classobject with handler for the first # encountered superclass - handler_name = c._ufl_handler_name_ + try: + handler_name = c._ufl_handler_name_ + except AttributeError as attribute_error: + if type(classobject) is not UFLType: + raise attribute_error + # Default handler name for UFL types + handler_name = UFLType._ufl_handler_name_ function = getattr(self, handler_name, None) if function: cache_data[ @@ -135,8 +142,8 @@ def always_reconstruct(self, o, *operands): "Always reconstruct expr." return o._ufl_expr_reconstruct_(*operands) - # Set default behaviour for any Expr - expr = undefined + # Set default behaviour for any UFLType + ufl_type = undefined # Set default behaviour for any Terminal terminal = reuse diff --git a/ufl/algorithms/traversal.py b/ufl/algorithms/traversal.py index 4623257e7..809ca1ca3 100644 --- a/ufl/algorithms/traversal.py +++ b/ufl/algorithms/traversal.py @@ -12,7 +12,9 @@ from ufl.log import error from ufl.core.expr import Expr from ufl.integral import Integral -from ufl.form import Form +from ufl.action import Action +from ufl.adjoint import Adjoint +from ufl.form import Form, FormSum, BaseForm # --- Traversal utilities --- @@ -24,11 +26,16 @@ def iter_expressions(a): - a is an Expr: (a,) - a is an Integral: the integrand expression of a - a is a Form: all integrand expressions of all integrals + - a is a FormSum: the components of a + - a is an Action: the left and right component of a + - a is an Adjoint: the underlying form of a """ if isinstance(a, Form): return (itg.integrand() for itg in a.integrals()) elif isinstance(a, Integral): return (a.integrand(),) - elif isinstance(a, Expr): + elif isinstance(a, (FormSum, Adjoint, Action)): + return tuple(e for op in a.ufl_operands for e in iter_expressions(op)) + elif isinstance(a, (Expr, BaseForm)): return (a,) error("Not an UFL type: %s" % str(type(a))) diff --git a/ufl/argument.py b/ufl/argument.py index 27f0a4de4..4c53c00d8 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -19,7 +19,9 @@ from ufl.split_functions import split from ufl.finiteelement import FiniteElementBase from ufl.domain import default_domain +from ufl.form import BaseForm from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace +from ufl.duals import is_primal, is_dual # Export list for ufl.classes (TODO: not actually classes: drop? these are in ufl.*) __all_classes__ = ["TestFunction", "TrialFunction", "TestFunctions", "TrialFunctions"] @@ -27,19 +29,15 @@ # --- Class representing an argument (basis function) in a form --- -@ufl_type() -class Argument(FormArgument): +class BaseArgument(object): """UFL value: Representation of an argument to a form.""" - __slots__ = ( - "_ufl_function_space", - "_ufl_shape", - "_number", - "_part", - "_repr", - ) + __slots__ = () + _ufl_is_abstract_ = True + + def __getnewargs__(self): + return (self._ufl_function_space, self._number, self._part) def __init__(self, function_space, number, part=None): - FormArgument.__init__(self) if isinstance(function_space, FiniteElementBase): # For legacy support for UFL files using cells, we map the cell to @@ -60,7 +58,7 @@ def __init__(self, function_space, number, part=None): self._number = number self._part = part - self._repr = "Argument(%s, %s, %s)" % ( + self._repr = "BaseArgument(%s, %s, %s)" % ( repr(self._ufl_function_space), repr(self._number), repr(self._part)) @property @@ -138,6 +136,95 @@ def __eq__(self, other): self._part == other._part and self._ufl_function_space == other._ufl_function_space) + +@ufl_type() +class Argument(FormArgument, BaseArgument): + """UFL value: Representation of an argument to a form.""" + __slots__ = ( + "_ufl_function_space", + "_ufl_shape", + "_number", + "_part", + "_repr", + ) + + _primal = True + _dual = False + + __getnewargs__ = BaseArgument.__getnewargs__ + __str__ = BaseArgument.__str__ + _ufl_signature_data_ = BaseArgument._ufl_signature_data_ + + def __new__(cls, *args, **kw): + if args[0] and is_dual(args[0]): + return Coargument(*args, **kw) + return super().__new__(cls) + + def __init__(self, function_space, number, part=None): + FormArgument.__init__(self) + BaseArgument.__init__(self, function_space, number, part) + + self._repr = "Argument(%s, %s, %s)" % ( + repr(self._ufl_function_space), repr(self._number), repr(self._part)) + + def ufl_domains(self): + return BaseArgument.ufl_domains(self) + + def __repr__(self): + return self._repr + + +@ufl_type() +class Coargument(BaseForm, BaseArgument): + """UFL value: Representation of an argument to a form in a dual space.""" + __slots__ = ( + "_ufl_function_space", + "_ufl_shape", + "_arguments", + "ufl_operands", + "_number", + "_part", + "_repr", + "_hash" + ) + + _primal = False + _dual = True + + def __new__(cls, *args, **kw): + if args[0] and is_primal(args[0]): + raise ValueError('ufl.Coargument takes in a dual space! If you want to define an argument in the primal space you should use ufl.Argument.') + return super().__new__(cls) + + def __init__(self, function_space, number, part=None): + BaseArgument.__init__(self, function_space, number, part) + BaseForm.__init__(self) + + self.ufl_operands = () + self._hash = None + self._repr = "Coargument(%s, %s, %s)" % ( + repr(self._ufl_function_space), repr(self._number), repr(self._part)) + + def _analyze_form_arguments(self): + "Analyze which Argument and Coefficient objects can be found in the form." + # Define canonical numbering of arguments and coefficients + self._arguments = (Argument(self._ufl_function_space, 0),) + + def equals(self, other): + if type(other) is not Coargument: + return False + if self is other: + return True + return (self._ufl_function_space == other._ufl_function_space and + self._number == other._number and self._part == other._part) + + def __hash__(self): + """Hash code for use in dicts.""" + return hash(("Coargument", + hash(self._ufl_function_space), + self._number, + self._part)) + # --- Helper functions for pretty syntax --- diff --git a/ufl/coefficient.py b/ufl/coefficient.py index dd6936cab..9caed09bd 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -18,24 +18,29 @@ from ufl.finiteelement import FiniteElementBase from ufl.domain import default_domain from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace +from ufl.form import BaseForm from ufl.split_functions import split from ufl.utils.counted import counted_init +from ufl.duals import is_primal, is_dual # --- The Coefficient class represents a coefficient in a form --- -@ufl_type() -class Coefficient(FormArgument): - """UFL form argument type: Representation of a form coefficient.""" +class BaseCoefficient(object): + """UFL form argument type: Parent Representation of a form coefficient.""" # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: # __slots__ = ("_count", "_ufl_function_space", "_repr", "_ufl_shape") _ufl_noslots_ = True + __slots__ = () _globalcount = 0 + _ufl_is_abstract_ = True + + def __getnewargs__(self): + return (self._ufl_function_space, self._count) def __init__(self, function_space, count=None): - FormArgument.__init__(self) counted_init(self, count, Coefficient) if isinstance(function_space, FiniteElementBase): @@ -50,7 +55,7 @@ def __init__(self, function_space, count=None): self._ufl_function_space = function_space self._ufl_shape = function_space.ufl_element().value_shape() - self._repr = "Coefficient(%s, %s)" % ( + self._repr = "BaseCoefficient(%s, %s)" % ( repr(self._ufl_function_space), repr(self._count)) def count(self): @@ -93,6 +98,94 @@ def __str__(self): def __repr__(self): return self._repr + def __eq__(self, other): + if not isinstance(other, BaseCoefficient): + return False + if self is other: + return True + return (self._count == other._count and + self._ufl_function_space == other._ufl_function_space) + + +@ufl_type() +class Cofunction(BaseCoefficient, BaseForm): + """UFL form argument type: Representation of a form coefficient from a dual space.""" + + __slots__ = ( + "_count", + "_arguments", + "_ufl_function_space", + "ufl_operands", + "_repr", + "_ufl_shape", + "_hash" + ) + # _globalcount = 0 + _primal = False + _dual = True + + def __new__(cls, *args, **kw): + if args[0] and is_primal(args[0]): + raise ValueError('ufl.Cofunction takes in a dual space. If you want to define a coefficient in the primal space you should use ufl.Coefficient.') + return super().__new__(cls) + + def __init__(self, function_space, count=None): + BaseCoefficient.__init__(self, function_space, count) + BaseForm.__init__(self) + + self.ufl_operands = () + self._hash = None + self._repr = "Cofunction(%s, %s)" % ( + repr(self._ufl_function_space), repr(self._count)) + + def equals(self, other): + if type(other) is not Cofunction: + return False + if self is other: + return True + return (self._count == other._count and + self._ufl_function_space == other._ufl_function_space) + + def __hash__(self): + """Hash code for use in dicts.""" + return hash(("Cofunction", + hash(self._ufl_function_space), + self._count)) + + def _analyze_form_arguments(self): + "Analyze which Argument and Coefficient objects can be found in the form." + # Define canonical numbering of arguments and coefficients + self._arguments = () + + +@ufl_type() +class Coefficient(FormArgument, BaseCoefficient): + """UFL form argument type: Representation of a form coefficient.""" + + _ufl_noslots_ = True + _globalcount = 0 + _primal = True + _dual = False + + __getnewargs__ = BaseCoefficient.__getnewargs__ + __str__ = BaseCoefficient.__str__ + _ufl_signature_data_ = BaseCoefficient._ufl_signature_data_ + + def __new__(cls, *args, **kw): + if args[0] and is_dual(args[0]): + return Cofunction(*args, **kw) + return super().__new__(cls) + + def __init__(self, function_space, count=None): + FormArgument.__init__(self) + BaseCoefficient.__init__(self, function_space, count) + + self._repr = "Coefficient(%s, %s)" % ( + repr(self._ufl_function_space), repr(self._count)) + + def ufl_domains(self): + return BaseCoefficient.ufl_domains(self) + def __eq__(self, other): if not isinstance(other, Coefficient): return False @@ -101,6 +194,9 @@ def __eq__(self, other): return (self._count == other._count and self._ufl_function_space == other._ufl_function_space) + def __repr__(self): + return self._repr + # --- Helper functions for subfunctions on mixed elements --- @@ -108,7 +204,7 @@ def Coefficients(function_space): """UFL value: Create a Coefficient in a mixed space, and return a tuple with the function components corresponding to the subelements.""" if isinstance(function_space, MixedFunctionSpace): - return [Coefficient(function_space.ufl_sub_space(i)) - for i in range(function_space.num_sub_spaces())] + return [Coefficient(fs) if is_primal(fs) else Cofunction(fs) + for fs in function_space.num_sub_spaces()] else: return split(Coefficient(function_space)) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 0f0a311a7..b8cf5add1 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -12,6 +12,7 @@ from math import atan2 +import ufl from ufl.log import error, UFLValueError from ufl.core.expr import Expr from ufl.core.terminal import Terminal @@ -425,7 +426,7 @@ def __eps(self, x): def as_ufl(expression): "Converts expression to an Expr if possible." - if isinstance(expression, Expr): + if isinstance(expression, (Expr, ufl.BaseForm)): return expression elif isinstance(expression, complex): return ComplexValue(expression) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index e6c355603..b856c3d61 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -22,11 +22,12 @@ import warnings from ufl.log import error +from ufl.core.ufl_type import UFLType, update_ufl_type_attributes -# --- The base object for all UFL expression tree nodes --- +# --- The base object for all UFL expression tree nodes --- -class Expr(object): +class Expr(object, metaclass=UFLType): """Base class for all UFL expression types. *Instance properties* @@ -130,22 +131,10 @@ def __init__(self): # implement for this type in a multifunction. _ufl_handler_name_ = "expr" - # The integer typecode, a contiguous index different for each - # type. This is used for fast lookup into e.g. multifunction - # handler tables. - _ufl_typecode_ = 0 - # Number of operands, "varying" for some types, or None if not # applicable for abstract types. _ufl_num_ops_ = None - # Type trait: If the type is abstract. An abstract class cannot - # be instantiated and does not need all properties specified. - _ufl_is_abstract_ = True - - # Type trait: If the type is terminal. - _ufl_is_terminal_ = None - # Type trait: If the type is a literal. _ufl_is_literal_ = None @@ -229,15 +218,6 @@ def __init__(self): # --- Global variables for collecting all types --- - # A global counter of the number of typecodes assigned - _ufl_num_typecodes_ = 1 - - # A global set of all handler names added - _ufl_all_handler_names_ = set() - - # A global array of all Expr subclasses, indexed by typecode - _ufl_all_classes_ = [] - # A global dict mapping language_operator_name to the type it # produces _ufl_language_operators_ = {} @@ -247,14 +227,6 @@ def __init__(self): # --- Mechanism for profiling object creation and deletion --- - # A global array of the number of initialized objects for each - # typecode - _ufl_obj_init_counts_ = [0] - - # A global array of the number of deleted objects for each - # typecode - _ufl_obj_del_counts_ = [0] - # Backup of default init and del _ufl_regular__init__ = __init__ @@ -424,8 +396,10 @@ def __round__(self, n=None): # Initializing traits here because Expr is not defined in the class # declaration Expr._ufl_class_ = Expr -Expr._ufl_all_handler_names_.add(Expr) -Expr._ufl_all_classes_.append(Expr) + +# Update Expr with metaclass properties (e.g. typecode or handler name) +# Explicitly done here instead of using `@ufl_type` to avoid circular imports. +update_ufl_type_attributes(Expr) def ufl_err_str(expr): diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 5b0840268..0f81e6bd1 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -99,7 +99,7 @@ def __eq__(self, other): @ufl_type(is_abstract=True) class FormArgument(Terminal): - "An abstract class for a form argument." + "An abstract class for a form argument (a thing in a primal finite element space)." __slots__ = () def __init__(self): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 9c4468a6f..e6435684c 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -8,9 +8,10 @@ # # Modified by Massimiliano Leoni, 2016 -from ufl.core.expr import Expr from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore +# Avoid circular import +import ufl.core as core # Make UFL type coercion available under the as_ufl name @@ -98,12 +99,12 @@ def check_is_terminal_consistency(cls): def check_abstract_trait_consistency(cls): "Check that the first base classes up to ``Expr`` are other UFL types." for base in cls.mro(): - if base is Expr: + if base is core.expr.Expr: break - if not issubclass(base, Expr) and base._ufl_is_abstract_: + if not issubclass(base, core.expr.Expr) and base._ufl_is_abstract_: msg = ("Base class {0.__name__} of class {1.__name__} " "is not an abstract subclass of {2.__name__}.") - raise TypeError(msg.format(base, cls, Expr)) + raise TypeError(msg.format(base, cls, core.expr.Expr)) def check_has_slots(cls): @@ -129,6 +130,7 @@ def check_type_traits_consistency(cls): "Execute a variety of consistency checks on the ufl type traits." # Check for consistency in global type collection sizes + Expr = core.expr.Expr assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_classes_) assert Expr._ufl_num_typecodes_ == len(Expr._ufl_obj_init_counts_) @@ -161,7 +163,7 @@ def check_type_traits_consistency(cls): def check_implements_required_methods(cls): """Check if type implements the required methods.""" if not cls._ufl_is_abstract_: - for attr in Expr._ufl_required_methods_: + for attr in core.expr.Expr._ufl_required_methods_: if not hasattr(cls, attr): msg = "Class {0.__name__} has no {1} method." raise TypeError(msg.format(cls, attr)) @@ -173,7 +175,7 @@ def check_implements_required_methods(cls): def check_implements_required_properties(cls): "Check if type implements the required properties." if not cls._ufl_is_abstract_: - for attr in Expr._ufl_required_properties_: + for attr in core.expr.Expr._ufl_required_properties_: if not hasattr(cls, attr): msg = "Class {0.__name__} has no {1} property." raise TypeError(msg.format(cls, attr)) @@ -214,11 +216,8 @@ def _inherited_ufl_index_dimensions(self): def update_global_expr_attributes(cls): "Update global ``Expr`` attributes, mainly by adding *cls* to global collections of ufl types." - Expr._ufl_all_classes_.append(cls) - Expr._ufl_all_handler_names_.add(cls._ufl_handler_name_) - if cls._ufl_is_terminal_modifier_: - Expr._ufl_terminal_modifiers_.append(cls) + core.expr.Expr._ufl_terminal_modifiers_.append(cls) # Add to collection of language operators. This collection is # used later to populate the official language namespace. @@ -226,12 +225,24 @@ def update_global_expr_attributes(cls): # it out later. if not cls._ufl_is_abstract_ and hasattr(cls, "_ufl_function_"): cls._ufl_function_.__func__.__doc__ = cls.__doc__ - Expr._ufl_language_operators_[cls._ufl_handler_name_] = cls._ufl_function_ + core.expr.Expr._ufl_language_operators_[cls._ufl_handler_name_] = cls._ufl_function_ + + +def update_ufl_type_attributes(cls): + # Determine integer typecode by incrementally counting all types + cls._ufl_typecode_ = UFLType._ufl_num_typecodes_ + UFLType._ufl_num_typecodes_ += 1 + + UFLType._ufl_all_classes_.append(cls) + + # Determine handler name by a mapping from "TypeName" to "type_name" + cls._ufl_handler_name_ = camel2underscore(cls.__name__) + UFLType._ufl_all_handler_names_.add(cls._ufl_handler_name_) # Append space for counting object creation and destriction of # this this type. - Expr._ufl_obj_init_counts_.append(0) - Expr._ufl_obj_del_counts_.append(0) + UFLType._ufl_obj_init_counts_.append(0) + UFLType._ufl_obj_del_counts_.append(0) def ufl_type(is_abstract=False, @@ -253,7 +264,7 @@ def ufl_type(is_abstract=False, unop=None, binop=None, rbinop=None): - """This decorator is to be applied to every subclass in the UFL ``Expr`` hierarchy. + """This decorator is to be applied to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. This decorator contains a number of checks that are intended to enforce uniform behaviour across UFL types. @@ -264,14 +275,14 @@ def ufl_type(is_abstract=False, """ def _ufl_type_decorator_(cls): - # Determine integer typecode by oncrementally counting all types - typecode = Expr._ufl_num_typecodes_ - Expr._ufl_num_typecodes_ += 1 - # Determine handler name by a mapping from "TypeName" to "type_name" - handler_name = camel2underscore(cls.__name__) + # Update attributes for UFLType instances (BaseForm and Expr objects) + update_ufl_type_attributes(cls) + if not issubclass(cls, core.expr.Expr): + # Don't need anything else for non Expr subclasses + return cls - # is_scalar implies is_index_free + # is_scalar implies is_index_freeg if is_scalar: _is_index_free = True else: @@ -279,8 +290,6 @@ def _ufl_type_decorator_(cls): # Store type traits cls._ufl_class_ = cls - set_trait(cls, "handler_name", handler_name, inherit=False) - set_trait(cls, "typecode", typecode, inherit=False) set_trait(cls, "is_abstract", is_abstract, inherit=False) set_trait(cls, "is_terminal", is_terminal, inherit=True) @@ -372,3 +381,37 @@ def _ufl_expr_rbinop_(self, other): return cls return _ufl_type_decorator_ + + +class UFLType(type): + """Base class for all UFL types. + + Equip UFL types with some ufl specific properties. + """ + + # A global counter of the number of typecodes assigned. + _ufl_num_typecodes_ = 0 + + # Set the handler name for UFLType + _ufl_handler_name_ = "ufl_type" + + # A global array of all Expr and BaseForm subclasses, indexed by typecode + _ufl_all_classes_ = [] + + # A global set of all handler names added + _ufl_all_handler_names_ = set() + + # A global array of the number of initialized objects for each + # typecode + _ufl_obj_init_counts_ = [] + + # A global array of the number of deleted objects for each + # typecode + _ufl_obj_del_counts_ = [] + + # Type trait: If the type is abstract. An abstract class cannot + # be instantiated and does not need all properties specified. + _ufl_is_abstract_ = True + + # Type trait: If the type is terminal. + _ufl_is_terminal_ = None diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index 16b08a3ba..e4d9f6ca6 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -13,6 +13,7 @@ from ufl.log import error from ufl.core.expr import Expr +from ufl.core.ufl_type import UFLType def get_num_args(function): @@ -66,7 +67,14 @@ def __init__(self): for c in classobject.mro(): # Register classobject with handler for the first # encountered superclass - handler_name = c._ufl_handler_name_ + try: + handler_name = c._ufl_handler_name_ + except AttributeError as attribute_error: + if type(classobject) is not UFLType: + raise attribute_error + # Default handler name for UFL types + handler_name = UFLType._ufl_handler_name_ + if hasattr(self, handler_name): handler_names[classobject._ufl_typecode_] = handler_name break @@ -107,5 +115,5 @@ def reuse_if_untouched(self, o, *ops): else: return o._ufl_expr_reconstruct_(*ops) - # Set default behaviour for any Expr as undefined - expr = undefined + # Set default behaviour for any UFLType as undefined + ufl_type = undefined diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 049dd2685..8f678a9d3 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -15,6 +15,7 @@ from ufl.core.ufl_type import ufl_type from ufl.domain import extract_unique_domain, find_geometric_dimension from ufl.exprcontainers import ExprList, ExprMapping +from ufl.form import BaseForm from ufl.log import error from ufl.precedence import parstr from ufl.variable import Variable @@ -76,6 +77,25 @@ def __str__(self): self.ufl_operands[2], self.ufl_operands[3]) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, + inherit_indices_from_operand=0) +class BaseFormDerivative(CoefficientDerivative, BaseForm): + """Derivative of a base form w.r.t the + degrees of freedom in a discrete Coefficient.""" + _ufl_noslots_ = True + + def __init__(self, base_form, coefficients, arguments, + coefficient_derivatives): + CoefficientDerivative.__init__(self, base_form, coefficients, arguments, + coefficient_derivatives) + BaseForm.__init__(self) + + def _analyze_form_arguments(self): + """Collect the arguments of the corresponding BaseForm""" + base_form = self.ufl_operands[0] + self._arguments = base_form.arguments() + + @ufl_type(num_ops=2) class VariableDerivative(Derivative): __slots__ = ( diff --git a/ufl/duals.py b/ufl/duals.py new file mode 100644 index 000000000..c0f1b15dc --- /dev/null +++ b/ufl/duals.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""Predicates for recognising duals""" + +# Copyright (C) 2021 India Marsden +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + + +def is_primal(object): + """Determine if the object belongs to a primal space + + This is not simply the negation of :func:`is_dual`, + because a mixed function space containing both primal + and dual components is neither primal nor dual.""" + return hasattr(object, '_primal') and object._primal + + +def is_dual(object): + """Determine if the object belongs to a dual space + + This is not simply the negation of :func:`is_primal`, + because a mixed function space containing both primal + and dual components is neither primal nor dual.""" + return hasattr(object, '_dual') and object._dual diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index 119321b01..c50050953 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -11,6 +11,8 @@ from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type +from ufl.coefficient import Cofunction +from ufl.argument import Coargument # --- Non-tensor types --- @@ -22,8 +24,9 @@ class ExprList(Operator): def __init__(self, *operands): Operator.__init__(self, operands) - if not all(isinstance(i, Expr) for i in operands): - error("Expecting Expr in ExprList.") + # Enable Cofunction/Coargument for BaseForm differentiation + if not all(isinstance(i, (Expr, Cofunction, Coargument)) for i in operands): + error("Expecting Expr, Cofunction or Coargument in ExprList.") def __getitem__(self, i): return self.ufl_operands[i] diff --git a/ufl/form.py b/ufl/form.py index 838a93a1b..a1959de5f 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -20,12 +20,12 @@ from ufl.integral import Integral from ufl.checks import is_scalar_constant_expression from ufl.equation import Equation -from ufl.core.expr import Expr -from ufl.core.expr import ufl_err_str +from ufl.core.expr import Expr, ufl_err_str +from ufl.core.ufl_type import UFLType, ufl_type from ufl.constantvalue import Zero # Export list for ufl.classes -__all_classes__ = ["Form"] +__all_classes__ = ["Form", "BaseForm", "ZeroBaseForm"] # --- The Form class, representing a complete variational form or functional --- @@ -70,7 +70,189 @@ def _sorted_integrals(integrals): return tuple(all_integrals) # integrals_dict -class Form(object): +@ufl_type() +class BaseForm(object, metaclass=UFLType): + """Description of an object containing arguments""" + + # Slots is kept empty to enable multiple inheritance with other classes. + __slots__ = () + _ufl_is_abstract_ = True + _ufl_required_methods_ = ('_analyze_form_arguments', "ufl_domains") + + def __init__(self): + # Internal variables for caching form argument data + self._arguments = None + + # --- Accessor interface --- + def arguments(self): + "Return all ``Argument`` objects found in form." + if self._arguments is None: + self._analyze_form_arguments() + return self._arguments + + # --- Operator implementations --- + + def __eq__(self, other): + """Delayed evaluation of the == operator! + + Just 'lhs_form == rhs_form' gives an Equation, + while 'bool(lhs_form == rhs_form)' delegates + to lhs_form.equals(rhs_form). + """ + return Equation(self, other) + + def __radd__(self, other): + # Ordering of form additions make no difference + return self.__add__(other) + + def __add__(self, other): + if isinstance(other, (int, float)) and other == 0: + # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) + return self + + elif isinstance( + other, + Zero) and not (other.ufl_shape or other.ufl_free_indices): + # Allow adding ufl Zero as a no-op, needed for sum([a,b]) + return self + + elif isinstance(other, ZeroBaseForm): + self._check_arguments_sum(other) + # Simplify addition with ZeroBaseForm + return self + + # For `ZeroBaseForm(...) + B` with B a BaseForm. + # We could overwrite ZeroBaseForm.__add__ but that implies + # duplicating cases with `0` and `ufl.Zero`. + elif isinstance(self, ZeroBaseForm): + self._check_arguments_sum(other) + # Simplify addition with ZeroBaseForm + return other + + elif isinstance(other, BaseForm): + # Add integrals from both forms + return FormSum((self, 1), (other, 1)) + + else: + # Let python protocols do their job if we don't handle it + return NotImplemented + + def _check_arguments_sum(self, other): + # Get component with the highest number of arguments + a = max((self, other), key=lambda x: len(x.arguments())) + b = self if a is other else other + # Components don't necessarily have the exact same arguments + # but the first argument(s) need to match as for `a + L` + # where a and L are a bilinear and linear form respectively. + a_args = sorted(a.arguments(), key=lambda x: x.number()) + b_args = sorted(b.arguments(), key=lambda x: x.number()) + if b_args != a_args[:len(b_args)]: + raise ValueError('Mismatching arguments when summing:\n %s\n and\n %s' % (self, other)) + + def __sub__(self, other): + "Subtract other form from this one." + return self + (-other) + + def __rsub__(self, other): + "Subtract this form from other." + return other + (-self) + + def __neg__(self): + """Negate all integrals in form. + + This enables the handy "-form" syntax for e.g. the + linearized system (J, -F) from a nonlinear form F.""" + if isinstance(self, ZeroBaseForm): + # `-` doesn't change anything for ZeroBaseForm. + # This also facilitates simplifying FormSum containing ZeroBaseForm objects. + return self + return FormSum((self, -1)) + + def __rmul__(self, scalar): + "Multiply all integrals in form with constant scalar value." + # This enables the handy "0*form" or "dt*form" syntax + if is_scalar_constant_expression(scalar): + return FormSum((self, scalar)) + return NotImplemented + + def __mul__(self, coefficient): + "Take the action of this form on the given coefficient." + if isinstance(coefficient, Expr): + from ufl.formoperators import action + return action(self, coefficient) + return NotImplemented + + def __ne__(self, other): + "Immediately evaluate the != operator (as opposed to the == operator)." + return not self.equals(other) + + def __call__(self, *args, **kwargs): + """Evaluate form by replacing arguments and coefficients. + + Replaces form.arguments() with given positional arguments in + same number and ordering. Number of positional arguments must + be 0 or equal to the number of Arguments in the form. + + The optional keyword argument coefficients can be set to a dict + to replace Coefficients with expressions of matching shapes. + + Example: + ------- + V = FiniteElement("CG", triangle, 1) + v = TestFunction(V) + u = TrialFunction(V) + f = Coefficient(V) + g = Coefficient(V) + a = g*inner(grad(u), grad(v))*dx + M = a(f, f, coefficients={ g: 1 }) + + Is equivalent to M == grad(f)**2*dx. + + """ + repdict = {} + + if args: + arguments = self.arguments() + if len(arguments) != len(args): + error("Need %d arguments to form(), got %d." % (len(arguments), + len(args))) + repdict.update(zip(arguments, args)) + + coefficients = kwargs.pop("coefficients") + if kwargs: + error("Unknown kwargs %s." % str(list(kwargs))) + + if coefficients is not None: + coeffs = self.coefficients() + for f in coefficients: + if f in coeffs: + repdict[f] = coefficients[f] + else: + warnings("Coefficient %s is not in form." % ufl_err_str(f)) + if repdict: + from ufl.formoperators import replace + return replace(self, repdict) + else: + return self + + def _ufl_compute_hash_(self): + "Compute the hash" + # Ensure compatibility with MultiFunction + # `hash(self)` will call the `__hash__` method of the subclass. + return hash(self) + + def _ufl_expr_reconstruct_(self, *operands): + "Return a new object of the same type with new operands." + return type(self)(*operands) + + # "a @ f" notation in python 3.5 + __matmul__ = __mul__ + + # --- String conversion functions, for UI purposes only --- + + +@ufl_type() +class Form(BaseForm): """Description of a weak form consisting of a sum of integrals over subdomains.""" __slots__ = ( # --- List of Integral objects (a Form is a sum of these Integrals, everything else is derived) @@ -93,6 +275,7 @@ class Form(object): ) def __init__(self, integrals): + BaseForm.__init__(self) # Basic input checking (further compatibilty analysis happens # later) if not all(isinstance(itg, Integral) for itg in integrals): @@ -110,7 +293,6 @@ def __init__(self, integrals): self._subdomain_data = None # Internal variables for caching form argument data - self._arguments = None self._coefficients = None self._coefficient_numbering = None self._constant_numbering = None @@ -261,15 +443,6 @@ def __hash__(self): self._hash = hash(tuple(hash(itg) for itg in self.integrals())) return self._hash - def __eq__(self, other): - """Delayed evaluation of the == operator! - - Just 'lhs_form == rhs_form' gives an Equation, - while 'bool(lhs_form == rhs_form)' delegates - to lhs_form.equals(rhs_form). - """ - return Equation(self, other) - def __ne__(self, other): "Immediate evaluation of the != operator (as opposed to the == operator)." return not self.equals(other) @@ -293,6 +466,15 @@ def __add__(self, other): # Add integrals from both forms return Form(list(chain(self.integrals(), other.integrals()))) + if isinstance(other, ZeroBaseForm): + self._check_arguments_sum(other) + # Simplify addition with ZeroBaseForm + return self + + elif isinstance(other, BaseForm): + # Create form sum if form is of other type + return FormSum((self, 1), (other, 1)) + elif isinstance(other, (int, float)) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self @@ -512,7 +694,7 @@ def sub_forms_by_domain(form): def as_form(form): "Convert to form if not a form, otherwise return form." - if not isinstance(form, Form): + if not isinstance(form, BaseForm): error("Unable to convert object to a UFL form: %s" % ufl_err_str(form)) return form @@ -547,3 +729,155 @@ def replace_integral_domains(form, common_domain): # TODO: Move elsewhere if reconstruct: form = Form(integrals) return form + + +@ufl_type() +class FormSum(BaseForm): + """Description of a weighted sum of variational forms and form-like objects + components is the list of Forms to be summed + arg_weights is a list of tuples of component index and weight""" + + __slots__ = ("_arguments", + "_weights", + "_components", + "ufl_operands", + "_domains", + "_domain_numbering", + "_hash") + _ufl_required_methods_ = ('_analyze_form_arguments') + + def __init__(self, *components): + BaseForm.__init__(self) + + weights = [] + full_components = [] + for (component, w) in components: + if isinstance(component, FormSum): + full_components.extend(component.components()) + weights.extend(w * component.weights()) + else: + full_components.append(component) + weights.append(w) + + self._arguments = None + self._domains = None + self._domain_numbering = None + self._hash = None + self._weights = weights + self._components = full_components + self._sum_variational_components() + self.ufl_operands = self._components + + def components(self): + return self._components + + def weights(self): + return self._weights + + def _sum_variational_components(self): + var_forms = None + other_components = [] + new_weights = [] + for (i, component) in enumerate(self._components): + if isinstance(component, Form): + if var_forms: + var_forms = var_forms + (self._weights[i] * component) + else: + var_forms = self._weights[i] * component + else: + other_components.append(component) + new_weights.append(self._weights[i]) + if var_forms: + other_components.insert(0, var_forms) + new_weights.insert(0, 1) + self._components = other_components + self._weights = new_weights + + def _analyze_form_arguments(self): + "Return all ``Argument`` objects found in form." + arguments = [] + for component in self._components: + arguments.extend(component.arguments()) + self._arguments = tuple(set(arguments)) + + def __hash__(self): + "Hash code for use in dicts (includes incidental numbering of indices etc.)" + if self._hash is None: + self._hash = hash(tuple(hash(component) for component in self.components())) + return self._hash + + def equals(self, other): + "Evaluate ``bool(lhs_form == rhs_form)``." + if type(other) != FormSum: + return False + if self is other: + return True + return (len(self.components()) == len(other.components()) and + all(a == b for a, b in zip(self.components(), other.components()))) + + def __str__(self): + "Compute shorter string representation of form. This can be huge for complicated forms." + # Warning used for making sure we don't use this in the general pipeline: + # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") + # Not caching this because it can be huge + s = "\n + ".join(str(component) for component in self.components()) + return s or "" + + def __repr__(self): + "Compute repr string of form. This can be huge for complicated forms." + # Warning used for making sure we don't use this in the general pipeline: + # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") + # Not caching this because it can be huge + itgs = ", ".join(repr(component) for component in self.components()) + r = "FormSum([" + itgs + "])" + return r + + +@ufl_type() +class ZeroBaseForm(BaseForm): + """Description of a zero base form. + ZeroBaseForm is idempotent with respect to assembly and is mostly used for sake of simplifying base-form expressions. + """ + + __slots__ = ("_arguments", + "_coefficients", + "ufl_operands", + "_hash", + # Pyadjoint compatibility + "form") + + def __init__(self, arguments): + BaseForm.__init__(self) + self._arguments = arguments + self.ufl_operands = arguments + self._hash = None + self.form = None + + def _analyze_form_arguments(self): + return self._arguments + + def __ne__(self, other): + # Overwrite BaseForm.__neq__ which relies on `equals` + return not self == other + + def __eq__(self, other): + if type(other) is ZeroBaseForm: + if self is other: + return True + return (self._arguments == other._arguments) + elif isinstance(other, (int, float)): + return other == 0 + else: + return False + + def __str__(self): + return "ZeroBaseForm(%s)" % (", ".join(str(arg) for arg in self._arguments)) + + def __repr__(self): + return "ZeroBaseForm(%s)" % (", ".join(repr(arg) for arg in self._arguments)) + + def __hash__(self): + """Hash code for use in dicts.""" + if self._hash is None: + self._hash = hash(("ZeroBaseForm", hash(self._arguments))) + return self._hash diff --git a/ufl/formoperators.py b/ufl/formoperators.py index 4fbfd7999..4a3f648ea 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -12,15 +12,17 @@ # Modified by Cecile Daversin-Catty, 2018 from ufl.log import error -from ufl.form import Form, as_form +from ufl.form import Form, FormSum, BaseForm, as_form from ufl.core.expr import Expr, ufl_err_str from ufl.split_functions import split from ufl.exprcontainers import ExprList, ExprMapping from ufl.variable import Variable from ufl.finiteelement import MixedElement from ufl.argument import Argument -from ufl.coefficient import Coefficient -from ufl.differentiation import CoefficientDerivative, CoordinateDerivative +from ufl.coefficient import Coefficient, Cofunction +from ufl.adjoint import Adjoint +from ufl.action import Action +from ufl.differentiation import CoefficientDerivative, BaseFormDerivative, CoordinateDerivative from ufl.constantvalue import is_true_ufl_scalar, as_ufl from ufl.indexed import Indexed from ufl.core.multiindex import FixedIndex, MultiIndex @@ -104,10 +106,14 @@ def action(form, coefficient=None): Given a bilinear form, return a linear form with an additional coefficient, representing the action of the form on the coefficient. This can be - used for matrix-free methods.""" + used for matrix-free methods. + For formbase objects,coefficient can be any object of the correct type, + and this function returns an Action object.""" form = as_form(form) - form = expand_derivatives(form) - return compute_form_action(form, coefficient) + if isinstance(form, Form) and not (isinstance(coefficient, BaseForm) and len(coefficient.arguments()) > 1): + form = expand_derivatives(form) + return compute_form_action(form, coefficient) + return Action(form, coefficient) def energy_norm(form, coefficient=None): @@ -129,10 +135,15 @@ def adjoint(form, reordered_arguments=None): opposite ordering. However, if the adjoint form is to be added to other forms later, their arguments must match. In that case, the user must provide a tuple *reordered_arguments*=(u2,v2). + + If the form is a baseform instance instead of a Form object, we return an Adjoint + object instructing the adjoint to be computed at a later point. """ form = as_form(form) - form = expand_derivatives(form) - return compute_form_adjoint(form, reordered_arguments) + if isinstance(form, Form): + form = expand_derivatives(form) + return compute_form_adjoint(form, reordered_arguments) + return Adjoint(form) def zero_lists(shape): @@ -162,7 +173,7 @@ def _handle_derivative_arguments(form, coefficient, argument): if argument is None: # Try to create argument if not provided - if not all(isinstance(c, Coefficient) for c in coefficients): + if not all(isinstance(c, (Coefficient, Cofunction)) for c in coefficients): error("Can only create arguments automatically for non-indexed coefficients.") # Get existing arguments from form and position the new one @@ -215,7 +226,7 @@ def _handle_derivative_arguments(form, coefficient, argument): for (c, a) in zip(coefficients, arguments): if c.ufl_shape != a.ufl_shape: error("Coefficient and argument shapes do not match!") - if isinstance(c, Coefficient) or isinstance(c, SpatialCoordinate): + if isinstance(c, (Coefficient, Cofunction, SpatialCoordinate)): m[c] = a else: if not isinstance(c, Indexed): @@ -269,9 +280,23 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): ``Coefficient`` instances to their derivatives w.r.t. *coefficient*. """ + if isinstance(form, FormSum): + # Distribute derivative over FormSum components + return FormSum(*[(derivative(component, coefficient, argument, coefficient_derivatives), 1) + for component in form.components()]) + elif isinstance(form, Adjoint): + # Push derivative through Adjoint + return adjoint(derivative(form._form, coefficient, argument, coefficient_derivatives)) + elif isinstance(form, Action): + # Push derivative through Action slots + left, right = form.ufl_operands + dleft = derivative(left, coefficient, argument, coefficient_derivatives) + dright = derivative(right, coefficient, argument, coefficient_derivatives) + # Leibniz formula + return action(dleft, right) + action(left, dright) + coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) - if coefficient_derivatives is None: coefficient_derivatives = ExprMapping() else: @@ -293,6 +318,10 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): integrals.append(itg.reconstruct(fd)) return Form(integrals) + elif isinstance(form, BaseForm): + if not isinstance(coefficient, SpatialCoordinate): + return BaseFormDerivative(form, coefficients, arguments, coefficient_derivatives) + elif isinstance(form, Expr): # What we got was in fact an integrand if not isinstance(coefficient, SpatialCoordinate): diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 2758b2e7d..6842d9ca2 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -13,11 +13,13 @@ from ufl.log import error from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.domain import join_domains +from ufl.duals import is_dual, is_primal # Export list for ufl.classes __all_classes__ = [ "AbstractFunctionSpace", "FunctionSpace", + "DualSpace", "MixedFunctionSpace", "TensorProductFunctionSpace", ] @@ -25,11 +27,14 @@ class AbstractFunctionSpace(object): def ufl_sub_spaces(self): - raise NotImplementedError("Missing implementation of IFunctionSpace.ufl_sub_spaces in %s." % self.__class__.__name__) + raise NotImplementedError( + "Missing implementation of IFunctionSpace.ufl_sub_spaces in %s." + % self.__class__.__name__ + ) @attach_operators_from_hash_data -class FunctionSpace(AbstractFunctionSpace): +class BaseFunctionSpace(AbstractFunctionSpace): def __init__(self, domain, element): if domain is None: # DOLFIN hack @@ -39,7 +44,8 @@ def __init__(self, domain, element): try: domain_cell = domain.ufl_cell() except AttributeError: - error("Expected non-abstract domain for initalization of function space.") + error("Expected non-abstract domain for initalization " + "of function space.") else: if element.cell() != domain_cell: error("Non-matching cell of finite element and domain.") @@ -68,7 +74,8 @@ def ufl_domains(self): else: return (domain,) - def _ufl_hash_data_(self): + def _ufl_hash_data_(self, name=None): + name = name or "BaseFunctionSpace" domain = self.ufl_domain() element = self.ufl_element() if domain is None: @@ -79,9 +86,10 @@ def _ufl_hash_data_(self): edata = None else: edata = element._ufl_hash_data_() - return ("FunctionSpace", ddata, edata) + return (name, ddata, edata) - def _ufl_signature_data_(self, renumbering): + def _ufl_signature_data_(self, renumbering, name=None): + name = name or "BaseFunctionSpace" domain = self.ufl_domain() element = self.ufl_element() if domain is None: @@ -92,10 +100,56 @@ def _ufl_signature_data_(self, renumbering): edata = None else: edata = element._ufl_signature_data_() - return ("FunctionSpace", ddata, edata) + return (name, ddata, edata) + + def __repr__(self): + r = "BaseFunctionSpace(%s, %s)" % (repr(self._ufl_domain), + repr(self._ufl_element)) + return r + + +@attach_operators_from_hash_data +class FunctionSpace(BaseFunctionSpace): + """Representation of a Function space.""" + _primal = True + _dual = False + + def dual(self): + return DualSpace(self._ufl_domain, self._ufl_element) + + def _ufl_hash_data_(self): + return BaseFunctionSpace._ufl_hash_data_(self, "FunctionSpace") + + def _ufl_signature_data_(self, renumbering): + return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "FunctionSpace") + + def __repr__(self): + r = "FunctionSpace(%s, %s)" % (repr(self._ufl_domain), + repr(self._ufl_element)) + return r + + +@attach_operators_from_hash_data +class DualSpace(BaseFunctionSpace): + """Representation of a Dual space.""" + _primal = False + _dual = True + + def __init__(self, domain, element): + BaseFunctionSpace.__init__(self, domain, element) + + def dual(self): + return FunctionSpace(self._ufl_domain, self._ufl_element) + + def _ufl_hash_data_(self): + return BaseFunctionSpace._ufl_hash_data_(self, "DualSpace") + + def _ufl_signature_data_(self, renumbering): + return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "DualSpace") def __repr__(self): - r = "FunctionSpace(%s, %s)" % (repr(self._ufl_domain), repr(self._ufl_element)) + r = "DualSpace(%s, %s)" % (repr(self._ufl_domain), + repr(self._ufl_element)) return r @@ -109,10 +163,13 @@ def ufl_sub_spaces(self): return self._ufl_function_spaces def _ufl_hash_data_(self): - return ("TensorProductFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) + return ("TensorProductFunctionSpace",) \ + + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): - return ("TensorProductFunctionSpace",) + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) + return ("TensorProductFunctionSpace",) \ + + tuple(V._ufl_signature_data_(renumbering) + for V in self.ufl_sub_spaces()) def __repr__(self): r = "TensorProductFunctionSpace(*%s)" % repr(self._ufl_function_spaces) @@ -121,15 +178,22 @@ def __repr__(self): @attach_operators_from_hash_data class MixedFunctionSpace(AbstractFunctionSpace): + def __init__(self, *args): AbstractFunctionSpace.__init__(self) self._ufl_function_spaces = args self._ufl_elements = list() for fs in args: - if isinstance(fs, FunctionSpace): + if isinstance(fs, BaseFunctionSpace): self._ufl_elements.append(fs.ufl_element()) else: - error("Expecting FunctionSpace objects") + error("Expecting BaseFunctionSpace objects") + + # A mixed FS is only primal/dual if all the subspaces are primal/dual" + self._primal = all([is_primal(subspace) + for subspace in self._ufl_function_spaces]) + self._dual = all([is_dual(subspace) + for subspace in self._ufl_function_spaces]) def ufl_sub_spaces(self): "Return ufl sub spaces." @@ -139,6 +203,25 @@ def ufl_sub_space(self, i): "Return i-th ufl sub space." return self._ufl_function_spaces[i] + def dual(self, *args): + """Return the dual to this function space. + + If no additional arguments are passed then a MixedFunctionSpace is + returned whose components are the duals of the originals. + + If additional arguments are passed, these must be integers. In this + case, the MixedFunctionSpace which is returned will have dual + components in the positions corresponding to the arguments passed, and + the original components in the other positions.""" + if args: + spaces = [space.dual() if i in args else space + for i, space in enumerate(self._ufl_function_spaces)] + return MixedFunctionSpace(*spaces) + else: + return MixedFunctionSpace( + *[space.dual()for space in self._ufl_function_spaces] + ) + def ufl_elements(self): "Return ufl elements." return self._ufl_elements @@ -172,10 +255,13 @@ def num_sub_spaces(self): return len(self._ufl_function_spaces) def _ufl_hash_data_(self): - return ("MixedFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) + return ("MixedFunctionSpace",) \ + + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): - return ("MixedFunctionSpace",) + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) + return ("MixedFunctionSpace",) \ + + tuple(V._ufl_signature_data_(renumbering) + for V in self.ufl_sub_spaces()) def __repr__(self): r = "MixedFunctionSpace(*%s)" % repr(self._ufl_function_spaces) diff --git a/ufl/matrix.py b/ufl/matrix.py new file mode 100644 index 000000000..a5523fe52 --- /dev/null +++ b/ufl/matrix.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +"""This module defines the Matrix class.""" + +# Copyright (C) 2021 India Marsden +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2021-2022. + +from ufl.log import error +from ufl.form import BaseForm +from ufl.core.ufl_type import ufl_type +from ufl.argument import Argument +from ufl.functionspace import AbstractFunctionSpace +from ufl.utils.counted import counted_init + + +# --- The Matrix class represents a matrix, an assembled two form --- + +@ufl_type() +class Matrix(BaseForm): + """An assemble linear operator between two function spaces.""" + + __slots__ = ( + "_count", + "_ufl_function_spaces", + "ufl_operands", + "_repr", + "_hash", + "_ufl_shape", + "_arguments") + _globalcount = 0 + + def __getnewargs__(self): + return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], + self._count) + + def __init__(self, row_space, column_space, count=None): + BaseForm.__init__(self) + counted_init(self, count, Matrix) + + if not isinstance(row_space, AbstractFunctionSpace): + error("Expecting a FunctionSpace as the row space.") + + if not isinstance(column_space, AbstractFunctionSpace): + error("Expecting a FunctionSpace as the column space.") + + self._ufl_function_spaces = (row_space, column_space) + + self.ufl_operands = () + self._hash = None + self._repr = "Matrix(%s,%s, %s)" % ( + repr(self._ufl_function_spaces[0]), + repr(self._ufl_function_spaces[1]), repr(self._count) + ) + + def count(self): + return self._count + + def ufl_function_spaces(self): + "Get the tuple of function spaces of this coefficient." + return self._ufl_function_spaces + + def ufl_row_space(self): + return self._ufl_function_spaces[0] + + def ufl_column_space(self): + return self._ufl_function_spaces[1] + + def _analyze_form_arguments(self): + "Define arguments of a matrix when considered as a form." + self._arguments = (Argument(self._ufl_function_spaces[0], 0), + Argument(self._ufl_function_spaces[1], 1)) + + def __str__(self): + count = str(self._count) + if len(count) == 1: + return "A_%s" % count + else: + return "A_{%s}" % count + + def __repr__(self): + return self._repr + + def __hash__(self): + "Hash code for use in dicts " + if self._hash is None: + self._hash = hash(self._repr) + return self._hash + + def equals(self, other): + if type(other) is not Matrix: + return False + if self is other: + return True + return (self._count == other._count and + self._ufl_function_spaces == other._ufl_function_spaces) From 703cad450f4ba27c2f03781059f0e38777374f1e Mon Sep 17 00:00:00 2001 From: Chris Richardson Date: Fri, 13 Jan 2023 10:50:50 +0000 Subject: [PATCH 014/136] Fix tag ref (#116) Co-authored-by: Matthew Scroggs --- .github/workflows/pythonapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index bb9845ecc..3feb86104 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -9,7 +9,7 @@ on: branches: - "**" tags: - - "v*" + - "**" pull_request: branches: - main From 46e9de65e2fe91de36f7ce494d9dbb836f6ed8d7 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 13 Jan 2023 15:05:04 +0000 Subject: [PATCH 015/136] Use Python warnings and exceptions instead of custom logging (#137) * start replacing ufl.log * change * remove comment * petsc arch * restore comment * remove more custom logging * flake * remove more logging * remove dicts util (almost entirely unused) * remove more logging * remove ufl.log * flake * fix tests * dolfinx branch * forked TSFC * revert to main branches --- test/test_apply_restrictions.py | 6 +- test/test_complex.py | 4 +- test/test_conditionals.py | 10 +- test/test_domains.py | 16 +- test/test_illegal.py | 12 +- test/test_indices.py | 14 +- test/test_scratch.py | 21 +- test/test_signature.py | 1 - test/test_tensoralgebra.py | 2 +- ufl/__init__.py | 12 - ufl/algebra.py | 42 ++-- ufl/algorithms/__init__.py | 1 - ufl/algorithms/analysis.py | 17 +- ufl/algorithms/apply_algebra_lowering.py | 12 +- ufl/algorithms/apply_derivatives.py | 117 +++++---- ufl/algorithms/apply_function_pullbacks.py | 12 +- ufl/algorithms/apply_geometry_lowering.py | 21 +- ufl/algorithms/apply_integral_scaling.py | 5 +- ufl/algorithms/apply_restrictions.py | 9 +- ufl/algorithms/change_to_reference.py | 32 ++- ufl/algorithms/check_arities.py | 3 +- ufl/algorithms/check_restrictions.py | 11 +- ufl/algorithms/checks.py | 15 +- ufl/algorithms/compute_form_data.py | 16 +- .../coordinate_derivative_helpers.py | 7 +- ufl/algorithms/domain_analysis.py | 15 +- ufl/algorithms/elementtransformations.py | 5 +- ufl/algorithms/estimate_degrees.py | 7 +- ufl/algorithms/expand_indices.py | 29 ++- ufl/algorithms/formfiles.py | 19 +- ufl/algorithms/formtransformations.py | 41 ++-- ufl/algorithms/map_integrands.py | 3 +- ufl/algorithms/preprocess_expression.py | 0 ufl/algorithms/remove_complex_nodes.py | 5 +- ufl/algorithms/renumbering.py | 3 +- ufl/algorithms/replace.py | 5 +- ufl/algorithms/signature.py | 3 +- ufl/algorithms/transformer.py | 5 +- ufl/algorithms/traversal.py | 3 +- ufl/argument.py | 10 +- ufl/assertions.py | 46 ---- ufl/cell.py | 9 +- ufl/coefficient.py | 3 +- ufl/compound_expressions.py | 15 +- ufl/conditional.py | 23 +- ufl/constantvalue.py | 21 +- ufl/core/expr.py | 3 +- ufl/core/multiindex.py | 13 +- ufl/core/terminal.py | 3 +- ufl/corealg/multifunction.py | 3 +- ufl/differentiation.py | 39 ++- ufl/domain.py | 26 +- ufl/equation.py | 4 +- ufl/exprcontainers.py | 25 +- ufl/exprequals.py | 3 +- ufl/exproperators.py | 21 +- ufl/finiteelement/elementlist.py | 86 +++---- ufl/finiteelement/enrichedelement.py | 11 +- ufl/finiteelement/finiteelement.py | 16 +- ufl/finiteelement/finiteelementbase.py | 28 +-- ufl/finiteelement/mixedelement.py | 38 ++- ufl/finiteelement/restrictedelement.py | 5 +- ufl/finiteelement/tensorproductelement.py | 7 +- ufl/form.py | 34 ++- ufl/formatting/printing.py | 7 +- ufl/formatting/ufl2dot.py | 3 +- ufl/formatting/ufl2unicode.py | 3 +- ufl/formoperators.py | 21 +- ufl/functionspace.py | 20 +- ufl/geometry.py | 21 +- ufl/index_combination_utils.py | 21 +- ufl/indexed.py | 15 +- ufl/indexsum.py | 7 +- ufl/integral.py | 9 +- ufl/log.py | 222 ------------------ ufl/mathfunctions.py | 17 +- ufl/matrix.py | 5 +- ufl/measure.py | 44 ++-- ufl/measureoperators.py | 0 ufl/operators.py | 23 +- ufl/referencevalue.py | 7 +- ufl/split_functions.py | 13 +- ufl/tensoralgebra.py | 61 ++--- ufl/tensors.py | 46 ++-- ufl/utils/dicts.py | 70 ------ ufl/variable.py | 13 +- 86 files changed, 627 insertions(+), 1074 deletions(-) delete mode 100644 ufl/algorithms/preprocess_expression.py delete mode 100644 ufl/assertions.py delete mode 100644 ufl/log.py delete mode 100644 ufl/measureoperators.py delete mode 100644 ufl/utils/dicts.py diff --git a/test/test_apply_restrictions.py b/test/test_apply_restrictions.py index c9775e10d..6e79c5f79 100755 --- a/test/test_apply_restrictions.py +++ b/test/test_apply_restrictions.py @@ -18,9 +18,9 @@ def test_apply_restrictions(): n = FacetNormal(cell) x = SpatialCoordinate(cell) - assert raises(UFLException, lambda: apply_restrictions(f0)) - assert raises(UFLException, lambda: apply_restrictions(grad(f))) - assert raises(UFLException, lambda: apply_restrictions(n)) + assert raises(BaseException, lambda: apply_restrictions(f0)) + assert raises(BaseException, lambda: apply_restrictions(grad(f))) + assert raises(BaseException, lambda: apply_restrictions(n)) # Continuous function gets default restriction if none # provided otherwise the user choice is respected diff --git a/test/test_complex.py b/test/test_complex.py index 597adfba0..4559457ef 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -124,9 +124,9 @@ def test_remove_complex_nodes(self): assert remove_complex_nodes(a) == v assert remove_complex_nodes(b) == u - with pytest.raises(ufl.log.UFLException): + with pytest.raises(BaseException): remove_complex_nodes(c) - with pytest.raises(ufl.log.UFLException): + with pytest.raises(BaseException): remove_complex_nodes(d) diff --git a/test/test_conditionals.py b/test/test_conditionals.py index 4d5f4f0a2..5b1b7badf 100755 --- a/test/test_conditionals.py +++ b/test/test_conditionals.py @@ -26,7 +26,7 @@ def g(): def test_conditional_does_not_allow_bool_condition(f, g): # The reason for this test is that it protects from the case # conditional(a == b, t, f) in which a == b means comparing representations - with pytest.raises(UFLException): + with pytest.raises(BaseException): conditional(True, 1, 0) @@ -123,7 +123,7 @@ def test_lt_produces_ufl_expr(f, g): # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: - with pytest.raises(UFLException): + with pytest.raises(BaseException): bool(expr1) @@ -136,7 +136,7 @@ def test_gt_produces_ufl_expr(f, g): # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: - with pytest.raises(UFLException): + with pytest.raises(BaseException): bool(expr1) @@ -149,7 +149,7 @@ def test_le_produces_ufl_expr(f, g): # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: - with pytest.raises(UFLException): + with pytest.raises(BaseException): bool(expr1) @@ -162,5 +162,5 @@ def test_ge_produces_ufl_expr(f, g): # Representations are the same: assert bool(expr1 == expr2) # Protection from misuse in boolean python expression context: - with pytest.raises(UFLException): + with pytest.raises(BaseException): bool(expr1) diff --git a/test/test_domains.py b/test/test_domains.py index fe18042ba..b072274a6 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -151,14 +151,14 @@ def test_join_domains(): assert 2 == len(join_domains([Mesh(xa), Mesh(xb)])) # Incompatible cells require labeling - # self.assertRaises(UFLException, lambda: join_domains([Mesh(triangle), Mesh(triangle3)])) # FIXME: Figure out - # self.assertRaises(UFLException, lambda: join_domains([Mesh(triangle), + # self.assertRaises(BaseException, lambda: join_domains([Mesh(triangle), Mesh(triangle3)])) # FIXME: Figure out + # self.assertRaises(BaseException, lambda: join_domains([Mesh(triangle), # Mesh(quadrilateral)])) # FIXME: Figure out # Incompatible coordinates require labeling xc = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) xd = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) - with pytest.raises(UFLException): + with pytest.raises(BaseException): join_domains([Mesh(xc), Mesh(xd)]) # Incompatible data is checked if and only if the domains are the same @@ -167,14 +167,14 @@ def test_join_domains(): assert 2 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(quadrilateral, ufl_id=8, cargo=mesh8)])) # Geometric dimensions must match - with pytest.raises(UFLException): + with pytest.raises(BaseException): join_domains([Mesh(triangle), Mesh(triangle3)]) - with pytest.raises(UFLException): + with pytest.raises(BaseException): join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), Mesh(triangle3, ufl_id=8, cargo=mesh8)]) # Cargo and mesh ids must match - with pytest.raises(UFLException): + with pytest.raises(BaseException): Mesh(triangle, ufl_id=7, cargo=mesh8) # Nones are removed @@ -284,8 +284,8 @@ def xtest_mixed_elements_on_overlapping_regions(): # Old sketch, not working # data. # These should fail because the functions are undefined on the integration domains - # self.assertRaises(UFLException, lambda: mr**2*dx(DD)) # FIXME: Make this fail - # self.assertRaises(UFLException, lambda: mr**2*dx(DD)) # FIXME: Make this + # self.assertRaises(BaseException, lambda: mr**2*dx(DD)) # FIXME: Make this fail + # self.assertRaises(BaseException, lambda: mr**2*dx(DD)) # FIXME: Make this # fail diff --git a/test/test_illegal.py b/test/test_illegal.py index cca7478e4..ba59c1475 100755 --- a/test/test_illegal.py +++ b/test/test_illegal.py @@ -58,31 +58,31 @@ def vg(): def test_mul_v_u(v, u): - with pytest.raises(UFLException): + with pytest.raises(BaseException): v * u def test_mul_vf_u(vf, u): - with pytest.raises(UFLException): + with pytest.raises(BaseException): vf * u def test_mul_vf_vg(vf, vg): - with pytest.raises(UFLException): + with pytest.raises(BaseException): vf * vg def test_add_a_v(a, v): - with pytest.raises(UFLException): + with pytest.raises(BaseException): a + v def test_add_vf_b(vf, b): - with pytest.raises(UFLException): + with pytest.raises(BaseException): vf + b def test_add_vectorexpr_b(vg, v, u, vf, b): tmp = vg + v + u + vf - with pytest.raises(UFLException): + with pytest.raises(BaseException): tmp + b diff --git a/test/test_indices.py b/test/test_indices.py index 806fd2d3e..a4b296dcb 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -43,7 +43,7 @@ def test_tensor_indices(self): a = u[i, j]*f[i, j]*dx b = u[j, i]*f[i, j]*dx c = u[j, i]*f[j, i]*dx - with pytest.raises(UFLException): + with pytest.raises(BaseException): d = (u[i, i]+f[j, i])*dx @@ -52,7 +52,7 @@ def test_indexed_sum1(self): u = Argument(element, 2) f = Coefficient(element) a = u[i]+f[i] - with pytest.raises(UFLException): + with pytest.raises(BaseException): a*dx @@ -62,7 +62,7 @@ def test_indexed_sum2(self): u = Argument(element, 3) f = Coefficient(element) a = u[j]+f[j]+v[j]+2*v[j]+exp(u[i]*u[i])/2*f[j] - with pytest.raises(UFLException): + with pytest.raises(BaseException): a*dx @@ -70,7 +70,7 @@ def test_indexed_sum3(self): element = VectorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) - with pytest.raises(UFLException): + with pytest.raises(BaseException): a = u[i]+f[j] @@ -103,7 +103,7 @@ def test_indexed_function3(self): v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) - with pytest.raises(UFLException): + with pytest.raises(BaseException): c = sin(u[i] + f[i])*dx @@ -200,11 +200,11 @@ def test_tensor(self): ww = f[i]*vv # this is well defined: ww = sum_i # illegal - with pytest.raises(UFLException): + with pytest.raises(BaseException): vv = as_vector([u[i], v[j]]) # illegal - with pytest.raises(UFLException): + with pytest.raises(BaseException): A = as_matrix([[u[0], u[1]], [v[0],]]) # ... diff --git a/test/test_scratch.py b/test/test_scratch.py index df8c177f8..81056f568 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -12,7 +12,6 @@ # This imports everything external code will see from ufl from ufl import * -from ufl.log import error from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor # TODO: Import only what you need from classes and algorithms: @@ -44,7 +43,7 @@ def grad(self, g): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): - error("Expecting gradient of a FormArgument, not %s" % repr(o)) + raise ValueError("Expecting gradient of a FormArgument, not %s" % repr(o)) def apply_grads(f): if not isinstance(f, FormArgument): @@ -53,7 +52,7 @@ def apply_grads(f): print(o) print(g) print((','*60)) - error("What?") + raise ValueError("What?") for i in range(ngrads): f = Grad(f) return f @@ -78,9 +77,9 @@ def analyse_variation_argument(v): vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: - error("Expecting argument or component of argument.") + raise ValueError("Expecting argument or component of argument.") if not all(isinstance(k, FixedIndex) for k in vcomp): - error("Expecting only fixed indices in variation.") + raise ValueError("Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): @@ -118,7 +117,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): else: if wshape != (): - error("Expecting scalar coefficient in this branch.") + raise ValueError("Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () @@ -133,14 +132,14 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): continue assert isinstance(wval, FormArgument) if not all(isinstance(k, FixedIndex) for k in wcomp): - error("Expecting only fixed indices in differentiation variable.") + raise ValueError("Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: - error("Expecting coefficient or component of coefficient.") + raise ValueError("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = self._cd._data.get(o) @@ -156,15 +155,15 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): if not isinstance(oprimes, tuple): oprimes = (oprimes,) if len(oprimes) != len(self._v): - error("Got a tuple of arguments, " - "expecting a matching tuple of coefficient derivatives.") + raise ValueError("Got a tuple of arguments, " + "expecting a matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in zip(oprimes, self._v): - error("FIXME: Figure out how to do this with ngrads") + raise ValueError("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] diff --git a/test/test_signature.py b/test/test_signature.py index 840a9565d..dd2c494c4 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -8,7 +8,6 @@ from ufl import * -from ufl.utils.dicts import EmptyDictType from ufl.classes import MultiIndex, FixedIndex from ufl.algorithms.signature import compute_multiindex_hashdata, \ compute_terminal_hashdata diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index 70eea5d88..0217311dc 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -75,7 +75,7 @@ def test_pow2_inner(self, A, u): assert A2 == remove_complex_nodes(inner(A, A)) # Only tensor**2 notation is supported: - self.assertRaises(UFLException, lambda: A**3) + self.assertRaises(BaseException, lambda: A**3) def test_dot(self, A, B, u, v): diff --git a/ufl/__init__.py b/ufl/__init__.py index 7bb84df1c..e6d823003 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -256,10 +256,6 @@ # python function sum, can be useful for users as well?) from ufl.utils.sequences import product -# Output control -from ufl.log import get_handler, get_logger, set_handler, set_level, add_logfile, \ - UFLException, DEBUG, INFO, ERROR, CRITICAL - # Types for geometric quantities from ufl.cell import as_cell, AbstractCell, Cell, TensorProductCell @@ -349,10 +345,6 @@ # Integral classes from ufl.integral import Integral -# Special functions for Measure class -# (ensure this is imported, since it attaches operators to Measure) -import ufl.measureoperators as __measureoperators - # Representations of transformed forms from ufl.formoperators import replace, derivative, action, energy_norm, rhs, lhs,\ system, functional, adjoint, sensitivity_rhs, extract_blocks #, dirichlet_functional @@ -370,12 +362,8 @@ # Useful constants from math import e, pi - -# Define ufl.* namespace __all__ = [ 'product', - 'get_handler', 'get_logger', 'set_handler', 'set_level', 'add_logfile', - 'UFLException', 'DEBUG', 'INFO', 'ERROR', 'CRITICAL', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'HInf', 'HEin', 'HDivDiv', diff --git a/ufl/algebra.py b/ufl/algebra.py index 3ccc499b8..107333107 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -9,7 +9,6 @@ # # Modified by Anders Logg, 2008 -from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.expr import ufl_err_str from ufl.core.operator import Operator @@ -38,11 +37,11 @@ def __new__(cls, a, b): fi = a.ufl_free_indices fid = a.ufl_index_dimensions if b.ufl_shape != sh: - error("Can't add expressions with different shapes.") + raise ValueError("Can't add expressions with different shapes.") if b.ufl_free_indices != fi: - error("Can't add expressions with different free indices.") + raise ValueError("Can't add expressions with different free indices.") if b.ufl_index_dimensions != fid: - error("Can't add expressions with different index dimensions.") + raise ValueError("Can't add expressions with different index dimensions.") # Skip adding zero if isinstance(a, Zero): @@ -107,7 +106,7 @@ def __str__(self): s += o return s # Implementation with no line splitting: - return "%s" % " + ".join(ops) + return " + ".join(ops) @ufl_type(num_ops=2, @@ -124,8 +123,8 @@ def __new__(cls, a, b): # Type checking # Make sure everything is scalar if a.ufl_shape or b.ufl_shape: - error("Product can only represent products of scalars, " - "got\n\t%s\nand\n\t%s" % (ufl_err_str(a), ufl_err_str(b))) + raise ValueError("Product can only represent products of scalars, " + f"got\n {ufl_err_str(a)}\nand\n {ufl_err_str(b)}") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): @@ -184,7 +183,7 @@ def evaluate(self, x, mapping, component, index_values): sh = self.ufl_shape if sh: if sh != ops[-1].ufl_shape: - error("Expecting nonscalar product operand to be the last by convention.") + raise ValueError("Expecting nonscalar product operand to be the last by convention.") tmp = ops[-1].evaluate(x, mapping, component, index_values) ops = ops[:-1] else: @@ -214,11 +213,11 @@ def __new__(cls, a, b): # so maybe we can keep this assertion. Some algorithms may # need updating. if not is_ufl_scalar(a): - error("Expecting scalar nominator in Division.") + raise ValueError("Expecting scalar nominator in Division.") if not is_true_ufl_scalar(b): - error("Division by non-scalar is undefined.") + raise ValueError("Division by non-scalar is undefined.") if isinstance(b, Zero): - error("Division by zero!") + raise ValueError("Division by zero!") # Simplification # Simplification a/b -> a @@ -260,8 +259,7 @@ def evaluate(self, x, mapping, component, index_values): return e def __str__(self): - return "%s / %s" % (parstr(self.ufl_operands[0], self), - parstr(self.ufl_operands[1], self)) + return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" @ufl_type(num_ops=2, @@ -277,9 +275,9 @@ def __new__(cls, a, b): # Type checking if not is_true_ufl_scalar(a): - error("Cannot take the power of a non-scalar expression %s." % ufl_err_str(a)) + raise ValueError(f"Cannot take the power of a non-scalar expression {ufl_err_str(a)}.") if not is_true_ufl_scalar(b): - error("Cannot raise an expression to a non-scalar power %s." % ufl_err_str(b)) + raise ValueError(f"Cannot raise an expression to a non-scalar power {ufl_err_str(b)}.") # Simplification if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): @@ -288,10 +286,10 @@ def __new__(cls, a, b): return IntValue(1) if isinstance(a, Zero) and isinstance(b, ScalarValue): if isinstance(b, ComplexValue): - error("Cannot raise zero to a complex power.") + raise ValueError("Cannot raise zero to a complex power.") bf = float(b) if bf < 0: - error("Division by zero, cannot raise 0 to a negative power.") + raise ValueError("Division by zero, cannot raise 0 to a negative power.") else: return zero() if isinstance(b, ScalarValue) and b._value == 1: @@ -318,7 +316,7 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): a, b = self.ufl_operands - return "%s ** %s" % (parstr(a, self), parstr(b, self)) + return f"{parstr(a, self)} ** {parstr(b, self)}" @ufl_type(num_ops=1, @@ -349,7 +347,7 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): a, = self.ufl_operands - return "|%s|" % (parstr(a, self),) + return f"|{parstr(a, self)}|" @ufl_type(num_ops=1, @@ -379,7 +377,7 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): a, = self.ufl_operands - return "conj(%s)" % (parstr(a, self),) + return f"conj({parstr(a, self)})" @ufl_type(num_ops=1, @@ -411,7 +409,7 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): a, = self.ufl_operands - return "Re[%s]" % (parstr(a, self),) + return f"Re[{parstr(a, self)}]" @ufl_type(num_ops=1, @@ -441,4 +439,4 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): a, = self.ufl_operands - return "Im[%s]" % (parstr(a, self),) + return f"Im[{parstr(a, self)}]" diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 096266657..f80bc0dbb 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -31,7 +31,6 @@ "extract_type", "extract_elements", "extract_sub_elements", - "preprocess_expression", "expand_indices", "replace", "expand_derivatives", diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index ab74c1582..ecbcaa8f1 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -12,7 +12,6 @@ from itertools import chain -from ufl.log import error from ufl.utils.sorting import sorted_by_count, topological_sorting from ufl.core.terminal import Terminal @@ -132,19 +131,17 @@ def extract_arguments_and_coefficients(a): # Build number,part: instance mappings, should be one to one bfnp = dict((f, (f.number(), f.part())) for f in arguments) if len(bfnp) != len(set(bfnp.values())): - msg = """\ -Found different Arguments with same number and part. -Did you combine test or trial functions from different spaces? -The Arguments found are:\n%s""" % "\n".join(" %s" % f for f in arguments) - error(msg) + raise ValueError( + "Found different Arguments with same number and part.\n" + "Did you combine test or trial functions from different spaces?\n" + "The Arguments found are:\n" + "\n".join(f" {a}" for a in arguments)) # Build count: instance mappings, should be one to one fcounts = dict((f, f.count()) for f in coefficients) if len(fcounts) != len(set(fcounts.values())): - msg = """\ -Found different coefficients with same counts. -The arguments found are:\n%s""" % "\n".join(" %s" % f for f in coefficients) - error(msg) + raise ValueError( + "Found different coefficients with same counts.\n" + "The arguments found are:\n" + "\n".join(f" {c}" for c in coefficients)) # Passed checks, so we can safely sort the instances by count arguments = _sorted_by_number_and_part(arguments) diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index 66846fb4e..ee6cd1754 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -10,8 +10,6 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.log import error - from ufl.classes import Product, Grad, Conj from ufl.core.multiindex import indices, Index, FixedIndex from ufl.tensors import as_tensor, as_matrix, as_vector @@ -44,9 +42,9 @@ def transposed(self, o, A): def _square_matrix_shape(self, A): sh = A.ufl_shape if sh[0] != sh[1]: - error("Expecting square matrix.") + raise ValueError("Expecting square matrix.") if sh[0] is None: - error("Unknown dimension.") + raise ValueError("Unknown dimension.") return sh def deviatoric(self, o, A): @@ -92,7 +90,7 @@ def alternative_inner(self, o, a, b): # TODO: Test this ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: - error("Nonmatching shapes.") + raise ValueError("Nonmatching shapes.") # Simplification for tensors with one or more dimensions of # length 1 ii = [] @@ -112,7 +110,7 @@ def inner(self, o, a, b): ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: - error("Nonmatching shapes.") + raise ValueError("Nonmatching shapes.") ii = indices(len(ash)) # Creates multiple IndexSums over a Product s = a[ii] * Conj(b[ii]) @@ -166,7 +164,7 @@ def c(i, j): return c(0, 1) if sh == (3,): return as_vector((c(1, 2), c(2, 0), c(0, 1))) - error("Invalid shape %s of curl argument." % (sh,)) + raise ValueError(f"Invalid shape {sh} of curl argument.") def apply_algebra_lowering(expr): diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index e6cdaade3..d60326880 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -26,7 +26,6 @@ from ufl.corealg.multifunction import MultiFunction from ufl.differentiation import CoordinateDerivative from ufl.domain import extract_unique_domain -from ufl.log import error from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, conditional, cos, cosh, exp, facet_avg, ln, sign, sin, sinh, sqrt) @@ -55,19 +54,16 @@ def __init__(self, var_shape): # --- Error checking for missing handlers and unexpected types def expr(self, o): - error("Missing differentiation handler for type {0}. Have you added a new type?".format(o._ufl_class_.__name__)) + raise ValueError(f"Missing differentiation handler for type {o._ufl_class_.__name__}. Have you added a new type?") def unexpected(self, o): - error("Unexpected type {0} in AD rules.".format(o._ufl_class_.__name__)) + raise ValueError(f"Unexpected type {o._ufl_class_.__name__} in AD rules.") def override(self, o): - error("Type {0} must be overridden in specialized AD rule set.".format(o._ufl_class_.__name__)) + raise ValueError(f"Type {o._ufl_class_.__name__} must be overridden in specialized AD rule set.") def derivative(self, o): - error("Unhandled derivative type {0}, nested differentiation has failed.".format(o._ufl_class_.__name__)) - - def fixme(self, o): - error("FIXME: Unimplemented differentiation handler for type {0}.".format(o._ufl_class_.__name__)) + raise ValueError(f"Unhandled derivative type {o._ufl_class_.__name__}, nested differentiation has failed.") # --- Some types just don't have any derivative, this is just to # --- make algorithm structure generic @@ -229,9 +225,9 @@ def division(self, o, fp, gp): f, g = o.ufl_operands if not is_ufl_scalar(f): - error("Not expecting nonscalar nominator") + raise ValueError("Not expecting nonscalar nominator") if not is_true_ufl_scalar(g): - error("Not expecting nonscalar denominator") + raise ValueError("Not expecting nonscalar denominator") # do_df = 1/g # do_dg = -h/g @@ -253,9 +249,9 @@ def power(self, o, fp, gp): f, g = o.ufl_operands if not is_true_ufl_scalar(f): - error("Expecting scalar expression f in f**g.") + raise ValueError("Expecting scalar expression f in f**g.") if not is_true_ufl_scalar(g): - error("Expecting scalar expression g in f**g.") + raise ValueError("Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # do/df = g * f**(g-1) = g / f * o @@ -310,7 +306,7 @@ def math_function(self, o, df): if hasattr(o, 'derivative'): f, = o.ufl_operands return df * o.derivative() - error("Unknown math function.") + raise ValueError("Unknown math function.") def sqrt(self, o, fp): return fp / (2 * o) @@ -321,7 +317,7 @@ def exp(self, o, fp): def ln(self, o, fp): f, = o.ufl_operands if isinstance(f, Zero): - error("Division by zero.") + raise ZeroDivisionError() return fp / f def cos(self, o, fp): @@ -376,7 +372,7 @@ def erf(self, o, fp): def bessel_j(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - error("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_J(1, f) @@ -387,7 +383,7 @@ def bessel_j(self, o, nup, fp): def bessel_y(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - error("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_Y(1, f) @@ -398,7 +394,7 @@ def bessel_y(self, o, nup, fp): def bessel_i(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - error("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = bessel_I(1, f) @@ -409,7 +405,7 @@ def bessel_i(self, o, nup, fp): def bessel_k(self, o, nup, fp): nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - error("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") if isinstance(nu, Zero): op = -bessel_K(1, f) @@ -501,7 +497,7 @@ def jacobian_inverse(self, o): if is_cellwise_constant(o): return self.independent_terminal(o) if not o._ufl_is_terminal_: - error("ReferenceValue can only wrap a terminal") + raise ValueError("ReferenceValue can only wrap a terminal") Do = grad_to_reference_grad(o, o) return Do @@ -543,7 +539,7 @@ def reference_value(self, o): return ReferenceGrad(o) if not f._ufl_is_terminal_: - error("ReferenceValue can only wrap a terminal") + raise ValueError("ReferenceValue can only wrap a terminal") domain = extract_unique_domain(f) K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) @@ -555,7 +551,7 @@ def reference_grad(self, o): valid_operand = f._ufl_is_in_reference_frame_ or isinstance(f, (JacobianInverse, SpatialCoordinate)) if not valid_operand: - error("ReferenceGrad can only wrap a reference frame type!") + raise ValueError("ReferenceGrad can only wrap a reference frame type!") domain = extract_unique_domain(f) K = JacobianInverse(domain) Do = grad_to_reference_grad(o, K) @@ -568,7 +564,7 @@ def grad(self, o): # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): - error("Expecting only grads applied to a terminal.") + raise ValueError("Expecting only grads applied to a terminal.") return Grad(o) @@ -640,26 +636,26 @@ def cell_coordinate(self, o): def reference_value(self, o): if not o.ufl_operands[0]._ufl_is_terminal_: - error("ReferenceValue can only wrap a terminal") + raise ValueError("ReferenceValue can only wrap a terminal") return ReferenceGrad(o) def coefficient(self, o): - error("Coefficient should be wrapped in ReferenceValue by now") + raise ValueError("Coefficient should be wrapped in ReferenceValue by now") def argument(self, o): - error("Argument should be wrapped in ReferenceValue by now") + raise ValueError("Argument should be wrapped in ReferenceValue by now") # --- Nesting of gradients def grad(self, o): - error("Grad should have been transformed by this point, but got {0}.".format(type(o).__name__)) + raise ValueError(f"Grad should have been transformed by this point, but got {type(o).__name__}.") def reference_grad(self, o): "Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f))." # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue, Terminal)): - error("Expecting only grads applied to a terminal.") + raise ValueError("Expecting only grads applied to a terminal.") return ReferenceGrad(o) cell_avg = GenericDerivativeRuleset.independent_operator @@ -670,7 +666,7 @@ class VariableRuleset(GenericDerivativeRuleset): def __init__(self, var): GenericDerivativeRuleset.__init__(self, var_shape=var.ufl_shape) if var.ufl_free_indices: - error("Differentiation variable cannot have free indices.") + raise ValueError("Differentiation variable cannot have free indices.") self._variable = var self._Id = self._make_identity(self._var_shape) @@ -738,7 +734,7 @@ def grad(self, o): "Variable derivative of a gradient of a terminal must be 0." # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): - error("Expecting only grads applied to a terminal.") + raise ValueError("Expecting only grads applied to a terminal.") return self.independent_terminal(o) # --- Rules for values or derivatives in reference frame @@ -751,8 +747,9 @@ def reference_value(self, o): # FIXME: This is a bit tricky, instead of Identity it is # actually inverse(transform), or we should rather not # convert to reference frame in the first place - error("Missing implementation: To handle derivatives of rv(f) w.r.t. f for" + - " mapped elements, rewriting to reference frame should not happen first...") + raise ValueError( + "Missing implementation: To handle derivatives of rv(f) w.r.t. f for " + "mapped elements, rewriting to reference frame should not happen first...") # dv/dv = identity of rank 2*rank(v) return self._Id else: @@ -763,7 +760,7 @@ def reference_grad(self, o): "Variable derivative of a gradient of a terminal must be 0." if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue)): - error("Unexpected argument to reference_grad.") + raise ValueError("Unexpected argument to reference_grad.") return self.independent_terminal(o) cell_avg = GenericDerivativeRuleset.independent_operator @@ -784,11 +781,11 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): # Type checking if not isinstance(coefficients, ExprList): - error("Expecting a ExprList of coefficients.") + raise ValueError("Expecting a ExprList of coefficients.") if not isinstance(arguments, ExprList): - error("Expecting a ExprList of arguments.") + raise ValueError("Expecting a ExprList of arguments.") if not isinstance(coefficient_derivatives, ExprMapping): - error("Expecting a coefficient-coefficient ExprMapping.") + raise ValueError("Expecting a coefficient-coefficient ExprMapping.") # The coefficient(s) to differentiate w.r.t. and the # argument(s) s.t. D_w[v](e) = d/dtau e(w+tau v)|tau=0 @@ -847,7 +844,7 @@ def coefficient(self, o): if not isinstance(dos, tuple): dos = (dos,) if len(dos) != len(self._v): - error("Got a tuple of arguments, expecting a matching tuple of coefficient derivatives.") + raise ValueError("Got a tuple of arguments, expecting a matching tuple of coefficient derivatives.") dosum = Zero(o.ufl_shape) for do, v in zip(dos, self._v): so, oi = as_scalar(do) @@ -862,7 +859,7 @@ def coefficient(self, o): return dosum def reference_value(self, o): - error("Currently no support for ReferenceValue in CoefficientDerivative.") + raise NotImplementedError("Currently no support for ReferenceValue in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need @@ -870,7 +867,7 @@ def reference_value(self, o): # derivative(...ReferenceValue...,...). # f, = o.ufl_operands # if not f._ufl_is_terminal_: - # error("ReferenceValue can only wrap terminals directly.") + # raise ValueError("ReferenceValue can only wrap terminals directly.") # FIXME: check all cases like in coefficient # if f is w: # # FIXME: requires that v is an Argument with the same element mapping! @@ -879,7 +876,7 @@ def reference_value(self, o): # return self.independent_terminal(o) def reference_grad(self, o): - error("Currently no support for ReferenceGrad in CoefficientDerivative.") + raise NotImplementedError("Currently no support for ReferenceGrad in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need @@ -900,7 +897,7 @@ def grad(self, g): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): - error("Expecting gradient of a FormArgument, not %s" % ufl_err_str(o)) + raise ValueError(f"Expecting gradient of a FormArgument, not {ufl_err_str(o)}.") def apply_grads(f): for i in range(ngrads): @@ -928,9 +925,9 @@ def analyse_variation_argument(v): vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: - error("Expecting argument or component of argument.") + raise ValueError("Expecting argument or component of argument.") if not all(isinstance(k, FixedIndex) for k in vcomp): - error("Expecting only fixed indices in variation.") + raise ValueError("Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): @@ -970,7 +967,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): else: if wshape != (): - error("Expecting scalar coefficient in this branch.") + raise ValueError("Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () @@ -987,14 +984,14 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): continue assert isinstance(wval, FormArgument) if not all(isinstance(k, FixedIndex) for k in wcomp): - error("Expecting only fixed indices in differentiation variable.") + raise ValueError("Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: - error("Expecting coefficient or component of coefficient.") + raise ValueError("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = # self._cd.get(o) @@ -1012,8 +1009,9 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): if not isinstance(oprimes, tuple): oprimes = (oprimes,) if len(oprimes) != len(self._v): - error("Got a tuple of arguments, expecting a" - " matching tuple of coefficient derivatives.") + raise ValueError( + "Got a tuple of arguments, expecting a" + " matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs @@ -1021,7 +1019,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # complete inner product. Using indices to define a # non-compound inner product. for (oprime, v) in zip(oprimes, self._v): - error("FIXME: Figure out how to do this with ngrads") + raise NotImplementedError("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] @@ -1075,7 +1073,7 @@ def terminal(self, o): return o def derivative(self, o): - error("Missing derivative handler for {0}.".format(type(o).__name__)) + raise NotImplementedError(f"Missing derivative handler for {type(o).__name__}.") ufl_type = MultiFunction.reuse_if_untouched @@ -1170,11 +1168,11 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): # Type checking if not isinstance(coefficients, ExprList): - error("Expecting a ExprList of coefficients.") + raise ValueError("Expecting a ExprList of coefficients.") if not isinstance(arguments, ExprList): - error("Expecting a ExprList of arguments.") + raise ValueError("Expecting a ExprList of arguments.") if not isinstance(coefficient_derivatives, ExprMapping): - error("Expecting a coefficient-coefficient ExprMapping.") + raise ValueError("Expecting a coefficient-coefficient ExprMapping.") # The coefficient(s) to differentiate w.r.t. and the # argument(s) s.t. D_w[v](e) = d/dtau e(w+tau v)|tau=0 @@ -1194,10 +1192,10 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): argument = GenericDerivativeRuleset.independent_terminal def coefficient(self, o): - error("CoordinateDerivative of coefficient in physical space is not implemented.") + raise NotImplementedError("CoordinateDerivative of coefficient in physical space is not implemented.") def grad(self, o): - error("CoordinateDerivative grad in physical space is not implemented.") + raise NotImplementedError("CoordinateDerivative grad in physical space is not implemented.") def spatial_coordinate(self, o): do = self._w2v.get(o) @@ -1205,7 +1203,7 @@ def spatial_coordinate(self, o): if do is not None: return do else: - error("Not implemented: CoordinateDerivative found a SpatialCoordinate that is different from the one being differentiated.") + raise NotImplementedError("CoordinateDerivative found a SpatialCoordinate that is different from the one being differentiated.") def reference_value(self, o): do = self._cd.get(o) @@ -1222,7 +1220,7 @@ def reference_grad(self, g): o, = o.ufl_operands ngrads += 1 if not (isinstance(o, SpatialCoordinate) or isinstance(o.ufl_operands[0], FormArgument)): - error("Expecting gradient of a FormArgument, not %s" % ufl_err_str(o)) + raise ValueError(f"Expecting gradient of a FormArgument, not {ufl_err_str(o)}") def apply_grads(f): for i in range(ngrads): @@ -1255,7 +1253,7 @@ def terminal(self, o): return o def derivative(self, o): - error("Missing derivative handler for {0}.".format(type(o).__name__)) + raise NotImplementedError(f"Missing derivative handler for {type(o).__name__}.") expr = MultiFunction.reuse_if_untouched @@ -1273,8 +1271,9 @@ def coordinate_derivative(self, o, f, w, v, cd): spaces = set(c.family() for c in extract_unique_elements(o)) unsupported_spaces = {"Argyris", "Bell", "Hermite", "Morley"} if spaces & unsupported_spaces: - error("CoordinateDerivative is not supported for elements of type %s. " - "This is because their pullback is not implemented in UFL." % unsupported_spaces) + raise NotImplementedError( + "CoordinateDerivative is not supported for elements of type {spaces & unsupported_spaces}. " + "This is because their pullback is not implemented in UFL.") _, w, v, cd = o.ufl_operands rules = CoordinateDerivativeRuleset(w, v, cd) key = (CoordinateDerivativeRuleset, w, v, cd) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index dd33fa5ea..5bd12cf83 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -16,7 +16,6 @@ from ufl.core.multiindex import indices from ufl.corealg.multifunction import MultiFunction, memoized_handler from ufl.domain import extract_unique_domain -from ufl.log import error from ufl.tensors import as_tensor, as_vector from ufl.utils.sequences import product @@ -107,7 +106,7 @@ def apply_known_single_pullback(r, element): f = as_tensor(K[m, i] * r[kmn] * K[n, j], (*k, i, j)) return f else: - error("Should never be reached!") + raise ValueError(f"Unsupported mapping: {mapping}.") def apply_single_function_pullbacks(r, element): @@ -118,7 +117,8 @@ def apply_single_function_pullbacks(r, element): :returns: a pulled back expression.""" mapping = element.mapping() if r.ufl_shape != element.reference_value_shape(): - error("Expecting reference space expression with shape '%s', got '%s'" % (element.reference_value_shape(), r.ufl_shape)) + raise ValueError( + f"Expecting reference space expression with shape '{element.reference_value_shape()}', got '{r.ufl_shape}'") if mapping in {"physical", "identity", "contravariant Piola", "covariant Piola", "double contravariant Piola", "double covariant Piola", @@ -128,7 +128,7 @@ def apply_single_function_pullbacks(r, element): # directly. f = apply_known_single_pullback(r, element) if f.ufl_shape != element.value_shape(): - error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape)) + raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', got '{f.ufl_shape}'") return f elif mapping in {"symmetries", "undefined"}: # Need to pull back each unique piece of the reference space thing @@ -161,10 +161,10 @@ def apply_single_function_pullbacks(r, element): # And reshape appropriately f = as_tensor(numpy.asarray(g_components).reshape(gsh)) if f.ufl_shape != element.value_shape(): - error("Expecting pulled back expression with shape '%s', got '%s'" % (element.value_shape(), f.ufl_shape)) + raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', got '{f.ufl_shape}'") return f else: - error("Unhandled mapping type '%s'" % mapping) + raise ValueError(f"Unsupported mapping type: {mapping}") class FunctionPullbackApplier(MultiFunction): diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index bd44124c5..5317f4415 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -29,7 +29,6 @@ # FacetJacobianInverse, # FacetOrientation, QuadratureWeight, from ufl.domain import extract_unique_domain -from ufl.log import error from ufl.measure import custom_integral_types, point_integral_types from ufl.operators import conj, max_value, min_value, real, sqrt from ufl.tensors import as_tensor, as_vector @@ -54,7 +53,7 @@ def jacobian(self, o): return o domain = extract_unique_domain(o) if domain.ufl_coordinate_element().mapping() != "identity": - error("Piola mapped coordinates are not implemented.") + raise ValueError("Piola mapped coordinates are not implemented.") # Note: No longer supporting domain.coordinates(), always # preserving SpatialCoordinate object. However if Jacobians # are not preserved, using @@ -146,7 +145,7 @@ def spatial_coordinate(self, o): if self._preserve_types[o._ufl_typecode_]: return o if extract_unique_domain(o).ufl_coordinate_element().mapping() != "identity": - error("Piola mapped coordinates are not implemented.") + raise ValueError("Piola mapped coordinates are not implemented.") # No longer supporting domain.coordinates(), always preserving # SpatialCoordinate object. return o @@ -170,8 +169,8 @@ def facet_cell_coordinate(self, o): if self._preserve_types[o._ufl_typecode_]: return o - error("Missing computation of facet reference coordinates " - "from physical coordinates via mappings.") + raise ValueError("Missing computation of facet reference coordinates " + "from physical coordinates via mappings.") @memoized_handler def cell_volume(self, o): @@ -218,7 +217,7 @@ def circumradius(self, o): domain = extract_unique_domain(o) if not domain.is_piecewise_linear_simplex_domain(): - error("Circumradius only makes sense for affine simplex cells") + raise ValueError("Circumradius only makes sense for affine simplex cells") cellname = domain.ufl_cell().cellname() cellvolume = self.cell_volume(CellVolume(domain)) @@ -320,7 +319,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): domain = extract_unique_domain(o) if domain.ufl_cell().topological_dimension() < 3: - error("Facet edge lengths only make sense for topological dimension >= 3.") + raise ValueError("Facet edge lengths only make sense for topological dimension >= 3.") elif not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler @@ -358,14 +357,14 @@ def cell_normal(self, o): # to the 'right') cell_normal = as_vector((-J[1, 0], J[0, 0])) else: - error("Cell normal not implemented for tdim %d, gdim %d" % (tdim, gdim)) + raise ValueError(f"Cell normal not implemented for tdim {tdim}, gdim {gdim}") # Return normalized vector, sign corrected by cell # orientation co = CellOrientation(domain) return co * cell_normal / sqrt(cell_normal[i] * cell_normal[i]) else: - error("What do you want cell normal in gdim={0}, tdim={1} to be?".format(gdim, tdim)) + raise ValueError(f"Cell normal undefined for tdim {tdim}, gdim {gdim}") @memoized_handler def facet_normal(self, o): @@ -409,7 +408,7 @@ def facet_normal(self, o): r = n if r.ufl_shape != o.ufl_shape: - error("Inconsistent dimensions (in=%d, out=%d)." % (o.ufl_shape[0], r.ufl_shape[0])) + raise ValueError(f"Inconsistent dimensions (in={o.ufl_shape[0]}, out={r.ufl_shape[0]}).") return r @@ -444,4 +443,4 @@ def apply_geometry_lowering(form, preserve_types=()): return map_expr_dag(mf, expr) else: - error("Invalid type %s" % (form.__class__.__name__,)) + raise ValueError(f"Invalid type {form.__class__.__name__}") diff --git a/ufl/algorithms/apply_integral_scaling.py b/ufl/algorithms/apply_integral_scaling.py index 79c50c794..111bf4444 100644 --- a/ufl/algorithms/apply_integral_scaling.py +++ b/ufl/algorithms/apply_integral_scaling.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.classes import JacobianDeterminant, FacetJacobianDeterminant, QuadratureWeight, Form, Integral from ufl.measure import custom_integral_types, point_integral_types from ufl.differentiation import CoordinateDerivative @@ -70,7 +69,7 @@ def compute_integrand_scaling_factor(integral): scale = 1 else: - error("Unknown integral type {}, don't know how to scale.".format(integral_type)) + raise ValueError(f"Unknown integral type {integral_type}, don't know how to scale.") return scale, degree @@ -116,4 +115,4 @@ def scale_coordinate_derivative(o, scale): return integral.reconstruct(integrand=newintegrand, metadata=md) else: - error("Invalid type %s" % (form.__class__.__name__,)) + raise ValueError(f"Invalid type {form.__class__.__name__}") diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 2f2745e1d..f53b98fac 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -12,7 +12,6 @@ from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction from ufl.domain import extract_unique_domain -from ufl.log import error from ufl.measure import integral_type_to_measure_name from ufl.sobolevspace import H1 @@ -34,7 +33,7 @@ def restricted(self, o): # Assure that we have only two levels here, inside or outside # the Restricted node if self.current_restriction is not None: - error("Cannot restrict an expression twice.") + raise ValueError("Cannot restrict an expression twice.") # Configure a propagator for this side and apply to subtree side = o.side() return map_expr_dag(self._rp[side], o.ufl_operands[0], @@ -50,7 +49,7 @@ def _ignore_restriction(self, o): def _require_restriction(self, o): "Restrict a discontinuous quantity to current side, require a side to be set." if self.current_restriction is None: - error("Discontinuous type %s must be restricted." % o._ufl_class_.__name__) + raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") return o(self.current_restriction) def _default_restricted(self, o): @@ -63,14 +62,14 @@ def _default_restricted(self, o): def _opposite(self, o): "Restrict a quantity to default side, if the current restriction is different swap the sign, require a side to be set." if self.current_restriction is None: - error("Discontinuous type %s must be restricted." % o._ufl_class_.__name__) + raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") elif self.current_restriction == self.default_restriction: return o(self.default_restriction) else: return -o(self.default_restriction) def _missing_rule(self, o): - error("Missing rule for %s" % o._ufl_class_.__name__) + raise ValueError(f"Missing rule for {o._ufl_class_.__name__}") # --- Rules for operators diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index bd989f946..b3b4e07e4 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -7,8 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error - from ufl.core.multiindex import indices from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag @@ -130,15 +128,15 @@ def terminal(self, o): return o def coefficient_derivative(self, o, *dummy_ops): - error("Coefficient derivatives should be expanded before applying change to reference grad.") + raise ValueError("Coefficient derivatives should be expanded before applying change to reference grad.") def reference_grad(self, o, *dummy_ops): - error("Not expecting reference grad while applying change to reference grad.") + raise ValueError("Not expecting reference grad while applying change to reference grad.") def restricted(self, o, *dummy_ops): "Store modifier state." if self._restricted != '': - error("Not expecting nested restrictions.") + raise ValueError("Not expecting nested restrictions.") self._restricted = o.side() f, = o.ufl_operands r = self(f) @@ -155,7 +153,7 @@ def grad(self, o, *dummy_ops): def facet_avg(self, o, *dummy_ops): if self._avg != '': - error("Not expecting nested averages.") + raise ValueError("Not expecting nested averages.") self._avg = "facet" f, = o.ufl_operands r = self(f) @@ -164,7 +162,7 @@ def facet_avg(self, o, *dummy_ops): def cell_avg(self, o, *dummy_ops): if self._avg != '': - error("Not expecting nested averages.") + raise ValueError("Not expecting nested averages.") self._avg = "cell" f, = o.ufl_operands r = self(f) @@ -183,14 +181,14 @@ def geometric_quantity(self, t): def _mapped(self, t): # Check that we have a valid input object if not isinstance(t, Terminal): - error("Expecting a Terminal.") + raise ValueError("Expecting a Terminal.") # Get modifiers accumulated by previous handler calls ngrads = self._ngrads restricted = self._restricted avg = self._avg if avg != "": - error("Averaging not implemented.") # FIXME + raise ValueError("Averaging not implemented.") # FIXME # These are the global (g) and reference (r) values if isinstance(t, FormArgument): @@ -200,7 +198,7 @@ def _mapped(self, t): g = t r = g else: - error("Unexpected type {0}.".format(type(t).__name__)) + raise ValueError(f"Unexpected type {type(t).__name__}.") # Some geometry mapping objects we may need multiple times below domain = t.ufl_domain() @@ -268,7 +266,7 @@ def ndarray(shape): # Select mapping M from element, pick row emapping = # M[ec,:], or emapping = [] if no mapping if isinstance(element, MixedElement): - error("Expecting a basic element here.") + raise ValueError("Expecting a basic element here.") mapping = element.mapping() if mapping == "contravariant Piola": # S == HDiv: # Handle HDiv elements with contravariant piola @@ -284,20 +282,20 @@ def ndarray(shape): elif mapping == "identity": emapping = None else: - error("Unknown mapping {0}".format(mapping)) + raise ValueError(f"Unknown mapping: {mapping}") elif isinstance(t, GeometricQuantity): eoffset = 0 emapping = None else: - error("Unexpected type {0}.".format(type(t).__name__)) + raise ValueError(f"Unexpected type {type(t).__name__}.") # Create indices # if rtsh: # i = Index() if len(dsh) != ngrads: - error("Mismatch between derivative shape and ngrads.") + raise ValueError("Mismatch between derivative shape and ngrads.") if ngrads: ii = indices(ngrads) else: @@ -379,7 +377,7 @@ def grad(self, o): rv = True o, = o.ufl_operands else: - error("Invalid type %s" % o._ufl_class_.__name__) + raise ValueError(f"Invalid type {o._ufl_class_.__name__}") f = o if rv: f = ReferenceValue(f) @@ -439,10 +437,10 @@ def grad(self, o): return jinv_lgrad_f def reference_grad(self, o): - error("Not expecting reference grad while applying change to reference grad.") + raise ValueError("Not expecting reference grad while applying change to reference grad.") def coefficient_derivative(self, o): - error("Coefficient derivatives should be expanded before applying change to reference grad.") + raise ValueError("Coefficient derivatives should be expanded before applying change to reference grad.") def change_to_reference_grad(e): diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 065ebf19d..0eefe866d 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -3,14 +3,13 @@ from itertools import chain -from ufl.log import UFLException from ufl.corealg.traversal import traverse_unique_terminals from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.classes import Argument, Zero -class ArityMismatch(UFLException): +class ArityMismatch(BaseException): pass diff --git a/ufl/algorithms/check_restrictions.py b/ufl/algorithms/check_restrictions.py index 43963fed6..3aaca1786 100644 --- a/ufl/algorithms/check_restrictions.py +++ b/ufl/algorithms/check_restrictions.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag @@ -23,7 +22,7 @@ def expr(self, o): def restricted(self, o): if self.current_restriction is not None: - error("Not expecting twice restricted expression.") + raise ValueError("Not expecting twice restricted expression.") self.current_restriction = o._side e, = o.ufl_operands self.visit(e) @@ -32,18 +31,18 @@ def restricted(self, o): def facet_normal(self, o): if self.require_restriction: if self.current_restriction is None: - error("Facet normal must be restricted in interior facet integrals.") + raise ValueError("Facet normal must be restricted in interior facet integrals.") else: if self.current_restriction is not None: - error("Restrictions are only allowed for interior facet integrals.") + raise ValueError("Restrictions are only allowed for interior facet integrals.") def form_argument(self, o): if self.require_restriction: if self.current_restriction is None: - error("Form argument must be restricted in interior facet integrals.") + raise ValueError("Form argument must be restricted in interior facet integrals.") else: if self.current_restriction is not None: - error("Restrictions are only allowed for interior facet integrals.") + raise ValueError("Restrictions are only allowed for interior facet integrals.") def check_restrictions(expression, require_restriction): diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index b65fe8642..738cf9aae 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -10,8 +10,6 @@ # Modified by Anders Logg, 2008-2009. # Modified by Mehdi Nikbakht, 2010. -from ufl.log import error - # UFL classes from ufl.core.expr import ufl_err_str from ufl.form import Form @@ -31,10 +29,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste errors = [] if not isinstance(form, Form): - msg = "Validation failed, not a Form:\n%s" % ufl_err_str(form) - error(msg) - # errors.append(msg) - # return errors + raise ValueError(f"Validation failed, not a Form:\n{ufl_err_str(form)}") # FIXME: There's a bunch of other checks we should do here. @@ -55,7 +50,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste if not cells: errors.append("Missing cell definition in form.") elif len(cells) > 1: - errors.append("Multiple cell definitions in form: %s" % str(cells)) + errors.append(f"Multiple cell definitions in form: {cells}") # Check that no Coefficient or Argument instance have the same # count unless they are the same @@ -69,8 +64,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste g = coefficients[c] if f is not g: errors.append("Found different Coefficients with " + - "same count: %s and %s." % (repr(f), - repr(g))) + f"same count: {f} and {g}.") else: coefficients[c] = f @@ -110,5 +104,4 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste # TODO: Return errors list instead, need to collect messages from # all validations above first. if errors: - final_msg = 'Found errors in validation of form:\n%s' % '\n\n'.join(errors) - error(final_msg) + raise ValueError("Found errors in validation of form:\n" + '\n\n'.join(errors)) diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index b9888d241..e665a880e 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -10,8 +10,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from itertools import chain +from logging import info -from ufl.log import error, info from ufl.utils.sequences import max_degree from ufl.classes import GeometricFacetQuantity, Coefficient, Form, FunctionSpace @@ -80,15 +80,15 @@ def _compute_element_mapping(form): domains = form.ufl_domains() if not all(domains[0].ufl_cell() == d.ufl_cell() for d in domains): - error("Cannot replace unknown element cell without unique common cell in form.") + raise ValueError("Cannot replace unknown element cell without unique common cell in form.") cell = domains[0].ufl_cell() - info("Adjusting missing element cell to %s." % (cell,)) + info(f"Adjusting missing element cell to {cell}.") reconstruct = True # Set degree degree = element.degree() if degree is None: - info("Adjusting missing element degree to %d" % (common_degree,)) + info(f"Adjusting missing element degree to {common_degree}.") degree = common_degree reconstruct = True @@ -140,9 +140,9 @@ def _check_elements(form_data): for element in chain(form_data.unique_elements, form_data.unique_sub_elements): if element.family() is None: - error("Found element with undefined familty: %s" % repr(element)) + raise ValueError(f"Found element with undefined family: {element}") if element.cell() is None: - error("Found element with undefined cell: %s" % repr(element)) + raise ValueError(f"Found element with undefined cell: {element}") def _check_facet_geometry(integral_data): @@ -157,7 +157,7 @@ def _check_facet_geometry(integral_data): for expr in traverse_unique_terminals(itg.integrand()): cls = expr._ufl_class_ if issubclass(cls, GeometricFacetQuantity): - error("Integral of type %s cannot contain a %s." % (it, cls.__name__)) + raise ValueError(f"Integral of type {it} cannot contain a {cls.__name__}.") def _check_form_arity(preprocessed_form): @@ -166,7 +166,7 @@ def _check_form_arity(preprocessed_form): # FIXME: This is slooow and should be moved to form compiler # and/or replaced with something faster if 1 != len(compute_form_arities(preprocessed_form)): - error("All terms in form must have same rank.") + raise ValueError("All terms in form must have same rank.") def _build_coefficient_replace_map(coefficients, element_mapping=None): diff --git a/ufl/algorithms/coordinate_derivative_helpers.py b/ufl/algorithms/coordinate_derivative_helpers.py index e1491a439..938e59a3f 100644 --- a/ufl/algorithms/coordinate_derivative_helpers.py +++ b/ufl/algorithms/coordinate_derivative_helpers.py @@ -8,7 +8,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.differentiation import CoordinateDerivative from ufl.algorithms.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dags @@ -31,7 +30,7 @@ def expr(self, o, *operands): expressions apart from more CoordinateDerivatives are allowed to wrap around it. """ if any(operands): - error("CoordinateDerivative(s) must be outermost") + raise ValueError("CoordinateDerivative(s) must be outermost") return False def coordinate_derivative(self, o, expr, *_): @@ -70,7 +69,7 @@ def take_top_coordinate_derivatives(o): return (integral.reconstruct(integrand=newintegrand), coordinate_derivatives) else: - error("Invalid type %s" % (integrals.__class__.__name__,)) + raise ValueError(f"Invalid type {integrals.__class__.__name__}") def attach_coordinate_derivatives(integral, coordinate_derivatives): @@ -84,4 +83,4 @@ def attach_coordinate_derivatives(integral, coordinate_derivatives): integrand = CoordinateDerivative(integrand, tup[0], tup[1], tup[2]) return integral.reconstruct(integrand=integrand) else: - error("Invalid type %s" % (integral.__class__.__name__,)) + raise ValueError(f"Invalid type {integral.__class__.__name__}") diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 0a1cf8f9e..2a6f4ba3f 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -10,7 +10,6 @@ from collections import defaultdict import ufl -from ufl.log import error from ufl.integral import Integral from ufl.form import Form from ufl.sorting import cmp_expr, sorted_expr @@ -35,11 +34,11 @@ class IntegralData(object): def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): if 1 != len(set(itg.ufl_domain() for itg in integrals)): - error("Multiple domains mismatch in integral data.") + raise ValueError("Multiple domains mismatch in integral data.") if not all(integral_type == itg.integral_type() for itg in integrals): - error("Integral type mismatch in integral data.") + raise ValueError("Integral type mismatch in integral data.") if not all(subdomain_id == itg.subdomain_id() for itg in integrals): - error("Subdomain id mismatch in integral data.") + raise ValueError("Subdomain id mismatch in integral data.") self.domain = domain self.integral_type = integral_type @@ -124,7 +123,7 @@ def group_integrals_by_domain_and_type(integrals, domains): integrals_by_domain_and_type = defaultdict(list) for itg in integrals: if itg.ufl_domain() is None: - error("Integral has no domain.") + raise ValueError("Integral has no domain.") key = (itg.ufl_domain(), itg.integral_type()) # Append integral to list of integrals with shared key @@ -140,13 +139,13 @@ def integral_subdomain_ids(integral): return (did,) elif isinstance(did, tuple): if not all(isinstance(d, numbers.Integral) for d in did): - error("Expecting only integer subdomains in tuple.") + raise ValueError("Expecting only integer subdomains in tuple.") return did elif did in ("everywhere", "otherwise"): # TODO: Define list of valid strings somewhere more central return did else: - error("Invalid domain id %s." % did) + raise ValueError(f"Invalid domain id {did}.") def rearrange_integrals_by_single_subdomains(integrals, do_append_everywhere_integrals): @@ -164,7 +163,7 @@ def rearrange_integrals_by_single_subdomains(integrals, do_append_everywhere_int for itg in integrals: dids = integral_subdomain_ids(itg) if dids == "otherwise": - error("'otherwise' integrals should never occur before preprocessing.") + raise ValueError("'otherwise' integrals should never occur before preprocessing.") elif dids == "everywhere": everywhere_integrals.append(itg) else: diff --git a/ufl/algorithms/elementtransformations.py b/ufl/algorithms/elementtransformations.py index 30d95ea6e..43e659cb6 100644 --- a/ufl/algorithms/elementtransformations.py +++ b/ufl/algorithms/elementtransformations.py @@ -9,7 +9,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.finiteelement import FiniteElement, VectorElement, TensorElement, \ MixedElement, EnrichedElement, NodalEnrichedElement @@ -50,5 +49,5 @@ def _increase_degree(element, degree_rise): return NodalEnrichedElement([_increase_degree(e, degree_rise) for e in element.sub_elements()]) else: - error("Element reconstruction is only done to stay compatible" - " with hacks in DOLFIN. Not expecting a %s" % repr(element)) + raise ValueError("Element reconstruction is only done to stay compatible" + f" with hacks in DOLFIN. Not expecting a {element}") diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index c0896740c..c8271bbce 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -18,7 +18,6 @@ from ufl.domain import extract_unique_domain from ufl.form import Form from ufl.integral import Integral -from ufl.log import error class IrreducibleInt(int): @@ -115,11 +114,11 @@ def max_single(ops): return max_single(ops + (0,)) def _not_handled(self, v, *args): - error("Missing degree handler for type %s" % v._ufl_class_.__name__) + raise ValueError(f"Missing degree handler for type {v._ufl_class_.__name__}") def expr(self, v, *ops): "For most operators we take the max degree of its operands." - warnings.warn("Missing degree estimation handler for type %s" % v._ufl_class_.__name__) + warnings.warn(f"Missing degree estimation handler for type {v._ufl_class_.__name__}") return self._add_degrees(v, *ops) # Utility types with no degree concept @@ -327,7 +326,7 @@ def estimate_total_polynomial_degree(e, default_degree=1, de = SumDegreeEstimator(default_degree, element_replace_map) if isinstance(e, Form): if not e.integrals(): - error("Got form with no integrals!") + raise ValueError("Form has no integrals.") degrees = map_expr_dags(de, [it.integrand() for it in e.integrals()]) elif isinstance(e, Integral): degrees = map_expr_dags(de, [e.integrand()]) diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index 118a8be23..ca904e6cc 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -11,7 +11,6 @@ # # Modified by Anders Logg, 2009. -from ufl.log import error from ufl.utils.stacks import Stack, StackDict from ufl.classes import Terminal, ListTensor from ufl.constantvalue import Zero @@ -39,7 +38,7 @@ def terminal(self, x): if x.ufl_shape: c = self.component() if len(x.ufl_shape) != len(c): - error("Component size mismatch.") + raise ValueError("Component size mismatch.") return x[c] return x @@ -54,23 +53,23 @@ def form_argument(self, x): # Get component c = self.component() if r != len(c): - error("Component size mismatch.") + raise ValueError("Component size mismatch.") # Map it through an eventual symmetry mapping s = e.symmetry() c = s.get(c, c) if r != len(c): - error("Component size mismatch after symmetry mapping.") + raise ValueError("Component size mismatch after symmetry mapping.") return x[c] def zero(self, x): if len(x.ufl_shape) != len(self.component()): - error("Component size mismatch.") + raise ValueError("Component size mismatch.") s = set(x.ufl_free_indices) - set(i.count() for i in self._index2value.keys()) if s: - error("Free index set mismatch, these indices have no value assigned: %s." % str(s)) + raise ValueError(f"Free index set mismatch, these indices have no value assigned: {s}.") # There is no index/shape info in this zero because that is asserted above return Zero() @@ -79,11 +78,11 @@ def scalar_value(self, x): if len(x.ufl_shape) != len(self.component()): self.print_visit_stack() if len(x.ufl_shape) != len(self.component()): - error("Component size mismatch.") + raise ValueError("Component size mismatch.") s = set(x.ufl_free_indices) - set(i.count() for i in self._index2value.keys()) if s: - error("Free index set mismatch, these indices have no value assigned: %s." % str(s)) + raise ValueError(f"Free index set mismatch, these indices have no value assigned: {s}.") return x._ufl_class_(x.value()) @@ -92,7 +91,7 @@ def conditional(self, x): # Not accepting nonscalars in condition if c.ufl_shape != (): - error("Not expecting tensor in condition.") + raise ValueError("Not expecting tensor in condition.") # Conditional may be indexed, push empty component self._components.push(()) @@ -110,12 +109,12 @@ def division(self, x): # Not accepting nonscalars in division anymore if a.ufl_shape != (): - error("Not expecting tensor in division.") + raise ValueError("Not expecting tensor in division.") if self.component() != (): - error("Not expecting component in division.") + raise ValueError("Not expecting component in division.") if b.ufl_shape != (): - error("Not expecting division by tensor.") + raise ValueError("Not expecting division by tensor.") a = self.visit(a) # self._components.push(()) @@ -179,12 +178,12 @@ def component_tensor(self, x): # with indices equal to the current component tuple expression, indices = x.ufl_operands if expression.ufl_shape != (): - error("Expecting scalar base expression.") + raise ValueError("Expecting scalar base expression.") # Update index map with component tuple values comp = self.component() if len(indices) != len(comp): - error("Index/component mismatch.") + raise ValueError("Index/component mismatch.") for i, v in zip(indices.indices(), comp): self._index2value.push(i, v) self._components.push(()) @@ -212,7 +211,7 @@ def list_tensor(self, x): def grad(self, x): f, = x.ufl_operands if not isinstance(f, (Terminal, Grad)): - error("Expecting expand_derivatives to have been applied.") + raise ValueError("Expecting expand_derivatives to have been applied.") # No need to visit child as long as it is on the form [Grad]([Grad](terminal)) return x[self.component()] diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index 2a67df93d..fdd453374 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -13,7 +13,6 @@ import io import os import re -from ufl.log import error from ufl.utils.sorting import sorted_by_key from ufl.form import Form from ufl.finiteelement import FiniteElementBase @@ -70,7 +69,7 @@ def match(line): def read_ufl_file(filename): "Read a UFL file." if not os.path.exists(filename): - error("File '%s' doesn't exist." % filename) + raise ValueError(f"File '{filename}' doesn't exist.") lines = read_lines_decoded(filename) code = "".join(lines) return code @@ -130,9 +129,9 @@ def get_form(name): # Validate types if not isinstance(ufd.forms, (list, tuple)): - error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) + raise ValueError(f"Expecting 'forms' to be a list or tuple, not '{type(ufd.forms)}'.") if not all(isinstance(a, Form) for a in ufd.forms): - error("Expecting 'forms' to be a list of Form instances.") + raise ValueError("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") @@ -143,9 +142,9 @@ def get_form(name): # Validate types if not isinstance(ufd.elements, (list, tuple)): - error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) + raise ValueError(f"Expecting 'elements' to be a list or tuple, not '{type(ufd.elements)}''.") if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): - error("Expecting 'elements' to be a list of FiniteElementBase instances.") + raise ValueError("Expecting 'elements' to be a list of FiniteElementBase instances.") # Get list of exported coefficients functions = [] @@ -153,18 +152,18 @@ def get_form(name): # Validate types if not isinstance(ufd.coefficients, (list, tuple)): - error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) + raise ValueError(f"Expecting 'coefficients' to be a list or tuple, not '{type(ufd.coefficients)}'.") if not all(isinstance(e, Coefficient) for e in ufd.coefficients): - error("Expecting 'coefficients' to be a list of Coefficient instances.") + raise ValueError("Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): - error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) + raise ValueError(f"Expecting 'expressions' to be a list or tuple, not '{type(ufd.expressions)}'.") if not all(isinstance(e[0], Expr) for e in ufd.expressions): - error("Expecting 'expressions' to be a list of Expr instances.") + raise ValueError("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 765b5163b..647e2bed1 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -13,8 +13,7 @@ # Modified by Marie E. Rognes, 2010. import warnings - -from ufl.log import error, debug +from logging import debug # All classes: from ufl.core.expr import ufl_err_str @@ -60,7 +59,7 @@ def expr(self, x): """The default is a nonlinear operator not accepting any Arguments among its children.""" if _expr_has_terminal_types(x, Argument): - error("Found Argument in %s, this is an invalid expression." % ufl_err_str(x)) + raise ValueError(f"Found Argument in {ufl_err_str(x)}, this is an invalid expression.") return (x, set()) # Terminals that are not Variables or Arguments behave as default @@ -156,7 +155,7 @@ def sum(self, x): # Throw error if size of sets are equal (and not zero) if len(provideds) == len(most_provided) and len(most_provided): - error("Don't know what to do with sums with different Arguments.") + raise ValueError("Don't know what to do with sums with different Arguments.") if provideds > most_provided: most_provided = provideds @@ -208,7 +207,7 @@ def division(self, x): # Check for Arguments in the denominator if _expr_has_terminal_types(denominator, Argument): - error("Found Argument in denominator of %s , this is an invalid expression." % ufl_err_str(x)) + raise ValueError(f"Found Argument in denominator of {ufl_err_str(x)}, this is an invalid expression.") # Visit numerator numerator_parts, provides = self.visit(numerator) @@ -296,7 +295,7 @@ def list_tensor(self, x, *ops): # least with the current transformer design.) for (component, provides) in ops: if (provides != most_provides and not isinstance(component, Zero)): - error("PartExtracter does not know how to handle list_tensors with non-zero components providing fewer arguments") + raise ValueError("PartExtracter does not know how to handle list_tensors with non-zero components providing fewer arguments") # Return components components = [op[0] for op in ops] @@ -314,7 +313,7 @@ def compute_form_with_arity(form, arity, arguments=None): parts = [arg.part() for arg in arguments] if set(parts) - {None}: - error("compute_form_with_arity cannot handle parts.") + raise ValueError("compute_form_with_arity cannot handle parts.") if len(arguments) < arity: warnings.warn("Form has no parts with arity %d." % arity) @@ -343,7 +342,7 @@ def compute_form_arities(form): parts = [arg.part() for arg in arguments] if set(parts) - {None}: - error("compute_form_arities cannot handle parts.") + raise ValueError("compute_form_arities cannot handle parts.") arities = set() for arity in range(len(arguments) + 1): @@ -406,7 +405,7 @@ def compute_form_action(form, coefficient): parts = [arg.part() for arg in arguments] if set(parts) - {None}: - error("compute_form_action cannot handle parts.") + raise ValueError("compute_form_action cannot handle parts.") # Pick last argument (will be replaced) u = arguments[-1] @@ -434,20 +433,20 @@ def compute_energy_norm(form, coefficient): parts = [arg.part() for arg in arguments] if set(parts) - {None}: - error("compute_energy_norm cannot handle parts.") + raise ValueError("compute_energy_norm cannot handle parts.") if len(arguments) != 2: - error("Expecting bilinear form.") + raise ValueError("Expecting bilinear form.") v, u = arguments U = u.ufl_function_space() V = v.ufl_function_space() if U != V: - error("Expecting equal finite elements for test and trial functions, got '%s' and '%s'." % (U, V)) + raise ValueError(f"Expecting equal finite elements for test and trial functions, got '{U}' and '{V}'.") if coefficient is None: coefficient = Coefficient(V) else: if coefficient.ufl_function_space() != U: - error("Trying to compute action of form on a " + raise ValueError("Trying to compute action of form on a " "coefficient in an incompatible element space.") return action(action(form, coefficient), coefficient) @@ -462,14 +461,14 @@ def compute_form_adjoint(form, reordered_arguments=None): parts = [arg.part() for arg in arguments] if set(parts) - {None}: - error("compute_form_adjoint cannot handle parts.") + raise ValueError("compute_form_adjoint cannot handle parts.") if len(arguments) != 2: - error("Expecting bilinear form.") + raise ValueError("Expecting bilinear form.") v, u = arguments if v.number() >= u.number(): - error("Mistaken assumption in code!") + raise ValueError("Mistaken assumption in code!") if reordered_arguments is None: reordered_u = Argument(u.ufl_function_space(), number=v.number(), @@ -480,16 +479,16 @@ def compute_form_adjoint(form, reordered_arguments=None): reordered_u, reordered_v = reordered_arguments if reordered_u.number() >= reordered_v.number(): - error("Ordering of new arguments is the same as the old arguments!") + raise ValueError("Ordering of new arguments is the same as the old arguments!") if reordered_u.part() != v.part(): - error("Ordering of new arguments is the same as the old arguments!") + raise ValueError("Ordering of new arguments is the same as the old arguments!") if reordered_v.part() != u.part(): - error("Ordering of new arguments is the same as the old arguments!") + raise ValueError("Ordering of new arguments is the same as the old arguments!") if reordered_u.ufl_function_space() != u.ufl_function_space(): - error("Element mismatch between new and old arguments (trial functions).") + raise ValueError("Element mismatch between new and old arguments (trial functions).") if reordered_v.ufl_function_space() != v.ufl_function_space(): - error("Element mismatch between new and old arguments (test functions).") + raise ValueError("Element mismatch between new and old arguments (test functions).") return map_integrands(Conj, replace(form, {v: reordered_v, u: reordered_u})) diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 728b35f47..568e65ddb 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -11,7 +11,6 @@ # as part of a careful refactoring process, and this file depends on ufl.form # which drags in a lot of stuff. -from ufl.log import error from ufl.core.expr import Expr from ufl.corealg.map_dag import map_expr_dag from ufl.integral import Integral @@ -59,7 +58,7 @@ def map_integrands(function, form, only_integral_type=None): integrand = form return function(integrand) else: - error("Expecting Form, Integral or Expr.") + raise ValueError("Expecting Form, Integral or Expr.") def map_integrand_dags(function, form, only_integral_type=None, compress=True): diff --git a/ufl/algorithms/preprocess_expression.py b/ufl/algorithms/preprocess_expression.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/ufl/algorithms/remove_complex_nodes.py b/ufl/algorithms/remove_complex_nodes.py index ed6bc92d6..061956b20 100644 --- a/ufl/algorithms/remove_complex_nodes.py +++ b/ufl/algorithms/remove_complex_nodes.py @@ -5,7 +5,6 @@ from ufl.corealg.multifunction import MultiFunction from ufl.constantvalue import ComplexValue from ufl.algorithms.map_integrands import map_integrand_dags -from ufl.log import error class ComplexNodeRemoval(MultiFunction): @@ -19,11 +18,11 @@ def real(self, o, a): return a def imag(self, o, a): - error("Unexpected imag in real expression.") + raise ValueError("Unexpected imag in real expression.") def terminal(self, t, *ops): if isinstance(t, ComplexValue): - error('Unexpected complex value in real expression.') + raise ValueError('Unexpected complex value in real expression.') else: return t diff --git a/ufl/algorithms/renumbering.py b/ufl/algorithms/renumbering.py index 1708a10d7..f46d6b2e9 100644 --- a/ufl/algorithms/renumbering.py +++ b/ufl/algorithms/renumbering.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.expr import Expr from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.variable import Label, Variable @@ -70,5 +69,5 @@ def renumber_indices(expr): if isinstance(expr, Expr): if num_free_indices != len(result.ufl_free_indices): - error("The number of free indices left in expression should be invariant w.r.t. renumbering.") + raise ValueError("The number of free indices left in expression should be invariant w.r.t. renumbering.") return result diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index e3a8fb815..8d44aa442 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -9,7 +9,6 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.log import error from ufl.classes import CoefficientDerivative from ufl.constantvalue import as_ufl from ufl.corealg.multifunction import MultiFunction @@ -22,7 +21,7 @@ def __init__(self, mapping): super().__init__() self.mapping = mapping if not all(k.ufl_shape == v.ufl_shape for k, v in mapping.items()): - error("Replacement expressions must have the same shape as what they replace.") + raise ValueError("Replacement expressions must have the same shape as what they replace.") def ufl_type(self, o, *args): try: @@ -31,7 +30,7 @@ def ufl_type(self, o, *args): return self.reuse_if_untouched(o, *args) def coefficient_derivative(self, o): - error("Derivatives should be applied before executing replace.") + raise ValueError("Derivatives should be applied before executing replace.") def replace(e, mapping): diff --git a/ufl/algorithms/signature.py b/ufl/algorithms/signature.py index 8a778b6c4..ab9690bd6 100644 --- a/ufl/algorithms/signature.py +++ b/ufl/algorithms/signature.py @@ -13,7 +13,6 @@ Coefficient, Argument, GeometricQuantity, ConstantValue, Constant, ExprList, ExprMapping) -from ufl.log import error from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal from ufl.algorithms.domain_analysis import canonicalize_metadata @@ -86,7 +85,7 @@ def compute_terminal_hashdata(expressions, renumbering): data = "{}" else: - error("Unknown terminal type %s" % type(expr)) + raise ValueError(f"Unknown terminal type {type(expr)}") terminal_hashdata[expr] = data diff --git a/ufl/algorithms/transformer.py b/ufl/algorithms/transformer.py index 5587aff35..4ed95f69c 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -17,7 +17,6 @@ from ufl.algorithms.map_integrands import map_integrands from ufl.classes import Variable, all_ufl_classes from ufl.core.ufl_type import UFLType -from ufl.log import error def is_post_handler(function): @@ -97,7 +96,7 @@ def visit(self, o): # if not h: # # Failed to find a handler! Should never happen, but will happen if a non-Expr object is visited. - # error("Can't handle objects of type %s" % str(type(o))) + # raise ValueError("Can't handle objects of type %s" % str(type(o))) # Is this a handler that expects transformed children as # input? @@ -115,7 +114,7 @@ def visit(self, o): def undefined(self, o): "Trigger error." - error("No handler defined for %s." % o._ufl_class_.__name__) + raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") def reuse(self, o): "Always reuse Expr (ignore children)" diff --git a/ufl/algorithms/traversal.py b/ufl/algorithms/traversal.py index 809ca1ca3..aab1573aa 100644 --- a/ufl/algorithms/traversal.py +++ b/ufl/algorithms/traversal.py @@ -9,7 +9,6 @@ # # Modified by Anders Logg, 2008 -from ufl.log import error from ufl.core.expr import Expr from ufl.integral import Integral from ufl.action import Action @@ -38,4 +37,4 @@ def iter_expressions(a): return tuple(e for op in a.ufl_operands for e in iter_expressions(op)) elif isinstance(a, (Expr, BaseForm)): return (a,) - error("Not an UFL type: %s" % str(type(a))) + raise ValueError(f"Not an UFL type: {type(a)}") diff --git a/ufl/argument.py b/ufl/argument.py index 4c53c00d8..3f143e0ef 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -13,7 +13,6 @@ # Modified by Cecile Daversin-Catty, 2018. import numbers -from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument from ufl.split_functions import split @@ -46,20 +45,19 @@ def __init__(self, function_space, number, part=None): domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) elif not isinstance(function_space, AbstractFunctionSpace): - error("Expecting a FunctionSpace or FiniteElement.") + raise ValueError("Expecting a FunctionSpace or FiniteElement.") self._ufl_function_space = function_space self._ufl_shape = function_space.ufl_element().value_shape() if not isinstance(number, numbers.Integral): - error("Expecting an int for number, not %s" % (number,)) + raise ValueError(f"Expecting an int for number, not {number}") if part is not None and not isinstance(part, numbers.Integral): - error("Expecting None or an int for part, not %s" % (part,)) + raise ValueError(f"Expecting None or an int for part, not {part}") self._number = number self._part = part - self._repr = "BaseArgument(%s, %s, %s)" % ( - repr(self._ufl_function_space), repr(self._number), repr(self._part)) + self._repr = f"BaseArgument({self._ufl_function_space}, {self._number}, {self._part})" @property def ufl_shape(self): diff --git a/ufl/assertions.py b/ufl/assertions.py deleted file mode 100644 index 5d2bc47f1..000000000 --- a/ufl/assertions.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module provides assertion functions used by the UFL implementation.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from ufl.log import error - - -# TODO: Move to this file and make other files import from here -from ufl.core.expr import ufl_err_str - - -# TODO: Use these and add more -# --- Standardized error messages --- - -def expecting_instance(v, c): - error("Expecting %s instance, not %s." % (c.__name__, ufl_err_str(v))) - - -def expecting_python_scalar(v): - error("Expecting Python scalar, not %s." % ufl_err_str(v)) - - -def expecting_expr(v): - error("Expecting Expr instance, not %s." % ufl_err_str(v)) - - -def expecting_terminal(v): - error("Expecting Terminal instance, not %s." % ufl_err_str(v)) - - -def expecting_true_ufl_scalar(v): - error("Expecting UFL scalar expression with no free indices, not %s." % ufl_err_str(v)) - - -# --- Standardized assertions --- - -# TODO: Stop using this -def ufl_assert(condition, *message): - "Assert that condition is true and otherwise issue an error with given message." - if not condition: - error(*message) diff --git a/ufl/cell.py b/ufl/cell.py index c423a94f8..4fd0cb8e7 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -11,7 +11,6 @@ import ufl.cell from ufl.core.ufl_type import attach_operators_from_hash_data -from ufl.log import error # Export list for ufl.classes __all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"] @@ -30,11 +29,11 @@ class AbstractCell(object): def __init__(self, topological_dimension, geometric_dimension): # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): - error("Expecting integer geometric_dimension.") + raise ValueError("Expecting integer geometric_dimension.") if not isinstance(topological_dimension, numbers.Integral): - error("Expecting integer topological_dimension.") + raise ValueError("Expecting integer topological_dimension.") if topological_dimension > geometric_dimension: - error("Topological dimension cannot be larger than geometric dimension.") + raise ValueError("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions self._topological_dimension = topological_dimension @@ -236,7 +235,7 @@ def num_vertices(self): def num_edges(self): "The number of cell edges." - error("Not defined for TensorProductCell.") + raise ValueError("Not defined for TensorProductCell.") def num_facets(self): "The number of cell facets." diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 9caed09bd..51a508c18 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -12,7 +12,6 @@ # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. -from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument from ufl.finiteelement import FiniteElementBase @@ -50,7 +49,7 @@ def __init__(self, function_space, count=None): domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) elif not isinstance(function_space, AbstractFunctionSpace): - error("Expecting a FunctionSpace or FiniteElement.") + raise ValueError("Expecting a FunctionSpace or FiniteElement.") self._ufl_function_space = function_space self._ufl_shape = function_space.ufl_element().value_shape() diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index d25a2ea8f..60acb3054 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -9,7 +9,6 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.log import error from ufl.core.multiindex import indices, Index from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.operators import sqrt @@ -98,7 +97,7 @@ def determinant_expr(A): return pseudo_determinant_expr(A) # TODO: Implement generally for all dimensions? - error("determinant_expr not implemented for shape %s." % (sh,)) + raise ValueError(f"determinant_expr not implemented for shape {sh}.") def _det_2x2(B, i, j, k, l): @@ -148,7 +147,7 @@ def inverse_expr(A): def adj_expr(A): sh = A.ufl_shape if sh[0] != sh[1]: - error("Expecting square matrix.") + raise ValueError("Expecting square matrix.") if sh[0] == 2: return adj_expr_2x2(A) @@ -157,7 +156,7 @@ def adj_expr(A): elif sh[0] == 4: return adj_expr_4x4(A) - error("adj_expr not implemented for dimension %s." % sh[0]) + raise ValueError(f"adj_expr not implemented for dimension {sh[0]}.") def adj_expr_2x2(A): @@ -197,7 +196,7 @@ def adj_expr_4x4(A): def cofactor_expr(A): sh = A.ufl_shape if sh[0] != sh[1]: - error("Expecting square matrix.") + raise ValueError("Expecting square matrix.") if sh[0] == 2: return cofactor_expr_2x2(A) @@ -206,7 +205,7 @@ def cofactor_expr(A): elif sh[0] == 4: return cofactor_expr_4x4(A) - error("cofactor_expr not implemented for dimension %s." % sh[0]) + raise ValueError(f"cofactor_expr not implemented for dimension {sh[0]}.") def cofactor_expr_2x2(A): @@ -246,14 +245,14 @@ def cofactor_expr_4x4(A): def deviatoric_expr(A): sh = A.ufl_shape if sh[0] != sh[1]: - error("Expecting square matrix.") + raise ValueError("Expecting square matrix.") if sh[0] == 2: return deviatoric_expr_2x2(A) elif sh[0] == 3: return deviatoric_expr_3x3(A) - error("deviatoric_expr not implemented for dimension %s." % sh[0]) + raise ValueError(f"deviatoric_expr not implemented for dimension {sh[0]}.") def deviatoric_expr_2x2(A): diff --git a/ufl/conditional.py b/ufl/conditional.py index 2cb8fab96..8b0253538 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -9,7 +9,6 @@ import warnings -from ufl.log import error from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator @@ -33,7 +32,7 @@ def __init__(self, operands): def __bool__(self): # Showing explicit error here to protect against misuse - error("UFL conditions cannot be evaluated as bool in a Python context.") + raise ValueError("UFL conditions cannot be evaluated as bool in a Python context.") __nonzero__ = __bool__ @@ -60,14 +59,14 @@ def __init__(self, name, left, right): # only conditions for arg in (left, right): if not isinstance(arg, Condition): - error("Expecting a Condition, not %s." % ufl_err_str(arg)) + raise ValueError(f"Expecting a Condition, not {ufl_err_str(arg)}.") else: # Binary operators acting on non-boolean expressions allow # only scalars if left.ufl_shape != () or right.ufl_shape != (): - error("Expecting scalar arguments.") + raise ValueError("Expecting scalar arguments.") if left.ufl_free_indices != () or right.ufl_free_indices != (): - error("Expecting scalar arguments.") + raise ValueError("Expecting scalar arguments.") def __str__(self): return "%s %s %s" % (parstr(self.ufl_operands[0], self), @@ -197,7 +196,7 @@ class NotCondition(Condition): def __init__(self, condition): Condition.__init__(self, (condition,)) if not isinstance(condition, Condition): - error("Expecting a condition.") + raise ValueError("Expecting a condition.") def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) @@ -216,23 +215,23 @@ class Conditional(Operator): def __init__(self, condition, true_value, false_value): if not isinstance(condition, Condition): - error("Expectiong condition as first argument.") + raise ValueError("Expectiong condition as first argument.") true_value = as_ufl(true_value) false_value = as_ufl(false_value) tsh = true_value.ufl_shape fsh = false_value.ufl_shape if tsh != fsh: - error("Shape mismatch between conditional branches.") + raise ValueError("Shape mismatch between conditional branches.") tfi = true_value.ufl_free_indices ffi = false_value.ufl_free_indices if tfi != ffi: - error("Free index mismatch between conditional branches.") + raise ValueError("Free index mismatch between conditional branches.") if isinstance(condition, (EQ, NE)): if not all((condition.ufl_operands[0].ufl_shape == (), condition.ufl_operands[0].ufl_free_indices == (), condition.ufl_operands[1].ufl_shape == (), condition.ufl_operands[1].ufl_free_indices == ())): - error("Non-scalar == or != is not allowed.") + raise ValueError("Non-scalar == or != is not allowed.") Operator.__init__(self, (condition, true_value, false_value)) @@ -258,7 +257,7 @@ class MinValue(Operator): def __init__(self, left, right): Operator.__init__(self, (left, right)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): - error("Expecting scalar arguments.") + raise ValueError("Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands @@ -283,7 +282,7 @@ class MaxValue(Operator): def __init__(self, left, right): Operator.__init__(self, (left, right)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): - error("Expecting scalar arguments.") + raise ValueError("Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index b8cf5add1..a67c25099 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -13,7 +13,6 @@ from math import atan2 import ufl -from ufl.log import error, UFLValueError from ufl.core.expr import Expr from ufl.core.terminal import Terminal from ufl.core.multiindex import Index, FixedIndex @@ -86,9 +85,9 @@ def _init(self, shape=(), free_indices=(), index_dimensions=None): ConstantValue.__init__(self) if not all(isinstance(i, int) for i in shape): - error("Expecting tuple of int.") + raise ValueError("Expecting tuple of int.") if not isinstance(free_indices, tuple): - error("Expecting tuple for free_indices, not %s" % str(free_indices)) + raise ValueError(f"Expecting tuple for free_indices, not {free_indices}.") self.ufl_shape = shape if not free_indices: @@ -97,19 +96,19 @@ def _init(self, shape=(), free_indices=(), index_dimensions=None): elif all(isinstance(i, Index) for i in free_indices): # Handle old input format if not (isinstance(index_dimensions, dict) and all(isinstance(i, Index) for i in index_dimensions.keys())): - error("Expecting tuple of index dimensions, not %s" % str(index_dimensions)) + raise ValueError(f"Expecting tuple of index dimensions, not {index_dimensions}") self.ufl_free_indices = tuple(sorted(i.count() for i in free_indices)) self.ufl_index_dimensions = tuple(d for i, d in sorted(index_dimensions.items(), key=lambda x: x[0].count())) else: # Handle new input format if not all(isinstance(i, int) for i in free_indices): - error("Expecting tuple of integer free index ids, not %s" % str(free_indices)) + raise ValueError(f"Expecting tuple of integer free index ids, not {free_indices}") if not (isinstance(index_dimensions, tuple) and all(isinstance(i, int) for i in index_dimensions)): - error("Expecting tuple of integer index dimensions, not %s" % str(index_dimensions)) + raise ValueError(f"Expecting tuple of integer index dimensions, not {index_dimensions}") # Assuming sorted now to avoid this cost, enable for debugging: # if sorted(free_indices) != list(free_indices): - # error("Expecting sorted input. Remove this check later for efficiency.") + # raise ValueError("Expecting sorted input. Remove this check later for efficiency.") self.ufl_free_indices = free_indices self.ufl_index_dimensions = index_dimensions @@ -356,7 +355,7 @@ def evaluate(self, x, mapping, component, index_values): def __getitem__(self, key): if len(key) != 2: - error("Size mismatch for Identity.") + raise ValueError("Size mismatch for Identity.") if all(isinstance(k, (int, FixedIndex)) for k in key): return IntValue(1) if (int(key[0]) == int(key[1])) else Zero() return Expr.__getitem__(self, key) @@ -393,7 +392,7 @@ def evaluate(self, x, mapping, component, index_values): def __getitem__(self, key): if len(key) != self._dim: - error("Size mismatch for PermutationSymbol.") + raise ValueError("Size mismatch for PermutationSymbol.") if all(isinstance(k, (int, FixedIndex)) for k in key): return self.__eps(key) return Expr.__getitem__(self, key) @@ -435,5 +434,5 @@ def as_ufl(expression): elif isinstance(expression, int): return IntValue(expression) else: - raise UFLValueError("Invalid type conversion: %s can not be converted" - " to any UFL type." % str(expression)) + raise ValueError( + f"Invalid type conversion: {expression} can not be converted to any UFL type.") diff --git a/ufl/core/expr.py b/ufl/core/expr.py index b856c3d61..d6d51e412 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -21,7 +21,6 @@ import warnings -from ufl.log import error from ufl.core.ufl_type import UFLType, update_ufl_type_attributes @@ -283,7 +282,7 @@ def ufl_domain(self): def evaluate(self, x, mapping, component, index_values): """Evaluate expression at given coordinate with given values for terminals.""" - error("Symbolic evaluation of %s not available." % self._ufl_class_.__name__) + raise ValueError(f"Symbolic evaluation of {self._ufl_class_.__name__} not available.") def _ufl_evaluate_scalar_(self): if self.ufl_shape or self.ufl_free_indices: diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 56ac44dd1..55857b8c7 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -10,7 +10,6 @@ # Modified by Massimiliano Leoni, 2016. -from ufl.log import error from ufl.utils.counted import counted_init from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal @@ -40,7 +39,7 @@ def __new__(cls, value): self = FixedIndex._cache.get(value) if self is None: if not isinstance(value, int): - error("Expecting integer value for fixed index.") + raise ValueError("Expecting integer value for fixed index.") self = IndexBase.__new__(cls) self._init(value) FixedIndex._cache[value] = self @@ -115,7 +114,7 @@ def __getnewargs__(self): def __new__(cls, indices): if not isinstance(indices, tuple): - error("Expecting a tuple of indices.") + raise ValueError("Expecting a tuple of indices.") if all(isinstance(ind, FixedIndex) for ind in indices): # Cache multiindices consisting of purely fixed indices @@ -130,7 +129,7 @@ def __new__(cls, indices): # Create a new object if we have any free indices (too # many combinations to cache) if not all(isinstance(ind, IndexBase) for ind in indices): - error("Expecting only Index and FixedIndex objects.") + raise ValueError("Expecting only Index and FixedIndex objects.") self = Terminal.__new__(cls) # Initialize here instead of in __init__ to avoid overwriting @@ -170,17 +169,17 @@ def evaluate(self, x, mapping, component, index_values): @property def ufl_shape(self): "This shall not be used." - error("Multiindex has no shape (it is not a tensor expression).") + raise ValueError("Multiindex has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): "This shall not be used." - error("Multiindex has no free indices (it is not a tensor expression).") + raise ValueError("Multiindex has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): "This shall not be used." - error("Multiindex has no free indices (it is not a tensor expression).") + raise ValueError("Multiindex has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): "Always True." diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 0f81e6bd1..c66c2463d 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -13,7 +13,6 @@ import warnings -from ufl.log import error from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type @@ -31,7 +30,7 @@ def __init__(self): def _ufl_expr_reconstruct_(self, *operands): "Return self." if operands: - error("Terminal has no operands.") + raise ValueError("Terminal has no operands.") return self ufl_operands = () diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index e4d9f6ca6..e0ee5e8f3 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -11,7 +11,6 @@ import inspect -from ufl.log import error from ufl.core.expr import Expr from ufl.core.ufl_type import UFLType @@ -98,7 +97,7 @@ def __call__(self, o, *args): def undefined(self, o, *args): "Trigger error for types with missing handlers." - error("No handler defined for %s." % o._ufl_class_.__name__) + raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 8f678a9d3..d1155387e 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -16,7 +16,6 @@ from ufl.domain import extract_unique_domain, find_geometric_dimension from ufl.exprcontainers import ExprList, ExprMapping from ufl.form import BaseForm -from ufl.log import error from ufl.precedence import parstr from ufl.variable import Variable @@ -43,11 +42,11 @@ class CoefficientDerivative(Derivative): def __new__(cls, integrand, coefficients, arguments, coefficient_derivatives): if not isinstance(coefficients, ExprList): - error("Expecting ExprList instance with Coefficients.") + raise ValueError("Expecting ExprList instance with Coefficients.") if not isinstance(arguments, ExprList): - error("Expecting ExprList instance with Arguments.") + raise ValueError("Expecting ExprList instance with Arguments.") if not isinstance(coefficient_derivatives, ExprMapping): - error("Expecting ExprMapping for coefficient derivatives.") + raise ValueError("Expecting ExprMapping for coefficient derivatives.") if isinstance(integrand, Zero): return integrand return Derivative.__new__(cls) @@ -107,11 +106,11 @@ class VariableDerivative(Derivative): def __new__(cls, f, v): # Checks if not isinstance(f, Expr): - error("Expecting an Expr in VariableDerivative.") + raise ValueError("Expecting an Expr in VariableDerivative.") if not isinstance(v, (Variable, Coefficient)): - error("Expecting a Variable in VariableDerivative.") + raise ValueError("Expecting a Variable in VariableDerivative.") if v.ufl_free_indices: - error("Differentiation variable cannot have free indices.") + raise ValueError("Differentiation variable cannot have free indices.") # Simplification # Return zero if expression is trivially independent of variable @@ -166,9 +165,9 @@ 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 Grad reconstruct.") + raise ValueError("Operand shape mismatch in Grad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: - error("Free index mismatch in Grad reconstruct.") + raise ValueError("Free index mismatch in Grad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @@ -211,9 +210,9 @@ 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 ReferenceGrad reconstruct.") + raise ValueError("Operand shape mismatch in ReferenceGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: - error("Free index mismatch in ReferenceGrad reconstruct.") + raise ValueError("Free index mismatch in ReferenceGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @@ -241,7 +240,7 @@ class Div(CompoundDerivative): def __new__(cls, f): if f.ufl_free_indices: - error("Free indices in the divergence argument is not allowed.") + raise ValueError("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): @@ -267,7 +266,7 @@ class ReferenceDiv(CompoundDerivative): def __new__(cls, f): if f.ufl_free_indices: - error("Free indices in the divergence argument is not allowed.") + raise ValueError("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): @@ -306,9 +305,9 @@ 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.") + raise ValueError("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.") + raise ValueError("Free index mismatch in NablaGrad reconstruct.") return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @@ -327,7 +326,7 @@ class NablaDiv(CompoundDerivative): def __new__(cls, f): if f.ufl_free_indices: - error("Free indices in the divergence argument is not allowed.") + raise ValueError("Free indices in the divergence argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): @@ -357,9 +356,9 @@ 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.") + raise ValueError("Expecting a scalar, 2D vector or 3D vector.") if f.ufl_free_indices: - error("Free indices in the curl argument is not allowed.") + raise ValueError("Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): @@ -385,9 +384,9 @@ 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.") + raise ValueError("Expecting a scalar, 2D vector or 3D vector.") if f.ufl_free_indices: - error("Free indices in the curl argument is not allowed.") + raise ValueError("Free indices in the curl argument is not allowed.") # Return zero if expression is trivially constant if is_cellwise_constant(f): diff --git a/ufl/domain.py b/ufl/domain.py index 4f95a26f0..b58acdcf4 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -13,7 +13,6 @@ from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.corealg.traversal import traverse_unique_terminals from ufl.finiteelement.tensorproductelement import TensorProductElement -from ufl.log import error # Export list for ufl.classes __all_classes__ = ["AbstractDomain", "Mesh", "MeshView", "TensorProductMesh"] @@ -28,11 +27,11 @@ class AbstractDomain(object): def __init__(self, topological_dimension, geometric_dimension): # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): - error("Expecting integer geometric dimension, not %s" % (geometric_dimension.__class__,)) + raise ValueError(f"Expecting integer geometric dimension, not {geometric_dimension.__class__}") if not isinstance(topological_dimension, numbers.Integral): - error("Expecting integer topological dimension, not %s" % (topological_dimension.__class__,)) + raise ValueError(f"Expecting integer topological dimension, not {topological_dimension.__class__}") if topological_dimension > geometric_dimension: - error("Topological dimension cannot be larger than geometric dimension.") + raise ValueError("Topological dimension cannot be larger than geometric dimension.") # Store validated dimensions self._topological_dimension = topological_dimension @@ -65,12 +64,12 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): # Store reference to object that will not be used by UFL self._ufl_cargo = cargo if cargo is not None and cargo.ufl_id() != self._ufl_id: - error("Expecting cargo object (e.g. dolfin.Mesh) to have the same 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): - error("Expecting a coordinate element in the ufl.Mesh construct.") + raise ValueError("Expecting a coordinate element in the ufl.Mesh construct.") # Accept a cell in place of an element for brevity Mesh(triangle) if isinstance(coordinate_element, AbstractCell): @@ -304,7 +303,7 @@ def join_domains(domains): for domain in domains: gdims.add(domain.geometric_dimension()) if len(gdims) != 1: - error("Found domains with different geometric dimensions.") + raise ValueError("Found domains with different geometric dimensions.") gdim, = gdims # Split into legacy and modern style domains @@ -320,10 +319,11 @@ def join_domains(domains): # Handle legacy domains checking if legacy_domains: if modern_domains: - error("Found both a new-style domain and a legacy default domain.\n" - "These should not be used interchangeably. To find the legacy\n" - "domain, note that it is automatically created from a cell so\n" - "look for constructors taking a cell.") + raise ValueError( + "Found both a new-style domain and a legacy default domain. " + "These should not be used interchangeably. To find the legacy " + "domain, note that it is automatically created from a cell so " + "look for constructors taking a cell.") return tuple(legacy_domains) # Handle modern domains checking (assuming correct by construction) @@ -346,7 +346,7 @@ def extract_unique_domain(expr): if len(domains) == 1: return domains[0] elif domains: - error("Found multiple domains, cannot return just one.") + raise ValueError("Found multiple domains, cannot return just one.") else: return None @@ -366,6 +366,6 @@ def find_geometric_dimension(expr): gdims.add(cell.geometric_dimension()) if len(gdims) != 1: - error("Cannot determine geometric dimension from expression.") + raise ValueError("Cannot determine geometric dimension from expression.") gdim, = gdims return gdim diff --git a/ufl/equation.py b/ufl/equation.py index 3e008a277..316e99cf4 100644 --- a/ufl/equation.py +++ b/ufl/equation.py @@ -7,8 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error - # Export list for ufl.classes __all_classes__ = ["Equation"] @@ -41,7 +39,7 @@ def __bool__(self): elif hasattr(self.rhs, "equals"): return self.rhs.equals(self.lhs) else: - error("Either lhs or rhs of Equation must implement self.equals(other).") + raise ValueError("Either lhs or rhs of Equation must implement self.equals(other).") __nonzero__ = __bool__ def __eq__(self, other): diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index c50050953..76858b19b 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type @@ -26,7 +25,7 @@ def __init__(self, *operands): Operator.__init__(self, operands) # Enable Cofunction/Coargument for BaseForm differentiation if not all(isinstance(i, (Expr, Cofunction, Coargument)) for i in operands): - error("Expecting Expr, Cofunction or Coargument in ExprList.") + raise ValueError("Expecting Expr, Cofunction or Coargument in ExprList.") def __getitem__(self, i): return self.ufl_operands[i] @@ -46,21 +45,21 @@ def __repr__(self): @property def ufl_shape(self): - error("A non-tensor type has no ufl_shape.") + raise ValueError("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): - error("A non-tensor type has no ufl_free_indices.") + raise ValueError("A non-tensor type has no ufl_free_indices.") def free_indices(self): - error("A non-tensor type has no free_indices.") + raise ValueError("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): - error("A non-tensor type has no ufl_index_dimensions.") + raise ValueError("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): - error("A non-tensor type has no index_dimensions.") + raise ValueError("A non-tensor type has no index_dimensions.") @ufl_type(num_ops="varying") @@ -71,7 +70,7 @@ class ExprMapping(Operator): def __init__(self, *operands): Operator.__init__(self, operands) if not all(isinstance(e, Expr) for e in operands): - error("Expecting Expr in ExprMapping.") + raise ValueError("Expecting Expr in ExprMapping.") def ufl_domains(self): # Because this type can act like a terminal if it has no @@ -99,18 +98,18 @@ def __repr__(self): @property def ufl_shape(self): - error("A non-tensor type has no ufl_shape.") + raise ValueError("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): - error("A non-tensor type has no ufl_free_indices.") + raise ValueError("A non-tensor type has no ufl_free_indices.") def free_indices(self): - error("A non-tensor type has no free_indices.") + raise ValueError("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): - error("A non-tensor type has no ufl_index_dimensions.") + raise ValueError("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): - error("A non-tensor type has no index_dimensions.") + raise ValueError("A non-tensor type has no index_dimensions.") diff --git a/ufl/exprequals.py b/ufl/exprequals.py index 653e39fe4..16505ee05 100644 --- a/ufl/exprequals.py +++ b/ufl/exprequals.py @@ -3,7 +3,6 @@ from collections import defaultdict from ufl.core.expr import Expr -from ufl.log import error hash_total = defaultdict(int) hash_collisions = defaultdict(int) @@ -54,7 +53,7 @@ def equals_func_with_collision_measuring(self, other): if sh == oh and not equal: hash_collisions[key] += 1 elif sh != oh and equal: - error("Equal objects must always have the same hash! Objects are:\n{0}\n{1}".format(self, other)) + raise ValueError(f"Equal objects must always have the same hash! Objects are:\n{self}\n{other}") elif sh == oh and equal: hash_equals[key] += 1 elif sh != oh and not equal: diff --git a/ufl/exproperators.py b/ufl/exproperators.py index 054cead59..72fa1039f 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -14,7 +14,6 @@ from itertools import chain import numbers -from ufl.log import error from ufl.utils.stacks import StackDict from ufl.core.expr import Expr from ufl.constantvalue import Zero, as_ufl @@ -88,9 +87,9 @@ def _ne(self, other): def _as_tensor(self, indices): "UFL operator: A^indices := as_tensor(A, indices)." if not isinstance(indices, tuple): - error("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") + raise ValueError("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") if not all(isinstance(i, Index) for i in indices): - error("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") + raise ValueError("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") return as_tensor(self, indices) @@ -137,7 +136,7 @@ def _mult(a, b): elif r1 == 2 and r2 in (1, 2): # Matrix-matrix or matrix-vector if ri: - error("Not expecting repeated indices in non-scalar product.") + raise ValueError("Not expecting repeated indices in non-scalar product.") # Check for zero, simplifying early if possible if isinstance(a, Zero) or isinstance(b, Zero): @@ -153,7 +152,7 @@ def _mult(a, b): ti = ai + bi else: - error("Invalid ranks {0} and {1} in product.".format(r1, r2)) + raise ValueError(f"Invalid ranks {r1} and {r2} in product.") # TODO: I think applying as_tensor after index sums results in # cleaner expression graphs. @@ -302,7 +301,7 @@ def _restrict(self, side): return PositiveRestricted(self) if side == "-": return NegativeRestricted(self) - error("Invalid side '%s' in restriction operator." % (side,)) + raise ValueError(f"Invalid side '{side}' in restriction operator.") def _eval(self, coord, mapping=None, component=()): @@ -324,7 +323,7 @@ def _call(self, arg, mapping=None, component=()): # Taking the restriction or evaluating depending on argument if arg in ("+", "-"): if mapping is not None: - error("Not expecting a mapping when taking restriction.") + raise ValueError("Not expecting a mapping when taking restriction.") return _restrict(self, arg) else: return _eval(self, arg, mapping, component) @@ -390,7 +389,7 @@ def analyse_key(ii, rank): # Switch from pre to post list when an ellipsis is # encountered if indexlist is not pre: - error("Found duplicate ellipsis.") + raise ValueError("Found duplicate ellipsis.") indexlist = post else: # Convert index to a proper type @@ -404,9 +403,9 @@ def analyse_key(ii, rank): axis_indices.add(idx) else: # TODO: Use ListTensor to support partial slices? - error("Partial slices not implemented, only complete slices like [:]") + raise ValueError("Partial slices not implemented, only complete slices like [:]") else: - error("Can't convert this object to index: %s" % (i,)) + raise ValueError(f"Can't convert this object to index: {i}") # Store index in pre or post list indexlist.append(idx) @@ -440,7 +439,7 @@ def _getitem(self, component): # Check that we have the right number of indices for a tensor with # this shape if len(shape) != len(all_indices): - error("Invalid number of indices {0} for expression of rank {1}.".format(len(all_indices), len(shape))) + raise ValueError(f"Invalid number of indices {len(all_indices)} for expression of rank {len(shape)}.") # Special case for simplifying foo[...] => foo, foo[:] => foo or # similar diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index f5ed2265b..a470279f3 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -16,7 +16,6 @@ import warnings from numpy import asarray -from ufl.log import error from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf from ufl.utils.formatting import istr from ufl.cell import Cell, TensorProductCell @@ -34,20 +33,12 @@ def register_element(family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames): "Register new finite element family." if family in ufl_elements: - error('Finite element \"%s\" has already been registered.' % family) + raise ValueError(f"Finite element '{family}%s' has already been registered.") ufl_elements[family] = (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) - ufl_elements[short_name] = (family, short_name, value_rank, sobolev_space, - mapping, degree_range, cellnames) - - -def register_element2(family, value_rank, sobolev_space, mapping, - degree_range, cellnames): - "Register new finite element family." - if family in ufl_elements: - error('Finite element \"%s\" has already been registered.' % family) - ufl_elements[family] = (family, family, value_rank, sobolev_space, - mapping, degree_range, cellnames) + if short_name is not None: + ufl_elements[short_name] = (family, short_name, value_rank, sobolev_space, + mapping, degree_range, cellnames) def register_alias(alias, to): @@ -161,7 +152,7 @@ def show_elements(): register_alias("Lob", lambda family, dim, order, degree: ("Gauss-Lobatto-Legendre", order)) -register_element2("Bernstein", 0, H1, "identity", (1, None), simplices) +register_element("Bernstein", None, 0, H1, "identity", (1, None), simplices) # Let Nedelec H(div) elements be aliases to BDMs/RTs @@ -184,27 +175,27 @@ def show_elements(): lambda family, dim, order, degree: ("HDiv Trace", order)) # New elements introduced for the periodic table 2014 -register_element2("Q", 0, H1, "identity", (1, None), cubes) -register_element2("DQ", 0, L2, "identity", (0, None), cubes) -register_element2("RTCE", 1, HCurl, "covariant Piola", (1, None), - ("quadrilateral",)) -register_element2("RTCF", 1, HDiv, "contravariant Piola", (1, None), - ("quadrilateral",)) -register_element2("NCE", 1, HCurl, "covariant Piola", (1, None), - ("hexahedron",)) -register_element2("NCF", 1, HDiv, "contravariant Piola", (1, None), - ("hexahedron",)) - -register_element2("S", 0, H1, "identity", (1, None), cubes) -register_element2("DPC", 0, L2, "identity", (0, None), cubes) -register_element2("BDMCE", 1, HCurl, "covariant Piola", (1, None), - ("quadrilateral",)) -register_element2("BDMCF", 1, HDiv, "contravariant Piola", (1, None), - ("quadrilateral",)) -register_element2("AAE", 1, HCurl, "covariant Piola", (1, None), - ("hexahedron",)) -register_element2("AAF", 1, HDiv, "contravariant Piola", (1, None), - ("hexahedron",)) +register_element("Q", None, 0, H1, "identity", (1, None), cubes) +register_element("DQ", None, 0, L2, "identity", (0, None), cubes) +register_element("RTCE", None, 1, HCurl, "covariant Piola", (1, None), + ("quadrilateral",)) +register_element("RTCF", None, 1, HDiv, "contravariant Piola", (1, None), + ("quadrilateral",)) +register_element("NCE", None, 1, HCurl, "covariant Piola", (1, None), + ("hexahedron",)) +register_element("NCF", None, 1, HDiv, "contravariant Piola", (1, None), + ("hexahedron",)) + +register_element("S", None, 0, H1, "identity", (1, None), cubes) +register_element("DPC", None, 0, L2, "identity", (0, None), cubes) +register_element("BDMCE", None, 1, HCurl, "covariant Piola", (1, None), + ("quadrilateral",)) +register_element("BDMCF", None, 1, HDiv, "contravariant Piola", (1, None), + ("quadrilateral",)) +register_element("AAE", None, 1, HCurl, "covariant Piola", (1, None), + ("hexahedron",)) +register_element("AAF", None, 1, HDiv, "contravariant Piola", (1, None), + ("hexahedron",)) # New aliases introduced for the periodic table 2014 register_alias("P", lambda family, dim, order, degree: ("Lagrange", order)) @@ -229,8 +220,8 @@ def show_elements(): degree: ("Brezzi-Douglas-Marini", order)) # discontinuous elements using l2 pullbacks -register_element2("DPC L2", 0, L2, "L2 Piola", (1, None), cubes) -register_element2("DQ L2", 0, L2, "L2 Piola", (0, None), cubes) +register_element("DPC L2", None, 0, L2, "L2 Piola", (1, None), cubes) +register_element("DQ L2", None, 0, L2, "L2 Piola", (0, None), cubes) register_element("Gauss-Legendre L2", "GL L2", 0, L2, "L2 Piola", (0, None), ("interval",)) register_element("Discontinuous Lagrange L2", "DG L2", 0, L2, "L2 Piola", (0, None), @@ -420,12 +411,12 @@ def canonical_element_description(family, cell, order, form_degree): # Check whether this family is an alias for something else while family in aliases: if tdim is None: - error("Need dimension to handle element aliases.") + raise ValueError("Need dimension to handle element aliases.") (family, order) = aliases[family](family, tdim, order, form_degree) # Check that the element family exists if family not in ufl_elements: - error('Unknown finite element "%s".' % family) + raise ValueError(f"Unknown finite element '{family}'.") # Check that element data is valid (and also get common family # name) @@ -446,31 +437,28 @@ def canonical_element_description(family, cell, order, form_degree): # Validate cellname if a valid cell is specified if not (cellname is None or cellname in cellnames): - error('Cellname "%s" invalid for "%s" finite element.' % (cellname, family)) + raise ValueError(f"Cellname '{cellname}' invalid for '{family}' finite element.") # Validate order if specified if order is not None: if krange is None: - error('Order "%s" invalid for "%s" finite element, ' - 'should be None.' % (order, family)) + raise ValueError(f"Order {order} invalid for '{family}' finite element, should be None.") kmin, kmax = krange if not (kmin is None or (asarray(order) >= kmin).all()): - error('Order "%s" invalid for "%s" finite element.' % - (order, family)) + raise ValueError(f"Order {order} invalid for '{family}' finite element.") if not (kmax is None or (asarray(order) <= kmax).all()): - error('Order "%s" invalid for "%s" finite element.' % - (istr(order), family)) + raise ValueError(f"Order {istr(order)} invalid for '{family}' finite element.") if value_rank == 2: # Tensor valued fundamental elements in HEin have this shape if gdim is None or tdim is None: - error("Cannot infer shape of element without topological and geometric dimensions.") + raise ValueError("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim, tdim) value_shape = (gdim, gdim) elif value_rank == 1: # Vector valued fundamental elements in HDiv and HCurl have a shape if gdim is None or tdim is None: - error("Cannot infer shape of element without topological and geometric dimensions.") + raise ValueError("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim,) value_shape = (gdim,) elif value_rank == 0: @@ -478,6 +466,6 @@ def canonical_element_description(family, cell, order, form_degree): reference_value_shape = () value_shape = () else: - error("Invalid value rank %d." % value_rank) + raise ValueError(f"Invalid value rank {value_rank}.") return family, short_name, order, value_shape, reference_value_shape, sobolev_space, mapping diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/finiteelement/enrichedelement.py index 8f082ad5a..e2b191a13 100644 --- a/ufl/finiteelement/enrichedelement.py +++ b/ufl/finiteelement/enrichedelement.py @@ -11,7 +11,6 @@ # Modified by Marie E. Rognes 2010, 2012 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error from ufl.finiteelement.finiteelementbase import FiniteElementBase @@ -25,7 +24,7 @@ def __init__(self, *elements): cell = elements[0].cell() if not all(e.cell() == cell for e in elements[1:]): - error("Cell mismatch for sub elements of enriched element.") + raise ValueError("Cell mismatch for sub elements of enriched element.") if isinstance(elements[0].degree(), int): degrees = {e.degree() for e in elements} - {None} @@ -39,19 +38,19 @@ def __init__(self, *elements): quad_schemes = [qs for qs in quad_schemes if qs is not None] quad_scheme = quad_schemes[0] if quad_schemes else None if not all(qs == quad_scheme for qs in quad_schemes): - error("Quadrature scheme mismatch.") + raise ValueError("Quadrature scheme mismatch.") value_shape = elements[0].value_shape() if not all(e.value_shape() == value_shape for e in elements[1:]): - error("Element value shape mismatch.") + raise ValueError("Element value shape mismatch.") reference_value_shape = elements[0].reference_value_shape() if not all(e.reference_value_shape() == reference_value_shape for e in elements[1:]): - error("Element reference value shape mismatch.") + raise ValueError("Element reference value shape mismatch.") # mapping = elements[0].mapping() # FIXME: This fails for a mixed subelement here. # if not all(e.mapping() == mapping for e in elements[1:]): - # error("Element mapping mismatch.") + # raise ValueError("Element mapping mismatch.") # Get name of subclass: EnrichedElement or NodalEnrichedElement class_name = self.__class__.__name__ diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index d9fd533a2..d86ae331e 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -12,7 +12,6 @@ # Modified by Anders Logg 2014 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error from ufl.utils.formatting import istr from ufl.cell import as_cell @@ -51,9 +50,9 @@ def __new__(cls, if family in ["RTCF", "RTCE"]: cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "interval": - error("%s is available on TensorProductCell(interval, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") if cell_v.cellname() != "interval": - error("%s is available on TensorProductCell(interval, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") C_elt = FiniteElement("CG", "interval", degree, variant=variant) D_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) @@ -69,9 +68,9 @@ def __new__(cls, elif family == "NCF": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": - error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") if cell_v.cellname() != "interval": - error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, variant=variant) @@ -85,9 +84,9 @@ def __new__(cls, elif family == "NCE": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": - error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") if cell_v.cellname() != "interval": - error("%s is available on TensorProductCell(quadrilateral, interval) only." % family) + raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") Qc_elt = FiniteElement("Q", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, variant=variant) @@ -228,8 +227,7 @@ def __str__(self): def shortstr(self): "Format as string for pretty printing." - return "%s%s(%s,%s)" % (self._short_name, istr(self.degree()), - istr(self.quadrature_scheme()), istr(self.variant())) + return f"{self._short_name}{istr(self.degree())}({self.quadrature_scheme()},{istr(self.variant())})" def __getnewargs__(self): """Return the arguments which pickle needs to recreate the object.""" diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index 17f687bb4..7d31006e0 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -12,8 +12,6 @@ # Modified by Massimiliano Leoni, 2016 from ufl.utils.sequences import product -from ufl.utils.dicts import EmptyDict -from ufl.log import error from ufl.cell import AbstractCell, as_cell from abc import ABC, abstractmethod @@ -29,18 +27,18 @@ def __init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape): """Initialize basic finite element data.""" if not isinstance(family, str): - error("Invalid family type.") + raise ValueError("Invalid family type.") if not (degree is None or isinstance(degree, (int, tuple))): - error("Invalid degree type.") + raise ValueError("Invalid degree type.") if not isinstance(value_shape, tuple): - error("Invalid value_shape type.") + raise ValueError("Invalid value_shape type.") if not isinstance(reference_value_shape, tuple): - error("Invalid reference_value_shape type.") + raise ValueError("Invalid reference_value_shape type.") if cell is not None: cell = as_cell(cell) if not isinstance(cell, AbstractCell): - error("Invalid cell type.") + raise ValueError("Invalid cell type.") self._family = family self._cell = cell @@ -134,15 +132,16 @@ def symmetry(self): # FIXME: different approach meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints.""" - return EmptyDict + return {} def _check_component(self, i): "Check that component index i is valid" sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): - error(("Illegal component index '%s' (value rank %d)" + - "for element (value rank %d).") % (i, len(i), r)) + raise ValueError( + f"Illegal component index {i} (value rank {len(i)}) " + f"for element (value rank {r}).") def extract_subelement_component(self, i): """Extract direct subelement index and subelement relative @@ -165,8 +164,9 @@ def _check_reference_component(self, i): sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): - error(("Illegal component index '%s' (value rank %d)" + - "for element (value rank %d).") % (i, len(i), r)) + raise ValueError( + f"Illegal component index {i} (value rank {len(i)}) " + f"for element (value rank {r}).") def extract_subelement_reference_component(self, i): """Extract direct subelement index and subelement relative @@ -195,14 +195,14 @@ def sub_elements(self): def __add__(self, other): "Add two elements, creating an enriched element" if not isinstance(other, FiniteElementBase): - error("Can't add element and %s." % other.__class__) + raise ValueError(f"Can't add element and {other.__class__}.") from ufl.finiteelement import EnrichedElement return EnrichedElement(self, other) def __mul__(self, other): "Multiply two elements, creating a mixed element" if not isinstance(other, FiniteElementBase): - error("Can't multiply element and %s." % other.__class__) + raise ValueError("Can't multiply element and {other.__class__}.") from ufl.finiteelement import MixedElement return MixedElement(self, other) diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index d6715ae11..482dfc73d 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -12,10 +12,8 @@ # Modified by Anders Logg 2014 # Modified by Massimiliano Leoni, 2016 -from ufl.log import error from ufl.permutation import compute_indices from ufl.utils.sequences import product, max_degree -from ufl.utils.dicts import EmptyDict from ufl.utils.indexflattening import flatten_multiindex, unflatten_index, shape_to_strides from ufl.cell import as_cell @@ -33,7 +31,7 @@ def __init__(self, *elements, **kwargs): if type(self) is MixedElement: if kwargs: - error("Not expecting keyword arguments to MixedElement constructor.") + raise ValueError("Not expecting keyword arguments to MixedElement constructor.") # Un-nest arguments if we get a single argument with a list of elements if len(elements) == 1 and isinstance(elements[0], (tuple, list)): @@ -50,7 +48,7 @@ def __init__(self, *elements, **kwargs): cell = cells[0] # Require that all elements are defined on the same cell if not all(c == cell for c in cells[1:]): - error("Sub elements must live on the same cell.") + raise ValueError("Sub elements must live on the same cell.") else: cell = None @@ -61,7 +59,7 @@ def __init__(self, *elements, **kwargs): else: quad_scheme = elements[0].quadrature_scheme() if not all(e.quadrature_scheme() == quad_scheme for e in elements): - error("Quadrature scheme mismatch for sub elements of mixed element.") + raise ValueError("Quadrature scheme mismatch for sub elements of mixed element.") # Compute value sizes in global and reference configurations value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) @@ -81,8 +79,8 @@ def __init__(self, *elements, **kwargs): # This is not valid for tensor elements with symmetries, # assume subclasses deal with their own validation if product(value_shape) != value_size_sum: - error("Provided value_shape doesn't match the " - "total value size of all subelements.") + raise ValueError("Provided value_shape doesn't match the " + "total value size of all subelements.") # Initialize element data degrees = {e.degree() for e in self._sub_elements} - {None} @@ -120,8 +118,8 @@ def symmetry(self): # Update base index for next element j += product(sh) if j != product(self.value_shape()): - error("Size mismatch in symmetry algorithm.") - return sm or EmptyDict + raise ValueError("Size mismatch in symmetry algorithm.") + return sm or {} def sobolev_space(self): return max(e.sobolev_space() for e in self._sub_elements) @@ -161,7 +159,7 @@ def extract_subelement_component(self, i): break j -= si if j < 0: - error("Moved past last value component!") + raise ValueError("Moved past last value component!") # Convert index into a shape tuple st = shape_to_strides(sh) @@ -171,7 +169,7 @@ def extract_subelement_component(self, i): # index is first axis sub_element_index = i[0] if sub_element_index >= len(self._sub_elements): - error("Illegal component index (dimension %d)." % sub_element_index) + raise ValueError(f"Illegal component index (dimension {sub_element_index}).") component = i[1:] return (sub_element_index, component) @@ -201,7 +199,7 @@ def extract_subelement_reference_component(self, i): break j -= si if j < 0: - error("Moved past last value reference_component!") + raise ValueError("Moved past last value reference_component!") # Convert index into a shape tuple st = shape_to_strides(sh) @@ -296,7 +294,7 @@ def __init__(self, family, cell=None, degree=None, dim=None, # Set default size if not specified if dim is None: if cell is None: - error("Cannot infer vector dimension without a cell.") + raise ValueError("Cannot infer vector dimension without a cell.") dim = cell.geometric_dimension() self._mapping = sub_element.mapping() @@ -385,29 +383,29 @@ def __init__(self, family, cell=None, degree=None, shape=None, # Set default shape if not specified if shape is None: if cell is None: - error("Cannot infer tensor shape without a cell.") + raise ValueError("Cannot infer tensor shape without a cell.") dim = cell.geometric_dimension() shape = (dim, dim) if symmetry is None: - symmetry = EmptyDict + symmetry = {} elif symmetry is True: # Construct default symmetry dict for matrix elements if not (len(shape) == 2 and shape[0] == shape[1]): - error("Cannot set automatic symmetry for non-square tensor.") + raise ValueError("Cannot set automatic symmetry for non-square tensor.") symmetry = dict(((i, j), (j, i)) for i in range(shape[0]) for j in range(shape[1]) if i > j) else: if not isinstance(symmetry, dict): - error("Expecting symmetry to be None (unset), True, or dict.") + raise ValueError("Expecting symmetry to be None (unset), True, or dict.") # Validate indices in symmetry dict for i, j in symmetry.items(): if len(i) != len(j): - error("Non-matching length of symmetry index tuples.") + raise ValueError("Non-matching length of symmetry index tuples.") for k in range(len(i)): if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]): - error("Symmetry dimensions out of bounds.") + raise ValueError("Symmetry dimensions out of bounds.") # Compute all index combinations for given shape indices = compute_indices(shape) @@ -487,7 +485,7 @@ def extract_subelement_component(self, i): ii = i[:l] jj = i[l:] if ii not in self._sub_element_mapping: - error("Illegal component index %s." % (i,)) + raise ValueError(f"Illegal component index {i}.") k = self._sub_element_mapping[ii] return (k, jj) diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index 2394f2703..90640e994 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -12,7 +12,6 @@ # Modified by Massimiliano Leoni, 2016 from ufl.finiteelement.finiteelementbase import FiniteElementBase -from ufl.log import error from ufl.sobolevspace import L2 valid_restriction_domains = ("interior", "facet", "face", "edge", "vertex") @@ -22,9 +21,9 @@ class RestrictedElement(FiniteElementBase): "Represents the restriction of a finite element to a type of cell entity." def __init__(self, element, restriction_domain): if not isinstance(element, FiniteElementBase): - error("Expecting a finite element instance.") + raise ValueError("Expecting a finite element instance.") if restriction_domain not in valid_restriction_domains: - error("Expecting one of the strings %s." % (valid_restriction_domains,)) + raise ValueError(f"Expecting one of the strings: {valid_restriction_domains}") FiniteElementBase.__init__(self, "RestrictedElement", element.cell(), element.degree(), diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py index 93ed2f231..8b882cf91 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/finiteelement/tensorproductelement.py @@ -13,7 +13,6 @@ from itertools import chain -from ufl.log import error from ufl.cell import TensorProductCell, as_cell from ufl.sobolevspace import DirectionalSobolevSpace @@ -34,7 +33,7 @@ class TensorProductElement(FiniteElementBase): def __init__(self, *elements, **kwargs): "Create TensorProductElement from a given list of elements." if not elements: - error("Cannot create TensorProductElement from empty list.") + raise ValueError("Cannot create TensorProductElement from empty list.") keywords = list(kwargs.keys()) if keywords and keywords != ["cell"]: @@ -59,9 +58,9 @@ def __init__(self, *elements, **kwargs): value_shape = tuple(chain(*[e.value_shape() for e in elements])) reference_value_shape = tuple(chain(*[e.reference_value_shape() for e in elements])) if len(value_shape) > 1: - error("Product of vector-valued elements not supported") + raise ValueError("Product of vector-valued elements not supported") if len(reference_value_shape) > 1: - error("Product of vector-valued elements not supported") + raise ValueError("Product of vector-valued elements not supported") FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, diff --git a/ufl/form.py b/ufl/form.py index a1959de5f..652becfa8 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -15,7 +15,6 @@ from itertools import chain from collections import defaultdict -from ufl.log import error from ufl.domain import sort_domains from ufl.integral import Integral from ufl.checks import is_scalar_constant_expression @@ -41,9 +40,8 @@ def _sorted_integrals(integrals): for integral in integrals: d = integral.ufl_domain() if d is None: - error( - "Each integral in a form must have a uniquely defined integration domain." - ) + raise ValueError( + "Each integral in a form must have a uniquely defined integration domain.") it = integral.integral_type() si = integral.subdomain_id() integrals_dict[d][it][si] += [integral] @@ -214,13 +212,12 @@ def __call__(self, *args, **kwargs): if args: arguments = self.arguments() if len(arguments) != len(args): - error("Need %d arguments to form(), got %d." % (len(arguments), - len(args))) + raise ValueError(f"Need {len(arguments)} arguments to form(), got {len(args)}.") repdict.update(zip(arguments, args)) coefficients = kwargs.pop("coefficients") if kwargs: - error("Unknown kwargs %s." % str(list(kwargs))) + raise ValueError(f"Unknown kwargs {list(kwargs)}.") if coefficients is not None: coeffs = self.coefficients() @@ -279,7 +276,7 @@ def __init__(self, integrals): # Basic input checking (further compatibilty analysis happens # later) if not all(isinstance(itg, Integral) for itg in integrals): - error("Expecting list of integrals.") + raise ValueError("Expecting list of integrals.") # Store integrals sorted canonically to increase signature # stability @@ -364,9 +361,8 @@ def ufl_domain(self): # Check that all are equal TODO: don't return more than one if # all are equal? if not all(domain == domains[0] for domain in domains): - error( - "Calling Form.ufl_domain() is only valid if all integrals share domain." - ) + raise ValueError( + "Calling Form.ufl_domain() is only valid if all integrals share domain.") # Return the one and only domain return domains[0] @@ -376,9 +372,8 @@ def geometric_dimension(self): gdims = tuple( set(domain.geometric_dimension() for domain in self.ufl_domains())) if len(gdims) != 1: - error("Expecting all domains and functions in a form " - "to share geometric dimension, got %s." % str( - tuple(sorted(gdims)))) + raise ValueError("Expecting all domains and functions in a form " + f"to share geometric dimension, got {tuple(sorted(gdims))}") return gdims[0] def domain_numbering(self): @@ -547,13 +542,12 @@ def __call__(self, *args, **kwargs): if args: arguments = self.arguments() if len(arguments) != len(args): - error("Need %d arguments to form(), got %d." % (len(arguments), - len(args))) + raise ValueError(f"Need {len(arguments)} arguments to form(), got {len(args)}.") repdict.update(zip(arguments, args)) coefficients = kwargs.pop("coefficients") if kwargs: - error("Unknown kwargs %s." % str(list(kwargs))) + raise ValueError(f"Unknown kwargs {list(kwargs)}") if coefficients is not None: coeffs = self.coefficients() @@ -688,14 +682,14 @@ def _compute_signature(self): def sub_forms_by_domain(form): "Return a list of forms each with an integration domain" if not isinstance(form, Form): - error("Unable to convert object to a UFL form: %s" % ufl_err_str(form)) + raise ValueError(f"Unable to convert object to a UFL form: {ufl_err_str(form)}") return [Form(form.integrals_by_domain(domain)) for domain in form.ufl_domains()] def as_form(form): "Convert to form if not a form, otherwise return form." if not isinstance(form, BaseForm): - error("Unable to convert object to a UFL form: %s" % ufl_err_str(form)) + raise ValueError(f"Unable to convert object to a UFL form: {ufl_err_str(form)}") return form @@ -716,7 +710,7 @@ def replace_integral_domains(form, common_domain): # TODO: Move elsewhere if not all((gdim == domain.geometric_dimension() and tdim == domain.topological_dimension()) for domain in domains): - error("Common domain does not share dimensions with form domains.") + raise ValueError("Common domain does not share dimensions with form domains.") reconstruct = False integrals = [] diff --git a/ufl/formatting/printing.py b/ufl/formatting/printing.py index f36c783f6..2c40e85fa 100644 --- a/ufl/formatting/printing.py +++ b/ufl/formatting/printing.py @@ -10,7 +10,6 @@ # # Modified by Anders Logg 2009, 2014 -from ufl.log import error from ufl.core.expr import Expr from ufl.form import Form from ufl.integral import Integral @@ -20,7 +19,7 @@ def integral_info(integral): if not isinstance(integral, Integral): - error("Expecting an Integral.") + raise ValueError("Expecting an Integral.") s = " Integral:\n" s += " Type:\n" s += " %s\n" % integral.integral_type() @@ -37,7 +36,7 @@ def integral_info(integral): def form_info(form): if not isinstance(form, Form): - error("Expecting a Form.") + raise ValueError("Expecting a Form.") bf = form.arguments() cf = form.coefficients() @@ -116,6 +115,6 @@ def tree_format(expression, indentation=0, parentheses=True): s += _tree_format_expression(expression, indentation, parentheses) else: - error("Invalid object type %s" % type(expression)) + raise ValueError(f"Invalid object type {type(expression)}") return s diff --git a/ufl/formatting/ufl2dot.py b/ufl/formatting/ufl2dot.py index 0b5309028..ab219343e 100644 --- a/ufl/formatting/ufl2dot.py +++ b/ufl/formatting/ufl2dot.py @@ -9,7 +9,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.expr import Expr from ufl.form import Form from ufl.variable import Variable @@ -284,6 +283,6 @@ def ufl2dot(expression, formname="a", nodeoffset=0, begin=True, end=True, nodeoffset += len(nodes) else: - error("Invalid object type %s" % type(expression)) + raise ValueError(f"Invalid object type {type(expression)}") return s, nodeoffset diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 9757b878e..82d90be41 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -3,7 +3,6 @@ import numbers import ufl -from ufl.log import error from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag from ufl.core.multiindex import Index, FixedIndex @@ -313,7 +312,7 @@ def format_index(ii): elif isinstance(ii, Index): s = "i%s" % subscript_number(ii._count) else: - error("Invalid index type %s." % type(ii)) + raise ValueError(f"Invalid index type {type(ii)}.") return s diff --git a/ufl/formoperators.py b/ufl/formoperators.py index 4a3f648ea..e2b60b4b7 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -11,7 +11,6 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -from ufl.log import error from ufl.form import Form, FormSum, BaseForm, as_form from ufl.core.expr import Expr, ufl_err_str from ufl.split_functions import split @@ -148,7 +147,7 @@ def adjoint(form, reordered_arguments=None): def zero_lists(shape): if len(shape) == 0: - error("Invalid shape.") + raise ValueError("Invalid shape.") elif len(shape) == 1: return [0] * shape[0] else: @@ -174,7 +173,7 @@ def _handle_derivative_arguments(form, coefficient, argument): if argument is None: # Try to create argument if not provided if not all(isinstance(c, (Coefficient, Cofunction)) for c in coefficients): - error("Can only create arguments automatically for non-indexed coefficients.") + raise ValueError("Can only create arguments automatically for non-indexed coefficients.") # Get existing arguments from form and position the new one # with the next argument number @@ -192,7 +191,7 @@ def _handle_derivative_arguments(form, coefficient, argument): # in that case parts = set(arg.part() for arg in form_arguments) if len(parts - {None}) != 0: - error("Not expecting parts here, provide your own arguments.") + raise ValueError("Not expecting parts here, provide your own arguments.") part = None # Create argument and split it if in a mixed space @@ -225,17 +224,17 @@ def _handle_derivative_arguments(form, coefficient, argument): m = {} for (c, a) in zip(coefficients, arguments): if c.ufl_shape != a.ufl_shape: - error("Coefficient and argument shapes do not match!") + raise ValueError("Coefficient and argument shapes do not match!") if isinstance(c, (Coefficient, Cofunction, SpatialCoordinate)): m[c] = a else: if not isinstance(c, Indexed): - error("Invalid coefficient type for %s" % ufl_err_str(c)) + raise ValueError(f"Invalid coefficient type for {ufl_err_str(c)}") f, i = c.ufl_operands if not isinstance(f, Coefficient): - error("Expecting an indexed coefficient, not %s" % ufl_err_str(f)) + raise ValueError(f"Expecting an indexed coefficient, not {ufl_err_str(f)}") if not (isinstance(i, MultiIndex) and all(isinstance(j, FixedIndex) for j in i)): - error("Expecting one or more fixed indices, not %s" % ufl_err_str(i)) + raise ValueError(f"Expecting one or more fixed indices, not {ufl_err_str(i)}") i = tuple(int(j) for j in i) if f not in m: m[f] = {} @@ -331,7 +330,7 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): return CoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) - error("Invalid argument type %s." % str(type(form))) + raise ValueError(f"Invalid argument type {type(form)}.") def sensitivity_rhs(a, u, L, v): @@ -394,8 +393,8 @@ def sensitivity_rhs(a, u, L, v): isinstance(u, Coefficient) and isinstance(L, Form) and isinstance(v, Variable)): - error("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") + raise ValueError("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") if not is_true_ufl_scalar(v): - error("Expecting scalar variable.") + raise ValueError("Expecting scalar variable.") from ufl.operators import diff return diff(L, v) - action(diff(a, v), u) diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 6842d9ca2..d0dc82bac 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -10,7 +10,6 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -from ufl.log import error from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.domain import join_domains from ufl.duals import is_dual, is_primal @@ -44,11 +43,10 @@ def __init__(self, domain, element): try: domain_cell = domain.ufl_cell() except AttributeError: - error("Expected non-abstract domain for initalization " - "of function space.") + raise ValueError("Expected non-abstract domain for initalization of function space.") else: if element.cell() != domain_cell: - error("Non-matching cell of finite element and domain.") + raise ValueError("Non-matching cell of finite element and domain.") AbstractFunctionSpace.__init__(self) self._ufl_domain = domain @@ -187,7 +185,7 @@ def __init__(self, *args): if isinstance(fs, BaseFunctionSpace): self._ufl_elements.append(fs.ufl_element()) else: - error("Expecting BaseFunctionSpace objects") + raise ValueError("Expecting BaseFunctionSpace objects") # A mixed FS is only primal/dual if all the subspaces are primal/dual" self._primal = all([is_primal(subspace) @@ -230,9 +228,10 @@ def ufl_element(self): if len(self._ufl_elements) == 1: return self._ufl_elements[0] else: - error("""Found multiple elements. Cannot return only one. - Consider building a FunctionSpace from a MixedElement - in case of homogeneous dimension.""") + raise ValueError( + "Found multiple elements. Cannot return only one. " + "Consider building a FunctionSpace from a MixedElement " + "in case of homogeneous dimension.") def ufl_domains(self): "Return ufl domains." @@ -247,7 +246,7 @@ def ufl_domain(self): if len(domains) == 1: return domains[0] elif domains: - error("Found multiple domains, cannot return just one.") + raise ValueError("Found multiple domains, cannot return just one.") else: return None @@ -264,5 +263,4 @@ def _ufl_signature_data_(self, renumbering): for V in self.ufl_sub_spaces()) def __repr__(self): - r = "MixedFunctionSpace(*%s)" % repr(self._ufl_function_spaces) - return r + return f"MixedFunctionSpace(*{self._ufl_function_spaces})" diff --git a/ufl/geometry.py b/ufl/geometry.py index b456d302d..96820e989 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -9,7 +9,6 @@ from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import as_domain, extract_unique_domain -from ufl.log import error """ @@ -211,7 +210,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("FacetCoordinate is only defined for topological dimensions >= 2.") + raise ValueError("FacetCoordinate is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -310,7 +309,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("FacetJacobian is only defined for topological dimensions >= 2.") + raise ValueError("FacetJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -338,7 +337,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("CellFacetJacobian is only defined for topological dimensions >= 2.") + raise ValueError("CellFacetJacobian is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -362,7 +361,7 @@ def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("CellEdgeVectors is only defined for topological dimensions >= 2.") + raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -387,7 +386,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: - error("FacetEdgeVectors is only defined for topological dimensions >= 3.") + raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.") @property def ufl_shape(self): @@ -440,7 +439,7 @@ def __init__(self, domain): GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("CellEdgeVectors is only defined for topological dimensions >= 2.") + raise ValueError("CellEdgeVectors is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -465,7 +464,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: - error("FacetEdgeVectors is only defined for topological dimensions >= 3.") + raise ValueError("FacetEdgeVectors is only defined for topological dimensions >= 3.") @property def ufl_shape(self): @@ -565,7 +564,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("FacetJacobianInverse is only defined for topological dimensions >= 2.") + raise ValueError("FacetJacobianInverse is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -590,7 +589,7 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - error("CellFacetJacobianInverse is only defined for topological dimensions >= 2.") + raise ValueError("CellFacetJacobianInverse is only defined for topological dimensions >= 2.") @property def ufl_shape(self): @@ -671,7 +670,7 @@ def ufl_shape(self): # GeometricFacetQuantity.__init__(self, domain) # t = self._domain.topological_dimension() # if t < 2: -# error("FacetTangents is only defined for topological dimensions >= 2.") +# raise ValueError("FacetTangents is only defined for topological dimensions >= 2.") # # @property # def ufl_shape(self): diff --git a/ufl/index_combination_utils.py b/ufl/index_combination_utils.py index c3bd5bf9f..52e3c906a 100644 --- a/ufl/index_combination_utils.py +++ b/ufl/index_combination_utils.py @@ -8,7 +8,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.multiindex import FixedIndex, Index, indices @@ -28,7 +27,7 @@ def unique_sorted_indices(indices): prev = i else: if i[1] != prev[1]: - error("Nonmatching dimensions for free indices with same id!") + raise ValueError("Nonmatching dimensions for free indices with same id!") return tuple(newindices) @@ -113,7 +112,7 @@ def remove_indices(fi, fid, rfi): # Expecting to find each index from rfi in fi if not removed: - error("Index to be removed ({0}) not part of indices ({1}).".format(rk, fi)) + raise ValueError(f"Index to be removed ({rk}) not part of indices ({fi}).") # Next to remove k += 1 @@ -145,15 +144,15 @@ def create_slice_indices(component, shape, fi): free_indices.append(ind) elif isinstance(ind, FixedIndex): if int(ind) >= shape[len(all_indices)]: - error("Index out of bounds.") + raise ValueError("Index out of bounds.") all_indices.append(ind) elif isinstance(ind, int): if int(ind) >= shape[len(all_indices)]: - error("Index out of bounds.") + raise ValueError("Index out of bounds.") all_indices.append(FixedIndex(ind)) elif isinstance(ind, slice): if ind != slice(None): - error("Only full slices (:) allowed.") + raise ValueError("Only full slices (:) allowed.") i = Index() slice_indices.append(i) all_indices.append(i) @@ -163,10 +162,10 @@ def create_slice_indices(component, shape, fi): slice_indices.extend(ii) all_indices.extend(ii) else: - error("Not expecting {0}.".format(ind)) + raise ValueError(f"Not expecting {ind}.") if len(all_indices) != len(shape): - error("Component and shape length don't match.") + raise ValueError("Component and shape length don't match.") return tuple(all_indices), tuple(slice_indices), tuple(repeated_indices) @@ -194,7 +193,7 @@ def merge_nonoverlapping_indices(a, b): free_indices, index_dimensions = zip(*s) # Consistency checks if len(set(free_indices)) != len(free_indices): - error("Not expecting repeated indices.") + raise ValueError("Not expecting repeated indices.") else: free_indices, index_dimensions = (), () return free_indices, index_dimensions @@ -237,8 +236,8 @@ def merge_overlapping_indices(afi, afid, bfi, bfid): # Consistency checks if len(set(free_indices)) != len(free_indices): - error("Not expecting repeated indices left.") + raise ValueError("Not expecting repeated indices left.") if len(free_indices) + 2 * len(repeated_indices) != an + bn: - error("Expecting only twice repeated indices.") + raise ValueError("Expecting only twice repeated indices.") return tuple(free_indices), tuple(index_dimensions), tuple(repeated_indices), tuple(repeated_index_dimensions) diff --git a/ufl/indexed.py b/ufl/indexed.py index ed337ee57..bde7714b5 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str from ufl.core.ufl_type import ufl_type @@ -53,21 +52,21 @@ def __init__(self, expression, multiindex): # Error checking if not isinstance(expression, Expr): - error("Expecting Expr instance, not %s." % ufl_err_str(expression)) + raise ValueError(f"Expecting Expr instance, not {ufl_err_str(expression)}.") if not isinstance(multiindex, MultiIndex): - error("Expecting MultiIndex instance, not %s." % ufl_err_str(multiindex)) + raise ValueError(f"Expecting MultiIndex instance, not {ufl_err_str(multiindex)}.") shape = expression.ufl_shape # Error checking if len(shape) != len(multiindex): - error("Invalid number of indices (%d) for tensor " - "expression of rank %d:\n\t%s\n" - % (len(multiindex), len(expression.ufl_shape), ufl_err_str(expression))) + raise ValueError( + f"Invalid number of indices ({len(multiindex)}) for tensor " + f"expression of rank {len(expression.ufl_shape)}:\n {ufl_err_str(expression)}") if any(int(di) >= int(si) or int(di) < 0 for si, di in zip(shape, multiindex) if isinstance(di, FixedIndex)): - error("Fixed index out of range!") + raise ValueError("Fixed index out of range!") # Build tuples of free index ids and dimensions if 1: @@ -115,4 +114,4 @@ def __getitem__(self, key): # So that one doesn't have to special case indexing of # expressions without shape. return self - error("Attempting to index with %s, but object is already indexed: %s" % (ufl_err_str(key), ufl_err_str(self))) + raise ValueError(f"Attempting to index with {ufl_err_str(key)}, but object is already indexed: {ufl_err_str(self)}") diff --git a/ufl/indexsum.py b/ufl/indexsum.py index 0d7f65bde..3c6a500aa 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -8,7 +8,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.expr import Expr, ufl_err_str from ufl.core.operator import Operator @@ -28,11 +27,11 @@ class IndexSum(Operator): def __new__(cls, summand, index): # Error checks if not isinstance(summand, Expr): - error("Expecting Expr instance, got %s" % ufl_err_str(summand)) + raise ValueError(f"Expecting Expr instance, got {ufl_err_str(summand)}") if not isinstance(index, MultiIndex): - error("Expecting MultiIndex instance, got %s" % ufl_err_str(index)) + raise ValueError(f"Expecting MultiIndex instance, got {ufl_err_str(index)}") if len(index) != 1: - error("Expecting a single Index but got %d." % len(index)) + raise ValueError(f"Expecting a single Index but got {len(index)}.") # Simplification to zero if isinstance(summand, Zero): diff --git a/ufl/integral.py b/ufl/integral.py index 5fc3e6efd..938f24347 100644 --- a/ufl/integral.py +++ b/ufl/integral.py @@ -11,7 +11,6 @@ # Modified by Massimiliano Leoni, 2016. import ufl -from ufl.log import error from ufl.core.expr import Expr from ufl.checks import is_python_scalar, is_scalar_constant_expression from ufl.measure import Measure # noqa @@ -33,7 +32,7 @@ class Integral(object): def __init__(self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data): if not isinstance(integrand, Expr): - error("Expecting integrand to be an Expr instance.") + raise ValueError("Expecting integrand to be an Expr instance.") self._integrand = integrand self._integral_type = integral_type self._ufl_domain = domain @@ -97,13 +96,13 @@ def __neg__(self): def __mul__(self, scalar): if not is_python_scalar(scalar): - error("Cannot multiply an integral with non-constant values.") + raise ValueError("Cannot multiply an integral with non-constant values.") return self.reconstruct(scalar * self._integrand) def __rmul__(self, scalar): if not is_scalar_constant_expression(scalar): - error("An integral can only be multiplied by a " - "globally constant scalar expression.") + raise ValueError("An integral can only be multiplied by a " + "globally constant scalar expression.") return self.reconstruct(scalar * self._integrand) def __str__(self): diff --git a/ufl/log.py b/ufl/log.py deleted file mode 100644 index 3379e8741..000000000 --- a/ufl/log.py +++ /dev/null @@ -1,222 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module provides functions used by the UFL implementation to -output messages. These may be redirected by the user of UFL.""" - -# Copyright (C) 2005-2016 Anders Logg and Martin Sandve Alnaes -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Johan Hake, 2009. -# Modified by Massimiliano Leoni, 2016 - -import sys -import types -import logging -import warnings -from logging import DEBUG, INFO, ERROR, CRITICAL # noqa: F401 - -log_functions = ["log", "debug", "info", "error", - "begin", "end", - "set_level", "push_level", "pop_level", "set_indent", - "add_indent", - "set_handler", "get_handler", "get_logger", "add_logfile", - "set_prefix", - "info_red", "info_green", "info_blue"] - -__all__ = log_functions + ["Logger", "log_functions"] +\ - ["DEBUG", "INFO", "ERROR", "CRITICAL"] - - -# This is used to override emit() in StreamHandler for printing -# without newline -def emit(self, record): - message = self.format(record) - format_string = "%s" if getattr(record, "continued", False) else "%s\n" - self.stream.write(format_string % message) - self.flush() - - -# Colors if the terminal supports it (disabled e.g. when piped to -# file) -if sys.stdout.isatty() and sys.stderr.isatty(): - RED = "\033[1;37;31m%s\033[0m" - BLUE = "\033[1;37;34m%s\033[0m" - GREEN = "\033[1;37;32m%s\033[0m" -else: - RED = "%s" - BLUE = "%s" - GREEN = "%s" - - -# Logger class -class Logger: - - def __init__(self, name, exception_type=Exception): - "Create logger instance." - self._name = name - self._exception_type = exception_type - - # Set up handler - h = logging.StreamHandler(sys.stdout) - h.setLevel(ERROR) - # Override emit() in handler for indentation - h.emit = types.MethodType(emit, h) - self._handler = h - - # Set up logger - self._log = logging.getLogger(name) - assert len(self._log.handlers) == 0 - self._log.addHandler(h) - self._log.setLevel(DEBUG) - - self._logfiles = {} - - # Set initial indentation level - self._indent_level = 0 - - # Setup stack with default logging level - self._level_stack = [DEBUG] - - # Set prefix - self._prefix = "" - - def add_logfile(self, filename=None, mode="a", level=DEBUG): - "Add a log file." - if filename is None: - filename = "%s.log" % self._name - if filename in self._logfiles: - warnings.warn("Adding logfile %s multiple times." % filename) - return - h = logging.FileHandler(filename, mode) - h.emit = types.MethodType(emit, h) - h.setLevel(level) - self._log.addHandler(h) - self._logfiles[filename] = h - return h - - def get_logfile_handler(self, filename): - "Gets the handler to the file identified by the given file name." - return self._logfiles[filename] - - def log(self, level, *message): - "Write a log message on given log level." - text = self._format_raw(*message) - if len(text) >= 3 and text[-3:] == "...": - self._log.log(level, self._format(*message), - extra={"continued": True}) - else: - self._log.log(level, self._format(*message)) - - def debug(self, *message): - "Write debug message." - self.log(DEBUG, *message) - - def info(self, *message): - "Write info message." - self.log(INFO, *message) - - def info_red(self, *message): - "Write info message in red." - self.log(INFO, RED % self._format_raw(*message)) - - def info_green(self, *message): - "Write info message in green." - self.log(INFO, GREEN % self._format_raw(*message)) - - def info_blue(self, *message): - "Write info message in blue." - self.log(INFO, BLUE % self._format_raw(*message)) - - def error(self, *message): - "Write error message and raise an exception." - self._log.error(*message) - raise self._exception_type(self._format_raw(*message)) - - def begin(self, *message): - "Begin task: write message and increase indentation level." - self.info(*message) - self.info("-" * len(self._format_raw(*message))) - self.add_indent() - - def end(self): - "End task: write a newline and decrease indentation level." - self.info("") - self.add_indent(-1) - - def push_level(self, level): - "Push a log level on the level stack." - self._level_stack.append(level) - self.set_level(level) - - def pop_level(self): - "Pop log level from the level stack, reverting to before the last push_level." - self._level_stack.pop() - level = self._level_stack[-1] - self.set_level(level) - - def set_level(self, level): - "Set log level." - self._level_stack[-1] = level - self._handler.setLevel(level) - - def set_indent(self, level): - "Set indentation level." - self._indent_level = level - - def add_indent(self, increment=1): - "Add to indentation level." - self._indent_level += increment - - def get_handler(self): - "Get handler for logging." - return self._handler - - def set_handler(self, handler): - """Replace handler for logging. - To add additional handlers instead of replacing the existing one, use - `log.get_logger().addHandler(myhandler)`. - See the logging module for more details. - """ - self._log.removeHandler(self._handler) - self._log.addHandler(handler) - self._handler = handler - handler.emit = types.MethodType(emit, self._handler) - - def get_logger(self): - "Return message logger." - return self._log - - def set_prefix(self, prefix): - "Set prefix for log messages." - self._prefix = prefix - - def _format(self, *message): - "Format message including indentation." - indent = self._prefix + 2 * self._indent_level * " " - return "\n".join([indent + line for line in (message[0] % message[1:]).split("\n")]) - - def _format_raw(self, *message): - "Format message without indentation." - return message[0] % message[1:] - - -# --- Set up global log functions --- - -class UFLException(Exception): - "Base class for UFL exceptions." - pass - - -class UFLValueError(UFLException): - "Value type error." - pass - - -ufl_logger = Logger("UFL", UFLException) - -for foo in log_functions: - exec("%s = ufl_logger.%s" % (foo, foo)) - -set_level(ERROR) # noqa diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index a53ff4e91..1761b7599 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -15,7 +15,6 @@ import numbers import warnings -from ufl.log import error from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type from ufl.constantvalue import is_true_ufl_scalar, Zero, RealValue, FloatValue, IntValue, ComplexValue, ConstantValue, as_ufl @@ -53,7 +52,7 @@ class MathFunction(Operator): def __init__(self, name, argument): Operator.__init__(self, (argument,)) if not is_true_ufl_scalar(argument): - error("Expecting scalar argument.") + raise ValueError("Expecting scalar argument.") self._name = name def evaluate(self, x, mapping, component, index_values): @@ -278,9 +277,9 @@ def __init__(self, arg1, arg2): if isinstance(arg1, (ComplexValue, complex)) or isinstance(arg2, (ComplexValue, complex)): raise TypeError("Atan2 does not support complex numbers.") if not is_true_ufl_scalar(arg1): - error("Expecting scalar argument 1.") + raise ValueError("Expecting scalar argument 1.") if not is_true_ufl_scalar(arg2): - error("Expecting scalar argument 2.") + raise ValueError("Expecting scalar argument 2.") def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) @@ -288,7 +287,7 @@ def evaluate(self, x, mapping, component, index_values): try: res = math.atan2(a, b) except TypeError: - error('Atan2 does not support complex numbers.') + raise ValueError('Atan2 does not support complex numbers.') except ValueError: warnings.warn('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) raise @@ -330,7 +329,7 @@ def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) erf = _find_erf() if erf is None: - error("No python implementation of erf available on this system, cannot evaluate. Upgrade python or install scipy.") + raise ValueError("No python implementation of erf available on this system, cannot evaluate. Upgrade python or install scipy.") return erf(a) @@ -341,9 +340,9 @@ class BesselFunction(Operator): def __init__(self, name, classname, nu, argument): if not is_true_ufl_scalar(nu): - error("Expecting scalar nu.") + raise ValueError("Expecting scalar nu.") if not is_true_ufl_scalar(argument): - error("Expecting scalar argument.") + raise ValueError("Expecting scalar argument.") # Use integer representation if suitable fnu = float(nu) @@ -363,7 +362,7 @@ def evaluate(self, x, mapping, component, index_values): try: import scipy.special except ImportError: - error("You must have scipy installed to evaluate bessel functions in python.") + raise ValueError("You must have scipy installed to evaluate bessel functions in python.") name = self._name[-1] if isinstance(self.ufl_operands[0], IntValue): nu = int(self.ufl_operands[0]) diff --git a/ufl/matrix.py b/ufl/matrix.py index a5523fe52..58e704d2b 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -9,7 +9,6 @@ # # Modified by Nacime Bouziani, 2021-2022. -from ufl.log import error from ufl.form import BaseForm from ufl.core.ufl_type import ufl_type from ufl.argument import Argument @@ -42,10 +41,10 @@ def __init__(self, row_space, column_space, count=None): counted_init(self, count, Matrix) if not isinstance(row_space, AbstractFunctionSpace): - error("Expecting a FunctionSpace as the row space.") + raise ValueError("Expecting a FunctionSpace as the row space.") if not isinstance(column_space, AbstractFunctionSpace): - error("Expecting a FunctionSpace as the column space.") + raise ValueError("Expecting a FunctionSpace as the column space.") self._ufl_function_spaces = (row_space, column_space) diff --git a/ufl/measure.py b/ufl/measure.py index 923ab14a0..c7c596f2f 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -14,11 +14,9 @@ from itertools import chain -from ufl.log import error from ufl.core.expr import Expr from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl -from ufl.utils.dicts import EmptyDict from ufl.domain import as_domain, AbstractDomain, extract_domains from ufl.protocols import id_or_none, metadata_equal, metadata_hashdata @@ -72,9 +70,9 @@ def register_integral_type(integral_type, measure_name): global integral_type_to_measure_name, measure_name_to_integral_type if measure_name != integral_type_to_measure_name.get(integral_type, measure_name): - error("Integral type already added with different measure name!") + raise ValueError("Integral type already added with different measure name!") if integral_type != measure_name_to_integral_type.get(measure_name, integral_type): - error("Measure name already used for another domain type!") + raise ValueError("Measure name already used for another domain type!") integral_type_to_measure_name[integral_type] = measure_name measure_name_to_integral_type[measure_name] = integral_type @@ -85,7 +83,7 @@ def as_integral_type(integral_type): integral_type = measure_name_to_integral_type.get(integral_type, integral_type) if integral_type not in integral_type_to_measure_name: - error("Invalid integral_type.") + raise ValueError("Invalid integral_type.") return integral_type @@ -146,30 +144,30 @@ def __init__(self, # Check that we either have a proper AbstractDomain or none self._domain = None if domain is None else as_domain(domain) if not (self._domain is None or isinstance(self._domain, AbstractDomain)): - error("Invalid domain.") + raise ValueError("Invalid domain.") # Store subdomain data self._subdomain_data = subdomain_data # FIXME: Cannot require this (yet) because we currently have # no way to implement ufl_id for dolfin SubDomain # if not (self._subdomain_data is None or hasattr(self._subdomain_data, "ufl_id")): - # error("Invalid domain data, missing ufl_id() implementation.") + # raise ValueError("Invalid domain data, missing ufl_id() implementation.") # Accept "everywhere", single subdomain, or multiple # subdomains if isinstance(subdomain_id, tuple): for did in subdomain_id: if not isinstance(did, numbers.Integral): - error("Invalid subdomain_id %s." % (did,)) + raise ValueError(f"Invalid subdomain_id {did}.") else: if not (subdomain_id in ("everywhere",) or isinstance(subdomain_id, numbers.Integral)): - error("Invalid subdomain_id %s." % (subdomain_id,)) + raise ValueError(f"Invalid subdomain_id {subdomain_id}.") self._subdomain_id = subdomain_id # Validate compiler options are None or dict if metadata is not None and not isinstance(metadata, dict): - error("Invalid metadata.") - self._metadata = metadata or EmptyDict + raise ValueError("Invalid metadata.") + self._metadata = metadata or {} def integral_type(self): """Return the domain type. @@ -257,7 +255,7 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, AbstractDomain) or hasattr(subdomain_id, 'ufl_domain')): if domain is not None: - error("Ambiguous: setting domain both as keyword argument and first argument.") + raise ValueError("Ambiguous: setting domain both as keyword argument and first argument.") subdomain_id, domain = "everywhere", as_domain(subdomain_id) # If degree or scheme is set, inject into metadata. This is a @@ -286,7 +284,7 @@ def __str__(self): args.append("subdomain_id=%s" % (self._subdomain_id,)) if self._domain is not None: args.append("domain=%s" % (self._domain,)) - if self._metadata: # Stored as EmptyDict if None + if self._metadata: # Stored as {} if None args.append("metadata=%s" % (self._metadata,)) if self._subdomain_data is not None: args.append("subdomain_data=%s" % (self._subdomain_data,)) @@ -304,7 +302,7 @@ def __repr__(self): args.append("subdomain_id=%s" % repr(self._subdomain_id)) if self._domain is not None: args.append("domain=%s" % repr(self._domain)) - if self._metadata: # Stored as EmptyDict if None + if self._metadata: # Stored as {} if None args.append("metadata=%s" % repr(self._metadata)) if self._subdomain_data is not None: args.append("subdomain_data=%s" % repr(self._subdomain_data)) @@ -386,9 +384,10 @@ def __rmul__(self, integrand): # Allow only scalar integrands if not is_true_ufl_scalar(integrand): - error("Can only integrate scalar expressions. The integrand is a " - "tensor expression with value shape %s and free indices with labels %s." % - (integrand.ufl_shape, integrand.ufl_free_indices)) + raise ValueError( + "Can only integrate scalar expressions. The integrand is a " + f"tensor expression with value shape {integrand.ufl_shape} and " + f"free indices with labels {integrand.ufl_free_indices}.") # If we have a tuple of domain ids build the integrals one by # one and construct as a Form in one go. @@ -400,7 +399,7 @@ def __rmul__(self, integrand): # Check that we have an integer subdomain or a string # ("everywhere" or "otherwise", any more?) if not isinstance(subdomain_id, (str, numbers.Integral,)): - error("Expecting integer or string domain id.") + raise ValueError("Expecting integer or string domain id.") # If we don't have an integration domain, try to find one in # integrand @@ -410,9 +409,9 @@ def __rmul__(self, integrand): if len(domains) == 1: domain, = domains elif len(domains) == 0: - error("This integral is missing an integration domain.") + raise ValueError("This integral is missing an integration domain.") else: - error("Multiple domains found, making the choice of integration domain ambiguous.") + raise ValueError("Multiple domains found, making the choice of integration domain ambiguous.") # Otherwise create and return a one-integral form integral = Integral(integrand=integrand, @@ -472,7 +471,7 @@ def __init__(self, *measures): "Create MeasureProduct from given list of measures." self._measures = measures if len(self._measures) < 2: - error("Expecting at least two measures.") + raise ValueError("Expecting at least two measures.") def __mul__(self, other): """Flatten multiplication of product measures. @@ -488,7 +487,8 @@ def __mul__(self, other): return NotImplemented def __rmul__(self, integrand): - error("TODO: Implement MeasureProduct.__rmul__ to construct integral and form somehow.") + # TODO: Implement MeasureProduct.__rmul__ to construct integral and form somehow. + raise NotImplementedError() def sub_measures(self): "Return submeasures." diff --git a/ufl/measureoperators.py b/ufl/measureoperators.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/ufl/operators.py b/ufl/operators.py index 786561289..c387b5ec3 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -16,7 +16,6 @@ import warnings import operator -from ufl.log import error from ufl.form import Form from ufl.constantvalue import Zero, RealValue, ComplexValue, as_ufl from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv @@ -98,7 +97,7 @@ def elem_op(op, *args): args = [as_ufl(arg) for arg in args] sh = args[0].ufl_shape if not all(sh == x.ufl_shape for x in args): - error("Cannot take elementwise operation with different shapes.") + raise ValueError("Cannot take elementwise operation with different shapes.") if sh == (): return op(*args) @@ -181,7 +180,7 @@ def contraction(a, a_axes, b, b_axes): "UFL operator: Take the contraction of a and b over given axes." ai, bi = a_axes, b_axes if len(ai) != len(bi): - error("Contraction must be over the same number of axes.") + raise ValueError("Contraction must be over the same number of axes.") ash = a.ufl_shape bsh = b.ufl_shape aii = indices(len(a.ufl_shape)) @@ -194,7 +193,7 @@ def contraction(a, a_axes, b, b_axes): for i, j in enumerate(bi): bii[j] = cii[i] if shape[i] != bsh[j]: - error("Shape mismatch in contraction.") + raise ValueError("Shape mismatch in contraction.") s = a[aii] * b[bii] cii = set(cii) ii = tuple(i for i in (aii + bii) if i not in cii) @@ -205,7 +204,7 @@ def perp(v): "UFL operator: Take the perp of *v*, i.e. :math:`(-v_1, +v_0)`." v = as_ufl(v) if v.ufl_shape != (2,): - error("Expecting a 2D vector expression.") + raise ValueError("Expecting a 2D vector expression.") return as_vector((-v[1], v[0])) @@ -259,9 +258,9 @@ def diag(A): elif r == 2: m, n = A.ufl_shape if m != n: - error("Can only take diagonal of square tensors.") + raise ValueError("Can only take diagonal of square tensors.") else: - error("Expecting rank 1 or 2 tensor.") + raise ValueError("Expecting rank 1 or 2 tensor.") # Build matrix row by row rows = [] @@ -281,10 +280,10 @@ def diag_vector(A): # Get and check dimensions if len(A.ufl_shape) != 2: - error("Expecting rank 2 tensor.") + raise ValueError("Expecting rank 2 tensor.") m, n = A.ufl_shape if m != n: - error("Can only take diagonal of square tensors.") + raise ValueError("Can only take diagonal of square tensors.") # Return diagonal vector return as_vector([A[i, i] for i in range(n)]) @@ -348,7 +347,7 @@ def diff(f, v): elif isinstance(v, (Variable, Coefficient)): return VariableDerivative(f, v) else: - error("Expecting a Variable or SpatialCoordinate in diff.") + raise ValueError("Expecting a Variable or SpatialCoordinate in diff.") def grad(f): @@ -719,7 +718,7 @@ def exterior_derivative(f): try: element = f.ufl_element() except Exception: - error("Unable to determine element from %s" % f) + raise ValueError(f"Unable to determine element from {f}") # Extract the family and the geometric dimension family = element.family() @@ -743,4 +742,4 @@ def exterior_derivative(f): if "Brezzi" in family or "Raviart" in family: return div(f) - error("Unable to determine exterior_derivative. Family is '%s'" % family) + raise ValueError(f"Unable to determine exterior_derivative. Family is '{family}'") diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 295192d63..0c2f70d0a 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,7 +10,6 @@ from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator from ufl.core.terminal import FormArgument -from ufl.log import error @ufl_type(num_ops=1, @@ -23,7 +22,7 @@ class ReferenceValue(Operator): def __init__(self, f): if not isinstance(f, FormArgument): - error("Can only take reference value of form arguments.") + raise ValueError("Can only take reference value of form arguments.") Operator.__init__(self, (f,)) @property @@ -32,7 +31,7 @@ def ufl_shape(self): def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get child from mapping and return the component asked for." - error("Evaluate not implemented.") + raise NotImplementedError() def __str__(self): - return "reference_value(%s)" % self.ufl_operands[0] + return f"reference_value({self.ufl_operands[0]})" diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 1b4439145..71fa3dcd8 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -9,7 +9,6 @@ # # Modified by Anders Logg, 2008 -from ufl.log import error from ufl.utils.sequences import product from ufl.finiteelement import TensorElement from ufl.tensors import as_vector, as_matrix, ListTensor @@ -45,9 +44,9 @@ def split(v): begin = int(begin) end = int(end) + 1 else: - error("Don't know how to split %s." % (v,)) + raise ValueError(f"Don't know how to split {v}.") else: - error("Don't know how to split %s." % (v,)) + raise ValueError(f"Don't know how to split {v}.") # Special case: simple element, just return function in a tuple element = v.ufl_element() @@ -57,10 +56,10 @@ def split(v): if isinstance(element, TensorElement): if element.symmetry(): - error("Split not implemented for symmetric tensor elements.") + raise ValueError("Split not implemented for symmetric tensor elements.") if len(v.ufl_shape) != 1: - error("Don't know how to split tensor valued mixed functions without flattened index space.") + raise ValueError("Don't know how to split tensor valued mixed functions without flattened index space.") # Compute value size and set default range end value_size = product(element.value_shape()) @@ -100,12 +99,12 @@ def split(v): subv = as_matrix([components[i * shape[1]: (i + 1) * shape[1]] for i in range(shape[0])]) else: - error("Don't know how to split functions with sub functions of rank %d." % rank) + raise ValueError(f"Don't know how to split functions with sub functions of rank {rank}.") offset += sub_size sub_functions.append(subv) if end != offset: - error("Function splitting failed to extract components for whole intended range. Something is wrong.") + raise ValueError("Function splitting failed to extract components for whole intended range. Something is wrong.") return tuple(sub_functions) diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 7f240c4c0..374931dc2 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -7,7 +7,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.log import error from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type from ufl.constantvalue import Zero @@ -97,7 +96,7 @@ def __new__(cls, A): def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) if len(A.ufl_shape) != 2: - error("Transposed is only defined for rank 2 tensors.") + raise ValueError("Transposed is only defined for rank 2 tensors.") @property def ufl_shape(self): @@ -144,7 +143,7 @@ def __new__(cls, a, b): # Checks ash, bsh = a.ufl_shape, b.ufl_shape if ash != bsh: - error("Shapes do not match: %s and %s." % (ufl_err_str(a), ufl_err_str(b))) + raise ValueError(f"Shapes do not match: {ufl_err_str(a)} and {ufl_err_str(b)}") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): @@ -187,10 +186,11 @@ def __new__(cls, a, b): # Checks if not ((ar >= 1 and br >= 1) or scalar): - error("Dot product requires non-scalar arguments, " - "got arguments with ranks %d and %d." % (ar, br)) + raise ValueError( + "Dot product requires non-scalar arguments, " + f"got arguments with ranks {ar} and {br}.") if not (scalar or ash[-1] == bsh[0]): - error("Dimension mismatch in dot product.") + raise ValueError("Dimension mismatch in dot product.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): @@ -227,8 +227,9 @@ def __new__(cls, a, b): # Checks if not (len(ash) == 1 and ash == bsh): - error("Cross product requires arguments of rank 1, got %s and %s." % ( - ufl_err_str(a), ufl_err_str(b))) + raise ValueError( + f"Cross product requires arguments of rank 1, got {ufl_err_str(a)} " + f"and {ufl_err_str(b)}.") # Simplification if isinstance(a, Zero) or isinstance(b, Zero): @@ -257,7 +258,7 @@ class Trace(CompoundTensorOperator): def __new__(cls, A): # Checks if len(A.ufl_shape) != 2: - error("Trace of tensor with rank != 2 is undefined.") + raise ValueError("Trace of tensor with rank != 2 is undefined.") # Simplification if isinstance(A, Zero): @@ -285,11 +286,11 @@ def __new__(cls, A): # Checks if r not in (0, 2): - error("Determinant of tensor with rank != 2 is undefined.") + raise ValueError("Determinant of tensor with rank != 2 is undefined.") if r == 2 and sh[0] != sh[1]: - error("Cannot take determinant of rectangular rank 2 tensor.") + raise ValueError("Cannot take determinant of rectangular rank 2 tensor.") if Afi: - error("Not expecting free indices in determinant.") + raise ValueError("Not expecting free indices in determinant.") # Simplification if isinstance(A, Zero): @@ -318,9 +319,9 @@ def __new__(cls, A): # Checks if A.ufl_free_indices: - error("Not expecting free indices in Inverse.") + raise ValueError("Not expecting free indices in Inverse.") if isinstance(A, Zero): - error("Division by zero!") + raise ValueError("Division by zero!") # Simplification if r == 0: @@ -328,9 +329,9 @@ def __new__(cls, A): # More checks if r != 2: - error("Inverse of tensor with rank != 2 is undefined.") + raise ValueError("Inverse of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - error("Cannot take inverse of rectangular matrix with dimensions %s." % (sh,)) + raise ValueError(f"Cannot take inverse of rectangular matrix with dimensions {sh}.") return CompoundTensorOperator.__new__(cls) @@ -355,13 +356,13 @@ def __init__(self, A): # Checks sh = A.ufl_shape if len(sh) != 2: - error("Cofactor of tensor with rank != 2 is undefined.") + raise ValueError("Cofactor of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - error("Cannot take cofactor of rectangular matrix with dimensions %s." % (sh,)) + raise ValueError(f"Cannot take cofactor of rectangular matrix with dimensions {sh}.") if A.ufl_free_indices: - error("Not expecting free indices in Cofactor.") + raise ValueError("Not expecting free indices in Cofactor.") if isinstance(A, Zero): - error("Cannot take cofactor of zero matrix.") + raise ValueError("Cannot take cofactor of zero matrix.") @property def ufl_shape(self): @@ -380,11 +381,11 @@ def __new__(cls, A): # Checks if len(sh) != 2: - error("Deviatoric part of tensor with rank != 2 is undefined.") + raise ValueError("Deviatoric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - error("Cannot take deviatoric part of rectangular matrix with dimensions %s." % (sh,)) + raise ValueError(f"Cannot take deviatoric part of rectangular matrix with dimensions {sh}.") if A.ufl_free_indices: - error("Not expecting free indices in Deviatoric.") + raise ValueError("Not expecting free indices in Deviatoric.") # Simplification if isinstance(A, Zero): @@ -409,11 +410,11 @@ def __new__(cls, A): # Checks if len(sh) != 2: - error("Skew symmetric part of tensor with rank != 2 is undefined.") + raise ValueError("Skew symmetric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - error("Cannot take skew part of rectangular matrix with dimensions %s." % (sh,)) + raise ValueError(f"Cannot take skew part of rectangular matrix with dimensions {sh}.") if Afi: - error("Not expecting free indices in Skew.") + raise ValueError("Not expecting free indices in Skew.") # Simplification if isinstance(A, Zero): @@ -438,11 +439,11 @@ def __new__(cls, A): # Checks if len(sh) != 2: - error("Symmetric part of tensor with rank != 2 is undefined.") + raise ValueError("Symmetric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - error("Cannot take symmetric part of rectangular matrix with dimensions %s." % (sh,)) + raise ValueError(f"Cannot take symmetric part of rectangular matrix with dimensions {sh}.") if Afi: - error("Not expecting free indices in Sym.") + raise ValueError("Not expecting free indices in Sym.") # Simplification if isinstance(A, Zero): @@ -454,4 +455,4 @@ def __init__(self, A): CompoundTensorOperator.__init__(self, (A,)) def __str__(self): - return "sym(%s)" % self.ufl_operands[0] + return f"sym({self.ufl_operands[0]})" diff --git a/ufl/tensors.py b/ufl/tensors.py index 728b36b7e..67fedb2dc 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -9,7 +9,6 @@ # # Modified by Massimiliano Leoni, 2016. -from ufl.log import error from ufl.core.ufl_type import ufl_type from ufl.core.expr import Expr from ufl.core.operator import Operator @@ -30,7 +29,7 @@ def __new__(cls, *expressions): # All lists and tuples should already be unwrapped in # as_tensor if any(not isinstance(e, Expr) for e in expressions): - error("Expecting only UFL expressions in ListTensor constructor.") + raise ValueError("Expecting only UFL expressions in ListTensor constructor.") # Get properties of the first expression e0 = expressions[0] @@ -40,11 +39,11 @@ def __new__(cls, *expressions): # Obviously, each subexpression must have the same shape if any(sh != e.ufl_shape for e in expressions[1:]): - error("Cannot create a tensor by joining subexpressions with different shapes.") + raise ValueError("Cannot create a tensor by joining subexpressions with different shapes.") if any(fi != e.ufl_free_indices for e in expressions[1:]): - error("Cannot create a tensor where the components have different free indices.") + raise ValueError("Cannot create a tensor where the components have different free indices.") if any(fid != e.ufl_index_dimensions for e in expressions[1:]): - error("Cannot create a tensor where the components have different free index dimensions.") + raise ValueError("Cannot create a tensor where the components have different free index dimensions.") # Simplify to Zero if possible if all(isinstance(e, Zero) for e in expressions): @@ -59,7 +58,7 @@ def __init__(self, *expressions): # Checks indexset = set(self.ufl_operands[0].ufl_free_indices) if not all(not (indexset ^ set(e.ufl_free_indices)) for e in self.ufl_operands): - error("Can't combine subtensor expressions with different sets of free indices.") + raise ValueError("Can't combine subtensor expressions with different sets of free indices.") @property def ufl_shape(self): @@ -67,8 +66,9 @@ def ufl_shape(self): def evaluate(self, x, mapping, component, index_values, derivatives=()): if len(component) != len(self.ufl_shape): - error("Can only evaluate scalars, expecting a component " - "tuple of length %d, not %s." % (len(self.ufl_shape), component)) + raise ValueError( + "Can only evaluate scalars, expecting a component " + "tuple of length {len(self.ufl_shape)}, not {component}.") a = self.ufl_operands[component[0]] component = component[1:] if derivatives: @@ -127,13 +127,13 @@ def __new__(cls, expression, indices): def __init__(self, expression, indices): if not isinstance(expression, Expr): - error("Expecting ufl expression.") + raise ValueError("Expecting ufl expression.") if expression.ufl_shape != (): - error("Expecting scalar valued expression.") + raise ValueError("Expecting scalar valued expression.") if not isinstance(indices, MultiIndex): - error("Expecting a MultiIndex.") + raise ValueError("Expecting a MultiIndex.") if not all(isinstance(i, Index) for i in indices): - error("Expecting sequence of Index objects, not %s." % indices._ufl_err_str_()) + raise ValueError(f"Expecting sequence of Index objects, not {indices._ufl_err_str_()}.") Operator.__init__(self, (expression, indices)) @@ -160,7 +160,7 @@ def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0] if len(indices) != len(component): - error("Expecting a component matching the indices tuple.") + raise ValueError("Expecting a component matching the indices tuple.") # Map component to indices for i, c in zip(indices, component): @@ -236,7 +236,7 @@ def as_tensor(expressions, indices=None): # Sanity check if not isinstance(expressions, (list, tuple, Expr)): - error("Expecting nested list or tuple.") + raise ValueError("Expecting nested list or tuple.") # Recursive conversion from nested lists to nested ListTensor # objects @@ -270,7 +270,7 @@ def as_matrix(expressions, indices=None): # Allow as_matrix(as_matrix(A)) in user code if isinstance(expressions, Expr): if len(expressions.ufl_shape) != 2: - error("Expecting rank 2 tensor.") + raise ValueError("Expecting rank 2 tensor.") return expressions # To avoid importing numpy unneeded, it's quite slow... @@ -279,12 +279,12 @@ def as_matrix(expressions, indices=None): # Check for expected list structure if not isinstance(expressions, (list, tuple)): - error("Expecting nested list or tuple of Exprs.") + raise ValueError("Expecting nested list or tuple of Exprs.") if not isinstance(expressions[0], (list, tuple)): - error("Expecting nested list or tuple of Exprs.") + raise ValueError("Expecting nested list or tuple of Exprs.") else: if len(indices) != 2: - error("Expecting exactly two indices.") + raise ValueError("Expecting exactly two indices.") return as_tensor(expressions, indices) @@ -295,7 +295,7 @@ def as_vector(expressions, index=None): # Allow as_vector(as_vector(v)) in user code if isinstance(expressions, Expr): if len(expressions.ufl_shape) != 1: - error("Expecting rank 1 tensor.") + raise ValueError("Expecting rank 1 tensor.") return expressions # To avoid importing numpy unneeded, it's quite slow... @@ -304,10 +304,10 @@ def as_vector(expressions, index=None): # Check for expected list structure if not isinstance(expressions, (list, tuple)): - error("Expecting nested list or tuple of Exprs.") + raise ValueError("Expecting nested list or tuple of Exprs.") else: if not isinstance(index, Index): - error("Expecting a single Index object.") + raise ValueError("Expecting a single Index object.") index = (index,) return as_tensor(expressions, index) @@ -344,9 +344,9 @@ def relabel(A, indexmap): ii = tuple(sorted(indexmap.keys())) jj = tuple(indexmap[i] for i in ii) if not all(isinstance(i, Index) for i in ii): - error("Expecting Index objects.") + raise ValueError("Expecting Index objects.") if not all(isinstance(j, Index) for j in jj): - error("Expecting Index objects.") + raise ValueError("Expecting Index objects.") return as_tensor(A, ii)[jj] diff --git a/ufl/utils/dicts.py b/ufl/utils/dicts.py deleted file mode 100644 index a1d7fdaa8..000000000 --- a/ufl/utils/dicts.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -"""Various dict manipulation utilities.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - - -def split_dict(d, criteria): - """Split a dict d into two dicts based on a criteria on the keys.""" - a = {} - b = {} - for (k, v) in d.items(): - if criteria(k): - a[k] = v - else: - b[k] = v - return a, b - - -def slice_dict(dictionary, keys, default=None): - return tuple(dictionary.get(k, default) for k in keys) - - -def some_key(a_dict): - """Return an arbitrary key from a dictionary.""" - return next(a_dict.keys()) - - -def mergedicts(dicts): - d = dict(dicts[0]) - for d2 in dicts[1:]: - d.update(d2) - return d - - -def mergedicts2(d1, d2): - d = dict(d1) - d.update(d2) - return d - - -def subdict(superdict, keys): - return dict((k, superdict[k]) for k in keys) - - -def dict_sum(items): - """Construct a dict, in between dict(items) and sum(items), by accumulating items for each key.""" - d = {} - for k, v in items: - if k not in d: - d[k] = v - else: - d[k] += v - return d - - -class EmptyDictType(dict): - def __setitem__(self, key, value): - from ufl.log import error - error("This is a frozen unique empty dictionary object, inserting values is an error.") - - def update(self, *args, **kwargs): - from ufl.log import error - error("This is a frozen unique empty dictionary object, inserting values is an error.") - - -EmptyDict = EmptyDictType() diff --git a/ufl/variable.py b/ufl/variable.py index c52c8d14c..fb14c43d6 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -9,7 +9,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from ufl.utils.counted import counted_init -from ufl.log import error from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal @@ -39,15 +38,15 @@ def __repr__(self): @property def ufl_shape(self): - error("Label has no shape (it is not a tensor expression).") + raise ValueError("Label has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): - error("Label has no free indices (it is not a tensor expression).") + raise ValueError("Label has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): - error("Label has no free indices (it is not a tensor expression).") + raise ValueError("Label has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): return True @@ -80,11 +79,11 @@ def __init__(self, expression, label=None): # Checks if not isinstance(expression, Expr): - error("Expecting Expr.") + raise ValueError("Expecting Expr.") if not isinstance(label, Label): - error("Expecting a Label.") + raise ValueError("Expecting a Label.") if expression.ufl_free_indices: - error("Variable cannot wrap an expression with free indices.") + raise ValueError("Variable cannot wrap an expression with free indices.") Operator.__init__(self, (expression, label)) From 2babe0db30c8576042091347fa05ba1dcaeb87f3 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 18 Jan 2023 16:59:27 +0000 Subject: [PATCH 016/136] Handle form coefficients as an optional kwarg when calling a form (#125) Co-authored-by: David A. Ham --- ufl/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/form.py b/ufl/form.py index 652becfa8..4baa506ea 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -545,7 +545,7 @@ def __call__(self, *args, **kwargs): raise ValueError(f"Need {len(arguments)} arguments to form(), got {len(args)}.") repdict.update(zip(arguments, args)) - coefficients = kwargs.pop("coefficients") + coefficients = kwargs.pop("coefficients", None) if kwargs: raise ValueError(f"Unknown kwargs {list(kwargs)}") From e68314821b6f3c157bd396a8c457cd9e4628296e Mon Sep 17 00:00:00 2001 From: Michal Habera Date: Fri, 27 Jan 2023 09:26:11 +0100 Subject: [PATCH 017/136] Use BaseArgument.__eq__ in Argument (#147) --- ufl/argument.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ufl/argument.py b/ufl/argument.py index 3f143e0ef..4d170b136 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -152,6 +152,7 @@ class Argument(FormArgument, BaseArgument): __getnewargs__ = BaseArgument.__getnewargs__ __str__ = BaseArgument.__str__ _ufl_signature_data_ = BaseArgument._ufl_signature_data_ + __eq__ = BaseArgument.__eq__ def __new__(cls, *args, **kw): if args[0] and is_dual(args[0]): From 4f04bf19ac8871827245a4ec2f1d3de2f37e2e70 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Fri, 27 Jan 2023 16:28:17 +0000 Subject: [PATCH 018/136] Updates to avoid `ufl_domain` deprecation warnings (#148) * Updates to avoid ufl_domain deprecation warnings. * Small fix * Formatting fix * Undo change for integrals --- ufl/core/expr.py | 2 +- ufl/form.py | 64 +++++++++++++++++++++--------------------------- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/ufl/core/expr.py b/ufl/core/expr.py index d6d51e412..b47ccadca 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -23,9 +23,9 @@ from ufl.core.ufl_type import UFLType, update_ufl_type_attributes - # --- The base object for all UFL expression tree nodes --- + class Expr(object, metaclass=UFLType): """Base class for all UFL expression types. diff --git a/ufl/form.py b/ufl/form.py index 4baa506ea..9c4ab0eda 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -12,16 +12,16 @@ # Modified by Cecile Daversin-Catty, 2018. import warnings -from itertools import chain from collections import defaultdict +from itertools import chain -from ufl.domain import sort_domains -from ufl.integral import Integral from ufl.checks import is_scalar_constant_expression -from ufl.equation import Equation +from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str from ufl.core.ufl_type import UFLType, ufl_type -from ufl.constantvalue import Zero +from ufl.domain import extract_unique_domain, sort_domains +from ufl.equation import Equation +from ufl.integral import Integral # Export list for ufl.classes __all_classes__ = ["Form", "BaseForm", "ZeroBaseForm"] @@ -51,17 +51,14 @@ def _sorted_integrals(integrals): # Order integrals canonically to increase signature stability for d in sort_domains(integrals_dict): for it in sorted(integrals_dict[d]): # str is sortable - for si in sorted( - integrals_dict[d][it], key=lambda x: (type(x).__name__, x) - ): # int/str are sortable + for si in sorted(integrals_dict[d][it], key=lambda x: (type(x).__name__, x)): # int/str are sortable unsorted_integrals = integrals_dict[d][it][si] # TODO: At this point we could order integrals by # metadata and integrand, or even add the - # integrands with the same metadata. This is - # done in - # accumulate_integrands_with_same_metadata in - # algorithms/domain_analysis.py and would - # further increase the signature stability. + # integrands with the same metadata. This is done + # in accumulate_integrands_with_same_metadata in + # algorithms/domain_analysis.py and would further + # increase the signature stability. all_integrals.extend(unsorted_integrals) # integrals_dict[d][it][si] = unsorted_integrals @@ -72,7 +69,8 @@ def _sorted_integrals(integrals): class BaseForm(object, metaclass=UFLType): """Description of an object containing arguments""" - # Slots is kept empty to enable multiple inheritance with other classes. + # Slots is kept empty to enable multiple inheritance with other + # classes __slots__ = () _ufl_is_abstract_ = True _ufl_required_methods_ = ('_analyze_form_arguments', "ufl_domains") @@ -107,10 +105,7 @@ def __add__(self, other): if isinstance(other, (int, float)) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self - - elif isinstance( - other, - Zero) and not (other.ufl_shape or other.ufl_free_indices): + elif isinstance(other, Zero) and not (other.ufl_shape or other.ufl_free_indices): # Allow adding ufl Zero as a no-op, needed for sum([a,b]) return self @@ -226,6 +221,7 @@ def __call__(self, *args, **kwargs): repdict[f] = coefficients[f] else: warnings("Coefficient %s is not in form." % ufl_err_str(f)) + if repdict: from ufl.formoperators import replace return replace(self, repdict) @@ -320,8 +316,7 @@ def integrals_by_type(self, integral_type): def integrals_by_domain(self, domain): "Return a sequence of all integrals with a particular integration domain." - return tuple(integral for integral in self.integrals() - if integral.ufl_domain() == domain) + return tuple(integral for integral in self.integrals() if integral.ufl_domain() == domain) def empty(self): "Returns whether the form has no integrals." @@ -361,19 +356,18 @@ def ufl_domain(self): # Check that all are equal TODO: don't return more than one if # all are equal? if not all(domain == domains[0] for domain in domains): - raise ValueError( - "Calling Form.ufl_domain() is only valid if all integrals share domain.") + raise ValueError("Calling Form.ufl_domain() is only valid if all integrals share domain.") # Return the one and only domain return domains[0] def geometric_dimension(self): - "Return the geometric dimension shared by all domains and functions in this form." + """Return the geometric dimension shared by all domains and functions in this form.""" gdims = tuple( set(domain.geometric_dimension() for domain in self.ufl_domains())) if len(gdims) != 1: raise ValueError("Expecting all domains and functions in a form " - f"to share geometric dimension, got {tuple(sorted(gdims))}") + f"to share geometric dimension, got {tuple(sorted(gdims))}") return gdims[0] def domain_numbering(self): @@ -590,16 +584,14 @@ def _analyze_domains(self): from ufl.domain import join_domains, sort_domains # Collect unique integration domains - integration_domains = join_domains( - [itg.ufl_domain() for itg in self._integrals]) + integration_domains = join_domains([itg.ufl_domain() for itg in self._integrals]) # Make canonically ordered list of the domains self._integration_domains = sort_domains(integration_domains) # TODO: Not including domains from coefficients and arguments # here, may need that later - self._domain_numbering = dict( - (d, i) for i, d in enumerate(self._integration_domains)) + self._domain_numbering = dict((d, i) for i, d in enumerate(self._integration_domains)) def _analyze_subdomain_data(self): integration_domains = self.ufl_domains() @@ -650,7 +642,7 @@ def _compute_renumbering(self): # among integration domains k = len(dn) for c in cn: - d = c.ufl_domain() + d = extract_unique_domain(c) if d is not None and d not in renumbering: renumbering[d] = k k += 1 @@ -666,7 +658,7 @@ def _compute_renumbering(self): # Add domains of constants, these may include domains not # among integration domains for c in self._constants: - d = c.ufl_domain() + d = extract_unique_domain(c) if d is not None and d not in renumbering: renumbering[d] = k k += 1 @@ -675,8 +667,7 @@ def _compute_renumbering(self): def _compute_signature(self): from ufl.algorithms.signature import compute_form_signature - self._signature = compute_form_signature(self, - self._compute_renumbering()) + self._signature = compute_form_signature(self, self._compute_renumbering()) def sub_forms_by_domain(form): @@ -707,9 +698,7 @@ def replace_integral_domains(form, common_domain): # TODO: Move elsewhere if common_domain is not None: gdim = common_domain.geometric_dimension() tdim = common_domain.topological_dimension() - if not all((gdim == domain.geometric_dimension() and - tdim == domain.topological_dimension()) - for domain in domains): + if not all((gdim == domain.geometric_dimension() and tdim == domain.topological_dimension()) for domain in domains): raise ValueError("Common domain does not share dimensions with form domains.") reconstruct = False @@ -830,7 +819,10 @@ def __repr__(self): @ufl_type() class ZeroBaseForm(BaseForm): """Description of a zero base form. - ZeroBaseForm is idempotent with respect to assembly and is mostly used for sake of simplifying base-form expressions. + + ZeroBaseForm is idempotent with respect to assembly and is mostly + used for sake of simplifying base-form expressions. + """ __slots__ = ("_arguments", From 6f181baabb3025a737a65d9725ea5183a4d3b710 Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Fri, 24 Feb 2023 09:05:34 +0000 Subject: [PATCH 019/136] fix TensorProductElement repr (#152) --- ufl/finiteelement/tensorproductelement.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py index 8b882cf91..341b018f5 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/finiteelement/tensorproductelement.py @@ -69,9 +69,7 @@ def __init__(self, *elements, **kwargs): self._cell = cell def __repr__(self): - return "TensorProductElement(" + ", ".join( - repr(e) for e in self._sub_elements - ) + f", {repr(self._cell)})" + return "TensorProductElement(" + ", ".join(repr(e) for e in self._sub_elements) + f", cell={repr(self._cell)})" def mapping(self): if all(e.mapping() == "identity" for e in self._sub_elements): From 2d3992dd8b0a39f00e8233eceaec3007fb6d89c6 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Fri, 24 Feb 2023 09:23:18 +0000 Subject: [PATCH 020/136] Fix dev version number so pip doesn't report UFL as upgradable (#153) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 77f81636d..48673d5f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ # future [metadata] name = fenics-ufl -version = 2022.3.0.dev0 +version = 2023.2.0.dev0 author = FEniCS Project Contributors email = fenics-dev@googlegroups.com maintainer = FEniCS Project Steering Council From 6eeef938c877634dcb7c8acb101bb2363ed1455b Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Sun, 26 Feb 2023 21:43:35 +0000 Subject: [PATCH 021/136] Bump min setuptools version and remove setup.py (#154) See https://github.com/pypa/setuptools/pull/3151. --- pyproject.toml | 2 +- setup.cfg | 2 +- setup.py | 16 ---------------- 3 files changed, 2 insertions(+), 18 deletions(-) delete mode 100755 setup.py diff --git a/pyproject.toml b/pyproject.toml index 9bf811c37..cfcfb19c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ [build-system] -requires = ["setuptools>=58", "wheel"] +requires = ["setuptools>=62", "wheel"] build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg index 48673d5f3..44b1f1651 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ include_package_data = True zip_safe = False python_requires = >= 3.7 setup_requires = - setuptools >= 58 + setuptools >= 62 wheel install_requires = numpy diff --git a/setup.py b/setup.py deleted file mode 100755 index daa285d2c..000000000 --- a/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -import setuptools - -try: - import pip - - from packaging import version - if version.parse(pip.__version__) < version.parse("21.3"): - # Issue with older version of pip https://github.com/pypa/pip/issues/7953 - import site - import sys - site.ENABLE_USER_SITE = "--user" in sys.argv[1:] - -except ImportError: - pass - -setuptools.setup() From 43b966ac95d166c5e3be959670e50d06379e2bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 25 Apr 2023 10:24:17 +0200 Subject: [PATCH 022/136] Replace `pkg_resources` with `importlib.metadata` and bump minimal Python version to 3.8 (#158) * Fix #157 * Bump python version * Add more versions to CI * Add pytest to tsfc * Remove clang from ufl tests * Switch env vars * Apply suggestions from code review * Consistent string-style --- .github/workflows/fenicsx-tests.yml | 2 -- .github/workflows/pythonapp.yml | 2 +- .github/workflows/tsfc-tests.yml | 1 + doc/sphinx/source/conf.py | 8 +++----- setup.cfg | 4 ++-- ufl/__init__.py | 4 ++-- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 7aaf42d7f..f9c7d811f 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -55,8 +55,6 @@ jobs: container: fenicsproject/test-env:nightly-openmpi env: - CC: clang - CXX: clang++ PETSC_ARCH: linux-gnu-complex-32 OMPI_ALLOW_RUN_AS_ROOT: 1 diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 3feb86104..695aad6c7 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 601422623..06b10a13d 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -42,5 +42,6 @@ jobs: pip install git+https://github.com/FInAT/FInAT.git#egg=finat pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy pip install .[ci] + pip install pytest - name: Run tsfc unit tests run: python3 -m pytest tsfc/tests diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 500ba06ed..60a0af8ee 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -12,11 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os -import shlex -import pkg_resources import datetime +import importlib.metadata # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -58,7 +55,8 @@ this_year = datetime.date.today().year copyright = u'%s, FEniCS Project' % this_year author = u'FEniCS Project' -version = pkg_resources.get_distribution("fenics-ufl").version + +version = importlib.metadata.version("fenics-ufl") release = version # The language for content autogenerated by Sphinx. Refer to documentation diff --git a/setup.cfg b/setup.cfg index 44b1f1651..256c55d64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,10 +26,10 @@ classifiers = Operating System :: MacOS :: MacOS X Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Scientific/Engineering :: Mathematics Topic :: Software Development :: Libraries :: Python Modules @@ -37,7 +37,7 @@ classifiers = packages = find: include_package_data = True zip_safe = False -python_requires = >= 3.7 +python_requires = >= 3.8 setup_requires = setuptools >= 62 wheel diff --git a/ufl/__init__.py b/ufl/__init__.py index e6d823003..8b356688a 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -241,9 +241,9 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -import pkg_resources +import importlib.metadata -__version__ = pkg_resources.get_distribution("fenics-ufl").version +__version__ = importlib.metadata.version("fenics-ufl") # README # Imports here should be what the user sees when doing "from ufl import *", From f195476fd31364fd4f01ba033b318b85a29cae02 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Fri, 5 May 2023 12:09:47 +0200 Subject: [PATCH 023/136] Fix typo in split (#144) * Fix typo in split * Add test for split on simple elements --------- Co-authored-by: Garth N. Wells Co-authored-by: David A. Ham --- test/test_split.py | 4 ++++ ufl/split_functions.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_split.py b/test/test_split.py index 380def3dc..1b7b4bd10 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -47,6 +47,10 @@ def test_split(self): assert (d+d,) == Coefficient(v2).ufl_shape assert (2*d*d,) == Coefficient(m2).ufl_shape + # Split simple element + t = TestFunction(f) + assert split(t) == (t,) + # Split twice on nested mixed elements gets # the innermost scalar subcomponents t = TestFunction(f*v) diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 71fa3dcd8..18129698e 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -50,7 +50,7 @@ def split(v): # Special case: simple element, just return function in a tuple element = v.ufl_element() - if element.num_sub_elements == 0: + if element.num_sub_elements() == 0: assert end is None return (v,) From d2dfbef9a60bede67ac8361712316bf427f9eecc Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Mon, 15 May 2023 10:02:19 +0100 Subject: [PATCH 024/136] fix str (#162) --- test/test_str.py | 5 +++++ ufl/finiteelement/finiteelement.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test_str.py b/test/test_str.py index f5021a042..ffab81ea2 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -103,3 +103,8 @@ def test_str_list_matrix_with_zero(): # FIXME: Add more tests for tensors collapsing # partly or completely into Zero! + + +def test_str_element(): + elem = FiniteElement("Q", quadrilateral, 1) + assert str(elem) == "" diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index d86ae331e..9ea00b37f 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -153,7 +153,7 @@ def __init__(self, # simplify base though. self._sobolev_space = sobolev_space self._mapping = mapping - self._short_name = short_name + self._short_name = short_name or family self._variant = variant # Finite elements on quadrilaterals and hexahedrons have an IrreducibleInt as degree From e3c6cdd5a83d9aa2c1b8d6ac946589a0f70fafe7 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 15 May 2023 12:07:08 +0100 Subject: [PATCH 025/136] Remove checks based on family name (#161) * Use sobolev space not family * update comment * don't use checks on family name * flake8 * l * remove check that family is a string * _is_linear in subclasses --- ufl/algorithms/apply_derivatives.py | 11 +++++----- ufl/algorithms/apply_function_pullbacks.py | 4 ++-- ufl/algorithms/apply_restrictions.py | 4 +--- ufl/algorithms/change_to_reference.py | 2 +- ufl/algorithms/compute_form_data.py | 2 -- ufl/algorithms/elementtransformations.py | 2 +- ufl/checks.py | 2 +- ufl/finiteelement/elementlist.py | 8 +++---- ufl/finiteelement/finiteelement.py | 6 ++++++ ufl/finiteelement/finiteelementbase.py | 15 ++++++++++--- ufl/finiteelement/mixedelement.py | 3 +++ ufl/finiteelement/restrictedelement.py | 3 +++ ufl/operators.py | 25 ++++++++-------------- 13 files changed, 48 insertions(+), 39 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index d60326880..4a7dd3a55 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1268,12 +1268,11 @@ def coefficient_derivative(self, o): def coordinate_derivative(self, o, f, w, v, cd): from ufl.algorithms import extract_unique_elements - spaces = set(c.family() for c in extract_unique_elements(o)) - unsupported_spaces = {"Argyris", "Bell", "Hermite", "Morley"} - if spaces & unsupported_spaces: - raise NotImplementedError( - "CoordinateDerivative is not supported for elements of type {spaces & unsupported_spaces}. " - "This is because their pullback is not implemented in UFL.") + for space in extract_unique_elements(o): + if space.mapping() == "custom": + raise NotImplementedError( + "CoordinateDerivative is not supported for elements with custom pull back.") + _, w, v, cd = o.ufl_operands rules = CoordinateDerivativeRuleset(w, v, cd) key = (CoordinateDerivativeRuleset, w, v, cd) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index 5bd12cf83..58930a113 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -68,7 +68,7 @@ def apply_known_single_pullback(r, element): domain = extract_unique_domain(r) if mapping == "physical": return r - elif mapping == "identity": + elif mapping == "identity" or mapping == "custom": return r elif mapping == "contravariant Piola": J = Jacobian(domain) @@ -122,7 +122,7 @@ def apply_single_function_pullbacks(r, element): if mapping in {"physical", "identity", "contravariant Piola", "covariant Piola", "double contravariant Piola", "double covariant Piola", - "L2 Piola"}: + "L2 Piola", "custom"}: # Base case in recursion through elements. If the element # advertises a mapping we know how to handle, do that # directly. diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index f53b98fac..9cca28b1e 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -137,12 +137,10 @@ def coefficient(self, o): def facet_normal(self, o): D = extract_unique_domain(o) e = D.ufl_coordinate_element() - f = e.family() - d = e.degree() gd = D.geometric_dimension() td = D.topological_dimension() - if f == "Lagrange" and d == 1 and gd == td: + if e._is_linear() and gd == td: # For meshes with a continuous linear non-manifold # coordinate field, the facet normal from side - points in # the opposite direction of the one from side +. We must diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index b3b4e07e4..168cad610 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -279,7 +279,7 @@ def ndarray(shape): # covariant_hcurl_mapping = JinvT * PullbackOf(o) ec, = ec emapping = K[:, ec] # Column of K is row of K.T - elif mapping == "identity": + elif mapping == "identity" or mapping == "custom": emapping = None else: raise ValueError(f"Unknown mapping: {mapping}") diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index e665a880e..1b5d4ca5c 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -139,8 +139,6 @@ def _compute_form_data_elements(self, arguments, coefficients, domains): def _check_elements(form_data): for element in chain(form_data.unique_elements, form_data.unique_sub_elements): - if element.family() is None: - raise ValueError(f"Found element with undefined family: {element}") if element.cell() is None: raise ValueError(f"Found element with undefined cell: {element}") diff --git a/ufl/algorithms/elementtransformations.py b/ufl/algorithms/elementtransformations.py index 43e659cb6..ae182d5ce 100644 --- a/ufl/algorithms/elementtransformations.py +++ b/ufl/algorithms/elementtransformations.py @@ -36,7 +36,7 @@ def tear(element): def _increase_degree(element, degree_rise): if isinstance(element, (FiniteElement, VectorElement, TensorElement)): # Can't increase degree for reals - if element.family() == "Real": + if element._is_globally_constant(): return element return element.reconstruct(degree=(element.degree() + degree_rise)) elif isinstance(element, MixedElement): diff --git a/ufl/checks.py b/ufl/checks.py index a3cad0e8b..11502cb65 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -54,7 +54,7 @@ def is_globally_constant(expr): continue elif isinstance(e, FormArgument): # Accept only Real valued Arguments and Coefficients - if e.ufl_element().family() == "Real": + if e.ufl_element()._is_globally_constant(): continue else: return False diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index a470279f3..b976b7e43 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -106,8 +106,8 @@ def show_elements(): (1, None), simplices[1:]) # "RTF" (2d), "N1F" (3d) # Elements not in the periodic table -register_element("Argyris", "ARG", 0, H2, "identity", (5, 5), ("triangle",)) -register_element("Bell", "BELL", 0, H2, "identity", (5, 5), ("triangle",)) +register_element("Argyris", "ARG", 0, H2, "custom", (5, 5), ("triangle",)) +register_element("Bell", "BELL", 0, H2, "custom", (5, 5), ("triangle",)) register_element("Brezzi-Douglas-Fortin-Marini", "BDFM", 1, HDiv, "contravariant Piola", (1, None), simplices[1:]) register_element("Crouzeix-Raviart", "CR", 0, L2, "identity", (1, 1), @@ -115,12 +115,12 @@ def show_elements(): # TODO: Implement generic Tear operator for elements instead of this: register_element("Discontinuous Raviart-Thomas", "DRT", 1, L2, "contravariant Piola", (1, None), simplices[1:]) -register_element("Hermite", "HER", 0, H1, "identity", (3, 3), simplices) +register_element("Hermite", "HER", 0, H1, "custom", (3, 3), simplices) register_element("Kong-Mulder-Veldhuizen", "KMV", 0, H1, "identity", (1, None), simplices[1:]) register_element("Mardal-Tai-Winther", "MTW", 1, H1, "contravariant Piola", (3, 3), ("triangle",)) -register_element("Morley", "MOR", 0, H2, "identity", (2, 2), ("triangle",)) +register_element("Morley", "MOR", 0, H2, "custom", (2, 2), ("triangle",)) # Special elements register_element("Boundary Quadrature", "BQ", 0, L2, "identity", (0, None), diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index 9ea00b37f..ca7071698 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -189,6 +189,12 @@ def __repr__(self): """Format as string for evaluation as Python object.""" return self._repr + def _is_globally_constant(self): + return self.family() == "Real" + + def _is_linear(self): + return self.family() == "Lagrange" and self.degree() == 1 + def mapping(self): """Return the mapping type for this element .""" return self._mapping diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index 7d31006e0..8f1b71b51 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -26,8 +26,6 @@ class FiniteElementBase(ABC): def __init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape): """Initialize basic finite element data.""" - if not isinstance(family, str): - raise ValueError("Invalid family type.") if not (degree is None or isinstance(degree, (int, tuple))): raise ValueError("Invalid degree type.") if not isinstance(value_shape, tuple): @@ -62,6 +60,17 @@ def mapping(self): """Return the mapping type for this element.""" pass + def _is_globally_constant(self): + """Check if the element is a global constant. + + For Real elements, this should return True. + """ + return False + + def _is_linear(self): + """Check if the element is Lagrange degree 1.""" + return False + def _ufl_hash_data_(self): return repr(self) @@ -109,7 +118,7 @@ def cell(self): def is_cellwise_constant(self, component=None): """Return whether the basis functions of this element is spatially constant over each cell.""" - return self.family() == "Real" or self.degree() == 0 + return self._is_globally_constant() or self.degree() == 0 def value_shape(self): "Return the shape of the value space on the global domain." diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index 482dfc73d..b67fc9f48 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -91,6 +91,9 @@ def __init__(self, *elements, **kwargs): def __repr__(self): return "MixedElement(" + ", ".join(repr(e) for e in self._sub_elements) + ")" + def _is_linear(self): + return all(i._is_linear() for i in self._sub_elements) + def reconstruct_from_elements(self, *elements): "Reconstruct a mixed element from new subelements." if all(a == b for (a, b) in zip(elements, self._sub_elements)): diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index 90640e994..82cd581f4 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -48,6 +48,9 @@ def is_cellwise_constant(self): """ return self._element.is_cellwise_constant() + def _is_linear(self): + return self._element._is_linear() + def sub_element(self): "Return the element which is restricted." return self._element diff --git a/ufl/operators.py b/ufl/operators.py index c387b5ec3..3295ca077 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -36,6 +36,7 @@ from ufl.geometry import SpatialCoordinate, FacetNormal from ufl.checks import is_cellwise_constant from ufl.domain import extract_domains +from ufl import sobolevspace # --- Basic operators --- @@ -691,7 +692,7 @@ def bessel_K(nu, f): def exterior_derivative(f): """UFL operator: Take the exterior derivative of *f*. - The exterior derivative uses the element family to + The exterior derivative uses the element Sobolev space to determine whether ``id``, ``grad``, ``curl`` or ``div`` should be used. Note that this uses the ``grad`` and ``div`` operators, @@ -720,26 +721,18 @@ def exterior_derivative(f): except Exception: raise ValueError(f"Unable to determine element from {f}") - # Extract the family and the geometric dimension - family = element.family() gdim = element.cell().geometric_dimension() + space = element.sobolev_space() - # L^2 elements: - if "Disc" in family: + if space == sobolevspace.L2: return f - - # H^1 elements: - if "Lagrange" in family: + elif space == sobolevspace.H1: if gdim == 1: return grad(f)[0] # Special-case 1D vectors as scalars return grad(f) - - # H(curl) elements: - if "curl" in family: + elif space == sobolevspace.HCurl: return curl(f) - - # H(div) elements: - if "Brezzi" in family or "Raviart" in family: + elif space == sobolevspace.HDiv: return div(f) - - raise ValueError(f"Unable to determine exterior_derivative. Family is '{family}'") + else: + raise ValueError(f"Unable to determine exterior_derivative for element '{element!r}'") From 64920dfb3ffa8ca922ba3bf002595bade3b67b32 Mon Sep 17 00:00:00 2001 From: Colin J Cotter Date: Thu, 1 Jun 2023 17:10:24 +0100 Subject: [PATCH 026/136] symbol for integration on 0D mesh (#156) --- ufl/formatting/ufl2unicode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 82d90be41..49cbf1d4d 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -225,6 +225,7 @@ def measure_font(dx): 3: UC.integral_triple, 2: UC.integral_double, 1: UC.integral, + 0: UC.integral } integral_type_to_codim = { From cc6c5a87e1fed1ea2357e0e84a2f46a4e72d684d Mon Sep 17 00:00:00 2001 From: albert-oliver Date: Tue, 6 Jun 2023 10:21:59 +0100 Subject: [PATCH 027/136] Update UFL repository references (#165) In the documentation, there are two links that pointed to the Bitbucket repository. Update them to the new GitHub repository. --- doc/sphinx/source/installation.rst | 4 ++-- ufl/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/sphinx/source/installation.rst b/doc/sphinx/source/installation.rst index bef267c12..72da83577 100644 --- a/doc/sphinx/source/installation.rst +++ b/doc/sphinx/source/installation.rst @@ -30,8 +30,8 @@ Installation instructions ========================= To install UFL, download the source code from the -`UFL Bitbucket repository -`__, +`UFL GitHub repository +`__, and run the following command: .. code-block:: console diff --git a/ufl/__init__.py b/ufl/__init__.py index 8b356688a..4298ab73f 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -38,7 +38,7 @@ The development version can be found in the repository at - https://www.bitbucket.org/fenics-project/ufl + https://github.com/FEniCS/ufl A very brief overview of the language contents follows: From 4b54aeda4bd3a7ca75b60ee3d74964a39f2a20cf Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 27 Jun 2023 10:58:46 +0200 Subject: [PATCH 028/136] Refactor cells (#168) * refactor cell to be an abstract base class * quadrilateral has simplex facets * rename classes (for Firedrake compatibility) * renamings I missed * ffc branch * Add reconstruct to base class * more renaming * _s * remove todo * typing_extensions * requirement for Python < 3.10? * 3.11 not 3.10 (!) * docstring fixes, remove volume * compute once, not every time * = * do it properly * reorder * prevent recursion (!) * make sub_entity_types a tuple * use tuples, simplify * use weakref --- .github/workflows/fenicsx-tests.yml | 4 +- setup.cfg | 1 + ufl/cell.py | 593 +++++++++++++++++----------- ufl/core/ufl_type.py | 35 +- 4 files changed, 404 insertions(+), 229 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index f9c7d811f..bf5c987f6 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -41,7 +41,7 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx - ref: main + ref: mscroggs/ufl-cell - name: Install FFCx run: | cd ffcx @@ -78,7 +78,7 @@ jobs: - name: Install Basix and FFCx run: | python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + python3 -m pip install git+https://github.com/FEniCS/ffcx.git@mscroggs/ufl-cell - name: Clone DOLFINx uses: actions/checkout@v3 diff --git a/setup.cfg b/setup.cfg index 256c55d64..1a0e9d878 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,7 @@ setup_requires = wheel install_requires = numpy + typing_extensions; python_version < "3.11" [options.extras_require] docs = sphinx; sphinx_rtd_theme diff --git a/ufl/cell.py b/ufl/cell.py index 4fd0cb8e7..d9f3cf801 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -1,4 +1,4 @@ -"Types for representing a cell." +"""Types for representing a cell.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -6,293 +6,438 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from __future__ import annotations import functools import numbers +import typing +import weakref -import ufl.cell -from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.core.ufl_type import UFLObject +from abc import abstractmethod + +try: + from typing import Self +except ImportError: + # This alternative is needed pre Python 3.11 + # Delete this after 04 Oct 2026 (Python 3.10 end of life) + from typing_extensions import Self -# Export list for ufl.classes __all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"] -# --- The most abstract cell class, base class for other cell types +class AbstractCell(UFLObject): + """A base class for all cells.""" + @abstractmethod + def topological_dimension(self) -> int: + """Return the dimension of the topology of this cell.""" + + @abstractmethod + def geometric_dimension(self) -> int: + """Return the dimension of the geometry of this cell.""" + + @abstractmethod + def is_simplex(self) -> bool: + """Return True if this is a simplex cell.""" + + @abstractmethod + def has_simplex_facets(self) -> bool: + """Return True if all the facets of this cell are simplex cells.""" + + @abstractmethod + def _lt(self, other: Self) -> bool: + """Define an arbitrarily chosen but fixed sort order for all instances of this type with the same dimensions.""" + + @abstractmethod + def num_sub_entities(self, dim: int) -> int: + """Get the number of sub-entities of the given dimension.""" + + @abstractmethod + def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the sub-entities of the given dimension.""" + + @abstractmethod + def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the unique sub-entity types of the given dimension.""" + + @abstractmethod + def cellname(self) -> str: + """Return the cellname of the cell.""" + + @abstractmethod + def reconstruct(self, **kwargs: typing.Any) -> Cell: + """Reconstruct this cell, overwriting properties by those in kwargs.""" + + def __lt__(self, other: AbstractCell) -> bool: + """Define an arbitrarily chosen but fixed sort order for all cells.""" + if type(self) == type(other): + s = (self.geometric_dimension(), self.topological_dimension()) + o = (other.geometric_dimension(), other.topological_dimension()) + if s != o: + return s < o + return self._lt(other) + else: + if type(self).__name__ == type(other).__name__: + raise ValueError("Cannot order cell types with the same name") + return type(self).__name__ < type(other).__name__ -class AbstractCell(object): - """Representation of an abstract finite element cell with only the - dimensions known. + def num_vertices(self) -> int: + """Get the number of vertices.""" + return self.num_sub_entities(0) - """ - __slots__ = ("_topological_dimension", - "_geometric_dimension") + def num_edges(self) -> int: + """Get the number of edges.""" + return self.num_sub_entities(1) - def __init__(self, topological_dimension, geometric_dimension): - # Validate dimensions - if not isinstance(geometric_dimension, numbers.Integral): - raise ValueError("Expecting integer geometric_dimension.") - if not isinstance(topological_dimension, numbers.Integral): - raise ValueError("Expecting integer topological_dimension.") - if topological_dimension > geometric_dimension: - raise ValueError("Topological dimension cannot be larger than geometric dimension.") + def num_faces(self) -> int: + """Get the number of faces.""" + return self.num_sub_entities(2) - # Store validated dimensions - self._topological_dimension = topological_dimension - self._geometric_dimension = geometric_dimension - - def topological_dimension(self): - "Return the dimension of the topology of this cell." - return self._topological_dimension - - def geometric_dimension(self): - "Return the dimension of the space this cell is embedded in." - return self._geometric_dimension - - def is_simplex(self): - "Return True if this is a simplex cell." - raise NotImplementedError("Implement this to allow important checks and optimizations.") - - def has_simplex_facets(self): - "Return True if all the facets of this cell are simplex cells." - raise NotImplementedError("Implement this to allow important checks and optimizations.") - - def __lt__(self, other): - "Define an arbitrarily chosen but fixed sort order for all cells." - if not isinstance(other, AbstractCell): - return NotImplemented - # Sort by gdim first, tdim next, then whatever's left - # depending on the subclass - s = (self.geometric_dimension(), self.topological_dimension()) - o = (other.geometric_dimension(), other.topological_dimension()) - if s != o: - return s < o - return self._ufl_hash_data_() < other._ufl_hash_data_() + def num_facets(self) -> int: + """Get the number of facets. + Facets are entities of dimension tdim-1. + """ + tdim = self.topological_dimension() + return self.num_sub_entities(tdim - 1) -# --- Basic topological properties of known basic cells + def num_ridges(self) -> int: + """Get the number of ridges. -# Mapping from cell name to number of cell entities of each -# topological dimension -num_cell_entities = {"vertex": (1,), - "interval": (2, 1), - "triangle": (3, 3, 1), - "quadrilateral": (4, 4, 1), - "tetrahedron": (4, 6, 4, 1), - "prism": (6, 9, 5, 1), - "pyramid": (5, 8, 5, 1), - "hexahedron": (8, 12, 6, 1)} + Ridges are entities of dimension tdim-2. + """ + tdim = self.topological_dimension() + return self.num_sub_entities(tdim - 2) -# Mapping from cell name to topological dimension -cellname2dim = dict((k, len(v) - 1) for k, v in num_cell_entities.items()) + def num_peaks(self) -> int: + """Get the number of peaks. + Peaks are entities of dimension tdim-3. + """ + tdim = self.topological_dimension() + return self.num_sub_entities(tdim - 3) -# --- Basic cell representation classes + def vertices(self) -> typing.Tuple[AbstractCell, ...]: + """Get the vertices.""" + return self.sub_entities(0) -@attach_operators_from_hash_data -class Cell(AbstractCell): - "Representation of a named finite element cell with known structure." - __slots__ = ("_cellname",) + def edges(self) -> typing.Tuple[AbstractCell, ...]: + """Get the edges.""" + return self.sub_entities(1) - def __init__(self, cellname, geometric_dimension=None): - "Initialize basic cell description." + def faces(self) -> typing.Tuple[AbstractCell, ...]: + """Get the faces.""" + return self.sub_entities(2) - self._cellname = cellname + def facets(self) -> typing.Tuple[AbstractCell, ...]: + """Get the facets. - # The topological dimension is defined by the cell type, so - # the cellname must be among the known ones, so we can find - # the known dimension, unless we have a product cell, in which - # the given dimension is used - topological_dimension = len(num_cell_entities[cellname]) - 1 + Facets are entities of dimension tdim-1. + """ + tdim = self.topological_dimension() + return self.sub_entities(tdim - 1) - # The geometric dimension defaults to equal the topological - # dimension unless overridden for embedded cells - if geometric_dimension is None: - geometric_dimension = topological_dimension + def ridges(self) -> typing.Tuple[AbstractCell, ...]: + """Get the ridges. - # Initialize and validate dimensions - AbstractCell.__init__(self, topological_dimension, geometric_dimension) + Ridges are entities of dimension tdim-2. + """ + tdim = self.topological_dimension() + return self.sub_entities(tdim - 2) - # --- Overrides of AbstractCell methods --- + def peaks(self) -> typing.Tuple[AbstractCell, ...]: + """Get the peaks. - def reconstruct(self, geometric_dimension=None): - if geometric_dimension is None: - geometric_dimension = self._geometric_dimension - return Cell(self._cellname, geometric_dimension=geometric_dimension) + Peaks are entities of dimension tdim-3. + """ + tdim = self.topological_dimension() + return self.sub_entities(tdim - 3) - def is_simplex(self): - " Return True if this is a simplex cell." - return self.num_vertices() == self.topological_dimension() + 1 + def vertex_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique vertices types.""" + return self.sub_entity_types(0) - def has_simplex_facets(self): - "Return True if all the facets of this cell are simplex cells." - return self.is_simplex() or self.cellname() == "quadrilateral" + def edge_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique edge types.""" + return self.sub_entity_types(1) - # --- Specific cell properties --- + def face_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique face types.""" + return self.sub_entity_types(2) - def cellname(self): - "Return the cellname of the cell." - return self._cellname + def facet_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique facet types. - def num_vertices(self): - "The number of cell vertices." - return num_cell_entities[self.cellname()][0] + Facets are entities of dimension tdim-1. + """ + tdim = self.topological_dimension() + return self.sub_entity_types(tdim - 1) - def num_edges(self): - "The number of cell edges." - return num_cell_entities[self.cellname()][1] + def ridge_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique ridge types. - def num_facets(self): - "The number of cell facets." + Ridges are entities of dimension tdim-2. + """ tdim = self.topological_dimension() - return num_cell_entities[self.cellname()][tdim - 1] - - # --- Facet properties --- - - def facet_types(self): - "A tuple of ufl.Cell representing the facets of self." - # TODO Move outside method? - facet_type_names = {"interval": ("vertex",), - "triangle": ("interval",), - "quadrilateral": ("interval",), - "tetrahedron": ("triangle",), - "hexahedron": ("quadrilateral",), - "prism": ("triangle", "quadrilateral")} - return tuple(ufl.Cell(facet_name, self.geometric_dimension()) - for facet_name in facet_type_names[self.cellname()]) - - # --- Special functions for proper object behaviour --- - - def __str__(self): - gdim = self.geometric_dimension() - tdim = self.topological_dimension() - s = self.cellname() - if gdim > tdim: - s += "%dD" % gdim - return s + return self.sub_entity_types(tdim - 2) - def __repr__(self): - # For standard cells, return name of builtin cell object if - # possible. This reduces the size of the repr strings for - # domains, elements, etc. as well - gdim = self.geometric_dimension() + def peak_types(self) -> typing.Tuple[AbstractCell, ...]: + """Get the unique peak types. + + Peaks are entities of dimension tdim-3. + """ tdim = self.topological_dimension() - name = self.cellname() - if gdim == tdim and name in cellname2dim: - r = name - else: - r = "Cell(%s, %s)" % (repr(name), repr(gdim)) - return r + return self.sub_entity_types(tdim - 3) - def _ufl_hash_data_(self): - return (self._geometric_dimension, self._topological_dimension, - self._cellname) +_sub_entity_celltypes = { + "vertex": [("vertex", )], + "interval": [tuple("vertex" for i in range(2)), ("interval", )], + "triangle": [tuple("vertex" for i in range(3)), tuple("interval" for i in range(3)), ("triangle", )], + "quadrilateral": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(4)), ("quadrilateral", )], + "tetrahedron": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(4)), + tuple("triangle" for i in range(4)), ("tetrahedron", )], + "hexahedron": [tuple("vertex" for i in range(8)), tuple("interval" for i in range(12)), + tuple("quadrilateral" for i in range(6)), ("hexahedron", )], + "prism": [tuple("vertex" for i in range(6)), tuple("interval" for i in range(9)), + ("triangle", "quadrilateral", "quadrilateral", "quadrilateral", "triangle"), ("prism", )], + "pyramid": [tuple("vertex" for i in range(5)), tuple("interval" for i in range(8)), + ("quadrilateral", "triangle", "triangle", "triangle", "triangle"), ("pyramid", )], +} -@attach_operators_from_hash_data -class TensorProductCell(AbstractCell): - __slots__ = ("_cells",) - def __init__(self, *cells, **kwargs): - keywords = list(kwargs.keys()) - if keywords and keywords != ["geometric_dimension"]: - raise ValueError( - "TensorProductCell got an unexpected keyword argument '%s'" % - keywords[0]) +class Cell(AbstractCell): + """Representation of a named finite element cell with known structure.""" + __slots__ = ("_cellname", "_tdim", "_gdim", "_num_cell_entities", "_sub_entity_types", + "_sub_entities", "_sub_entity_types") - self._cells = tuple(as_cell(cell) for cell in cells) + def __init__(self, cellname: str, geometric_dimension: typing.Optional[int] = None): + if cellname not in _sub_entity_celltypes: + raise ValueError(f"Unsupported cell type: {cellname}") + + self._sub_entity_celltypes = _sub_entity_celltypes[cellname] + + self._cellname = cellname + self._tdim = len(self._sub_entity_celltypes) - 1 + self._gdim = self._tdim if geometric_dimension is None else geometric_dimension + + self._num_cell_entities = [len(i) for i in self._sub_entity_celltypes] + self._sub_entities = [tuple(Cell(t, self._gdim) for t in se_types) for se_types in self._sub_entity_celltypes[:-1]] + self._sub_entity_types = [tuple(Cell(t, self._gdim) for t in set(se_types)) for se_types in self._sub_entity_celltypes[:-1]] + self._sub_entities.append((weakref.proxy(self), )) + self._sub_entity_types.append((weakref.proxy(self), )) + + if not isinstance(self._gdim, numbers.Integral): + raise ValueError("Expecting integer geometric_dimension.") + if not isinstance(self._tdim, numbers.Integral): + raise ValueError("Expecting integer topological_dimension.") + if self._tdim > self._gdim: + raise ValueError("Topological dimension cannot be larger than geometric dimension.") + + def topological_dimension(self) -> int: + """Return the dimension of the topology of this cell.""" + return self._tdim + + def geometric_dimension(self) -> int: + """Return the dimension of the geometry of this cell.""" + return self._gdim + + def is_simplex(self) -> bool: + """Return True if this is a simplex cell.""" + return self._cellname in ["vertex", "interval", "triangle", "tetrahedron"] + + def has_simplex_facets(self) -> bool: + """Return True if all the facets of this cell are simplex cells.""" + return self._cellname in ["interval", "triangle", "quadrilateral", "tetrahedron"] + + def num_sub_entities(self, dim: int) -> int: + """Get the number of sub-entities of the given dimension.""" + try: + return self._num_cell_entities[dim] + except IndexError: + return 0 + + def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the sub-entities of the given dimension.""" + try: + return self._sub_entities[dim] + except IndexError: + return () + + def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the unique sub-entity types of the given dimension.""" + try: + return self._sub_entity_types[dim] + except IndexError: + return () + + def _lt(self, other: Self) -> bool: + return self._cellname < other._cellname + + def cellname(self) -> str: + """Return the cellname of the cell.""" + return self._cellname - tdim = sum([cell.topological_dimension() for cell in self._cells]) - if kwargs: - gdim = kwargs["geometric_dimension"] + def __str__(self) -> str: + if self._gdim == self._tdim: + return self._cellname else: - gdim = sum([cell.geometric_dimension() for cell in self._cells]) + return f"{self._cellname}{self._gdim}D" - AbstractCell.__init__(self, tdim, gdim) + def __repr__(self) -> str: + if self._gdim == self._tdim: + return self._cellname + else: + return f"Cell({self._cellname}, {self._gdim})" - def cellname(self): - "Return the cellname of the cell." - return " * ".join([cell._cellname for cell in self._cells]) + def _ufl_hash_data_(self) -> typing.Hashable: + return (self._cellname, self._gdim) - def reconstruct(self, geometric_dimension=None): - if geometric_dimension is None: - geometric_dimension = self._geometric_dimension - return TensorProductCell(*(self._cells), geometric_dimension=geometric_dimension) + def reconstruct(self, **kwargs: typing.Any) -> Cell: + """Reconstruct this cell, overwriting properties by those in kwargs.""" + gdim = self._gdim + for key, value in kwargs.items(): + if key == "geometric_dimension": + gdim = value + else: + raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") + return Cell(self._cellname, geometric_dimension=gdim) - def is_simplex(self): - "Return True if this is a simplex cell." - if len(self._cells) == 1: - return self._cells[0].is_simplex() - return False - def has_simplex_facets(self): - "Return True if all the facets of this cell are simplex cells." - if len(self._cells) == 1: - return self._cells[0].has_simplex_facets() - return False +class TensorProductCell(AbstractCell): + __slots__ = ("_cells", "_tdim", "_gdim") - def num_vertices(self): - "The number of cell vertices." - return functools.reduce(lambda x, y: x * y, [c.num_vertices() for c in self._cells]) + def __init__(self, *cells: Cell, geometric_dimension: typing.Optional[int] = None): + self._cells = tuple(as_cell(cell) for cell in cells) - def num_edges(self): - "The number of cell edges." - raise ValueError("Not defined for TensorProductCell.") + self._tdim = sum([cell.topological_dimension() for cell in self._cells]) + self._gdim = self._tdim if geometric_dimension is None else geometric_dimension - def num_facets(self): - "The number of cell facets." - return sum(c.num_facets() for c in self._cells if c.topological_dimension() > 0) + if not isinstance(self._gdim, numbers.Integral): + raise ValueError("Expecting integer geometric_dimension.") + if not isinstance(self._tdim, numbers.Integral): + raise ValueError("Expecting integer topological_dimension.") + if self._tdim > self._gdim: + raise ValueError("Topological dimension cannot be larger than geometric dimension.") - def sub_cells(self): - "Return list of cell factors." + def sub_cells(self) -> typing.List[AbstractCell]: + """Return list of cell factors.""" return self._cells - def __str__(self): - gdim = self.geometric_dimension() - tdim = self.topological_dimension() - reprs = ", ".join(repr(c) for c in self._cells) - if gdim == tdim: - gdimstr = "" - else: - gdimstr = ", geometric_dimension=%d" % gdim - r = "TensorProductCell(%s%s)" % (reprs, gdimstr) - return r + def topological_dimension(self) -> int: + """Return the dimension of the topology of this cell.""" + return self._tdim - def __repr__(self): - return str(self) + def geometric_dimension(self) -> int: + """Return the dimension of the geometry of this cell.""" + return self._gdim - def _ufl_hash_data_(self): - return tuple(c._ufl_hash_data_() for c in self._cells) + (self._geometric_dimension,) + def is_simplex(self) -> bool: + """Return True if this is a simplex cell.""" + if len(self._cells) == 1: + return self._cells[0].is_simplex() + return False + def has_simplex_facets(self) -> bool: + """Return True if all the facets of this cell are simplex cells.""" + if len(self._cells) == 1: + return self._cells[0].has_simplex_facets() + if self._tdim == 1: + return True + return False -# --- Utility conversion functions + def num_sub_entities(self, dim: int) -> int: + """Get the number of sub-entities of the given dimension.""" + if dim < 0 or dim > self._tdim: + return 0 + if dim == 0: + return functools.reduce(lambda x, y: x * y, [c.num_vertices() for c in self._cells]) + if dim == self._tdim - 1: + # Note: This is not the number of facets that the cell has, but I'm leaving it here for now + # to not change past behaviour + return sum(c.num_facets() for c in self._cells if c.topological_dimension() > 0) + if dim == self._tdim: + return 1 + raise NotImplementedError(f"TensorProductCell.num_sub_entities({dim}) is not implemented.") + + def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the sub-entities of the given dimension.""" + if dim < 0 or dim > self._tdim: + return [] + if dim == 0: + return [Cell("vertex", self._gdim) for i in range(self.num_sub_entities(0))] + if dim == self._tdim: + return [self] + raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.") + + def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: + """Get the unique sub-entity types of the given dimension.""" + if dim < 0 or dim > self._tdim: + return [] + if dim == 0: + return [Cell("vertex", self._gdim)] + if dim == self._tdim: + return [self] + raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.") + + def _lt(self, other: Self) -> bool: + return self._ufl_hash_data_() < other._ufl_hash_data_() -# Mapping from topological dimension to reference cell name for -# simplices -_simplex_dim2cellname = {0: "vertex", - 1: "interval", - 2: "triangle", - 3: "tetrahedron"} + def cellname(self) -> str: + """Return the cellname of the cell.""" + return " * ".join([cell.cellname() for cell in self._cells]) -# Mapping from topological dimension to reference cell name for -# hypercubes -_hypercube_dim2cellname = {0: "vertex", - 1: "interval", - 2: "quadrilateral", - 3: "hexahedron"} + def __str__(self) -> str: + s = "TensorProductCell(" + s += ", ".join(f"{c!r}" for c in self._cells) + if self._tdim != self._gdim: + s += f", geometric_dimension={self._gdim}" + s += ")" + return s + def __repr__(self) -> str: + return str(self) -def simplex(topological_dimension, geometric_dimension=None): - "Return a simplex cell of given dimension." - return Cell(_simplex_dim2cellname[topological_dimension], - geometric_dimension) + def _ufl_hash_data_(self) -> typing.Hashable: + return tuple(c._ufl_hash_data_() for c in self._cells) + (self._gdim,) + + def reconstruct(self, **kwargs: typing.Any) -> Cell: + """Reconstruct this cell, overwriting properties by those in kwargs.""" + gdim = self._gdim + for key, value in kwargs.items(): + if key == "geometric_dimension": + gdim = value + else: + raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") + return TensorProductCell(self._cellname, geometric_dimension=gdim) + + +def simplex(topological_dimension: int, geometric_dimension: typing.Optional[int] = None): + """Return a simplex cell of the given dimension.""" + if topological_dimension == 0: + return Cell("vertex", geometric_dimension) + if topological_dimension == 1: + return Cell("interval", geometric_dimension) + if topological_dimension == 2: + return Cell("triangle", geometric_dimension) + if topological_dimension == 3: + return Cell("tetrahedron", geometric_dimension) + raise ValueError(f"Unsupported topological dimension for simplex: {topological_dimension}") def hypercube(topological_dimension, geometric_dimension=None): - "Return a hypercube cell of given dimension." - return Cell(_hypercube_dim2cellname[topological_dimension], - geometric_dimension) - - -def as_cell(cell): + """Return a hypercube cell of the given dimension.""" + if topological_dimension == 0: + return Cell("vertex", geometric_dimension) + if topological_dimension == 1: + return Cell("interval", geometric_dimension) + if topological_dimension == 2: + return Cell("quadrilateral", geometric_dimension) + if topological_dimension == 3: + return Cell("hexahedron", geometric_dimension) + raise ValueError(f"Unsupported topological dimension for hypercube: {topological_dimension}") + + +def as_cell(cell: typing.Union[AbstractCell, str, typing.Tuple[AbstractCell, ...]]) -> AbstractCell: """Convert any valid object to a Cell or return cell if it is already a Cell. Allows an already valid cell, a known cellname string, or a tuple of cells for a product cell. diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index e6435684c..df6c991e5 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -7,15 +7,43 @@ # SPDX-License-Identifier: LGPL-3.0-or-later # # Modified by Massimiliano Leoni, 2016 +# Modified by Matthew Scroggs, 2023 + +from __future__ import annotations +import typing +import warnings from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore +from abc import ABC, abstractmethod # Avoid circular import import ufl.core as core -# Make UFL type coercion available under the as_ufl name -# as_ufl = Expr._ufl_coerce_ +class UFLObject(ABC): + """A UFL Object.""" + + @abstractmethod + def _ufl_hash_data_(self) -> typing.Hashable: + """Return hashable data that uniquely defines this object.""" + + @abstractmethod + def __str__(self) -> str: + """Return a human-readable string representation of the object.""" + + @abstractmethod + def __repr__(self) -> str: + """Return a string representation of the object.""" + + def __hash__(self) -> int: + return hash(self._ufl_hash_data_()) + + def __eq__(self, other): + return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() + + def __ne__(self, other): + return not self.__eq__(other) + def attach_operators_from_hash_data(cls): """Class decorator to attach ``__hash__``, ``__eq__`` and ``__ne__`` implementations. @@ -23,6 +51,7 @@ def attach_operators_from_hash_data(cls): These are implemented in terms of a ``._ufl_hash_data()`` method on the class, which should return a tuple or hashable and comparable data. """ + warnings.warn("attach_operators_from_hash_data deprecated, please use UFLObject instead.", DeprecationWarning) assert hasattr(cls, "_ufl_hash_data_") def __hash__(self): @@ -37,7 +66,7 @@ def __eq__(self, other): def __ne__(self, other): "__ne__ implementation attached in attach_operators_from_hash_data" - return type(self) != type(other) or self._ufl_hash_data_() != other._ufl_hash_data_() + return not self.__eq__(other) cls.__ne__ = __ne__ return cls From 05adfeebb8495ab1b6d40ecdc093d78a7c876774 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 27 Jun 2023 11:20:42 +0200 Subject: [PATCH 029/136] ffc main (#172) --- .github/workflows/fenicsx-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index bf5c987f6..30d8094c2 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -41,7 +41,6 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx - ref: mscroggs/ufl-cell - name: Install FFCx run: | cd ffcx @@ -78,7 +77,7 @@ jobs: - name: Install Basix and FFCx run: | python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git@mscroggs/ufl-cell + python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx uses: actions/checkout@v3 From 79ef1d4ac04388e629fbcc3c5b8935d5cbd6ceff Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 27 Jun 2023 12:29:23 +0200 Subject: [PATCH 030/136] Fix typo in definition of tetrahedron (#173) * fix cell typo * Add test --- test/test_cell.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ ufl/cell.py | 8 ++++++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 test/test_cell.py diff --git a/test/test_cell.py b/test/test_cell.py new file mode 100644 index 000000000..0be9b7a7b --- /dev/null +++ b/test/test_cell.py @@ -0,0 +1,61 @@ +import ufl +import pytest + + +def test_interval(): + cell = ufl.interval + assert cell.num_vertices() == 2 + assert cell.num_edges() == 1 + assert cell.num_faces() == 0 + + +def test_triangle(): + cell = ufl.triangle + assert cell.num_vertices() == 3 + assert cell.num_edges() == 3 + assert cell.num_faces() == 1 + + +def test_quadrilateral(): + cell = ufl.quadrilateral + assert cell.num_vertices() == 4 + assert cell.num_edges() == 4 + assert cell.num_faces() == 1 + + +def test_tetrahedron(): + cell = ufl.tetrahedron + assert cell.num_vertices() == 4 + assert cell.num_edges() == 6 + assert cell.num_faces() == 4 + + +def test_hexahedron(): + cell = ufl.hexahedron + assert cell.num_vertices() == 8 + assert cell.num_edges() == 12 + assert cell.num_faces() == 6 + + + +@pytest.mark.parametrize("cell", [ufl.interval]) +def test_cells_1d(cell): + assert cell.num_facets() == cell.num_vertices() + assert cell.num_ridges() == 0 + assert cell.num_peaks() == 0 + + +@pytest.mark.parametrize("cell", [ufl.triangle, ufl.quadrilateral]) +def test_cells_2d(cell): + assert cell.num_facets() == cell.num_edges() + assert cell.num_ridges() == cell.num_vertices() + assert cell.num_peaks() == 0 + + +@pytest.mark.parametrize("cell", [ufl.tetrahedron, ufl.hexahedron, ufl.prism, ufl.pyramid]) +def test_cells_2d(cell): + assert cell.num_facets() == cell.num_faces() + assert cell.num_ridges() == cell.num_edges() + assert cell.num_peaks() == cell.num_vertices() + + diff --git a/ufl/cell.py b/ufl/cell.py index d9f3cf801..7982490f9 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -194,7 +194,7 @@ def peak_types(self) -> typing.Tuple[AbstractCell, ...]: "interval": [tuple("vertex" for i in range(2)), ("interval", )], "triangle": [tuple("vertex" for i in range(3)), tuple("interval" for i in range(3)), ("triangle", )], "quadrilateral": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(4)), ("quadrilateral", )], - "tetrahedron": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(4)), + "tetrahedron": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(6)), tuple("triangle" for i in range(4)), ("tetrahedron", )], "hexahedron": [tuple("vertex" for i in range(8)), tuple("interval" for i in range(12)), tuple("quadrilateral" for i in range(6)), ("hexahedron", )], @@ -251,6 +251,8 @@ def has_simplex_facets(self) -> bool: def num_sub_entities(self, dim: int) -> int: """Get the number of sub-entities of the given dimension.""" + if dim < 0: + return 0 try: return self._num_cell_entities[dim] except IndexError: @@ -258,6 +260,8 @@ def num_sub_entities(self, dim: int) -> int: def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: """Get the sub-entities of the given dimension.""" + if dim < 0: + return () try: return self._sub_entities[dim] except IndexError: @@ -265,6 +269,8 @@ def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: """Get the unique sub-entity types of the given dimension.""" + if dim < 0: + return () try: return self._sub_entity_types[dim] except IndexError: From 7b24065a5f39ab4296c4ab8246f2b493d7bcd803 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Thu, 29 Jun 2023 12:31:50 +0100 Subject: [PATCH 031/136] Remove type_extensions. (#175) It introduced an extra dependency but used in very few places. In the mid-term, we can require python 3.11 rather than require type_extensions. --- setup.cfg | 1 - ufl/cell.py | 12 +++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/setup.cfg b/setup.cfg index 1a0e9d878..256c55d64 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,7 +43,6 @@ setup_requires = wheel install_requires = numpy - typing_extensions; python_version < "3.11" [options.extras_require] docs = sphinx; sphinx_rtd_theme diff --git a/ufl/cell.py b/ufl/cell.py index 7982490f9..61c8e69e8 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -15,12 +15,6 @@ from ufl.core.ufl_type import UFLObject from abc import abstractmethod -try: - from typing import Self -except ImportError: - # This alternative is needed pre Python 3.11 - # Delete this after 04 Oct 2026 (Python 3.10 end of life) - from typing_extensions import Self __all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"] @@ -44,7 +38,7 @@ def has_simplex_facets(self) -> bool: """Return True if all the facets of this cell are simplex cells.""" @abstractmethod - def _lt(self, other: Self) -> bool: + def _lt(self, other) -> bool: """Define an arbitrarily chosen but fixed sort order for all instances of this type with the same dimensions.""" @abstractmethod @@ -276,7 +270,7 @@ def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: except IndexError: return () - def _lt(self, other: Self) -> bool: + def _lt(self, other) -> bool: return self._cellname < other._cellname def cellname(self) -> str: @@ -385,7 +379,7 @@ def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: return [self] raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.") - def _lt(self, other: Self) -> bool: + def _lt(self, other) -> bool: return self._ufl_hash_data_() < other._ufl_hash_data_() def cellname(self) -> str: From ebfb2df5690d795838f955639232dee8d532c476 Mon Sep 17 00:00:00 2001 From: Connor Pierce Date: Wed, 12 Jul 2023 03:44:13 -0500 Subject: [PATCH 032/136] Make `FiniteElementBase` subscriptable but not iterable (#180) * Raise KeyError in finite element restrictions * Finite element is not iterable --- ufl/finiteelement/finiteelementbase.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index 8f1b71b51..9e6aa58cf 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -220,4 +220,8 @@ def __getitem__(self, index): if index in ("facet", "interior"): from ufl.finiteelement import RestrictedElement return RestrictedElement(self, index) - return NotImplemented + else: + raise KeyError(f"Invalid index for restriction: {repr(index)}") + + def __iter__(self): + raise TypeError(f"'{type(self).__name__}' object is not iterable") From 0d03500feae995177c10f6a36a3da40c59e7b81d Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 13 Jul 2023 10:34:07 +0100 Subject: [PATCH 033/136] Delete unused code (#163) * remove util functions that are never used * delete more code * remove more code * flake * ,m * has_type is used by TSFC * Revert "remove more code" This reverts commit 8fd8479a7eb90650a66710adb09fa29671b1dfb0. * remove ufl2ufl * ffc branch * ffc main * Readd ufl2unicode * fix E129 * Use math.erf (has existed since Python 3.2, so exists on all supported versions) * fix E501 * fix W503, W504 * remove unnecssary excludes * fix E741 * remove ufl builtin * delete a lot more unused code * remove unnecessary globals * put has_type back * remove outdated comment * revert changes to compute_form_data * flake8 --- demo/FEEC.py | 3 +- demo/HyperElasticity.py | 6 +- demo/MixedElasticity.py | 8 +- demo/VectorLaplaceGradCurl.py | 3 +- setup.cfg | 11 +- ufl/__init__.py | 71 +++-- ufl/action.py | 19 +- ufl/adjoint.py | 7 +- ufl/algebra.py | 21 +- ufl/algorithms/__init__.py | 7 +- ufl/algorithms/analysis.py | 8 - ufl/algorithms/apply_algebra_lowering.py | 45 +--- ufl/algorithms/apply_derivatives.py | 24 +- ufl/algorithms/apply_function_pullbacks.py | 30 +-- ufl/algorithms/apply_restrictions.py | 9 +- ufl/algorithms/change_to_reference.py | 255 +----------------- ufl/algorithms/check_arities.py | 30 ++- ufl/algorithms/checks.py | 2 +- ufl/algorithms/compute_form_data.py | 6 +- ufl/algorithms/domain_analysis.py | 32 +-- ufl/algorithms/elementtransformations.py | 53 ---- ufl/algorithms/estimate_degrees.py | 6 +- ufl/algorithms/expand_indices.py | 12 +- ufl/algorithms/formdata.py | 27 -- ufl/algorithms/formfiles.py | 3 +- ufl/algorithms/formtransformations.py | 3 +- ufl/algorithms/transformer.py | 14 - ufl/argument.py | 13 +- ufl/cell.py | 5 +- ufl/checks.py | 3 +- ufl/coefficient.py | 12 +- ufl/compound_expressions.py | 120 +++++---- ufl/constant.py | 3 +- ufl/constantvalue.py | 13 +- ufl/core/multiindex.py | 9 - ufl/core/terminal.py | 6 - ufl/core/ufl_type.py | 8 +- ufl/differentiation.py | 2 - ufl/equation.py | 7 +- ufl/exprcontainers.py | 9 - ufl/exprequals.py | 101 +------- ufl/exproperators.py | 83 +----- ufl/finiteelement/elementlist.py | 18 +- ufl/finiteelement/finiteelement.py | 4 +- ufl/form.py | 39 +-- ufl/formatting/graph.py | 278 -------------------- ufl/formatting/printing.py | 120 --------- ufl/formatting/ufl2dot.py | 288 --------------------- ufl/formatting/ufl2unicode.py | 4 +- ufl/formoperators.py | 8 +- ufl/indexed.py | 35 +-- ufl/integral.py | 18 +- ufl/mathfunctions.py | 39 +-- ufl/matrix.py | 18 +- ufl/measure.py | 62 ++--- ufl/operators.py | 48 +--- ufl/permutation.py | 68 ----- ufl/precedence.py | 42 +-- ufl/protocols.py | 9 - ufl/restriction.py | 2 +- ufl/sobolevspace.py | 30 +-- ufl/sorting.py | 14 +- ufl/split_functions.py | 3 +- ufl/tensors.py | 25 -- ufl/utils/derivativetuples.py | 70 ----- ufl/utils/formatting.py | 87 +++++-- ufl/utils/sequences.py | 41 --- ufl/utils/sorting.py | 8 +- ufl/utils/ufltypedicts.py | 46 ---- ufl/variable.py | 3 +- 70 files changed, 393 insertions(+), 2143 deletions(-) delete mode 100644 ufl/algorithms/elementtransformations.py delete mode 100644 ufl/formatting/graph.py delete mode 100644 ufl/formatting/printing.py delete mode 100644 ufl/formatting/ufl2dot.py delete mode 100644 ufl/utils/derivativetuples.py delete mode 100644 ufl/utils/ufltypedicts.py diff --git a/demo/FEEC.py b/demo/FEEC.py index 1253cbd86..58801b7df 100644 --- a/demo/FEEC.py +++ b/demo/FEEC.py @@ -50,5 +50,4 @@ (sigma, u) = TrialFunctions(W) (tau, v) = TestFunctions(W) - a = (inner(sigma, tau) - inner(d(tau), u) + - inner(d(sigma), v) + inner(d(u), d(v))) * dx + a = (inner(sigma, tau) - inner(d(tau), u) + inner(d(sigma), v) + inner(d(u), d(v))) * dx diff --git a/demo/HyperElasticity.py b/demo/HyperElasticity.py index 81bd49a29..dba19f36d 100644 --- a/demo/HyperElasticity.py +++ b/demo/HyperElasticity.py @@ -47,8 +47,8 @@ c22 = Constant(cell) # Deformation gradient -I = Identity(d) -F = I + grad(u) +Id = Identity(d) +F = Id + grad(u) F = variable(F) Finv = inv(F) J = det(F) @@ -66,7 +66,7 @@ I3_C = J**2 # Green strain tensor -E = (C - I) / 2 +E = (C - Id) / 2 # Mapping of strain in fiber directions Ef = A * E * A.T diff --git a/demo/MixedElasticity.py b/demo/MixedElasticity.py index 742329683..0e63e6c3d 100644 --- a/demo/MixedElasticity.py +++ b/demo/MixedElasticity.py @@ -46,6 +46,8 @@ def skw(tau): (sigma, u, gamma) = TrialFunctions(W) (tau, v, eta) = TestFunctions(W) -a = (inner(sigma, tau) - tr(sigma) * tr(tau) + - dot(div(tau), u) - dot(div(sigma), v) + - inner(skw(tau), gamma) + inner(skw(sigma), eta)) * dx +a = ( + inner(sigma, tau) - tr(sigma) * tr(tau) + dot( + div(tau), u + ) - dot(div(sigma), v) + inner(skw(tau), gamma) + inner(skw(sigma), eta) +) * dx diff --git a/demo/VectorLaplaceGradCurl.py b/demo/VectorLaplaceGradCurl.py index fcc94eea4..012611299 100644 --- a/demo/VectorLaplaceGradCurl.py +++ b/demo/VectorLaplaceGradCurl.py @@ -27,8 +27,7 @@ def HodgeLaplaceGradCurl(element, felement): sigma, u = TrialFunctions(element) f = Coefficient(felement) - a = (inner(tau, sigma) - inner(grad(tau), u) + - inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx + a = (inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx L = inner(v, f) * dx return a, L diff --git a/setup.cfg b/setup.cfg index 256c55d64..4a58031e9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,15 +58,8 @@ ci = fenics-ufl[test] [flake8] -ignore = E501, W504, - # Line break before operator, no longer PEP8. - W503, - # Indentation, can trigger for valid code. - E129, - # ambiguous variable name - E741 -builtins = ufl -exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test +max-line-length = 120 +exclude = doc/sphinx/source/conf.py,test [pydocstyle] # Work on removing these ignores diff --git a/ufl/__init__.py b/ufl/__init__.py index 4298ab73f..7e7ca3cdb 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# flake8: noqa """The Unified Form Language is an embedded domain specific language for definition of variational forms intended for finite element discretization. More precisely, it defines a fixed interface for choosing @@ -11,19 +9,19 @@ * To import the language, type:: - from ufl import * + import ufl * To import the underlying classes an UFL expression tree is built from, type :: - from ufl.classes import * + import ufl.classes * Various algorithms for working with UFL expression trees can be accessed by :: - from ufl.algorithms import * + import ufl.algorithms Classes and algorithms are considered implementation details and should not be used in form definitions. @@ -219,7 +217,7 @@ - rhs, lhs - system - functional - - replace, replace_integral_domains + - replace - adjoint - action - energy_norm, @@ -246,7 +244,7 @@ __version__ = importlib.metadata.version("fenics-ufl") # README -# Imports here should be what the user sees when doing "from ufl import *", +# Imports here should be what the user sees # which means we should _not_ import f.ex. "Grad", but "grad". # This way we expose the language, the operation "grad", but less # of the implementation, the particular class "Grad". @@ -272,20 +270,19 @@ from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf # Finite elements classes -from ufl.finiteelement import FiniteElementBase, FiniteElement, \ - MixedElement, VectorElement, TensorElement, EnrichedElement, \ - NodalEnrichedElement, RestrictedElement, TensorProductElement, \ - HDivElement, HCurlElement, BrokenElement, WithMapping +from ufl.finiteelement import ( + FiniteElementBase, FiniteElement, MixedElement, VectorElement, TensorElement, EnrichedElement, + NodalEnrichedElement, RestrictedElement, TensorProductElement, HDivElement, HCurlElement, BrokenElement, + WithMapping) # Hook to extend predefined element families -from ufl.finiteelement.elementlist import register_element, show_elements # , ufl_elements +from ufl.finiteelement.elementlist import register_element, show_elements # Function spaces from ufl.functionspace import FunctionSpace, MixedFunctionSpace # Arguments -from ufl.argument import Argument, Coargument, TestFunction, TrialFunction, \ - Arguments, TestFunctions, TrialFunctions +from ufl.argument import Argument, Coargument, TestFunction, TrialFunction, Arguments, TestFunctions, TrialFunctions # Coefficients from ufl.coefficient import Coefficient, Cofunction, Coefficients @@ -311,43 +308,34 @@ # Special functions for expression base classes # (ensure this is imported, since it attaches operators to Expr) -import ufl.exproperators as __exproperators +import ufl.exproperators as __exproperators # noqa: F401 # Containers for expressions with value rank > 0 -from ufl.tensors import as_tensor, as_vector, as_matrix, relabel +from ufl.tensors import as_tensor, as_vector, as_matrix from ufl.tensors import unit_vector, unit_vectors, unit_matrix, unit_matrices # Operators -from ufl.operators import rank, shape, \ - conj, real, imag, \ - outer, inner, dot, cross, perp, \ - det, inv, cofac, \ - transpose, tr, diag, diag_vector, \ - dev, skew, sym, \ - sqrt, exp, ln, erf, \ - cos, sin, tan, \ - acos, asin, atan, atan_2, \ - cosh, sinh, tanh, \ - bessel_J, bessel_Y, bessel_I, bessel_K, \ - eq, ne, le, ge, lt, gt, And, Or, Not, \ - conditional, sign, max_value, min_value, \ - variable, diff, \ - Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, \ - jump, avg, cell_avg, facet_avg, \ - elem_mult, elem_div, elem_pow, elem_op +from ufl.operators import ( + rank, shape, conj, real, imag, outer, inner, dot, cross, perp, + det, inv, cofac, transpose, tr, diag, diag_vector, dev, skew, sym, + sqrt, exp, ln, erf, cos, sin, tan, acos, asin, atan, atan_2, cosh, sinh, tanh, + bessel_J, bessel_Y, bessel_I, bessel_K, eq, ne, le, ge, lt, gt, And, Or, Not, + conditional, sign, max_value, min_value, variable, diff, + Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, + jump, avg, cell_avg, facet_avg, elem_mult, elem_div, elem_pow, elem_op) # Measure classes from ufl.measure import Measure, register_integral_type, integral_types, custom_integral_types # Form class -from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm, replace_integral_domains +from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm # Integral classes from ufl.integral import Integral # Representations of transformed forms -from ufl.formoperators import replace, derivative, action, energy_norm, rhs, lhs,\ -system, functional, adjoint, sensitivity_rhs, extract_blocks #, dirichlet_functional +from ufl.formoperators import (replace, derivative, action, energy_norm, rhs, lhs, + system, functional, adjoint, sensitivity_rhs, extract_blocks) # Predefined convenience objects from ufl.objects import ( @@ -356,8 +344,7 @@ i, j, k, l, p, q, r, s, dx, ds, dS, dP, dc, dC, dO, dI, dX, - ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v -) + ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v) # Useful constants from math import e, pi @@ -380,7 +367,7 @@ 'BrokenElement', "WithMapping", 'register_element', 'show_elements', 'FunctionSpace', 'MixedFunctionSpace', - 'Argument','Coargument', 'TestFunction', 'TrialFunction', + 'Argument', 'Coargument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', 'Coefficient', 'Cofunction', 'Coefficients', 'Matrix', 'Adjoint', 'Action', @@ -388,7 +375,7 @@ 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', 'Index', 'indices', - 'as_tensor', 'as_vector', 'as_matrix', 'relabel', + 'as_tensor', 'as_vector', 'as_matrix', 'unit_vector', 'unit_vectors', 'unit_matrix', 'unit_matrices', 'rank', 'shape', 'conj', 'real', 'imag', 'outer', 'inner', 'dot', 'cross', 'perp', @@ -405,9 +392,9 @@ 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', 'jump', 'avg', 'cell_avg', 'facet_avg', 'elem_mult', 'elem_div', 'elem_pow', 'elem_op', - 'Form','FormSum', 'ZeroBaseForm', + 'Form', 'BaseForm', 'FormSum', 'ZeroBaseForm', 'Integral', 'Measure', 'register_integral_type', 'integral_types', 'custom_integral_types', - 'replace', 'replace_integral_domains', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'extract_blocks', + 'replace', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'extract_blocks', 'system', 'functional', 'adjoint', 'sensitivity_rhs', 'dx', 'ds', 'dS', 'dP', 'dc', 'dC', 'dO', 'dI', 'dX', diff --git a/ufl/action.py b/ufl/action.py index 7b32b7bf9..e7ec6ad5e 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -42,9 +42,6 @@ class Action(BaseForm): "_arguments", "_hash") - def __getnewargs__(self): - return (self._left, self._right) - def __new__(cls, *args, **kw): left, right = args @@ -107,10 +104,10 @@ def equals(self, other): return False if self is other: return True - return (self._left == other._left and self._right == other._right) + return self._left == other._left and self._right == other._right def __str__(self): - return "Action(%s, %s)" % (str(self._left), str(self._right)) + return f"Action({self._left}, {self._right})" def __repr__(self): return self._repr @@ -118,9 +115,7 @@ def __repr__(self): def __hash__(self): "Hash code for use in dicts " if self._hash is None: - self._hash = hash(("Action", - hash(self._right), - hash(self._left))) + self._hash = hash(("Action", hash(self._right), hash(self._left))) return self._hash @@ -133,14 +128,10 @@ def _check_function_spaces(left, right): right, *_ = right.ufl_operands if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)): - if (left.arguments()[-1].ufl_function_space().dual() - != right.arguments()[0].ufl_function_space()): - + if left.arguments()[-1].ufl_function_space().dual() != right.arguments()[0].ufl_function_space(): raise TypeError("Incompatible function spaces in Action") elif isinstance(right, (Coefficient, Cofunction, Argument)): - if (left.arguments()[-1].ufl_function_space() - != right.ufl_function_space()): - + if left.arguments()[-1].ufl_function_space() != right.ufl_function_space(): raise TypeError("Incompatible function spaces in Action") else: raise TypeError("Incompatible argument in Action: %s" % type(right)) diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 8129596de..29dce30e8 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -31,9 +31,6 @@ class Adjoint(BaseForm): "ufl_operands", "_hash") - def __getnewargs__(self): - return (self._form) - def __new__(cls, *args, **kw): form = args[0] # Check trivial case: This is not a ufl.Zero but a ZeroBaseForm! @@ -77,10 +74,10 @@ def equals(self, other): return False if self is other: return True - return (self._form == other._form) + return self._form == other._form def __str__(self): - return "Adjoint(%s)" % str(self._form) + return f"Adjoint({self._form})" def __repr__(self): return self._repr diff --git a/ufl/algebra.py b/ufl/algebra.py index 107333107..20624f5e7 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -87,26 +87,7 @@ def evaluate(self, x, mapping, component, index_values): index_values) for o in self.ufl_operands) def __str__(self): - ops = [parstr(o, self) for o in self.ufl_operands] - if False: - # Implementation with line splitting: - limit = 70 - delimop = " + \\\n + " - op = " + " - s = ops[0] - n = len(s) - for o in ops[1:]: - m = len(o) - if n + m > limit: - s += delimop - n = m - else: - s += op - n += m - s += o - return s - # Implementation with no line splitting: - return " + ".join(ops) + return " + ".join([parstr(o, self) for o in self.ufl_operands]) @ufl_type(num_ops=2, diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index f80bc0dbb..687e23db2 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -21,7 +21,6 @@ "estimate_total_polynomial_degree", "sort_elements", "compute_form_data", - "purge_list_tensors", "apply_transformer", "ReuseTransformer", "load_ufl_file", @@ -102,7 +101,7 @@ from ufl.algorithms.expand_compounds import expand_compounds # from ufl.algorithms.estimate_degrees import SumDegreeEstimator from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree -from ufl.algorithms.expand_indices import expand_indices, purge_list_tensors +from ufl.algorithms.expand_indices import expand_indices # Utilities for transforming complete Forms into other Forms from ufl.algorithms.formtransformations import compute_form_adjoint @@ -123,6 +122,4 @@ from ufl.algorithms.formfiles import load_ufl_file from ufl.algorithms.formfiles import load_forms -# Utilities for UFL object printing -# from ufl.formatting.printing import integral_info, form_info -from ufl.formatting.printing import tree_format +from ufl.utils.formatting import tree_format diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index ecbcaa8f1..6c2c2190d 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -43,14 +43,6 @@ def unique_tuple(objects): # --- Utilities to extract information from an expression --- -def __unused__extract_classes(a): - """Build a set of all unique Expr subclasses used in a. - The argument a can be a BaseForm, Integral or Expr.""" - return set(o._ufl_class_ - for e in iter_expressions(a) - for o in unique_pre_traversal(e)) - - def extract_type(a, ufl_types): """Build a set of all objects found in a whose class is in ufl_types. The argument a can be a BaseForm, Integral or Expr.""" diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index ee6cd1754..1aec94467 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -11,7 +11,7 @@ # Modified by Anders Logg, 2009-2010 from ufl.classes import Product, Grad, Conj -from ufl.core.multiindex import indices, Index, FixedIndex +from ufl.core.multiindex import indices, Index from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.compound_expressions import deviatoric_expr, determinant_expr, cofactor_expr, inverse_expr @@ -39,14 +39,6 @@ def transposed(self, o, A): i, j = indices(2) return as_tensor(A[i, j], (j, i)) - def _square_matrix_shape(self, A): - sh = A.ufl_shape - if sh[0] != sh[1]: - raise ValueError("Expecting square matrix.") - if sh[0] is None: - raise ValueError("Unknown dimension.") - return sh - def deviatoric(self, o, A): return deviatoric_expr(A) @@ -63,21 +55,6 @@ def c(i, j): return Product(a[i], b[j]) - Product(a[j], b[i]) return as_vector((c(1, 2), c(2, 0), c(0, 1))) - def altenative_dot(self, o, a, b): # TODO: Test this - ash = a.ufl_shape - bsh = b.ufl_shape - ai = indices(len(ash) - 1) - bi = indices(len(bsh) - 1) - # Simplification for tensors where the dot-sum dimension has - # length 1 - if ash[-1] == 1: - k = (FixedIndex(0),) - else: - k = (Index(),) - # Potentially creates a single IndexSum over a Product - s = a[ai + k] * b[k + bi] - return as_tensor(s, ai + bi) - def dot(self, o, a, b): ai = indices(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 1) @@ -86,26 +63,6 @@ def dot(self, o, a, b): s = a[ai + k] * b[k + bi] return as_tensor(s, ai + bi) - def alternative_inner(self, o, a, b): # TODO: Test this - ash = a.ufl_shape - bsh = b.ufl_shape - if ash != bsh: - raise ValueError("Nonmatching shapes.") - # Simplification for tensors with one or more dimensions of - # length 1 - ii = [] - zi = FixedIndex(0) - for n in ash: - if n == 1: - ii.append(zi) - else: - ii.append(Index()) - ii = tuple(ii) - # ii = indices(len(a.ufl_shape)) - # Potentially creates multiple IndexSums over a Product - s = a[ii] * Conj(b[ii]) - return s - def inner(self, o, a, b): ash = a.ufl_shape bsh = b.ufl_shape diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 4a7dd3a55..01c79b8cd 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -40,12 +40,6 @@ # - ReferenceDivRuleset -# Set this to True to enable previously default workaround -# for bug in FFC handling of conditionals, uflacs does not -# have this bug. -CONDITIONAL_WORKAROUND = False - - class GenericDerivativeRuleset(MultiFunction): def __init__(self, var_shape): MultiFunction.__init__(self) @@ -54,7 +48,8 @@ def __init__(self, var_shape): # --- Error checking for missing handlers and unexpected types def expr(self, o): - raise ValueError(f"Missing differentiation handler for type {o._ufl_class_.__name__}. Have you added a new type?") + raise ValueError(f"Missing differentiation handler for type {o._ufl_class_.__name__}. " + "Have you added a new type?") def unexpected(self, o): raise ValueError(f"Unexpected type {o._ufl_class_.__name__} in AD rules.") @@ -433,18 +428,10 @@ def not_condition(self, o, c): return None def conditional(self, o, unused_dc, dt, df): - global CONDITIONAL_WORKAROUND if isinstance(dt, Zero) and isinstance(df, Zero): # Assuming dt and df have the same indices here, which # should be the case return dt - elif CONDITIONAL_WORKAROUND: - # Placing t[1],f[1] outside here to avoid getting - # arguments inside conditionals. This will fail when dt - # or df become NaN or Inf in floating point computations! - c = o.ufl_operands[0] - dc = conditional(c, 1, 0) - return dc * dt + (1.0 - dc) * df else: # Not placing t[1],f[1] outside, allowing arguments inside # conditionals. This will make legacy ffc fail, but @@ -721,9 +708,9 @@ def coefficient(self, o): # df/v = 0 return self.independent_terminal(o) - def variable(self, o, df, l): + def variable(self, o, df, a): v = self._variable - if isinstance(v, Variable) and v.label() == l: + if isinstance(v, Variable) and v.label() == a: # dv/dv = identity of rank 2*rank(v) return self._Id else: @@ -1203,7 +1190,8 @@ def spatial_coordinate(self, o): if do is not None: return do else: - raise NotImplementedError("CoordinateDerivative found a SpatialCoordinate that is different from the one being differentiated.") + raise NotImplementedError("CoordinateDerivative found a SpatialCoordinate that is different " + "from the one being differentiated.") def reference_value(self, o): do = self._cd.get(o) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index 58930a113..b7790c7fb 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -33,27 +33,6 @@ def sub_elements_with_mappings(element): return elements -def create_nested_lists(shape): - if len(shape) == 0: - return [None] - elif len(shape) == 1: - return [None] * shape[0] - else: - return [create_nested_lists(shape[1:]) for i in range(shape[0])] - - -def reshape_to_nested_list(components, shape): - if len(shape) == 0: - assert len(components) == 1 - return [components[0]] - elif len(shape) == 1: - assert len(components) == shape[0] - return components - else: - n = product(shape[1:]) - return [reshape_to_nested_list(components[n * i:n * (i + 1)], shape[1:]) for i in range(shape[0])] - - def apply_known_single_pullback(r, element): """Apply pullback with given mapping. @@ -118,7 +97,8 @@ def apply_single_function_pullbacks(r, element): mapping = element.mapping() if r.ufl_shape != element.reference_value_shape(): raise ValueError( - f"Expecting reference space expression with shape '{element.reference_value_shape()}', got '{r.ufl_shape}'") + f"Expecting reference space expression with shape '{element.reference_value_shape()}', " + f"got '{r.ufl_shape}'") if mapping in {"physical", "identity", "contravariant Piola", "covariant Piola", "double contravariant Piola", "double covariant Piola", @@ -128,7 +108,8 @@ def apply_single_function_pullbacks(r, element): # directly. f = apply_known_single_pullback(r, element) if f.ufl_shape != element.value_shape(): - raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', got '{f.ufl_shape}'") + raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', " + f"got '{f.ufl_shape}'") return f elif mapping in {"symmetries", "undefined"}: # Need to pull back each unique piece of the reference space thing @@ -161,7 +142,8 @@ def apply_single_function_pullbacks(r, element): # And reshape appropriately f = as_tensor(numpy.asarray(g_components).reshape(gsh)) if f.ufl_shape != element.value_shape(): - raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', got '{f.ufl_shape}'") + raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', " + f"got '{f.ufl_shape}'") return f else: raise ValueError(f"Unsupported mapping type: {mapping}") diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 9cca28b1e..0cd3417c2 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -1,4 +1,5 @@ -"""This module contains the apply_restrictions algorithm which propagates restrictions in a form towards the terminals.""" +"""This module contains the apply_restrictions algorithm which propagates +restrictions in a form towards the terminals.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -60,7 +61,8 @@ def _default_restricted(self, o): return o(r) def _opposite(self, o): - "Restrict a quantity to default side, if the current restriction is different swap the sign, require a side to be set." + """Restrict a quantity to default side, if the current restriction + is different swap the sign, require a side to be set.""" if self.current_restriction is None: raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") elif self.current_restriction == self.default_restriction: @@ -127,7 +129,8 @@ def reference_value(self, o): reference_facet_volume = _ignore_restriction def coefficient(self, o): - "Allow coefficients to be unrestricted (apply default if so) if the values are fully continuous across the facet." + """Allow coefficients to be unrestricted (apply default if so) if the values are + fully continuous across the facet.""" 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 diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index 168cad610..88ad22d12 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -11,16 +11,9 @@ from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag -from ufl.classes import (FormArgument, GeometricQuantity, - Terminal, ReferenceGrad, Grad, Restricted, ReferenceValue, - Jacobian, JacobianInverse, JacobianDeterminant, - Indexed, MultiIndex, FixedIndex) +from ufl.classes import ReferenceGrad, Grad, Restricted, ReferenceValue, JacobianInverse -from ufl.constantvalue import as_ufl from ufl.tensors import as_tensor -from ufl.permutation import compute_indices - -from ufl.finiteelement import MixedElement from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering @@ -51,7 +44,8 @@ e2 = || J[:,0] . < 1, 0> || = || J[:,0] || = || dx/dX0 || = edge length of edge 2 (v0-v1) e1 = || J[:,1] . < 0, 1> || = || J[:,1] || = || dx/dX1 || = edge length of edge 1 (v0-v2) -e0 = || J[:,:] . <-1, 1> || = || < J[0,1]-J[0,0], J[1,1]-J[1,0] > || = || dx/dX <-1,1> || = edge length of edge 0 (v1-v2) +e0 = || J[:,:] . <-1, 1> || = || < J[0,1]-J[0,0], J[1,1]-J[1,0] > || = || dx/dX <-1,1> || + = edge length of edge 0 (v1-v2) trev = triangle_reference_edge_vector evec0 = J00 * trev[edge][0] + J01 * trev[edge][1] = J*trev[edge] @@ -113,245 +107,7 @@ """ -# FIXME: This implementation semeed to work last year but lead to performance problems. Look through and test again now. -class NEWChangeToReferenceGrad(MultiFunction): - def __init__(self): - MultiFunction.__init__(self) - self._ngrads = 0 - self._restricted = '' - self._avg = '' - - def expr(self, o, *ops): - return o._ufl_expr_reconstruct_(*ops) - - def terminal(self, o): - return o - - def coefficient_derivative(self, o, *dummy_ops): - raise ValueError("Coefficient derivatives should be expanded before applying change to reference grad.") - - def reference_grad(self, o, *dummy_ops): - raise ValueError("Not expecting reference grad while applying change to reference grad.") - - def restricted(self, o, *dummy_ops): - "Store modifier state." - if self._restricted != '': - raise ValueError("Not expecting nested restrictions.") - self._restricted = o.side() - f, = o.ufl_operands - r = self(f) - self._restricted = '' - return r - - def grad(self, o, *dummy_ops): - "Store modifier state." - self._ngrads += 1 - f, = o.ufl_operands - r = self(f) - self._ngrads -= 1 - return r - - def facet_avg(self, o, *dummy_ops): - if self._avg != '': - raise ValueError("Not expecting nested averages.") - self._avg = "facet" - f, = o.ufl_operands - r = self(f) - self._avg = "" - return r - - def cell_avg(self, o, *dummy_ops): - if self._avg != '': - raise ValueError("Not expecting nested averages.") - self._avg = "cell" - f, = o.ufl_operands - r = self(f) - self._avg = "" - return r - - def form_argument(self, t): - return self._mapped(t) - - def geometric_quantity(self, t): - if self._restricted or self._ngrads or self._avg: - return self._mapped(t) - else: - return t - - def _mapped(self, t): - # Check that we have a valid input object - if not isinstance(t, Terminal): - raise ValueError("Expecting a Terminal.") - - # Get modifiers accumulated by previous handler calls - ngrads = self._ngrads - restricted = self._restricted - avg = self._avg - if avg != "": - raise ValueError("Averaging not implemented.") # FIXME - - # These are the global (g) and reference (r) values - if isinstance(t, FormArgument): - g = t - r = ReferenceValue(g) - elif isinstance(t, GeometricQuantity): - g = t - r = g - else: - raise ValueError(f"Unexpected type {type(t).__name__}.") - - # Some geometry mapping objects we may need multiple times below - domain = t.ufl_domain() - J = Jacobian(domain) - detJ = JacobianDeterminant(domain) - K = JacobianInverse(domain) - - # Restrict geometry objects if applicable - if restricted: - J = J(restricted) - detJ = detJ(restricted) - K = K(restricted) - - # Create Hdiv mapping from possibly restricted geometry objects - Mdiv = (1.0 / detJ) * J - - # Get component indices of global and reference terminal objects - gtsh = g.ufl_shape - # rtsh = r.ufl_shape - gtcomponents = compute_indices(gtsh) - # rtcomponents = compute_indices(rtsh) - - # Create core modified terminal, with eventual - # layers of grad applied directly to the terminal, - # then eventual restriction applied last - for i in range(ngrads): - g = Grad(g) - r = ReferenceGrad(r) - if restricted: - g = g(restricted) - r = r(restricted) - - # Get component indices of global and reference objects with - # grads applied - gsh = g.ufl_shape - # rsh = r.ufl_shape - # gcomponents = compute_indices(gsh) - # rcomponents = compute_indices(rsh) - - # Get derivative component indices - dsh = gsh[len(gtsh):] - dcomponents = compute_indices(dsh) - - # Create nested array to hold expressions for global - # components mapped from reference values - def ndarray(shape): - if len(shape) == 0: - return [None] - elif len(shape) == 1: - return [None] * shape[-1] - else: - return [ndarray(shape[1:]) for i in range(shape[0])] - global_components = ndarray(gsh) - - # Compute mapping from reference values for each global component - for gtc in gtcomponents: - - if isinstance(t, FormArgument): - - # Find basic subelement and element-local component - # ec, element, eoffset = t.ufl_element().extract_component2(gtc) # FIXME: Translate this correctly - eoffset = 0 - ec, element = t.ufl_element().extract_reference_component(gtc) - - # Select mapping M from element, pick row emapping = - # M[ec,:], or emapping = [] if no mapping - if isinstance(element, MixedElement): - raise ValueError("Expecting a basic element here.") - mapping = element.mapping() - if mapping == "contravariant Piola": # S == HDiv: - # Handle HDiv elements with contravariant piola - # mapping contravariant_hdiv_mapping = (1/det J) * - # J * PullbackOf(o) - ec, = ec - emapping = Mdiv[ec, :] - elif mapping == "covariant Piola": # S == HCurl: - # Handle HCurl elements with covariant piola mapping - # covariant_hcurl_mapping = JinvT * PullbackOf(o) - ec, = ec - emapping = K[:, ec] # Column of K is row of K.T - elif mapping == "identity" or mapping == "custom": - emapping = None - else: - raise ValueError(f"Unknown mapping: {mapping}") - - elif isinstance(t, GeometricQuantity): - eoffset = 0 - emapping = None - - else: - raise ValueError(f"Unexpected type {type(t).__name__}.") - - # Create indices - # if rtsh: - # i = Index() - if len(dsh) != ngrads: - raise ValueError("Mismatch between derivative shape and ngrads.") - if ngrads: - ii = indices(ngrads) - else: - ii = () - - # Apply mapping row to reference object - if emapping: # Mapped, always nonscalar terminal Not - # using IndexSum for the mapping row dot product to - # keep it simple, because we don't have a slice type - emapped_ops = [emapping[s] * Indexed(r, MultiIndex((FixedIndex(eoffset + s),) + ii)) - for s in range(len(emapping))] - emapped = sum(emapped_ops[1:], emapped_ops[0]) - elif gtc: # Nonscalar terminal, unmapped - emapped = Indexed(r, MultiIndex((FixedIndex(eoffset),) + ii)) - elif ngrads: # Scalar terminal, unmapped, with derivatives - emapped = Indexed(r, MultiIndex(ii)) - else: # Scalar terminal, unmapped, no derivatives - emapped = r - - for di in dcomponents: - # Multiply derivative mapping rows, parameterized by - # free column indices - dmapping = as_ufl(1) - for j in range(ngrads): - dmapping *= K[ii[j], di[j]] # Row of K is column of JinvT - - # Compute mapping from reference values for this - # particular global component - global_value = dmapping * emapped - - # Apply index sums - # if rtsh: - # global_value = IndexSum(global_value, MultiIndex((i,))) - # for j in range(ngrads): # Applied implicitly in the dmapping * emapped above - # global_value = IndexSum(global_value, MultiIndex((ii[j],))) - - # This is the component index into the full object - # with grads applied - gc = gtc + di - - # Insert in nested list - comp = global_components - for i in gc[:-1]: - comp = comp[i] - comp[0 if gc == () else gc[-1]] = global_value - - # Wrap nested list in as_tensor unless we have a scalar - # expression - if gsh: - tensor = as_tensor(global_components) - else: - tensor, = global_components - return tensor - - -class OLDChangeToReferenceGrad(MultiFunction): +class ChangeToReferenceGrad(MultiFunction): def __init__(self): MultiFunction.__init__(self) @@ -451,8 +207,7 @@ def change_to_reference_grad(e): @param e: An Expr or Form. """ - mf = OLDChangeToReferenceGrad() - # mf = NEWChangeToReferenceGrad() + mf = ChangeToReferenceGrad() return map_expr_dag(mf, e) diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 0eefe866d..3f2f64a7d 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -15,8 +15,7 @@ class ArityMismatch(BaseException): # String representation of an arity tuple: def _afmt(atuple): - return tuple("conj({0})".format(arg) if conj else str(arg) - for arg, conj in atuple) + return tuple(f"conj({arg})" if conj else str(arg) for arg, conj in atuple) class ArityChecker(MultiFunction): @@ -37,19 +36,20 @@ def nonlinear_operator(self, o): # way we know of: for t in traverse_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: - raise ArityMismatch("Applying nonlinear operator {0} to expression depending on form argument {1}.".format(o._ufl_class_.__name__, t)) + raise ArityMismatch(f"Applying nonlinear operator {o._ufl_class_.__name__} to " + f"expression depending on form argument {t}.") return self._et expr = nonlinear_operator def sum(self, o, a, b): if a != b: - raise ArityMismatch("Adding expressions with non-matching form arguments {0} vs {1}.".format(_afmt(a), _afmt(b))) + raise ArityMismatch(f"Adding expressions with non-matching form arguments {_afmt(a)} vs {_afmt(b)}.") return a def division(self, o, a, b): if b: - raise ArityMismatch("Cannot divide by form argument {0}.".format(b)) + raise ArityMismatch(f"Cannot divide by form argument {b}.") return a def product(self, o, a, b): @@ -59,13 +59,15 @@ def product(self, o, a, b): anumbers = set(x[0].number() for x in a) for x in b: if x[0].number() in anumbers: - raise ArityMismatch("Multiplying expressions with overlapping form argument number {0}, argument is {1}.".format(x[0].number(), _afmt(x))) + raise ArityMismatch("Multiplying expressions with overlapping form argument number " + f"{x[0].number()}, argument is {_afmt(x)}.") # Combine argument lists c = tuple(sorted(set(a + b), key=lambda x: (x[0].number(), x[0].part()))) # Check that we don't have any arguments shared between a # and b if len(c) != len(a) + len(b) or len(c) != len({x[0] for x in c}): - raise ArityMismatch("Multiplying expressions with overlapping form arguments {0} vs {1}.".format(_afmt(a), _afmt(b))) + raise ArityMismatch("Multiplying expressions with overlapping form arguments " + f"{_afmt(a)} vs {_afmt(b)}.") # It's fine for argument parts to overlap return c elif a: @@ -104,13 +106,13 @@ def conj(self, o, a): # Does it make sense to have a Variable(Argument)? I see no # problem. - def variable(self, o, f, l): + def variable(self, o, f, a): return f # Conditional is linear on each side of the condition def conditional(self, o, c, a, b): if c: - raise ArityMismatch("Condition cannot depend on form arguments ({0}).".format(_afmt(a))) + raise ArityMismatch(f"Condition cannot depend on form arguments ({_afmt(a)}).") if a and isinstance(o.ufl_operands[2], Zero): # Allow conditional(c, arg, 0) return a @@ -123,7 +125,8 @@ def conditional(self, o, c, a, b): else: # Do not allow e.g. conditional(c, test, trial), # conditional(c, test, nonzeroconstant) - raise ArityMismatch("Conditional subexpressions with non-matching form arguments {0} vs {1}.".format(_afmt(a), _afmt(b))) + raise ArityMismatch("Conditional subexpressions with non-matching form arguments " + f"{_afmt(a)} vs {_afmt(b)}.") def linear_indexed_type(self, o, a, i): return a @@ -142,7 +145,8 @@ def list_tensor(self, o, *ops): if () in numbers: # Allow e.g. but not numbers.remove(()) if len(numbers) > 1: - raise ArityMismatch("Listtensor components must depend on the same argument numbers, found {0}.".format(numbers)) + raise ArityMismatch("Listtensor components must depend on the same argument numbers, " + f"found {numbers}.") # Allow different parts with the same number return tuple(sorted(args, key=lambda x: (x[0].number(), x[0].part()))) @@ -158,7 +162,7 @@ def check_integrand_arity(expr, arguments, complex_mode=False): arg_tuples = map_expr_dag(rules, expr, compress=False) args = tuple(a[0] for a in arg_tuples) if args != arguments: - raise ArityMismatch("Integrand arguments {0} differ from form arguments {1}.".format(args, arguments)) + raise ArityMismatch(f"Integrand arguments {args} differ from form arguments {arguments}.") if complex_mode: # Check that the test function is conjugated and that any # trial function is not conjugated. Further arguments are @@ -168,7 +172,7 @@ def check_integrand_arity(expr, arguments, complex_mode=False): if arg.number() == 0 and not conj: raise ArityMismatch("Failure to conjugate test function in complex Form") elif arg.number() > 0 and conj: - raise ArityMismatch("Argument {0} is spuriously conjugated in complex Form".format(arg)) + raise ArityMismatch(f"Argument {arg} is spuriously conjugated in complex Form") def check_form_arity(form, arguments, complex_mode=False): diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index 738cf9aae..a850f5ff1 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -63,7 +63,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste if c in coefficients: g = coefficients[c] if f is not g: - errors.append("Found different Coefficients with " + + errors.append("Found different Coefficients with " f"same count: {f} and {g}.") else: coefficients[c] = f diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 1b5d4ca5c..842d43d9f 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -10,7 +10,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from itertools import chain -from logging import info from ufl.utils.sequences import max_degree @@ -82,13 +81,11 @@ def _compute_element_mapping(form): for d in domains): raise ValueError("Cannot replace unknown element cell without unique common cell in form.") cell = domains[0].ufl_cell() - info(f"Adjusting missing element cell to {cell}.") reconstruct = True # Set degree degree = element.degree() if degree is None: - info(f"Adjusting missing element degree to {common_degree}.") degree = common_degree reconstruct = True @@ -402,7 +399,8 @@ def compute_form_data(form, # faster! preprocessed_form = reconstruct_form_from_integral_data(self.integral_data) - check_form_arity(preprocessed_form, self.original_form.arguments(), complex_mode) # Currently testing how fast this is + # TODO: Test how fast this is + check_form_arity(preprocessed_form, self.original_form.arguments(), complex_mode) # TODO: This member is used by unit tests, change the tests to # remove this! diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 2a6f4ba3f..e12668ac9 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -14,7 +14,8 @@ from ufl.form import Form from ufl.sorting import cmp_expr, sorted_expr from ufl.utils.sorting import canonicalize_metadata, sorted_by_key -from ufl.algorithms.coordinate_derivative_helpers import attach_coordinate_derivatives, strip_coordinate_derivatives +from ufl.algorithms.coordinate_derivative_helpers import ( + attach_coordinate_derivatives, strip_coordinate_derivatives) import numbers @@ -57,17 +58,16 @@ def __init__(self, domain, integral_type, subdomain_id, integrals, def __lt__(self, other): # To preserve behaviour of extract_integral_data: - return ((self.integral_type, self.subdomain_id, - self.integrals, self.metadata) < - (other.integral_type, other.subdomain_id, other.integrals, - other.metadata)) + return ( + self.integral_type, self.subdomain_id, self.integrals, self.metadata + ) < ( + other.integral_type, other.subdomain_id, other.integrals, other.metadata + ) def __eq__(self, other): # Currently only used for tests: - return (self.integral_type == other.integral_type and - self.subdomain_id == other.subdomain_id and - self.integrals == other.integrals and - self.metadata == other.metadata) + return (self.integral_type == other.integral_type and self.subdomain_id == other.subdomain_id and # noqa: W504 + self.integrals == other.integrals and self.metadata == other.metadata) def __str__(self): s = "IntegralData over domain(%s, %s), with integrals:\n%s\nand metadata:\n%s" % ( @@ -76,20 +76,6 @@ def __str__(self): return s -def dicts_lt(a, b): - na = 0 if a is None else len(a) - nb = 0 if b is None else len(b) - if na != nb: - return len(a) < len(b) - for ia, ib in zip(sorted_by_key(a), sorted_by_key(b)): - # Assuming keys are sortable (usually str) - if ia[0] != ib[0]: - return (ia[0].__class__.__name__, ia[0]) < (ib[0].__class__.__name__, ib[0]) # Hack to preserve type sorting in py3 - # Assuming values are sortable - if ia[1] != ib[1]: - return (ia[1].__class__.__name__, ia[1]) < (ib[1].__class__.__name__, ib[1]) # Hack to preserve type sorting in py3 - - # Tuple comparison helper class ExprTupleKey(object): __slots__ = ('x',) diff --git a/ufl/algorithms/elementtransformations.py b/ufl/algorithms/elementtransformations.py deleted file mode 100644 index ae182d5ce..000000000 --- a/ufl/algorithms/elementtransformations.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module provides helper functions to - - FFC/DOLFIN adaptive chain, - - UFL algorithms taking care of underspecified DOLFIN expressions.""" - -# Copyright (C) 2012 Marie E. Rognes, 2015 Jan Blechta -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from ufl.finiteelement import FiniteElement, VectorElement, TensorElement, \ - MixedElement, EnrichedElement, NodalEnrichedElement - -__all__ = ['increase_order', 'tear'] - - -def increase_order(element): - "Return element of same family, but a polynomial degree higher." - return _increase_degree(element, +1) - - -def change_regularity(element, family): - """ - For a given finite element, return the corresponding space - specified by 'family'. - """ - return element.reconstruct(family=family) - - -def tear(element): - "For a finite element, return the corresponding discontinuous element." - return change_regularity(element, "DG") - - -def _increase_degree(element, degree_rise): - if isinstance(element, (FiniteElement, VectorElement, TensorElement)): - # Can't increase degree for reals - if element._is_globally_constant(): - return element - return element.reconstruct(degree=(element.degree() + degree_rise)) - elif isinstance(element, MixedElement): - return MixedElement([_increase_degree(e, degree_rise) - for e in element.sub_elements()]) - elif isinstance(element, EnrichedElement): - return EnrichedElement([_increase_degree(e, degree_rise) - for e in element.sub_elements()]) - elif isinstance(element, NodalEnrichedElement): - return NodalEnrichedElement([_increase_degree(e, degree_rise) - for e in element.sub_elements()]) - else: - raise ValueError("Element reconstruction is only done to stay compatible" - f" with hacks in DOLFIN. Not expecting a {element}") diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index c8271bbce..53dae9354 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -132,7 +132,7 @@ def label(self, v): def reference_value(self, rv, f): return f - def variable(self, v, e, l): + def variable(self, v, e, a): return e def transposed(self, v, A): @@ -293,9 +293,9 @@ def conditional(self, v, c, t, f): quadrature order must be adjusted manually.""" return self._max_degrees(v, t, f) - def min_value(self, v, l, r): + def min_value(self, v, a, r): """Same as conditional.""" - return self._max_degrees(v, l, r) + return self._max_degrees(v, a, r) max_value = min_value def coordinate_derivative(self, v, integrand_degree, b, direction_degree, d): diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index ca904e6cc..7b937bf51 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -12,12 +12,11 @@ # Modified by Anders Logg, 2009. from ufl.utils.stacks import Stack, StackDict -from ufl.classes import Terminal, ListTensor +from ufl.classes import Terminal from ufl.constantvalue import Zero from ufl.core.multiindex import Index, FixedIndex, MultiIndex from ufl.differentiation import Grad from ufl.algorithms.transformer import ReuseTransformer, apply_transformer -from ufl.corealg.traversal import unique_pre_traversal class IndexExpander(ReuseTransformer): @@ -218,12 +217,3 @@ def grad(self, x): def expand_indices(e): return apply_transformer(e, IndexExpander()) - - -def purge_list_tensors(expr): - """Get rid of all ListTensor instances by expanding - expressions to use their components directly. - Will usually increase the size of the expression.""" - if any(isinstance(subexpr, ListTensor) for subexpr in unique_pre_traversal(expr)): - return expand_indices(expr) # TODO: Only expand what's necessary to get rid of list tensors - return expr diff --git a/ufl/algorithms/formdata.py b/ufl/algorithms/formdata.py index 850bc7809..c6d66e729 100644 --- a/ufl/algorithms/formdata.py +++ b/ufl/algorithms/formdata.py @@ -41,30 +41,3 @@ def __str__(self): ("Unique sub elements", estr(self.unique_sub_elements)), ) return tstr(geometry + subdomains + functions) - - -class ExprData(object): - """ - Class collecting various information extracted from a Expr by - calling preprocess. - """ - - def __init__(self): - "Create empty expr data for given expr." - - def __str__(self): - "Return formatted summary of expr data" - return tstr((("Name", self.name), - ("Cell", self.cell), - ("Topological dimension", self.topological_dimension), - ("Geometric dimension", self.geometric_dimension), - ("Rank", self.rank), - ("Number of coefficients", self.num_coefficients), - ("Arguments", lstr(self.arguments)), - ("Coefficients", lstr(self.coefficients)), - ("Argument names", lstr(self.argument_names)), - ("Coefficient names", lstr(self.coefficient_names)), - ("Unique elements", estr(self.unique_elements)), - ("Unique sub elements", estr(self.unique_sub_elements)), - # FIXME DOMAINS what is "the domain(s)" for an expression? - ("Domains", self.domains), )) diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index fdd453374..622461580 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -33,8 +33,9 @@ def __init__(self): self.reserved_objects = {} def __bool__(self): - return bool(self.elements or self.coefficients or self.forms or self.expressions or + return bool(self.elements or self.coefficients or self.forms or self.expressions or # noqa: W504 self.object_names or self.object_by_name or self.reserved_objects) + __nonzero__ = __bool__ diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 647e2bed1..84757999a 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -295,7 +295,8 @@ def list_tensor(self, x, *ops): # least with the current transformer design.) for (component, provides) in ops: if (provides != most_provides and not isinstance(component, Zero)): - raise ValueError("PartExtracter does not know how to handle list_tensors with non-zero components providing fewer arguments") + raise ValueError("PartExtracter does not know how to handle list_tensors with " + "non-zero components providing fewer arguments") # Return components components = [op[0] for op in ops] diff --git a/ufl/algorithms/transformer.py b/ufl/algorithms/transformer.py index 4ed95f69c..dbbc96f69 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -229,20 +229,6 @@ def apply_transformer(e, transformer, integral_type=None): integral_type) -def ufl2ufl(e): - """Convert an UFL expression to a new UFL expression, with no changes. - This is used for testing that objects in the expression behave as expected.""" - return apply_transformer(e, ReuseTransformer()) - - -def ufl2uflcopy(e): - """Convert an UFL expression to a new UFL expression. - All nonterminal object instances are replaced with identical - copies, while terminal objects are kept. This is used for - testing that objects in the expression behave as expected.""" - return apply_transformer(e, CopyTransformer()) - - def strip_variables(e): "Replace all Variable instances with the expression they represent." return apply_transformer(e, VariableStripper()) diff --git a/ufl/argument.py b/ufl/argument.py index 4d170b136..a29fcefda 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -129,10 +129,10 @@ def __eq__(self, other): point of view, e.g. TestFunction(V1) == TestFunction(V2) if V1 and V2 are the same ufl element but different dolfin function spaces. """ - return (type(self) == type(other) and - self._number == other._number and - self._part == other._part and - self._ufl_function_space == other._ufl_function_space) + return ( + type(self) == type(other) and self._number == other._number and # noqa: W504 + self._part == other._part and self._ufl_function_space == other._ufl_function_space + ) @ufl_type() @@ -192,7 +192,8 @@ class Coargument(BaseForm, BaseArgument): def __new__(cls, *args, **kw): if args[0] and is_primal(args[0]): - raise ValueError('ufl.Coargument takes in a dual space! If you want to define an argument in the primal space you should use ufl.Argument.') + raise ValueError("ufl.Coargument takes in a dual space! If you want to define an argument " + "in the primal space you should use ufl.Argument.") return super().__new__(cls) def __init__(self, function_space, number, part=None): @@ -214,7 +215,7 @@ def equals(self, other): return False if self is other: return True - return (self._ufl_function_space == other._ufl_function_space and + return (self._ufl_function_space == other._ufl_function_space and # noqa: W504 self._number == other._number and self._part == other._part) def __hash__(self): diff --git a/ufl/cell.py b/ufl/cell.py index 61c8e69e8..625887a10 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -215,8 +215,9 @@ def __init__(self, cellname: str, geometric_dimension: typing.Optional[int] = No self._gdim = self._tdim if geometric_dimension is None else geometric_dimension self._num_cell_entities = [len(i) for i in self._sub_entity_celltypes] - self._sub_entities = [tuple(Cell(t, self._gdim) for t in se_types) for se_types in self._sub_entity_celltypes[:-1]] - self._sub_entity_types = [tuple(Cell(t, self._gdim) for t in set(se_types)) for se_types in self._sub_entity_celltypes[:-1]] + self._sub_entities = [tuple(Cell(t, self._gdim) for t in se_types) + for se_types in self._sub_entity_celltypes[:-1]] + self._sub_entity_types = [tuple(set(i)) for i in self._sub_entities] self._sub_entities.append((weakref.proxy(self), )) self._sub_entity_types.append((weakref.proxy(self), )) diff --git a/ufl/checks.py b/ufl/checks.py index 11502cb65..ca84d7274 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -26,8 +26,7 @@ def is_ufl_scalar(expression): def is_true_ufl_scalar(expression): """Return True iff expression is scalar-valued, with no free indices.""" - return isinstance(expression, Expr) and \ - not (expression.ufl_shape or expression.ufl_free_indices) + return isinstance(expression, Expr) and not (expression.ufl_shape or expression.ufl_free_indices) def is_cellwise_constant(expr): diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 51a508c18..d88743375 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -102,8 +102,7 @@ def __eq__(self, other): return False if self is other: return True - return (self._count == other._count and - self._ufl_function_space == other._ufl_function_space) + return self._count == other._count and self._ufl_function_space == other._ufl_function_space @ufl_type() @@ -125,7 +124,8 @@ class Cofunction(BaseCoefficient, BaseForm): def __new__(cls, *args, **kw): if args[0] and is_primal(args[0]): - raise ValueError('ufl.Cofunction takes in a dual space. If you want to define a coefficient in the primal space you should use ufl.Coefficient.') + raise ValueError("ufl.Cofunction takes in a dual space. If you want to define a coefficient " + "in the primal space you should use ufl.Coefficient.") return super().__new__(cls) def __init__(self, function_space, count=None): @@ -142,8 +142,7 @@ def equals(self, other): return False if self is other: return True - return (self._count == other._count and - self._ufl_function_space == other._ufl_function_space) + return self._count == other._count and self._ufl_function_space == other._ufl_function_space def __hash__(self): """Hash code for use in dicts.""" @@ -190,8 +189,7 @@ def __eq__(self, other): return False if self is other: return True - return (self._count == other._count and - self._ufl_function_space == other._ufl_function_space) + return self._count == other._count and self._ufl_function_space == other._ufl_function_space def __repr__(self): return self._repr diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 60acb3054..a25307298 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -100,7 +100,7 @@ def determinant_expr(A): raise ValueError(f"determinant_expr not implemented for shape {sh}.") -def _det_2x2(B, i, j, k, l): +def _det_2x2(B, i, j, k, l): # noqa: E741 return B[i, k] * B[j, l] - B[i, l] * B[j, k] @@ -109,9 +109,7 @@ def determinant_expr_2x2(B): def old_determinant_expr_3x3(A): - return (A[0, 0] * _det_2x2(A, 1, 2, 1, 2) + - A[0, 1] * _det_2x2(A, 1, 2, 2, 0) + - A[0, 2] * _det_2x2(A, 1, 2, 0, 1)) + return A[0, 0] * _det_2x2(A, 1, 2, 1, 2) + A[0, 1] * _det_2x2(A, 1, 2, 2, 0) + A[0, 2] * _det_2x2(A, 1, 2, 0, 1) def determinant_expr_3x3(A): @@ -165,32 +163,43 @@ def adj_expr_2x2(A): def adj_expr_3x3(A): - return as_matrix([ - [A[2, 2] * A[1, 1] - A[1, 2] * A[2, 1], -A[0, 1] * A[2, 2] + A[0, 2] * A[2, 1], A[0, 1] * A[1, 2] - A[0, 2] * A[1, 1]], - [-A[2, 2] * A[1, 0] + A[1, 2] * A[2, 0], -A[0, 2] * A[2, 0] + A[2, 2] * A[0, 0], A[0, 2] * A[1, 0] - A[1, 2] * A[0, 0]], - [A[1, 0] * A[2, 1] - A[2, 0] * A[1, 1], A[0, 1] * A[2, 0] - A[0, 0] * A[2, 1], A[0, 0] * A[1, 1] - A[0, 1] * A[1, 0]], - ]) + return as_matrix([[ + A[2, 2] * A[1, 1] - A[1, 2] * A[2, 1], + -A[0, 1] * A[2, 2] + A[0, 2] * A[2, 1], + A[0, 1] * A[1, 2] - A[0, 2] * A[1, 1], + ], [ + -A[2, 2] * A[1, 0] + A[1, 2] * A[2, 0], + -A[0, 2] * A[2, 0] + A[2, 2] * A[0, 0], + A[0, 2] * A[1, 0] - A[1, 2] * A[0, 0], + ], [ + A[1, 0] * A[2, 1] - A[2, 0] * A[1, 1], + A[0, 1] * A[2, 0] - A[0, 0] * A[2, 1], + A[0, 0] * A[1, 1] - A[0, 1] * A[1, 0], + ]]) def adj_expr_4x4(A): - return as_matrix([ - [-A[3, 3] * A[2, 1] * A[1, 2] + A[1, 2] * A[3, 1] * A[2, 3] + A[1, 1] * A[3, 3] * A[2, 2] - A[3, 1] * A[2, 2] * A[1, 3] + A[2, 1] * A[1, 3] * A[3, 2] - A[1, 1] * A[3, 2] * A[2, 3], - -A[3, 1] * A[0, 2] * A[2, 3] + A[0, 1] * A[3, 2] * A[2, 3] - A[0, 3] * A[2, 1] * A[3, 2] + A[3, 3] * A[2, 1] * A[0, 2] - A[3, 3] * A[0, 1] * A[2, 2] + A[0, 3] * A[3, 1] * A[2, 2], - A[3, 1] * A[1, 3] * A[0, 2] + A[1, 1] * A[0, 3] * A[3, 2] - A[0, 3] * A[1, 2] * A[3, 1] - A[0, 1] * A[1, 3] * A[3, 2] + A[3, 3] * A[1, 2] * A[0, 1] - A[1, 1] * A[3, 3] * A[0, 2], - A[1, 1] * A[0, 2] * A[2, 3] - A[2, 1] * A[1, 3] * A[0, 2] + A[0, 3] * A[2, 1] * A[1, 2] - A[1, 2] * A[0, 1] * A[2, 3] - A[1, 1] * A[0, 3] * A[2, 2] + A[0, 1] * A[2, 2] * A[1, 3]], - [A[3, 3] * A[1, 2] * A[2, 0] - A[3, 0] * A[1, 2] * A[2, 3] + A[1, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[1, 0] * A[2, 2] - A[1, 3] * A[3, 2] * A[2, 0] + A[3, 0] * A[2, 2] * A[1, 3], - A[0, 3] * A[3, 2] * A[2, 0] - A[0, 3] * A[3, 0] * A[2, 2] + A[3, 3] * A[0, 0] * A[2, 2] + A[3, 0] * A[0, 2] * A[2, 3] - A[0, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[0, 2] * A[2, 0], - -A[3, 3] * A[0, 0] * A[1, 2] + A[0, 0] * A[1, 3] * A[3, 2] - A[3, 0] * A[1, 3] * A[0, 2] + A[3, 3] * A[1, 0] * A[0, 2] + A[0, 3] * A[3, 0] * A[1, 2] - A[0, 3] * A[1, 0] * A[3, 2], - A[0, 3] * A[1, 0] * A[2, 2] + A[1, 3] * A[0, 2] * A[2, 0] - A[0, 0] * A[2, 2] * A[1, 3] - A[0, 3] * A[1, 2] * A[2, 0] + A[0, 0] * A[1, 2] * A[2, 3] - A[1, 0] * A[0, 2] * A[2, 3]], - [A[3, 1] * A[1, 3] * A[2, 0] + A[3, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[3, 0] * A[2, 3] - A[1, 0] * A[3, 1] * A[2, 3] - A[3, 0] * A[2, 1] * A[1, 3] - A[1, 1] * A[3, 3] * A[2, 0], - A[3, 3] * A[0, 1] * A[2, 0] - A[3, 3] * A[0, 0] * A[2, 1] - A[0, 3] * A[3, 1] * A[2, 0] - A[3, 0] * A[0, 1] * A[2, 3] + A[0, 0] * A[3, 1] * A[2, 3] + A[0, 3] * A[3, 0] * A[2, 1], - -A[0, 0] * A[3, 1] * A[1, 3] + A[0, 3] * A[1, 0] * A[3, 1] - A[3, 3] * A[1, 0] * A[0, 1] + A[1, 1] * A[3, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[3, 0] * A[0, 1] * A[1, 3], - A[0, 0] * A[2, 1] * A[1, 3] + A[1, 0] * A[0, 1] * A[2, 3] - A[0, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[0, 3] * A[2, 0] - A[1, 1] * A[0, 0] * A[2, 3] - A[0, 1] * A[1, 3] * A[2, 0]], - [-A[1, 2] * A[3, 1] * A[2, 0] - A[2, 1] * A[1, 0] * A[3, 2] + A[3, 0] * A[2, 1] * A[1, 2] - A[1, 1] * A[3, 0] * A[2, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[1, 1] * A[3, 2] * A[2, 0], - -A[3, 0] * A[2, 1] * A[0, 2] - A[0, 1] * A[3, 2] * A[2, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 0] * A[3, 1] * A[2, 2] + A[3, 0] * A[0, 1] * A[2, 2] + A[0, 0] * A[2, 1] * A[3, 2], - A[0, 0] * A[1, 2] * A[3, 1] - A[1, 0] * A[3, 1] * A[0, 2] + A[1, 1] * A[3, 0] * A[0, 2] + A[1, 0] * A[0, 1] * A[3, 2] - A[3, 0] * A[1, 2] * A[0, 1] - A[1, 1] * A[0, 0] * A[3, 2], - -A[1, 1] * A[0, 2] * A[2, 0] + A[2, 1] * A[1, 0] * A[0, 2] + A[1, 2] * A[0, 1] * A[2, 0] + A[1, 1] * A[0, 0] * A[2, 2] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2]], - ]) + return as_matrix([[ + -A[3, 3] * A[2, 1] * A[1, 2] + A[1, 2] * A[3, 1] * A[2, 3] + A[1, 1] * A[3, 3] * A[2, 2] - A[3, 1] * A[2, 2] * A[1, 3] + A[2, 1] * A[1, 3] * A[3, 2] - A[1, 1] * A[3, 2] * A[2, 3], # noqa: E501 + -A[3, 1] * A[0, 2] * A[2, 3] + A[0, 1] * A[3, 2] * A[2, 3] - A[0, 3] * A[2, 1] * A[3, 2] + A[3, 3] * A[2, 1] * A[0, 2] - A[3, 3] * A[0, 1] * A[2, 2] + A[0, 3] * A[3, 1] * A[2, 2], # noqa: E501 + A[3, 1] * A[1, 3] * A[0, 2] + A[1, 1] * A[0, 3] * A[3, 2] - A[0, 3] * A[1, 2] * A[3, 1] - A[0, 1] * A[1, 3] * A[3, 2] + A[3, 3] * A[1, 2] * A[0, 1] - A[1, 1] * A[3, 3] * A[0, 2], # noqa: E501 + A[1, 1] * A[0, 2] * A[2, 3] - A[2, 1] * A[1, 3] * A[0, 2] + A[0, 3] * A[2, 1] * A[1, 2] - A[1, 2] * A[0, 1] * A[2, 3] - A[1, 1] * A[0, 3] * A[2, 2] + A[0, 1] * A[2, 2] * A[1, 3], # noqa: E501 + ], [ + A[3, 3] * A[1, 2] * A[2, 0] - A[3, 0] * A[1, 2] * A[2, 3] + A[1, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[1, 0] * A[2, 2] - A[1, 3] * A[3, 2] * A[2, 0] + A[3, 0] * A[2, 2] * A[1, 3], # noqa: E501 + A[0, 3] * A[3, 2] * A[2, 0] - A[0, 3] * A[3, 0] * A[2, 2] + A[3, 3] * A[0, 0] * A[2, 2] + A[3, 0] * A[0, 2] * A[2, 3] - A[0, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[0, 2] * A[2, 0], # noqa: E501 + -A[3, 3] * A[0, 0] * A[1, 2] + A[0, 0] * A[1, 3] * A[3, 2] - A[3, 0] * A[1, 3] * A[0, 2] + A[3, 3] * A[1, 0] * A[0, 2] + A[0, 3] * A[3, 0] * A[1, 2] - A[0, 3] * A[1, 0] * A[3, 2], # noqa: E501 + A[0, 3] * A[1, 0] * A[2, 2] + A[1, 3] * A[0, 2] * A[2, 0] - A[0, 0] * A[2, 2] * A[1, 3] - A[0, 3] * A[1, 2] * A[2, 0] + A[0, 0] * A[1, 2] * A[2, 3] - A[1, 0] * A[0, 2] * A[2, 3], # noqa: E501 + ], [ + A[3, 1] * A[1, 3] * A[2, 0] + A[3, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[3, 0] * A[2, 3] - A[1, 0] * A[3, 1] * A[2, 3] - A[3, 0] * A[2, 1] * A[1, 3] - A[1, 1] * A[3, 3] * A[2, 0], # noqa: E501 + A[3, 3] * A[0, 1] * A[2, 0] - A[3, 3] * A[0, 0] * A[2, 1] - A[0, 3] * A[3, 1] * A[2, 0] - A[3, 0] * A[0, 1] * A[2, 3] + A[0, 0] * A[3, 1] * A[2, 3] + A[0, 3] * A[3, 0] * A[2, 1], # noqa: E501 + -A[0, 0] * A[3, 1] * A[1, 3] + A[0, 3] * A[1, 0] * A[3, 1] - A[3, 3] * A[1, 0] * A[0, 1] + A[1, 1] * A[3, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[3, 0] * A[0, 1] * A[1, 3], # noqa: E501 + A[0, 0] * A[2, 1] * A[1, 3] + A[1, 0] * A[0, 1] * A[2, 3] - A[0, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[0, 3] * A[2, 0] - A[1, 1] * A[0, 0] * A[2, 3] - A[0, 1] * A[1, 3] * A[2, 0], # noqa: E501 + ], [ + -A[1, 2] * A[3, 1] * A[2, 0] - A[2, 1] * A[1, 0] * A[3, 2] + A[3, 0] * A[2, 1] * A[1, 2] - A[1, 1] * A[3, 0] * A[2, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[1, 1] * A[3, 2] * A[2, 0], # noqa: E501 + -A[3, 0] * A[2, 1] * A[0, 2] - A[0, 1] * A[3, 2] * A[2, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 0] * A[3, 1] * A[2, 2] + A[3, 0] * A[0, 1] * A[2, 2] + A[0, 0] * A[2, 1] * A[3, 2], # noqa: E501 + A[0, 0] * A[1, 2] * A[3, 1] - A[1, 0] * A[3, 1] * A[0, 2] + A[1, 1] * A[3, 0] * A[0, 2] + A[1, 0] * A[0, 1] * A[3, 2] - A[3, 0] * A[1, 2] * A[0, 1] - A[1, 1] * A[0, 0] * A[3, 2], # noqa: E501 + -A[1, 1] * A[0, 2] * A[2, 0] + A[2, 1] * A[1, 0] * A[0, 2] + A[1, 2] * A[0, 1] * A[2, 0] + A[1, 1] * A[0, 0] * A[2, 2] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2], # noqa: E501 + ]]) def cofactor_expr(A): @@ -214,32 +223,43 @@ def cofactor_expr_2x2(A): def cofactor_expr_3x3(A): - return as_matrix([ - [A[1, 1] * A[2, 2] - A[2, 1] * A[1, 2], A[2, 0] * A[1, 2] - A[1, 0] * A[2, 2], - A[2, 0] * A[1, 1] + A[1, 0] * A[2, 1]], - [A[2, 1] * A[0, 2] - A[0, 1] * A[2, 2], A[0, 0] * A[2, 2] - A[2, 0] * A[0, 2], - A[0, 0] * A[2, 1] + A[2, 0] * A[0, 1]], - [A[0, 1] * A[1, 2] - A[1, 1] * A[0, 2], A[1, 0] * A[0, 2] - A[0, 0] * A[1, 2], - A[1, 0] * A[0, 1] + A[0, 0] * A[1, 1]], - ]) + return as_matrix([[ + A[1, 1] * A[2, 2] - A[2, 1] * A[1, 2], + A[2, 0] * A[1, 2] - A[1, 0] * A[2, 2], + -A[2, 0] * A[1, 1] + A[1, 0] * A[2, 1], + ], [ + A[2, 1] * A[0, 2] - A[0, 1] * A[2, 2], + A[0, 0] * A[2, 2] - A[2, 0] * A[0, 2], + -A[0, 0] * A[2, 1] + A[2, 0] * A[0, 1], + ], [ + A[0, 1] * A[1, 2] - A[1, 1] * A[0, 2], + A[1, 0] * A[0, 2] - A[0, 0] * A[1, 2], + -A[1, 0] * A[0, 1] + A[0, 0] * A[1, 1], + ]]) def cofactor_expr_4x4(A): - return as_matrix([ - [-A[3, 1] * A[2, 2] * A[1, 3] - A[3, 2] * A[2, 3] * A[1, 1] + A[1, 3] * A[3, 2] * A[2, 1] + A[3, 1] * A[2, 3] * A[1, 2] + A[2, 2] * A[1, 1] * A[3, 3] - A[3, 3] * A[2, 1] * A[1, 2], - -A[1, 0] * A[2, 2] * A[3, 3] + A[2, 0] * A[3, 3] * A[1, 2] + A[2, 2] * A[1, 3] * A[3, 0] - A[2, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[3, 2] * A[2, 3] - A[1, 3] * A[3, 2] * A[2, 0], - A[1, 0] * A[3, 3] * A[2, 1] + A[2, 3] * A[1, 1] * A[3, 0] - A[2, 0] * A[1, 1] * A[3, 3] - A[1, 3] * A[3, 0] * A[2, 1] - A[1, 0] * A[3, 1] * A[2, 3] + A[3, 1] * A[1, 3] * A[2, 0], - A[3, 0] * A[2, 1] * A[1, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[3, 2] * A[2, 0] * A[1, 1] - A[2, 2] * A[1, 1] * A[3, 0] - A[3, 1] * A[2, 0] * A[1, 2] - A[1, 0] * A[3, 2] * A[2, 1]], - [A[3, 1] * A[2, 2] * A[0, 3] + A[0, 2] * A[3, 3] * A[2, 1] + A[0, 1] * A[3, 2] * A[2, 3] - A[3, 1] * A[0, 2] * A[2, 3] - A[0, 1] * A[2, 2] * A[3, 3] - A[3, 2] * A[0, 3] * A[2, 1], - -A[2, 2] * A[0, 3] * A[3, 0] - A[0, 2] * A[2, 0] * A[3, 3] - A[3, 2] * A[2, 3] * A[0, 0] + A[2, 2] * A[3, 3] * A[0, 0] + A[0, 2] * A[2, 3] * A[3, 0] + A[3, 2] * A[2, 0] * A[0, 3], - A[3, 1] * A[2, 3] * A[0, 0] - A[0, 1] * A[2, 3] * A[3, 0] - A[3, 1] * A[2, 0] * A[0, 3] - A[3, 3] * A[0, 0] * A[2, 1] + A[0, 3] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 0] * A[3, 3], - A[3, 2] * A[0, 0] * A[2, 1] - A[0, 2] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 2] * A[3, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 1] * A[3, 2] * A[2, 0] - A[3, 1] * A[2, 2] * A[0, 0]], - [A[3, 1] * A[1, 3] * A[0, 2] - A[0, 2] * A[1, 1] * A[3, 3] - A[3, 1] * A[0, 3] * A[1, 2] + A[3, 2] * A[1, 1] * A[0, 3] + A[0, 1] * A[3, 3] * A[1, 2] - A[0, 1] * A[1, 3] * A[3, 2], - A[1, 3] * A[3, 2] * A[0, 0] - A[1, 0] * A[3, 2] * A[0, 3] - A[1, 3] * A[0, 2] * A[3, 0] + A[0, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[0, 2] * A[3, 3] - A[3, 3] * A[0, 0] * A[1, 2], - -A[1, 0] * A[0, 1] * A[3, 3] + A[0, 1] * A[1, 3] * A[3, 0] - A[3, 1] * A[1, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[1, 0] * A[3, 1] * A[0, 3] + A[1, 1] * A[3, 3] * A[0, 0], - A[0, 2] * A[1, 1] * A[3, 0] - A[3, 2] * A[1, 1] * A[0, 0] - A[0, 1] * A[3, 0] * A[1, 2] - A[1, 0] * A[3, 1] * A[0, 2] + A[3, 1] * A[0, 0] * A[1, 2] + A[1, 0] * A[0, 1] * A[3, 2]], - [A[0, 3] * A[2, 1] * A[1, 2] + A[0, 2] * A[2, 3] * A[1, 1] + A[0, 1] * A[2, 2] * A[1, 3] - A[2, 2] * A[1, 1] * A[0, 3] - A[1, 3] * A[0, 2] * A[2, 1] - A[0, 1] * A[2, 3] * A[1, 2], - A[1, 0] * A[2, 2] * A[0, 3] + A[1, 3] * A[0, 2] * A[2, 0] - A[1, 0] * A[0, 2] * A[2, 3] - A[2, 0] * A[0, 3] * A[1, 2] - A[2, 2] * A[1, 3] * A[0, 0] + A[2, 3] * A[0, 0] * A[1, 2], - -A[0, 1] * A[1, 3] * A[2, 0] + A[2, 0] * A[1, 1] * A[0, 3] + A[1, 3] * A[0, 0] * A[2, 1] - A[1, 0] * A[0, 3] * A[2, 1] + A[1, 0] * A[0, 1] * A[2, 3] - A[2, 3] * A[1, 1] * A[0, 0], - A[1, 0] * A[0, 2] * A[2, 1] - A[0, 2] * A[2, 0] * A[1, 1] + A[0, 1] * A[2, 0] * A[1, 2] + A[2, 2] * A[1, 1] * A[0, 0] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2]] - ]) + return as_matrix([[ + -A[3, 1] * A[2, 2] * A[1, 3] - A[3, 2] * A[2, 3] * A[1, 1] + A[1, 3] * A[3, 2] * A[2, 1] + A[3, 1] * A[2, 3] * A[1, 2] + A[2, 2] * A[1, 1] * A[3, 3] - A[3, 3] * A[2, 1] * A[1, 2], # noqa: E501 + -A[1, 0] * A[2, 2] * A[3, 3] + A[2, 0] * A[3, 3] * A[1, 2] + A[2, 2] * A[1, 3] * A[3, 0] - A[2, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[3, 2] * A[2, 3] - A[1, 3] * A[3, 2] * A[2, 0], # noqa: E501 + A[1, 0] * A[3, 3] * A[2, 1] + A[2, 3] * A[1, 1] * A[3, 0] - A[2, 0] * A[1, 1] * A[3, 3] - A[1, 3] * A[3, 0] * A[2, 1] - A[1, 0] * A[3, 1] * A[2, 3] + A[3, 1] * A[1, 3] * A[2, 0], # noqa: E501 + A[3, 0] * A[2, 1] * A[1, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[3, 2] * A[2, 0] * A[1, 1] - A[2, 2] * A[1, 1] * A[3, 0] - A[3, 1] * A[2, 0] * A[1, 2] - A[1, 0] * A[3, 2] * A[2, 1], # noqa: E501 + ], [ + A[3, 1] * A[2, 2] * A[0, 3] + A[0, 2] * A[3, 3] * A[2, 1] + A[0, 1] * A[3, 2] * A[2, 3] - A[3, 1] * A[0, 2] * A[2, 3] - A[0, 1] * A[2, 2] * A[3, 3] - A[3, 2] * A[0, 3] * A[2, 1], # noqa: E501 + -A[2, 2] * A[0, 3] * A[3, 0] - A[0, 2] * A[2, 0] * A[3, 3] - A[3, 2] * A[2, 3] * A[0, 0] + A[2, 2] * A[3, 3] * A[0, 0] + A[0, 2] * A[2, 3] * A[3, 0] + A[3, 2] * A[2, 0] * A[0, 3], # noqa: E501 + A[3, 1] * A[2, 3] * A[0, 0] - A[0, 1] * A[2, 3] * A[3, 0] - A[3, 1] * A[2, 0] * A[0, 3] - A[3, 3] * A[0, 0] * A[2, 1] + A[0, 3] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 0] * A[3, 3], # noqa: E501 + A[3, 2] * A[0, 0] * A[2, 1] - A[0, 2] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 2] * A[3, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 1] * A[3, 2] * A[2, 0] - A[3, 1] * A[2, 2] * A[0, 0], # noqa: E501 + ], [ + A[3, 1] * A[1, 3] * A[0, 2] - A[0, 2] * A[1, 1] * A[3, 3] - A[3, 1] * A[0, 3] * A[1, 2] + A[3, 2] * A[1, 1] * A[0, 3] + A[0, 1] * A[3, 3] * A[1, 2] - A[0, 1] * A[1, 3] * A[3, 2], # noqa: E501 + A[1, 3] * A[3, 2] * A[0, 0] - A[1, 0] * A[3, 2] * A[0, 3] - A[1, 3] * A[0, 2] * A[3, 0] + A[0, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[0, 2] * A[3, 3] - A[3, 3] * A[0, 0] * A[1, 2], # noqa: E501 + -A[1, 0] * A[0, 1] * A[3, 3] + A[0, 1] * A[1, 3] * A[3, 0] - A[3, 1] * A[1, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[1, 0] * A[3, 1] * A[0, 3] + A[1, 1] * A[3, 3] * A[0, 0], # noqa: E501 + A[0, 2] * A[1, 1] * A[3, 0] - A[3, 2] * A[1, 1] * A[0, 0] - A[0, 1] * A[3, 0] * A[1, 2] - A[1, 0] * A[3, 1] * A[0, 2] + A[3, 1] * A[0, 0] * A[1, 2] + A[1, 0] * A[0, 1] * A[3, 2], # noqa: E501 + ], [ + A[0, 3] * A[2, 1] * A[1, 2] + A[0, 2] * A[2, 3] * A[1, 1] + A[0, 1] * A[2, 2] * A[1, 3] - A[2, 2] * A[1, 1] * A[0, 3] - A[1, 3] * A[0, 2] * A[2, 1] - A[0, 1] * A[2, 3] * A[1, 2], # noqa: E501 + A[1, 0] * A[2, 2] * A[0, 3] + A[1, 3] * A[0, 2] * A[2, 0] - A[1, 0] * A[0, 2] * A[2, 3] - A[2, 0] * A[0, 3] * A[1, 2] - A[2, 2] * A[1, 3] * A[0, 0] + A[2, 3] * A[0, 0] * A[1, 2], # noqa: E501 + -A[0, 1] * A[1, 3] * A[2, 0] + A[2, 0] * A[1, 1] * A[0, 3] + A[1, 3] * A[0, 0] * A[2, 1] - A[1, 0] * A[0, 3] * A[2, 1] + A[1, 0] * A[0, 1] * A[2, 3] - A[2, 3] * A[1, 1] * A[0, 0], # noqa: E501 + A[1, 0] * A[0, 2] * A[2, 1] - A[0, 2] * A[2, 0] * A[1, 1] + A[0, 1] * A[2, 0] * A[1, 2] + A[2, 2] * A[1, 1] * A[0, 0] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2], # noqa: E501 + ]]) def deviatoric_expr(A): diff --git a/ufl/constant.py b/ufl/constant.py index f2359cc7c..66e7a8f33 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -58,8 +58,7 @@ def __eq__(self, other): return False if self is other: return True - return (self._count == other._count and - self._ufl_domain == other._ufl_domain and + return (self._count == other._count and self._ufl_domain == other._ufl_domain and # noqa: W504 self._ufl_shape == self._ufl_shape) def _ufl_signature_data_(self, renumbering): diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index a67c25099..1367e6036 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -94,16 +94,15 @@ def _init(self, shape=(), free_indices=(), index_dimensions=None): self.ufl_free_indices = () self.ufl_index_dimensions = () elif all(isinstance(i, Index) for i in free_indices): # Handle old input format - if not (isinstance(index_dimensions, dict) and - all(isinstance(i, Index) for i in index_dimensions.keys())): + if not isinstance(index_dimensions, dict) and all(isinstance(i, Index) for i in index_dimensions.keys()): raise ValueError(f"Expecting tuple of index dimensions, not {index_dimensions}") self.ufl_free_indices = tuple(sorted(i.count() for i in free_indices)) - self.ufl_index_dimensions = tuple(d for i, d in sorted(index_dimensions.items(), key=lambda x: x[0].count())) + self.ufl_index_dimensions = tuple( + d for i, d in sorted(index_dimensions.items(), key=lambda x: x[0].count())) else: # Handle new input format if not all(isinstance(i, int) for i in free_indices): raise ValueError(f"Expecting tuple of integer free index ids, not {free_indices}") - if not (isinstance(index_dimensions, tuple) and - all(isinstance(i, int) for i in index_dimensions)): + if not isinstance(index_dimensions, tuple) and all(isinstance(i, int) for i in index_dimensions): raise ValueError(f"Expecting tuple of integer index dimensions, not {index_dimensions}") # Assuming sorted now to avoid this cost, enable for debugging: @@ -136,8 +135,8 @@ def __eq__(self, other): if isinstance(other, Zero): if self is other: return True - return (self.ufl_shape == other.ufl_shape and - self.ufl_free_indices == other.ufl_free_indices and + return (self.ufl_shape == other.ufl_shape and # noqa: W504 + self.ufl_free_indices == other.ufl_free_indices and # noqa: W504 self.ufl_index_dimensions == other.ufl_index_dimensions) elif isinstance(other, (int, float)): return other == 0 diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 55857b8c7..9b9d15b8d 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -226,15 +226,6 @@ def __iter__(self): return iter(self._indices) -def as_multi_index(ii, shape=None): - "Return a ``MultiIndex`` version of *ii*." - if isinstance(ii, MultiIndex): - return ii - elif not isinstance(ii, tuple): - ii = (ii,) - return MultiIndex(ii) - - def indices(n): "UFL value: Return a tuple of :math:`n` new Index objects." return tuple(Index() for i in range(n)) diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index c66c2463d..8f7ee653b 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -27,12 +27,6 @@ class Terminal(Expr): def __init__(self): Expr.__init__(self) - def _ufl_expr_reconstruct_(self, *operands): - "Return self." - if operands: - raise ValueError("Terminal has no operands.") - return self - ufl_operands = () ufl_free_indices = () ufl_index_dimensions = () diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index df6c991e5..85b917e79 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -114,15 +114,15 @@ def determine_num_ops(cls, num_ops, unop, binop, rbinop): def check_is_terminal_consistency(cls): "Check for consistency in ``is_terminal`` trait among superclasses." if cls._ufl_is_terminal_ is None: - msg = ("Class {0.__name__} has not specified the is_terminal trait." + + msg = (f"Class {cls.__name__} has not specified the is_terminal trait." " Did you forget to inherit from Terminal or Operator?") - raise TypeError(msg.format(cls)) + raise TypeError(msg) base_is_terminal = get_base_attr(cls, "_ufl_is_terminal_") if base_is_terminal is not None and cls._ufl_is_terminal_ != base_is_terminal: - msg = ("Conflicting given and automatic 'is_terminal' trait for class {0.__name__}." + + msg = (f"Conflicting given and automatic 'is_terminal' trait for class {cls.__name__}." " Check if you meant to inherit from Terminal or Operator.") - raise TypeError(msg.format(cls)) + raise TypeError(msg) def check_abstract_trait_consistency(cls): diff --git a/ufl/differentiation.py b/ufl/differentiation.py index d1155387e..75c4197db 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -367,7 +367,6 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): - global _curl_shapes CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] @@ -395,7 +394,6 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): - global _curl_shapes CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] diff --git a/ufl/equation.py b/ufl/equation.py index 316e99cf4..2800da880 100644 --- a/ufl/equation.py +++ b/ufl/equation.py @@ -44,13 +44,10 @@ def __bool__(self): def __eq__(self, other): "Compare two equations by comparing lhs and rhs." - return isinstance(other, Equation) and \ - bool(self.lhs == other.lhs) and \ - bool(self.rhs == other.rhs) + return isinstance(other, Equation) and self.lhs == other.lhs and self.rhs == other.rhs def __hash__(self): return hash((hash(self.lhs), hash(self.rhs))) def __repr__(self): - r = "Equation(%s, %s)" % (repr(self.lhs), repr(self.rhs)) - return r + return f"Equation({self.lhs!r}, {self.rhs!r})" diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index 76858b19b..f7e717061 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -80,15 +80,6 @@ def ufl_domains(self): else: return [] - # def __getitem__(self, key): - # return self.ufl_operands[key] - - # def __len__(self): - # return len(self.ufl_operands) // 2 - - # def __iter__(self): - # return iter(self.ufl_operands[::2]) - def __str__(self): return "ExprMapping(*%s)" % repr(self.ufl_operands) diff --git a/ufl/exprequals.py b/ufl/exprequals.py index 16505ee05..e3cdd3c38 100644 --- a/ufl/exprequals.py +++ b/ufl/exprequals.py @@ -2,108 +2,13 @@ from collections import defaultdict -from ufl.core.expr import Expr - hash_total = defaultdict(int) hash_collisions = defaultdict(int) hash_equals = defaultdict(int) hash_notequals = defaultdict(int) -def print_collisions(): - - keys = sorted(hash_total.keys(), key=lambda x: (hash_collisions[x], x)) - - print("Collision statistics ({0} keys):".format(len(keys))) - print("[key: equals; notequals; collisions]") - n = max(len(str(k)) for k in keys) - fmt = ("%%%ds" % n) + ": \t %6d (%3d%%); %6d (%3d%%); %6d (%3d%%) col; tot %d" - for k in keys: - co = hash_collisions[k] - eq = hash_equals[k] - ne = hash_notequals[k] - tot = hash_total[k] - sn, on = k - # Skip those that are all not equal - if sn != on and ne == tot: - continue - print(fmt % (k, eq, int(100.0 * eq / tot), - ne, int(100.0 * ne / tot), - co, int(100.0 * co / tot), - tot)) - - -def measure_collisions(equals_func): - def equals_func_with_collision_measuring(self, other): - # Call equals - equal = equals_func(self, other) - - # Get properties - st = type(self) - ot = type(other) - sn = st.__name__ - on = ot.__name__ - sh = hash(self) - oh = hash(other) - key = (sn, on) - - # If hashes are the same but objects are not equal, we have a - # collision - hash_total[key] += 1 - if sh == oh and not equal: - hash_collisions[key] += 1 - elif sh != oh and equal: - raise ValueError(f"Equal objects must always have the same hash! Objects are:\n{self}\n{other}") - elif sh == oh and equal: - hash_equals[key] += 1 - elif sh != oh and not equal: - hash_notequals[key] += 1 - - return equal - return equals_func_with_collision_measuring - - -# @measure_collisions -def recursive_expr_equals(self, other): # Much faster than the more complex algorithms above! - """Checks whether the two expressions are represented the - exact same way. This does not check if the expressions are - mathematically equal or equivalent! Used by sets and dicts.""" - - # To handle expr == int/float - if not isinstance(other, Expr): - return False - - # Fast cutoff for common case - if self._ufl_typecode_ != other._ufl_typecode_: - return False - - # Compare hashes, will cutoff more or less all nonequal types - if hash(self) != hash(other): - return False - - # Large objects are costly to compare with themselves - if self is other: - return True - - # Terminals - if self._ufl_is_terminal_: - # Compare terminals with custom == to capture subclass - # overloading of __eq__ - return self == other - - # --- Operators, most likely equal, below here is the costly part - # --- if it recurses through a large tree! --- - - # Recurse manually to call expr_equals directly without the class - # EQ overhead! - equal = all(recursive_expr_equals(a, b) for (a, b) in zip(self.ufl_operands, - other.ufl_operands)) - - return equal - - -# @measure_collisions -def nonrecursive_expr_equals(self, other): +def expr_equals(self, other): """Checks whether the two expressions are represented the exact same way. This does not check if the expressions are mathematically equal or equivalent! Used by sets and dicts.""" @@ -147,7 +52,3 @@ def nonrecursive_expr_equals(self, other): # Eagerly DAGify to reduce the size of the tree. self.ufl_operands = other.ufl_operands return True - - -# expr_equals = recursive_expr_equals -expr_equals = nonrecursive_expr_equals diff --git a/ufl/exproperators.py b/ufl/exproperators.py index 72fa1039f..8715f18de 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -11,7 +11,6 @@ # # Modified by Massimiliano Leoni, 2016. -from itertools import chain import numbers from ufl.utils.stacks import StackDict @@ -19,7 +18,7 @@ from ufl.constantvalue import Zero, as_ufl from ufl.algebra import Sum, Product, Division, Power, Abs from ufl.tensoralgebra import Transposed, Inner -from ufl.core.multiindex import MultiIndex, Index, FixedIndex, IndexBase, indices +from ufl.core.multiindex import MultiIndex, Index, indices from ufl.indexed import Indexed from ufl.indexsum import IndexSum from ufl.tensors import as_tensor, ComponentTensor @@ -345,86 +344,6 @@ def _transpose(self): # --- Extend Expr with indexing operator a[i] --- -def analyse_key(ii, rank): - """Takes something the user might input as an index tuple - inside [], which could include complete slices (:) and - ellipsis (...), and returns tuples of actual UFL index objects. - - The return value is a tuple (indices, axis_indices), - each being a tuple of IndexBase instances. - - The return value 'indices' corresponds to all - input objects of these types: - - Index - - FixedIndex - - int => Wrapped in FixedIndex - - The return value 'axis_indices' corresponds to all - input objects of these types: - - Complete slice (:) => Replaced by a single new index - - Ellipsis (...) => Replaced by multiple new indices - """ - # Wrap in tuple - if not isinstance(ii, (tuple, MultiIndex)): - ii = (ii,) - else: - # Flatten nested tuples, happens with f[...,ii] where ii is a - # tuple of indices - jj = [] - for j in ii: - if isinstance(j, (tuple, MultiIndex)): - jj.extend(j) - else: - jj.append(j) - ii = tuple(jj) - - # Convert all indices to Index or FixedIndex objects. If there is - # an ellipsis, split the indices into before and after. - axis_indices = set() - pre = [] - post = [] - indexlist = pre - for i in ii: - if i == Ellipsis: - # Switch from pre to post list when an ellipsis is - # encountered - if indexlist is not pre: - raise ValueError("Found duplicate ellipsis.") - indexlist = post - else: - # Convert index to a proper type - if isinstance(i, numbers.Integral): - idx = FixedIndex(i) - elif isinstance(i, IndexBase): - idx = i - elif isinstance(i, slice): - if i == slice(None): - idx = Index() - axis_indices.add(idx) - else: - # TODO: Use ListTensor to support partial slices? - raise ValueError("Partial slices not implemented, only complete slices like [:]") - else: - raise ValueError(f"Can't convert this object to index: {i}") - - # Store index in pre or post list - indexlist.append(idx) - - # Handle ellipsis as a number of complete slices, that is create a - # number of new axis indices - num_axis = rank - len(pre) - len(post) - if indexlist is post: - ellipsis_indices = indices(num_axis) - axis_indices.update(ellipsis_indices) - else: - ellipsis_indices = () - - # Construct final tuples to return - all_indices = tuple(chain(pre, ellipsis_indices, post)) - axis_indices = tuple(i for i in all_indices if i in axis_indices) - return all_indices, axis_indices - - def _getitem(self, component): # Treat component consistently as tuple below diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index b976b7e43..da3855641 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -56,12 +56,12 @@ def show_elements(): continue shown.add(data) (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) = data - print("Finite element family: '%s', '%s'" % (family, short_name)) - print("Sobolev space: %s" % (sobolev_space,)) - print("Mapping: %s" % (mapping,)) - print("Degree range: %s" % (degree_range,)) - print("Value rank: %s" % (value_rank,)) - print("Defined on cellnames: %s" % (cellnames,)) + print(f"Finite element family: '{family}', '{short_name}'") + print(f"Sobolev space: {sobolev_space}%s") + print(f"Mapping: {mapping}") + print(f"Degree range: {degree_range}") + print(f"Value rank: {value_rank}") + print(f"Defined on cellnames: {cellnames}") print() @@ -266,7 +266,8 @@ def show_elements(): ("quadrilateral",)) -# NOTE- the edge elements for primal mimetic spectral elements are accessed by using variant='mse' in the appropriate places +# NOTE- the edge elements for primal mimetic spectral elements are accessed by using +# variant='mse' in the appropriate places def feec_element(family, n, r, k): """Finite element exterior calculus notation @@ -432,7 +433,8 @@ def canonical_element_description(family, cell, order, form_degree): family = "DQ" elif family == "Discontinuous Lagrange L2": if order >= 1: - warnings.warn("Discontinuous Lagrange L2 element requested on %s, creating DQ L2 element." % cell.cellname()) + warnings.warn(f"Discontinuous Lagrange L2 element requested on {cell.cellname()}, " + "creating DQ L2 element.") family = "DQ L2" # Validate cellname if a valid cell is specified diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index ca7071698..f603e12dc 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -147,7 +147,9 @@ def __init__(self, if cell is not None: cell = as_cell(cell) - family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = canonical_element_description(family, cell, degree, form_degree) + ( + family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping + ) = canonical_element_description(family, cell, degree, form_degree) # TODO: Move these to base? Might be better to instead # simplify base though. diff --git a/ufl/form.py b/ufl/form.py index 9c4ab0eda..1c7b8cf95 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -670,13 +670,6 @@ def _compute_signature(self): self._signature = compute_form_signature(self, self._compute_renumbering()) -def sub_forms_by_domain(form): - "Return a list of forms each with an integration domain" - if not isinstance(form, Form): - raise ValueError(f"Unable to convert object to a UFL form: {ufl_err_str(form)}") - return [Form(form.integrals_by_domain(domain)) for domain in form.ufl_domains()] - - def as_form(form): "Convert to form if not a form, otherwise return form." if not isinstance(form, BaseForm): @@ -684,36 +677,6 @@ def as_form(form): return form -def replace_integral_domains(form, common_domain): # TODO: Move elsewhere - """Given a form and a domain, assign a common integration domain to - all integrals. - - Does not modify the input form (``Form`` should always be - immutable). This is to support ill formed forms with no domain - specified, sometimes occurring in pydolfin, e.g. assemble(1*dx, - mesh=mesh). - - """ - domains = form.ufl_domains() - if common_domain is not None: - gdim = common_domain.geometric_dimension() - tdim = common_domain.topological_dimension() - if not all((gdim == domain.geometric_dimension() and tdim == domain.topological_dimension()) for domain in domains): - raise ValueError("Common domain does not share dimensions with form domains.") - - reconstruct = False - integrals = [] - for itg in form.integrals(): - domain = itg.ufl_domain() - if domain != common_domain: - itg = itg.reconstruct(domain=common_domain) - reconstruct = True - integrals.append(itg) - if reconstruct: - form = Form(integrals) - return form - - @ufl_type() class FormSum(BaseForm): """Description of a weighted sum of variational forms and form-like objects @@ -795,7 +758,7 @@ def equals(self, other): return False if self is other: return True - return (len(self.components()) == len(other.components()) and + return (len(self.components()) == len(other.components()) and # noqa: W504 all(a == b for a, b in zip(self.components(), other.components()))) def __str__(self): diff --git a/ufl/formatting/graph.py b/ufl/formatting/graph.py deleted file mode 100644 index 8c4dfdf6a..000000000 --- a/ufl/formatting/graph.py +++ /dev/null @@ -1,278 +0,0 @@ -# -*- coding: utf-8 -*- -"""Algorithms for working with linearized computational graphs.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from collections import defaultdict -from heapq import heapify, heappop - -from ufl.corealg.traversal import unique_pre_traversal -from ufl.corealg.multifunction import MultiFunction - -# O(n) = O(|V|) = O(|E|), since |E| < c|V| for a fairly small c. - - -# --- Basic utility functions --- - -def lists(n): - return [[] for i in range(n)] - - -def len_items(sequence): - return list(map(len, sequence)) - - -# --- Graph building functions --- - -def build_graph(expr): # O(n) - """Build a linearized graph from an UFL Expr. - - Returns G = (V, E), with V being a list of - graph nodes (Expr objects) in post traversal - ordering and E being a list of edges. Each edge - is represented as a (i, j) tuple where i and j - are vertex indices into V. - """ - V = [] - E = [] - handled = {} - for v in reversed(list(unique_pre_traversal(expr))): - i = handled.get(v) - if i is None: - i = len(V) - handled[v] = i - V.append(v) - for o in v.ufl_operands: - j = handled[o] - e = (i, j) - E.append(e) - G = V, E - return G - - -def extract_incoming_edges(G): # O(n) - "Build lists of incoming edges to each vertex in a linearized graph." - V, E = G - n = len(V) - Ein = lists(n) - for i, e in enumerate(E): - Ein[e[1]].append(i) - return Ein - - -def extract_outgoing_edges(G): # O(n) - "Build list of outgoing edges from each vertex in a linearized graph." - V, E = G - n = len(V) - Eout = lists(n) - for i, e in enumerate(E): - Eout[e[0]].append(i) - return Eout - - -def extract_incoming_vertex_connections(G): # O(n) - """Build lists of vertices in incoming and outgoing - edges to and from each vertex in a linearized graph. - - Returns lists Vin and Vout.""" - V, E = G - n = len(V) - Vin = lists(n) - for a, b in E: - Vin[b].append(a) - return Vin - - -def extract_outgoing_vertex_connections(G): # O(n) - """Build lists of vertices in incoming and outgoing - edges to and from each vertex in a linearized graph. - - Returns lists Vin and Vout.""" - V, E = G - n = len(V) - Vout = lists(n) - for a, b in E: - Vout[a].append(b) - return Vout - - -# --- Graph class --- - -class Graph: - "Graph class which computes connectivity on demand." - - def __init__(self, expression): - self._V, self._E = build_graph(expression) - self._Ein = None - self._Eout = None - self._Vin = None - self._Vout = None - - def V(self): - return self._V - - def E(self): - return self._E - - def Ein(self): - if self._Ein is None: - self._Ein = extract_incoming_edges((self._V, self._E)) - return self._Ein - - def Eout(self): - if self._Eout is None: - self._Eout = extract_outgoing_edges((self._V, self._E)) - return self._Eout - - def Vin(self): - if self._Vin is None: - self._Vin = extract_incoming_vertex_connections((self._V, self._E)) - return self._Vin - - def Vout(self): - if self._Vout is None: - self._Vout = extract_outgoing_vertex_connections((self._V, self._E)) - return self._Vout - - def __iter__(self): - return iter((self._V, self._E)) - - -# --- Scheduling algorithms --- - -class HeapItem(object): - def __init__(self, incoming, outgoing, i): - self.incoming = incoming - self.outgoing = outgoing - self.i = i - - def __lt__(self, other): - a = (self.outgoing[self.i], self.incoming[self.i]) - b = (other.outgoing[other.i], other.incoming[other.i]) - return a < b - - def __le__(self, other): - a = (self.outgoing[self.i], self.incoming[self.i]) - b = (other.outgoing[other.i], other.incoming[other.i]) - return a <= b - - def __eq__(self, other): - a = (self.outgoing[self.i], self.incoming[self.i]) - b = (other.outgoing[other.i], other.incoming[other.i]) - return a == b - - -def depth_first_ordering(G): - V, E = G - Vin = G.Vin() - Vout = G.Vout() - Ein_count = len_items(Vin) - Eout_count = len_items(Vout) - - # Make a list and a heap of the same items - n = len(V) - q = [HeapItem(Ein_count, Eout_count, i) for i in range(n)] - heapify(q) - - ordering = [] - while q: - item = heappop(q) - iv = item.i - ordering.append(iv) - for i in Vin[iv]: - Eout_count[i] -= 1 - # Resort heap, worst case linear time, makes this algorithm - # O(n^2)... TODO: Not good! - heapify(q) - - # TODO: Can later accumulate dependencies as well during dft-like - # algorithm. - return ordering - - -# --- Graph partitoning --- - -class StringDependencyDefiner(MultiFunction): - """Given an expr, returns a frozenset of its dependencies. - - Possible dependency values are: - "c" - depends on runtime information like the cell, local<->global coordinate mappings, facet normals, or coefficients - "x" - depends on local coordinates - "v%d" % i - depends on argument i, for i in [0,rank) - """ - - def __init__(self, argument_deps=None, coefficient_deps=None): - MultiFunction.__init__(self) - if argument_deps is None: - argument_deps = {} - if coefficient_deps is None: - coefficient_deps = {} - self.argument_deps = argument_deps - self.coefficient_deps = coefficient_deps - - def expr(self, o): - return frozenset() - - def argument(self, x): - default = frozenset(("v%d" % x.number(), "x")) # TODO: This is missing the part, but this code is ready for deletion anyway? - return self.argument_deps.get(x, default) - - def coefficient(self, x): - default = frozenset(("c", "x")) - return self.coefficient_deps.get(x, default) - - def geometric_quantity(self, x): - deps = frozenset(("c", "x",)) - return deps - - def facet_normal(self, o): - deps = frozenset(("c",)) - # Enabling coordinate dependency for higher order geometries - # (not handled anywhere else though, so consider this experimental) - # if o.has_higher_degree_cell_geometry(): - # deps = deps | frozenset(("x",)) - return deps - - def spatial_derivative(self, o): # TODO: What about (basis) functions of which derivatives are constant? Should special-case spatial_derivative in partition(). - deps = frozenset(("c",)) - # Enabling coordinate dependency for higher order geometries - # (not handled anywhere else though). - # if o.has_higher_degree_cell_geometry(): - # deps = deps | frozenset(("x",)) - return deps - - -dd = StringDependencyDefiner() - - -def string_set_criteria(v, keys): - # Using sets of ufl objects - key = dd(v) - for k in keys: - key |= k - return frozenset(key) - - -def partition(G, criteria=string_set_criteria): - V, E = G - n = len(V) - - Vout = G.Vout() - - partitions = defaultdict(list) - keys = [None] * n - for iv, v in enumerate(V): - # Get keys from all outgoing edges - edge_keys = [keys[ii] for ii in Vout[iv]] - - # Calculate key for this vertex - key = criteria(v, edge_keys) - - # Store mappings from key to vertex and back - partitions[key].append(iv) - keys[iv] = key - return partitions, keys diff --git a/ufl/formatting/printing.py b/ufl/formatting/printing.py deleted file mode 100644 index 2c40e85fa..000000000 --- a/ufl/formatting/printing.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -"""A collection of utility algorithms for printing -of UFL objects, mostly intended for debugging purposes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg 2009, 2014 - -from ufl.core.expr import Expr -from ufl.form import Form -from ufl.integral import Integral - - -# --- Utilities for constructing informative strings from UFL objects - -def integral_info(integral): - if not isinstance(integral, Integral): - raise ValueError("Expecting an Integral.") - s = " Integral:\n" - s += " Type:\n" - s += " %s\n" % integral.integral_type() - s += " Domain:\n" - s += " %s\n" % integral.ufl_domain() - s += " Domain id:\n" - s += " %s\n" % integral.subdomain_id() - s += " Domain data:\n" - s += " %s\n" % integral.subdomain_data() - s += " Compiler metadata:\n" - s += " %s\n" % integral.metadata() - return s - - -def form_info(form): - if not isinstance(form, Form): - raise ValueError("Expecting a Form.") - - bf = form.arguments() - cf = form.coefficients() - - s = "Form info:\n" - s += " rank: %d\n" % len(bf) - s += " num_coefficients: %d\n" % len(cf) - s += "\n" - - for f in cf: - if f._name: - s += "\n" - s += " Coefficient %d is named '%s'" % (f._count, f._name) - s += "\n" - - integrals = form.integrals() - integral_types = sorted(set(itg.integral_type() for itg in integrals)) - for integral_type in integral_types: - itgs = form.integrals_by_type(integral_type) - s += " num_{0}_integrals: {1}\n".format(integral_type, len(itgs)) - s += "\n" - - for integral_type in integral_types: - itgs = form.integrals_by_type(integral_type) - for itg in itgs: - s += integral_info(itg) - s += "\n" - - return s - - -def _indent_string(n): - return " " * n - - -def _tree_format_expression(expression, indentation, parentheses): - ind = _indent_string(indentation) - if expression._ufl_is_terminal_: - s = "%s%s" % (ind, repr(expression)) - else: - sops = [_tree_format_expression(o, indentation + 1, parentheses) for o in expression.ufl_operands] - s = "%s%s\n" % (ind, expression._ufl_class_.__name__) - if parentheses and len(sops) > 1: - s += "%s(\n" % (ind,) - s += "\n".join(sops) - if parentheses and len(sops) > 1: - s += "\n%s)" % (ind,) - return s - - -def tree_format(expression, indentation=0, parentheses=True): - s = "" - - if isinstance(expression, Form): - form = expression - integrals = form.integrals() - integral_types = sorted(set(itg.integral_type() for itg in integrals)) - itgs = [] - for integral_type in integral_types: - itgs += list(form.integrals_by_type(integral_type)) - - ind = _indent_string(indentation) - s += ind + "Form:\n" - s += "\n".join(tree_format(itg, indentation + 1, parentheses) for itg in itgs) - - elif isinstance(expression, Integral): - ind = _indent_string(indentation) - s += ind + "Integral:\n" - ind = _indent_string(indentation + 1) - s += ind + "integral type: %s\n" % expression.integral_type() - s += ind + "subdomain id: %s\n" % expression.subdomain_id() - s += ind + "integrand:\n" - s += tree_format(expression._integrand, indentation + 2, parentheses) - - elif isinstance(expression, Expr): - s += _tree_format_expression(expression, indentation, parentheses) - - else: - raise ValueError(f"Invalid object type {type(expression)}") - - return s diff --git a/ufl/formatting/ufl2dot.py b/ufl/formatting/ufl2dot.py deleted file mode 100644 index ab219343e..000000000 --- a/ufl/formatting/ufl2dot.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- -"""A collection of utility algorithms for printing -of UFL objects in the DOT graph visualization language, -mostly intended for debugging purposers.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -from ufl.core.expr import Expr -from ufl.form import Form -from ufl.variable import Variable -from ufl.algorithms.multifunction import MultiFunction - - -class ReprLabeller(MultiFunction): - def __init__(self): - MultiFunction.__init__(self) - - def terminal(self, e): - return repr(e) - - def operator(self, e): - return e._ufl_class_.__name__.split(".")[-1] - - -class CompactLabeller(ReprLabeller): - def __init__(self, function_mapping=None): - ReprLabeller.__init__(self) - self._function_mapping = function_mapping - - # Terminals: - def scalar_value(self, e): - return repr(e._value) - - def zero(self, e): - return "0" - - def identity(self, e): - return "Id" - - def multi_index(self, e): - return str(e) - - def form_argument(self, e): - return self._function_mapping.get(id(e)) or str(e) - - def geometric_quantity(self, e): - return str(e) - - # Operators: - def sum(self, e): - return "+" - - def product(self, e): - return "*" - - def division(self, e): - return "/" - - def power(self, e): - return "**" - - def math_function(self, e): - return e._name - - def index_sum(self, e): - return "∑" - - def indexed(self, e): - return "[]" - - def component_tensor(self, e): # TODO: Understandable short notation for this? - return "][" - - def negative_restricted(self, e): - return "[-]" - - def positive_restricted(self, e): - return "[+]" - - def cell_avg(self, e): # TODO: Understandable short notation for this? - return "_K_" - - def facet_avg(self, e): # TODO: Understandable short notation for this? - return "_F_" - - def conj(self, e): - return "conj" - - def real(self, e): - return "real" - - def imag(self, e): - return "imag" - - def inner(self, e): - return "inner" - - def dot(self, e): - return "dot" - - def outer(self, e): - return "outer" - - def transposed(self, e): - return "transp." - - def determinant(self, e): - return "det" - - def trace(self, e): - return "tr" - - def dev(self, e): - return "dev" - - def skew(self, e): - return "skew" - - def grad(self, e): - return "grad" - - def div(self, e): - return "div" - - def curl(self, e): - return "curl" - - def nabla_grad(self, e): - return "nabla_grad" - - def nabla_div(self, e): - return "nabla_div" - - def diff(self, e): - return "diff" - - -# Make this class like the ones above to use fancy math symbol labels -class2label = {"IndexSum": "∑", - "Sum": "∑", - "Product": "∏", - "Division": "/", - "Inner": ":", - "Dot": "⋅", - "Outer": "⊗", - "Grad": "grad", - "Div": "div", - "NablaGrad": "∇⊗", - "NablaDiv": "∇⋅", - "Curl": "∇×", } - - -class FancyLabeller(CompactLabeller): - pass - - -def build_entities(e, nodes, edges, nodeoffset, prefix="", labeller=None): - # TODO: Maybe this can be cleaner written using the graph - # utilities. - # TODO: To collapse equal nodes with different objects, do not use - # id as key. Make this an option? - - # Cutoff if we have handled e before - if id(e) in nodes: - return - if labeller is None: - labeller = ReprLabeller() - - # Special-case Variable instances - if isinstance(e, Variable): # FIXME: Is this really necessary? - ops = (e._expression,) - label = "variable %d" % e._label._count - else: - ops = e.ufl_operands - label = labeller(e) - - # Create node for parent e - nodename = "%sn%04d" % (prefix, len(nodes) + nodeoffset) - nodes[id(e)] = (nodename, label) - - # Handle all children recursively - n = len(ops) - if n == 2: - oplabels = ["L", "R"] - elif n > 2: - oplabels = ["op%d" % i for i in range(n)] - else: - oplabels = [None] * n - - for i, o in enumerate(ops): - # Handle entire subtree for expression o - build_entities(o, nodes, edges, nodeoffset, prefix, labeller) - - # Add edge between e and child node o - edges.append((id(e), id(o), oplabels[i])) - - -def format_entities(nodes, edges): - entities = [] - for (nodename, label) in nodes.values(): - node = ' %s [label="%s"];' % (nodename, label) - entities.append(node) - for (aid, bid, label) in edges: - anodename = nodes[aid][0] - bnodename = nodes[bid][0] - if label is None: - edge = ' %s -> %s ;' % (anodename, bnodename) - else: - edge = ' %s -> %s [label="%s"] ;' % (anodename, bnodename, label) - entities.append(edge) - return "\n".join(entities) - - -integralgraphformat = """ %(node)s [label="%(label)s"] - form_%(formname)s -> %(node)s ; - %(node)s -> %(root)s ; -%(entities)s""" - -exprgraphformat = """ digraph ufl_expression - { - %s - }""" - - -def ufl2dot(expression, formname="a", nodeoffset=0, begin=True, end=True, - labeling="repr", object_names=None): - if labeling == "repr": - labeller = ReprLabeller() - elif labeling == "compact": - labeller = CompactLabeller(object_names or {}) - print(object_names) - - if isinstance(expression, Form): - form = expression - - subgraphs = [] - k = 0 - for itg in form.integrals(): - prefix = "itg%d_" % k - integralkey = "%s%s" % (itg.integral_type(), itg.subdomain_id()) - - integrallabel = "%s %s" % (itg.integral_type().capitalize().replace("_", " "), "integral") - integrallabel += " %s" % (itg.subdomain_id(),) - - integrand = itg.integrand() - - nodes = {} - edges = [] - - build_entities(integrand, nodes, edges, nodeoffset, prefix, - labeller) - rootnode = nodes[id(integrand)][0] - entitylist = format_entities(nodes, edges) - integralnode = "%s_%s" % (formname, integralkey) - subgraphs.append(integralgraphformat % { - 'node': integralnode, - 'label': integrallabel, - 'formname': formname, - 'root': rootnode, - 'entities': entitylist, }) - nodeoffset += len(nodes) - - s = "" - if begin: - s += 'digraph ufl_form\n{\n node [shape="box"] ;\n' - s += ' form_%s [label="Form %s"] ;' % (formname, formname) - s += "\n".join(subgraphs) - if end: - s += "\n}" - - elif isinstance(expression, Expr): - nodes = {} - edges = [] - - build_entities(expression, nodes, edges, nodeoffset, '', labeller) - entitylist = format_entities(nodes, edges) - s = exprgraphformat % entitylist - - nodeoffset += len(nodes) - - else: - raise ValueError(f"Invalid object type {type(expression)}") - - return s, nodeoffset diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 49cbf1d4d..8fda43db7 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -460,8 +460,8 @@ def label(self, o): # --- Non-terminal objects --- - def variable(self, o, f, l): - return "var(%s,%s)" % (f, l) + def variable(self, o, f, a): + return "var(%s,%s)" % (f, a) def index_sum(self, o, f, i): if 1: # prec(o.ufl_operands[0]) >? prec(o): diff --git a/ufl/formoperators.py b/ufl/formoperators.py index e2b60b4b7..355bc9713 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -35,6 +35,7 @@ from ufl.algorithms import compute_energy_norm from ufl.algorithms import compute_form_lhs, compute_form_rhs, compute_form_functional from ufl.algorithms import expand_derivatives, extract_arguments +from ufl.algorithms import formsplitter # Part of the external interface from ufl.algorithms import replace # noqa @@ -52,7 +53,7 @@ def extract_blocks(form, i=None, j=None): extract_blocks(a) -> [inner(grad(u), grad(v))*dx, div(v)*p*dx, div(u)*q*dx, 0] """ - return ufl.algorithms.formsplitter.extract_blocks(form, i, j) + return formsplitter.extract_blocks(form, i, j) def lhs(form): @@ -389,10 +390,7 @@ def sensitivity_rhs(a, u, L, v): dL = sensitivity_rhs(a, u, L, v) """ - if not (isinstance(a, Form) and - isinstance(u, Coefficient) and - isinstance(L, Form) and - isinstance(v, Variable)): + if not (isinstance(a, Form) and isinstance(u, Coefficient) and isinstance(L, Form) and isinstance(v, Variable)): raise ValueError("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") if not is_true_ufl_scalar(v): raise ValueError("Expecting scalar variable.") diff --git a/ufl/indexed.py b/ufl/indexed.py index bde7714b5..65c28b8ae 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -12,7 +12,7 @@ from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator from ufl.core.multiindex import Index, FixedIndex, MultiIndex -from ufl.index_combination_utils import unique_sorted_indices, merge_unique_indices +from ufl.index_combination_utils import unique_sorted_indices from ufl.precedence import parstr @@ -69,27 +69,17 @@ def __init__(self, expression, multiindex): raise ValueError("Fixed index out of range!") # Build tuples of free index ids and dimensions - if 1: - efi = expression.ufl_free_indices - efid = expression.ufl_index_dimensions - fi = list(zip(efi, efid)) - for pos, ind in enumerate(multiindex._indices): - if isinstance(ind, Index): - fi.append((ind.count(), shape[pos])) - fi = unique_sorted_indices(sorted(fi)) - if fi: - fi, fid = zip(*fi) - else: - fi, fid = (), () - + efi = expression.ufl_free_indices + efid = expression.ufl_index_dimensions + fi = list(zip(efi, efid)) + for pos, ind in enumerate(multiindex._indices): + if isinstance(ind, Index): + fi.append((ind.count(), shape[pos])) + fi = unique_sorted_indices(sorted(fi)) + if fi: + fi, fid = zip(*fi) else: - mfiid = [(ind.count(), shape[pos]) - for pos, ind in enumerate(multiindex._indices) - if isinstance(ind, Index)] - mfi, mfid = zip(*mfiid) if mfiid else ((), ()) - fi, fid = merge_unique_indices(expression.ufl_free_indices, - expression.ufl_index_dimensions, - mfi, mfid) + fi, fid = (), () # Cache free index and dimensions self.ufl_free_indices = fi @@ -114,4 +104,5 @@ def __getitem__(self, key): # So that one doesn't have to special case indexing of # expressions without shape. return self - raise ValueError(f"Attempting to index with {ufl_err_str(key)}, but object is already indexed: {ufl_err_str(self)}") + raise ValueError(f"Attempting to index with {ufl_err_str(key)}, " + f"but object is already indexed: {ufl_err_str(self)}") diff --git a/ufl/integral.py b/ufl/integral.py index 938f24347..6f41cda21 100644 --- a/ufl/integral.py +++ b/ufl/integral.py @@ -112,21 +112,13 @@ def __str__(self): return s def __repr__(self): - r = "Integral(%s, %s, %s, %s, %s, %s)" % (repr(self._integrand), - repr(self._integral_type), - repr(self._ufl_domain), - repr(self._subdomain_id), - repr(self._metadata), - repr(self._subdomain_data)) - return r + return (f"Integral({self._integrand!r}, {self._integral_type!r}, {self._ufl_domain!r}, " + f"{self._subdomain_id!r}, {self._metadata!r}, {self._subdomain_data!r})") def __eq__(self, other): - return (isinstance(other, Integral) and - self._integral_type == other._integral_type and - self._ufl_domain == other._ufl_domain and - self._subdomain_id == other._subdomain_id and - self._integrand == other._integrand and - self._metadata == other._metadata and + return (isinstance(other, Integral) and self._integral_type == other._integral_type and # noqa: W504 + self._ufl_domain == other._ufl_domain and self._subdomain_id == other._subdomain_id and # noqa: W504 + self._integrand == other._integrand and self._metadata == other._metadata and # noqa: W504 id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data)) def __hash__(self): diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 1761b7599..c4bc9aca4 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -17,7 +17,8 @@ from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import is_true_ufl_scalar, Zero, RealValue, FloatValue, IntValue, ComplexValue, ConstantValue, as_ufl +from ufl.constantvalue import (is_true_ufl_scalar, Zero, RealValue, FloatValue, IntValue, ComplexValue, + ConstantValue, as_ufl) """ TODO: Include additional functions available in (need derivatives as well): @@ -297,29 +298,15 @@ def __str__(self): return "atan_2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) -def _find_erf(): - import math - if hasattr(math, 'erf'): - return math.erf - import scipy.special - if hasattr(scipy.special, 'erf'): - return scipy.special.erf - return None - - @ufl_type() class Erf(MathFunction): __slots__ = () def __new__(cls, argument): if isinstance(argument, (RealValue, Zero)): - erf = _find_erf() - if erf is not None: - return FloatValue(erf(float(argument))) + return FloatValue(math.erf(float(argument))) if isinstance(argument, (ConstantValue)): - erf = _find_erf() - if erf is not None: - return ComplexValue(erf(complex(argument))) + return ComplexValue(math.erf(complex(argument))) return MathFunction.__new__(cls) def __init__(self, argument): @@ -327,18 +314,15 @@ def __init__(self, argument): def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) - erf = _find_erf() - if erf is None: - raise ValueError("No python implementation of erf available on this system, cannot evaluate. Upgrade python or install scipy.") - return erf(a) + return math.erf(a) @ufl_type(is_abstract=True, is_scalar=True, num_ops=2) class BesselFunction(Operator): "Base class for all bessel functions" - __slots__ = ("_name", "_classname") + __slots__ = ("_name") - def __init__(self, name, classname, nu, argument): + def __init__(self, name, nu, argument): if not is_true_ufl_scalar(nu): raise ValueError("Expecting scalar nu.") if not is_true_ufl_scalar(argument): @@ -354,7 +338,6 @@ def __init__(self, name, classname, nu, argument): Operator.__init__(self, (nu, argument)) - self._classname = classname self._name = name def evaluate(self, x, mapping, component, index_values): @@ -384,7 +367,7 @@ class BesselJ(BesselFunction): __slots__ = () def __init__(self, nu, argument): - BesselFunction.__init__(self, "cyl_bessel_j", "BesselJ", nu, argument) + BesselFunction.__init__(self, "cyl_bessel_j", nu, argument) @ufl_type() @@ -392,7 +375,7 @@ class BesselY(BesselFunction): __slots__ = () def __init__(self, nu, argument): - BesselFunction.__init__(self, "cyl_bessel_y", "BesselY", nu, argument) + BesselFunction.__init__(self, "cyl_bessel_y", nu, argument) @ufl_type() @@ -400,7 +383,7 @@ class BesselI(BesselFunction): __slots__ = () def __init__(self, nu, argument): - BesselFunction.__init__(self, "cyl_bessel_i", "BesselI", nu, argument) + BesselFunction.__init__(self, "cyl_bessel_i", nu, argument) @ufl_type() @@ -408,4 +391,4 @@ class BesselK(BesselFunction): __slots__ = () def __init__(self, nu, argument): - BesselFunction.__init__(self, "cyl_bessel_k", "BesselK", nu, argument) + BesselFunction.__init__(self, "cyl_bessel_k", nu, argument) diff --git a/ufl/matrix.py b/ufl/matrix.py index 58e704d2b..bd3a25d91 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -50,10 +50,7 @@ def __init__(self, row_space, column_space, count=None): self.ufl_operands = () self._hash = None - self._repr = "Matrix(%s,%s, %s)" % ( - repr(self._ufl_function_spaces[0]), - repr(self._ufl_function_spaces[1]), repr(self._count) - ) + self._repr = f"Matrix({self._ufl_function_spaces[0]!r}, {self._ufl_function_spaces[1]!r}, {self._count!r})" def count(self): return self._count @@ -62,12 +59,6 @@ def ufl_function_spaces(self): "Get the tuple of function spaces of this coefficient." return self._ufl_function_spaces - def ufl_row_space(self): - return self._ufl_function_spaces[0] - - def ufl_column_space(self): - return self._ufl_function_spaces[1] - def _analyze_form_arguments(self): "Define arguments of a matrix when considered as a form." self._arguments = (Argument(self._ufl_function_spaces[0], 0), @@ -76,9 +67,9 @@ def _analyze_form_arguments(self): def __str__(self): count = str(self._count) if len(count) == 1: - return "A_%s" % count + return f"A_{count}" else: - return "A_{%s}" % count + return f"A_{{{count}}}" def __repr__(self): return self._repr @@ -94,5 +85,4 @@ def equals(self, other): return False if self is other: return True - return (self._count == other._count and - self._ufl_function_spaces == other._ufl_function_spaces) + return self._count == other._count and self._ufl_function_spaces == other._ufl_function_spaces diff --git a/ufl/measure.py b/ufl/measure.py index c7c596f2f..222cac149 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -18,7 +18,7 @@ from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl from ufl.domain import as_domain, AbstractDomain, extract_domains -from ufl.protocols import id_or_none, metadata_equal, metadata_hashdata +from ufl.protocols import id_or_none # Export list for ufl.classes @@ -31,36 +31,31 @@ # Enumeration of valid domain types _integral_types = [ # === Integration over full topological dimension: - ("cell", "dx"), # Over cells of a mesh - # ("macro_cell", "dE"), # Over a group of adjacent cells (TODO: Arbitrary cell group? Not currently used.) + ("cell", "dx"), # Over cells of a mesh # === Integration over topological dimension - 1: - ("exterior_facet", "ds"), # Over one-sided exterior facets of a mesh - ("interior_facet", "dS"), # Over two-sided facets between pairs of adjacent cells of a mesh + ("exterior_facet", "ds"), # Over one-sided exterior facets of a mesh + ("interior_facet", "dS"), # Over two-sided facets between pairs of adjacent cells of a mesh # === Integration over topological dimension 0 - ("vertex", "dP"), # Over vertices of a mesh - # ("vertex", "dV"), # TODO: Use this over vertices? - # ("point", "dP"), # TODO: Use this over arbitrary points inside cells? + ("vertex", "dP"), # Over vertices of a mesh # === Integration over custom domains - ("custom", "dc"), # Over custom user-defined domains (run-time quadrature points) - ("cutcell", "dC"), # Over a cell with some part cut away (run-time quadrature points) - ("interface", "dI"), # Over a facet fragment overlapping with two or more cells (run-time quadrature points) - ("overlap", "dO"), # Over a cell fragment overlapping with two or more cells (run-time quadrature points) - - # === Firedrake specific hacks on the way out: - # TODO: Remove these, firedrake can use metadata instead - # and create the measure objects in firedrake: + ("custom", "dc"), # Over custom user-defined domains (run-time quadrature points) + ("cutcell", "dC"), # Over a cell with some part cut away (run-time quadrature points) + ("interface", "dI"), # Over a facet fragment overlapping with two or more cells (run-time quadrature points) + ("overlap", "dO"), # Over a cell fragment overlapping with two or more cells (run-time quadrature points) + + # === Firedrake specifics: ("exterior_facet_bottom", "ds_b"), # Over bottom facets on extruded mesh - ("exterior_facet_top", "ds_t"), # Over top facets on extruded mesh - ("exterior_facet_vert", "ds_v"), # Over side facets of an extruded mesh - ("interior_facet_horiz", "dS_h"), # Over horizontal facets of an extruded mesh + ("exterior_facet_top", "ds_t"), # Over top facets on extruded mesh + ("exterior_facet_vert", "ds_v"), # Over side facets of an extruded mesh + ("interior_facet_horiz", "dS_h"), # Over horizontal facets of an extruded mesh ("interior_facet_vert", "dS_v"), # Over vertical facets of an extruded mesh ] -integral_type_to_measure_name = dict((l, s) for l, s in _integral_types) -measure_name_to_integral_type = dict((s, l) for l, s in _integral_types) +integral_type_to_measure_name = {i: s for i, s in _integral_types} +measure_name_to_integral_type = {s: i for i, s in _integral_types} custom_integral_types = ("custom", "cutcell", "interface", "overlap") point_integral_types = ("vertex",) # "point") @@ -251,9 +246,9 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, # Let syntax dx(domain) or dx(domain, metadata) mean integral # over entire domain. To do this we need to hijack the first # argument: - if subdomain_id is not None and (isinstance(subdomain_id, - AbstractDomain) or - hasattr(subdomain_id, 'ufl_domain')): + if subdomain_id is not None and ( + isinstance(subdomain_id, AbstractDomain) or hasattr(subdomain_id, 'ufl_domain') + ): if domain is not None: raise ValueError("Ambiguous: setting domain both as keyword argument and first argument.") subdomain_id, domain = "everywhere", as_domain(subdomain_id) @@ -276,7 +271,6 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, subdomain_data=subdomain_data) def __str__(self): - global integral_type_to_measure_name name = integral_type_to_measure_name[self._integral_type] args = [] @@ -293,8 +287,6 @@ def __str__(self): def __repr__(self): "Return a repr string for this Measure." - global integral_type_to_measure_name - args = [] args.append(repr(self._integral_type)) @@ -312,21 +304,23 @@ def __repr__(self): def __hash__(self): "Return a hash value for this Measure." + metadata_hashdata = tuple(sorted((k, id(v)) for k, v in list(self._metadata.items()))) hashdata = (self._integral_type, self._subdomain_id, hash(self._domain), - metadata_hashdata(self._metadata), + metadata_hashdata, id_or_none(self._subdomain_data)) return hash(hashdata) def __eq__(self, other): "Checks if two Measures are equal." - return (isinstance(other, Measure) and - self._integral_type == other._integral_type and - self._subdomain_id == other._subdomain_id and - self._domain == other._domain and - id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) and - metadata_equal(self._metadata, other._metadata)) + sorted_metadata = sorted((k, id(v)) for k, v in list(self._metadata.items())) + sorted_other_metadata = sorted((k, id(v)) for k, v in list(other._metadata.items())) + + return (isinstance(other, Measure) and self._integral_type == other._integral_type and # noqa: W504 + self._subdomain_id == other._subdomain_id and self._domain == other._domain and # noqa: W504 + id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) and # noqa: W504 + sorted_metadata == sorted_other_metadata) def __add__(self, other): """Add two measures (self+other). diff --git a/ufl/operators.py b/ufl/operators.py index 3295ca077..7b3c6171d 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -31,7 +31,6 @@ Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2,\ BesselJ, BesselY, BesselI, BesselK from ufl.averaging import CellAvg, FacetAvg -from ufl.core.multiindex import indices from ufl.indexed import Indexed from ufl.geometry import SpatialCoordinate, FacetNormal from ufl.checks import is_cellwise_constant @@ -94,7 +93,7 @@ def extind(ii): def elem_op(op, *args): - "UFL operator: Take the elementwise application of operator *op* on scalar values from one or more tensor arguments." + "UFL operator: Take the elementwise application of operator op on scalar values from one or more tensor arguments." args = [as_ufl(arg) for arg in args] sh = args[0].ufl_shape if not all(sh == x.ufl_shape for x in args): @@ -134,7 +133,9 @@ def transpose(A): def outer(*operands): - "UFL operator: Take the outer product of two or more operands. The complex conjugate of the first argument is taken." + """UFL operator: Take the outer product of two or more operands. + The complex conjugate of the first argument is taken. + """ n = len(operands) if n == 1: return operands[0] @@ -159,15 +160,6 @@ def inner(a, b): return Inner(a, b) -# TODO: Something like this would be useful in some cases, but should -# inner just support len(a.ufl_shape) != len(b.ufl_shape) instead? -def _partial_inner(a, b): - "UFL operator: Take the partial inner product of a and b." - ar, br = len(a.ufl_shape), len(b.ufl_shape) - n = min(ar, br) - return contraction(a, list(range(n - ar, n - ar + n)), b, list(range(n))) - - def dot(a, b): "UFL operator: Take the dot product of *a* and *b*. This won't take the complex conjugate of the second argument." a = as_ufl(a) @@ -177,30 +169,6 @@ def dot(a, b): return Dot(a, b) -def contraction(a, a_axes, b, b_axes): - "UFL operator: Take the contraction of a and b over given axes." - ai, bi = a_axes, b_axes - if len(ai) != len(bi): - raise ValueError("Contraction must be over the same number of axes.") - ash = a.ufl_shape - bsh = b.ufl_shape - aii = indices(len(a.ufl_shape)) - bii = indices(len(b.ufl_shape)) - cii = indices(len(ai)) - shape = [None] * len(ai) - for i, j in enumerate(ai): - aii[j] = cii[i] - shape[i] = ash[j] - for i, j in enumerate(bi): - bii[j] = cii[i] - if shape[i] != bsh[j]: - raise ValueError("Shape mismatch in contraction.") - s = a[aii] * b[bii] - cii = set(cii) - ii = tuple(i for i in (aii + bii) if i not in cii) - return as_tensor(s, ii) - - def perp(v): "UFL operator: Take the perp of *v*, i.e. :math:`(-v_1, +v_0)`." v = as_ufl(v) @@ -317,11 +285,6 @@ def Dx(f, *i): return f.dx(*i) -def Dt(f): - "UFL operator: The partial derivative of *f* with respect to time." - raise NotImplementedError - - def Dn(f): """UFL operator: Take the directional derivative of *f* in the facet normal direction, Dn(f) := dot(grad(f), n).""" @@ -451,7 +414,8 @@ def jump(v, n=None): else: return dot(v('+'), n('+')) + dot(v('-'), n('-')) else: - warnings.warn("Returning zero from jump of expression without a domain. This may be erroneous if a dolfin.Expression is involved.") + warnings.warn("Returning zero from jump of expression without a domain. " + "This may be erroneous if a dolfin.Expression is involved.") # FIXME: Is this right? If v has no domain, it doesn't depend # on anything spatially variable or any form arguments, and # thus the jump is zero. In other words, I'm assuming that "v diff --git a/ufl/permutation.py b/ufl/permutation.py index 2e2b8cf43..7675e3065 100644 --- a/ufl/permutation.py +++ b/ufl/permutation.py @@ -23,12 +23,6 @@ def compute_indices(shape): return tuple(indices) -# functional version: -def compute_indices2(shape): - "Compute all index combinations for given shape" - return ((),) if len(shape) == 0 else tuple((i,) + sub_index for i in range(shape[0]) for sub_index in compute_indices2(shape[1:])) - - def build_component_numbering(shape, symmetry): """Build a numbering of components within the given value shape, taking into consideration a symmetry mapping which leaves the @@ -53,65 +47,3 @@ def build_component_numbering(shape, symmetry): for k, c in enumerate(si2vi): assert vi2si[c] == k return vi2si, si2vi - - -def compute_permutations(k, n, skip=None): - """Compute all permutations of k elements from (0, n) in rising order. - Any elements that are contained in the list skip are not included. - - """ - if k == 0: - return [] - if skip is None: - skip = [] - if k == 1: - return [(i,) for i in range(n) if i not in skip] - pp = compute_permutations(k - 1, n, skip) - permutations = [] - for i in range(n): - if i in skip: - continue - for p in pp: - if i < p[0]: - permutations.append((i,) + p) - return permutations - - -def compute_permutation_pairs(j, k): - """Compute all permutations of j + k elements from (0, j + k) in - rising order within (0, j) and (j, j + k) respectively. - - """ - permutations = [] - pp0 = compute_permutations(j, j + k) - for p0 in pp0: - pp1 = compute_permutations(k, j + k, p0) - for p1 in pp1: - permutations.append((p0, p1)) - return permutations - - -def compute_sign(permutation): - "Compute sign by sorting." - sign = 1 - n = len(permutation) - p = [p for p in permutation] - for i in range(n - 1): - for j in range(n - 1): - if p[j] > p[j + 1]: - (p[j], p[j + 1]) = (p[j + 1], p[j]) - sign = -sign - elif p[j] == p[j + 1]: - return 0 - return sign - - -def compute_order_tuples(k, n): - "Compute all tuples of n integers such that the sum is k" - if n == 1: - return ((k,),) - order_tuples = [] - for i in range(k + 1): - for order_tuple in compute_order_tuples(k - i, n - 1): - order_tuples.append(order_tuple + (i,)) - return tuple(order_tuples) diff --git a/ufl/precedence.py b/ufl/precedence.py index aee5c1537..0813f0045 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -40,7 +40,8 @@ def parstr(child, parent, pre="(", post=")", format=str): def build_precedence_list(): - from ufl.classes import Operator, Terminal, Sum, IndexSum, Product, Division, Power, MathFunction, BesselFunction, Abs, Indexed + from ufl.classes import (Operator, Terminal, Sum, IndexSum, Product, Division, Power, + MathFunction, BesselFunction, Abs, Indexed) # TODO: Fill in other types... # Power <= Transposed @@ -100,41 +101,4 @@ def assign_precedences(precedence_list): for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p if missing: - msg = "Missing precedence levels for classes:\n" +\ - "\n".join(' %s' % c for c in sorted(missing)) - warnings.warn(msg) - - -""" -# Code from uflacs: -import ufl - -def build_precedence_list(): - "Builds a list of operator types by precedence order in the C language." - # FIXME: Add all types we need here. - pl = [] - pl.append((ufl.classes.Conditional,)) - pl.append((ufl.classes.OrCondition,)) - pl.append((ufl.classes.AndCondition,)) - pl.append((ufl.classes.EQ, ufl.classes.NE)) - pl.append((ufl.classes.Condition,)) # <,>,<=,>= - pl.append((ufl.classes.NotCondition,)) # FIXME - pl.append((ufl.classes.Sum,)) - pl.append((ufl.classes.Product, ufl.classes.Division,)) - # The highest precedence items will never need - # parentheses around them or their operands - pl.append((ufl.classes.Power, ufl.classes.MathFunction, ufl.classes.Abs, ufl.classes.BesselFunction, - ufl.classes.Indexed, ufl.classes.Grad, - ufl.classes.PositiveRestricted, ufl.classes.NegativeRestricted, - ufl.classes.Terminal)) - # FIXME: Write a unit test that checks this list against all ufl classes - return pl - -def build_precedence_map(): - from ufl.precedence import build_precedence_mapping - pm, missing = build_precedence_mapping(build_precedence_list()) - if 0 and missing: # Enable to see which types we are missing - print("Missing precedence levels for the types:") - print("\n".join(' %s' % c for c in missing)) - return pm -""" + warnings.warn("Missing precedence levels for classes:\n" + "\n".join(f" {c}" for c in sorted(missing))) diff --git a/ufl/protocols.py b/ufl/protocols.py index bb235f34c..5cc6106d9 100644 --- a/ufl/protocols.py +++ b/ufl/protocols.py @@ -19,12 +19,3 @@ def id_or_none(obj): return obj.ufl_id() else: return id(obj) - - -def metadata_equal(a, b): - return (sorted((k, id(v)) for k, v in list(a.items())) == - sorted((k, id(v)) for k, v in list(b.items()))) - - -def metadata_hashdata(md): - return tuple(sorted((k, id(v)) for k, v in list(md.items()))) diff --git a/ufl/restriction.py b/ufl/restriction.py index 82bf3a226..06ab116cf 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -35,7 +35,7 @@ def evaluate(self, x, mapping, component, index_values): index_values) def __str__(self): - return "%s('%s')" % (parstr(self.ufl_operands[0], self), self._side) + return f"{parstr(self.ufl_operands[0], self)}({self._side})" @ufl_type(is_terminal_modifier=True) diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index d54034459..fb6056c40 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -54,9 +54,7 @@ def __str__(self): return self.name def __repr__(self): - r = "SobolevSpace(%s, %s)" % (repr(self.name), repr( - list(self.parents))) - return r + return f"SobolevSpace({self.name!r}, {list(self.parents)!r})" def __eq__(self, other): return isinstance(other, SobolevSpace) and self.name == other.name @@ -68,9 +66,7 @@ def __hash__(self): return hash(("SobolevSpace", self.name)) def __getitem__(self, spatial_index): - """Returns the Sobolev space associated with a particular - spatial coordinate. - """ + """Returns the Sobolev space associated with a particular spatial coordinate.""" return self def __contains__(self, other): @@ -78,11 +74,10 @@ def __contains__(self, other): :class:`~finiteelement.FiniteElement` and `s` is a :class:`SobolevSpace`""" if isinstance(other, SobolevSpace): - raise TypeError("Unable to test for inclusion of a " + - "SobolevSpace in another SobolevSpace. " + + raise TypeError("Unable to test for inclusion of a " + "SobolevSpace in another SobolevSpace. " "Did you mean to use <= instead?") - return (other.sobolev_space() == self or - self in other.sobolev_space().parents) + return other.sobolev_space() == self or self in other.sobolev_space().parents def __lt__(self, other): """In common with intrinsic Python sets, < indicates "is a proper @@ -138,12 +133,12 @@ def __contains__(self, other): :class:`~finiteelement.FiniteElement` and `s` is a :class:`DirectionalSobolevSpace`""" if isinstance(other, SobolevSpace): - raise TypeError("Unable to test for inclusion of a " + - "SobolevSpace in another SobolevSpace. " + + raise TypeError("Unable to test for inclusion of a " + "SobolevSpace in another SobolevSpace. " "Did you mean to use <= instead?") - return (other.sobolev_space() == self or - all(self[i] in other.sobolev_space().parents - for i in self._spatial_indices)) + return (other.sobolev_space() == self or all( + self[i] in other.sobolev_space().parents + for i in self._spatial_indices)) def __eq__(self, other): if isinstance(other, DirectionalSobolevSpace): @@ -163,14 +158,13 @@ def __lt__(self, other): return all(self._orders[i] >= 1 for i in self._spatial_indices) elif other.name in ["HDivDiv", "HEin"]: # Don't know how these spaces compare - return NotImplementedError( - "Don't know how to compare with %s" % other.name) + return NotImplementedError(f"Don't know how to compare with {other.name}") else: return any( self._orders[i] > other._order for i in self._spatial_indices) def __str__(self): - return self.name + "(%s)" % ", ".join(map(str, self._orders)) + return f"{self.name}({', '.join(map(str, self._orders))})" L2 = SobolevSpace("L2") diff --git a/ufl/sorting.py b/ufl/sorting.py index 117c4aa1c..2b74d560e 100644 --- a/ufl/sorting.py +++ b/ufl/sorting.py @@ -34,7 +34,7 @@ def _cmp_multi_index(a, b): if x < y: return -1 elif x > y: - return +1 + return 1 else: # Same value, no decision continue @@ -43,7 +43,7 @@ def _cmp_multi_index(a, b): return -1 elif fix2: # Sort fixed before free - return +1 + return 1 else: # Both are Index, no decision, do not depend on count! pass @@ -67,7 +67,7 @@ def _cmp_coefficient(a, b): if x < y: return -1 elif x > y: - return +1 + return 1 else: return 0 @@ -80,7 +80,7 @@ def _cmp_argument(a, b): if x < y: return -1 elif x > y: - return +1 + return 1 else: return 0 @@ -89,7 +89,7 @@ def _cmp_terminal_by_repr(a, b): # The cost of repr on a terminal is fairly small, and bounded x = repr(a) y = repr(b) - return -1 if x < y else (0 if x == y else +1) + return -1 if x < y else (0 if x == y else 1) # Hack up a MultiFunction-like type dispatch for terminal comparisons @@ -111,7 +111,7 @@ def cmp_expr(a, b): # First sort quickly by type code x, y = a._ufl_typecode_, b._ufl_typecode_ if x != y: - return -1 if x < y else +1 + return -1 if x < y else 1 # Now we know that the type is the same, check further based # on type specific properties. @@ -149,7 +149,7 @@ def cmp_expr(a, b): # try to avoid the cost of this check for most nodes. x, y = len(aops), len(bops) if x != y: - return -1 if x < y else +1 + return -1 if x < y else 1 # Equal if we get out of the above loop! return 0 diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 18129698e..b0579985a 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -105,6 +105,7 @@ def split(v): sub_functions.append(subv) if end != offset: - raise ValueError("Function splitting failed to extract components for whole intended range. Something is wrong.") + raise ValueError( + "Function splitting failed to extract components for whole intended range. Something is wrong.") return tuple(sub_functions) diff --git a/ufl/tensors.py b/ufl/tensors.py index 67fedb2dc..3f3ac9249 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -339,19 +339,6 @@ def as_scalars(*expressions): return expressions, ii -def relabel(A, indexmap): - "UFL operator: Relabel free indices of :math:`A` with new indices, using the given mapping." - ii = tuple(sorted(indexmap.keys())) - jj = tuple(indexmap[i] for i in ii) - if not all(isinstance(i, Index) for i in ii): - raise ValueError("Expecting Index objects.") - if not all(isinstance(j, Index) for j in jj): - raise ValueError("Expecting Index objects.") - return as_tensor(A, ii)[jj] - - -# --- Experimental support for dyadic notation: - def unit_list(i, n): return [(1 if i == j else 0) for j in range(n)] @@ -382,18 +369,6 @@ def unit_matrices(d): return tuple(unit_matrix(i, j, d) for i in range(d) for j in range(d)) -def dyad(d, *iota): - "TODO: Develop this concept, can e.g. write A[i,j]*dyad(j,i) for the transpose." - from ufl.constantvalue import Identity - from ufl.operators import outer # a bit of circular dependency issue here - Id = Identity(d) - i = iota[0] - e = as_vector(Id[i, :], i) - for i in iota[1:]: - e = outer(e, as_vector(Id[i, :], i)) - return e - - def unit_indexed_tensor(shape, component): from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here diff --git a/ufl/utils/derivativetuples.py b/ufl/utils/derivativetuples.py deleted file mode 100644 index 498f63a3d..000000000 --- a/ufl/utils/derivativetuples.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -"This module contains a collection of utilities for representing partial derivatives as integer tuples." - -# Copyright (C) 2013-2016 Martin Sandve Alnæs and Anders Logg -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -import itertools - - -def derivative_counts_to_listing(derivative_counts): - """Convert a derivative count tuple to a derivative listing tuple. - - The derivative d^3 / dy^2 dz is represented - in counting form as (0, 2, 1) meaning (dx^0, dy^2, dz^1) - and in listing form as (1, 1, 2) meaning (dy, dy, dz). - """ - derivatives = [] # = 1 - for i, d in enumerate(derivative_counts): - derivatives.extend((i,) * d) # *= d/dx_i^d - return tuple(derivatives) - - -def derivative_listing_to_counts(derivatives, gdim): - """Convert a derivative listing tuple to a derivative count tuple. - - The derivative d^3 / dy^2 dz is represented - in counting form as (0, 2, 1) meaning (dx^0, dy^2, dz^1) - and in listing form as (1, 1, 2) meaning (dy, dy, dz). - """ - derivative_counts = [0] * gdim - for d in derivatives: - derivative_counts[d] += 1 - return tuple(derivative_counts) - - -def compute_derivative_tuples(n, gdim): - """Compute the list of all derivative tuples for derivatives of - given total order n and given geometric dimension gdim. This - function returns two lists. The first is a list of tuples, where - each tuple of length n specifies the coordinate directions of the - n derivatives. The second is a corresponding list of tuples, where - each tuple of length gdim specifies the number of derivatives in - each direction. Both lists have length gdim^n and are ordered as - expected by the UFC function tabulate_basis_derivatives. - - Example: If n = 2 and gdim = 3, then the nice tuples are - - (0, 0) <--> (2, 0, 0) <--> d^2/dxdx - (0, 1) <--> (1, 1, 0) <--> d^2/dxdy - (0, 2) <--> (1, 0, 1) <--> d^2/dxdz - (1, 0) <--> (1, 1, 0) <--> d^2/dydx - (1, 1) <--> (0, 2, 0) <--> d^2/dydy - (1, 2) <--> (0, 1, 1) <--> d^2/dydz - (2, 0) <--> (1, 0, 1) <--> d^2/dzdx - (2, 1) <--> (0, 1, 1) <--> d^2/dzdy - (2, 2) <--> (0, 0, 2) <--> d^2/dzdz - """ - - # Create list of derivatives (note that we have d^n derivatives) - deriv_tuples = [d for d in itertools.product(*(n * [range(0, gdim)]))] - - # Translate from list of derivative tuples to list of tuples - # expressing the number of derivatives in each dimension... - _deriv_tuples = [tuple(len([_d for _d in d if _d == i]) for i in range(gdim)) - for d in deriv_tuples] - - return deriv_tuples, _deriv_tuples diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index 76f55f7b9..f435653f2 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -12,31 +12,25 @@ def camel2underscore(name): """Convert a CamelCaps string to underscore_syntax.""" letters = [] lastlower = False - for l in name: - thislower = l.islower() + for i in name: + thislower = i.islower() if not thislower: # Don't insert _ between multiple upper case letters if lastlower: letters.append("_") - l = l.lower() # noqa: E741 + i = i.lower() # noqa: E741 lastlower = thislower - letters.append(l) + letters.append(i) return "".join(letters) -def lstr(l): +def lstr(a): """Pretty-print list or tuple, invoking str() on items instead of repr() like str() does.""" - if isinstance(l, list): - return "[" + ", ".join(lstr(item) for item in l) + "]" - elif isinstance(l, tuple): - return "(" + ", ".join(lstr(item) for item in l) + ")" - return str(l) - - -def dstr(d, colsize=80): - """Pretty-print dictionary of key-value pairs.""" - sorted_keys = sorted(d.keys()) - return tstr([(key, d[key]) for key in sorted_keys], colsize) + if isinstance(a, list): + return "[" + ", ".join(lstr(item) for item in a) + "]" + elif isinstance(a, tuple): + return "(" + ", ".join(lstr(item) for item in a) + ")" + return str(a) def tstr(t, colsize=80): @@ -69,11 +63,6 @@ def tstr(t, colsize=80): return s -def sstr(s): - """Pretty-print set.""" - return ", ".join(str(x) for x in s) - - def istr(o): """Format object as string, inserting ? for None.""" if o is None: @@ -85,3 +74,59 @@ def istr(o): def estr(elements): """Format list of elements for printing.""" return ", ".join(e.shortstr() for e in elements) + + +def _indent_string(n): + return " " * n + + +def _tree_format_expression(expression, indentation, parentheses): + ind = _indent_string(indentation) + if expression._ufl_is_terminal_: + s = "%s%s" % (ind, repr(expression)) + else: + sops = [_tree_format_expression(o, indentation + 1, parentheses) for o in expression.ufl_operands] + s = "%s%s\n" % (ind, expression._ufl_class_.__name__) + if parentheses and len(sops) > 1: + s += "%s(\n" % (ind,) + s += "\n".join(sops) + if parentheses and len(sops) > 1: + s += "\n%s)" % (ind,) + return s + + +def tree_format(expression, indentation=0, parentheses=True): + from ufl.core.expr import Expr + from ufl.form import Form + from ufl.integral import Integral + + s = "" + + if isinstance(expression, Form): + form = expression + integrals = form.integrals() + integral_types = sorted(set(itg.integral_type() for itg in integrals)) + itgs = [] + for integral_type in integral_types: + itgs += list(form.integrals_by_type(integral_type)) + + ind = _indent_string(indentation) + s += ind + "Form:\n" + s += "\n".join(tree_format(itg, indentation + 1, parentheses) for itg in itgs) + + elif isinstance(expression, Integral): + ind = _indent_string(indentation) + s += ind + "Integral:\n" + ind = _indent_string(indentation + 1) + s += ind + "integral type: %s\n" % expression.integral_type() + s += ind + "subdomain id: %s\n" % expression.subdomain_id() + s += ind + "integrand:\n" + s += tree_format(expression._integrand, indentation + 2, parentheses) + + elif isinstance(expression, Expr): + s += _tree_format_expression(expression, indentation, parentheses) + + else: + raise ValueError(f"Invalid object type {type(expression)}") + + return s diff --git a/ufl/utils/sequences.py b/ufl/utils/sequences.py index 110a5b75a..2f0afc339 100644 --- a/ufl/utils/sequences.py +++ b/ufl/utils/sequences.py @@ -19,47 +19,6 @@ def product(sequence): return p -def unzip(seq): - """Inverse operation of zip: - - unzip(zip(a, b)) == (a, b).""" - return [s[0] for s in seq], [s[1] for s in seq] - - -def xor(a, b): - return bool(a) if b else not a - - -def or_tuples(seqa, seqb): - """Return 'or' of all pairs in two sequences of same length.""" - return tuple(a or b for (a, b) in zip(seqa, seqb)) - - -def and_tuples(seqa, seqb): - """Return 'and' of all pairs in two sequences of same length.""" - return tuple(a and b for (a, b) in zip(seqa, seqb)) - - -def iter_tree(tree): - """Iterate over all nodes in a tree represented - by lists of lists of leaves.""" - if isinstance(tree, list): - for node in tree: - for i in iter_tree(node): - yield i - else: - yield tree - - -def recursive_chain(lists): - for l in lists: - if isinstance(l, str): - yield l - else: - for s in recursive_chain(l): - yield s - - def max_degree(degrees): """Maximum degree for mixture of scalar and tuple degrees.""" # numpy.maximum broadcasts scalar degrees to tuple degrees if diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index 14cb9e33c..fdd2c104d 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -51,11 +51,6 @@ def sorted_by_count(seq): return sorted(seq, key=lambda x: x.count()) -def sorted_by_ufl_id(seq): - "Sort a sequence by the item.ufl_id()." - return sorted(seq, key=lambda x: x.ufl_id()) - - def sorted_by_key(mapping): "Sort dict items by key, allowing different key types." # Python3 doesn't allow comparing builtins of different type, @@ -91,7 +86,8 @@ def canonicalize_metadata(metadata): elif isinstance(value, (int, float, str)) or value is None: value = str(value) else: - warnings.warn("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__)) + warnings.warn(f"Applying str() to a metadata value of type {type(value).__name__}, " + "don't know if this is safe.") value = str(value) newvalues.append(value) diff --git a/ufl/utils/ufltypedicts.py b/ufl/utils/ufltypedicts.py deleted file mode 100644 index e6fc7f93e..000000000 --- a/ufl/utils/ufltypedicts.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -"""Various utility data structures.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later - - -class UFLTypeDict(dict): - def __init__(self): - dict.__init__(self) - - def __getitem__(self, key): - return dict.__getitem__(self, key._ufl_class_) - - def __setitem__(self, key, value): - return dict.__setitem__(self, key._ufl_class_, value) - - def __delitem__(self, key): - return dict.__delitem__(self, key._ufl_class_) - - def __contains__(self, key): - return dict.__contains__(self, key._ufl_class_) - - -class UFLTypeDefaultDict(dict): - def __init__(self, default): - dict.__init__(self) - - def make_default(): - return default - self.setdefault(make_default) - - def __getitem__(self, key): - return dict.__getitem__(self, key._ufl_class_) - - def __setitem__(self, key, value): - return dict.__setitem__(self, key._ufl_class_, value) - - def __delitem__(self, key): - return dict.__delitem__(self, key._ufl_class_) - - def __contains__(self, key): - return dict.__contains__(self, key._ufl_class_) diff --git a/ufl/variable.py b/ufl/variable.py index fb14c43d6..1b68d6c06 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -101,8 +101,7 @@ def label(self): return self.ufl_operands[1] def __eq__(self, other): - return (isinstance(other, Variable) and - self.ufl_operands[1] == other.ufl_operands[1] and + return (isinstance(other, Variable) and self.ufl_operands[1] == other.ufl_operands[1] and # noqa: W504 self.ufl_operands[0] == other.ufl_operands[0]) def __str__(self): From 4e32e8617ac9f08938af4d66455453bc37fd45e3 Mon Sep 17 00:00:00 2001 From: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Date: Sat, 15 Jul 2023 08:17:51 +0100 Subject: [PATCH 034/136] Ksagiyam/fix repr withmapping (#182) * WithMapping: fix repr * TensorProductCell: fix reconstruct --- test/test_cell.py | 6 ++++++ test/test_elements.py | 6 ++++++ ufl/cell.py | 2 +- ufl/finiteelement/hdivcurl.py | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/test/test_cell.py b/test/test_cell.py index 0be9b7a7b..21a5240fa 100644 --- a/test/test_cell.py +++ b/test/test_cell.py @@ -59,3 +59,9 @@ def test_cells_2d(cell): assert cell.num_peaks() == cell.num_vertices() +def test_tensorproductcell(): + orig = ufl.TensorProductCell(ufl.interval, ufl.interval) + cell = orig.reconstruct() + assert cell.sub_cells() == orig.sub_cells() + assert cell.topological_dimension() == orig.topological_dimension() + assert cell.geometric_dimension() == orig.geometric_dimension() diff --git a/test/test_elements.py b/test/test_elements.py index a83c24f69..49a700cd7 100755 --- a/test/test_elements.py +++ b/test/test_elements.py @@ -229,3 +229,9 @@ def test_mse(): element = FiniteElement('GLL-Edge L2', interval, degree - 1) assert element == eval(repr(element)) + + +def test_withmapping(): + base = FiniteElement("CG", interval, 1) + element = WithMapping(base, "identity") + assert element == eval(repr(element)) diff --git a/ufl/cell.py b/ufl/cell.py index 625887a10..96150bc61 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -409,7 +409,7 @@ def reconstruct(self, **kwargs: typing.Any) -> Cell: gdim = value else: raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") - return TensorProductCell(self._cellname, geometric_dimension=gdim) + return TensorProductCell(*self._cells, geometric_dimension=gdim) def simplex(topological_dimension: int, geometric_dimension: typing.Optional[int] = None): diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py index 6dda827bc..f6412ef23 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/finiteelement/hdivcurl.py @@ -118,7 +118,7 @@ def __getattr__(self, attr): (type(self).__name__, attr)) def __repr__(self): - return f"WithMapping({repr(self.wrapee)}, {self._mapping})" + return f"WithMapping({repr(self.wrapee)}, '{self._mapping}')" def value_shape(self): gdim = self.cell().geometric_dimension() From 83f340873822c0e95bc8bd19e6e3a7bb8ae55a16 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 8 Aug 2023 13:12:12 +0100 Subject: [PATCH 035/136] Fix flake8 checks with new flake8 release (#185) * fix flake * more flake --- test/test_complex.py | 8 ++++---- test/test_duals.py | 8 ++++---- test/test_new_ad.py | 5 +++-- test/test_signature.py | 3 +-- ufl/argument.py | 2 +- ufl/cell.py | 2 +- ufl/core/ufl_type.py | 4 ++-- ufl/exprequals.py | 2 +- ufl/finiteelement/finiteelementbase.py | 2 +- ufl/form.py | 4 ++-- ufl/operators.py | 15 ++++++++------- ufl/sobolevspace.py | 5 +++-- 12 files changed, 31 insertions(+), 29 deletions(-) diff --git a/test/test_complex.py b/test/test_complex.py index 4559457ef..72926d225 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -10,10 +10,10 @@ from ufl.algorithms import estimate_total_polynomial_degree from ufl.algorithms.comparison_checker import do_comparison_check, ComplexComparisonError from ufl.algorithms.formtransformations import compute_form_adjoint -from ufl import TestFunction, TrialFunction, triangle, FiniteElement, \ - as_ufl, inner, grad, dx, dot, outer, conj, sqrt, sin, cosh, \ - atan, ln, exp, as_tensor, real, imag, conditional, \ - min_value, max_value, gt, lt, cos, ge, le, Coefficient +from ufl import (TestFunction, TrialFunction, triangle, FiniteElement, + as_ufl, inner, grad, dx, dot, outer, conj, sqrt, sin, cosh, + atan, ln, exp, as_tensor, real, imag, conditional, + min_value, max_value, gt, lt, cos, ge, le, Coefficient) def test_conj(self): diff --git a/test/test_duals.py b/test/test_duals.py index 8fdf357bb..d29c62639 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -1,10 +1,10 @@ #!/usr/bin/env py.test # -*- coding: utf-8 -*- -from ufl import FiniteElement, FunctionSpace, MixedFunctionSpace, \ - Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument,\ - TestFunction, TrialFunction, Adjoint, Action, \ - action, adjoint, derivative, tetrahedron, triangle, interval, dx +from ufl import (FiniteElement, FunctionSpace, MixedFunctionSpace, + Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument, + TestFunction, TrialFunction, Adjoint, Action, + action, adjoint, derivative, tetrahedron, triangle, interval, dx) from ufl.constantvalue import Zero from ufl.form import ZeroBaseForm diff --git a/test/test_new_ad.py b/test/test_new_ad.py index 99022fd80..65b3dd158 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -8,8 +8,9 @@ from ufl.classes import Grad from ufl.algorithms import tree_format from ufl.algorithms.renumbering import renumber_indices -from ufl.algorithms.apply_derivatives import apply_derivatives, GenericDerivativeRuleset, \ - GradRuleset, VariableRuleset, GateauxDerivativeRuleset +from ufl.algorithms.apply_derivatives import ( + apply_derivatives, GenericDerivativeRuleset, + GradRuleset, VariableRuleset, GateauxDerivativeRuleset) # Note: the old tests in test_automatic_differentiation.py are a bit messy diff --git a/test/test_signature.py b/test/test_signature.py index dd2c494c4..96d6754e2 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -9,8 +9,7 @@ from ufl import * from ufl.classes import MultiIndex, FixedIndex -from ufl.algorithms.signature import compute_multiindex_hashdata, \ - compute_terminal_hashdata +from ufl.algorithms.signature import compute_multiindex_hashdata, compute_terminal_hashdata from itertools import chain diff --git a/ufl/argument.py b/ufl/argument.py index a29fcefda..74358d503 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -130,7 +130,7 @@ def __eq__(self, other): are the same ufl element but different dolfin function spaces. """ return ( - type(self) == type(other) and self._number == other._number and # noqa: W504 + type(self) is type(other) and self._number == other._number and # noqa: W504 self._part == other._part and self._ufl_function_space == other._ufl_function_space ) diff --git a/ufl/cell.py b/ufl/cell.py index 96150bc61..9ef8e9975 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -63,7 +63,7 @@ def reconstruct(self, **kwargs: typing.Any) -> Cell: def __lt__(self, other: AbstractCell) -> bool: """Define an arbitrarily chosen but fixed sort order for all cells.""" - if type(self) == type(other): + if type(self) is type(other): s = (self.geometric_dimension(), self.topological_dimension()) o = (other.geometric_dimension(), other.topological_dimension()) if s != o: diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index 85b917e79..da86467d6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -39,7 +39,7 @@ def __hash__(self) -> int: return hash(self._ufl_hash_data_()) def __eq__(self, other): - return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() + return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() def __ne__(self, other): return not self.__eq__(other) @@ -61,7 +61,7 @@ def __hash__(self): def __eq__(self, other): "__eq__ implementation attached in attach_operators_from_hash_data" - return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() + return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() cls.__eq__ = __eq__ def __ne__(self, other): diff --git a/ufl/exprequals.py b/ufl/exprequals.py index e3cdd3c38..dc00ae4fb 100644 --- a/ufl/exprequals.py +++ b/ufl/exprequals.py @@ -15,7 +15,7 @@ def expr_equals(self, other): # Fast cutoffs for common cases, type difference or hash # difference will cutoff more or less all nonequal types - if type(self) != type(other) or hash(self) != hash(other): + if type(self) is not type(other) or hash(self) != hash(other): return False # Large objects are costly to compare with themselves diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index 9e6aa58cf..c92178e6f 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -83,7 +83,7 @@ def __hash__(self): def __eq__(self, other): "Compute element equality for insertion in hashmaps." - return type(self) == type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() + return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() def __ne__(self, other): "Compute element inequality for insertion in hashmaps." diff --git a/ufl/form.py b/ufl/form.py index 1c7b8cf95..019c7c12f 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -438,7 +438,7 @@ def __ne__(self, other): def equals(self, other): "Evaluate ``bool(lhs_form == rhs_form)``." - if type(other) != Form: + if type(other) is not Form: return False if len(self._integrals) != len(other._integrals): return False @@ -754,7 +754,7 @@ def __hash__(self): def equals(self, other): "Evaluate ``bool(lhs_form == rhs_form)``." - if type(other) != FormSum: + if type(other) is not FormSum: return False if self is other: return True diff --git a/ufl/operators.py b/ufl/operators.py index 7b3c6171d..44dfea0c4 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -19,17 +19,18 @@ from ufl.form import Form from ufl.constantvalue import Zero, RealValue, ComplexValue, as_ufl from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv -from ufl.tensoralgebra import Transposed, Inner, Outer, Dot, Cross, \ - Determinant, Inverse, Cofactor, Trace, Deviatoric, Skew, Sym +from ufl.tensoralgebra import ( + Transposed, Inner, Outer, Dot, Cross, + Determinant, Inverse, Cofactor, Trace, Deviatoric, Skew, Sym) from ufl.coefficient import Coefficient from ufl.variable import Variable from ufl.tensors import as_tensor, as_matrix, as_vector, ListTensor -from ufl.conditional import EQ, NE, \ - AndCondition, OrCondition, NotCondition, Conditional, MaxValue, MinValue +from ufl.conditional import ( + EQ, NE, AndCondition, OrCondition, NotCondition, Conditional, MaxValue, MinValue) from ufl.algebra import Conj, Real, Imag -from ufl.mathfunctions import Sqrt, Exp, Ln, Erf,\ - Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2,\ - BesselJ, BesselY, BesselI, BesselK +from ufl.mathfunctions import ( + Sqrt, Exp, Ln, Erf, Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2, + BesselJ, BesselY, BesselI, BesselK) from ufl.averaging import CellAvg, FacetAvg from ufl.indexed import Indexed from ufl.geometry import SpatialCoordinate, FacetNormal diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index fb6056c40..f6a624d14 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -111,8 +111,9 @@ def __init__(self, orders): the position denotes in what spatial variable the smoothness requirement is enforced. """ - assert all(isinstance(x, int) or isinf(x) for x in orders), \ - ("Order must be an integer or infinity.") + 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) From b0d635a2315bba4530d90bff40966d3705631fd1 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Aug 2023 13:31:50 +0100 Subject: [PATCH 036/136] Add Counted mixin class and refactor form signature computation (#178) * Add Counted mixin class and refactor form signature computation * fixup * fixup --------- Co-authored-by: Matthew Scroggs --- ufl/algorithms/signature.py | 8 +---- ufl/coefficient.py | 13 +++------ ufl/constant.py | 10 ++----- ufl/core/multiindex.py | 13 +++------ ufl/form.py | 58 ++++++++++++++++++++++++++++++------- ufl/matrix.py | 11 +++---- ufl/utils/counted.py | 45 ++++++++++++++-------------- ufl/variable.py | 16 +++++----- 8 files changed, 91 insertions(+), 83 deletions(-) diff --git a/ufl/algorithms/signature.py b/ufl/algorithms/signature.py index ab9690bd6..4650818c1 100644 --- a/ufl/algorithms/signature.py +++ b/ufl/algorithms/signature.py @@ -43,7 +43,6 @@ def compute_terminal_hashdata(expressions, renumbering): # arguments, and just take repr of the rest of the terminals while # we're iterating over them terminal_hashdata = {} - labels = {} index_numbering = {} for expression in expressions: for expr in traverse_unique_terminals(expression): @@ -69,12 +68,7 @@ def compute_terminal_hashdata(expressions, renumbering): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Label): - # Numbering labels as we visit them # TODO: Include in - # renumbering - data = labels.get(expr) - if data is None: - data = "L%d" % len(labels) - labels[expr] = data + data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, ExprList): # Not really a terminal but can have 0 operands... diff --git a/ufl/coefficient.py b/ufl/coefficient.py index d88743375..704c3912d 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -19,13 +19,13 @@ from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace from ufl.form import BaseForm from ufl.split_functions import split -from ufl.utils.counted import counted_init +from ufl.utils.counted import Counted from ufl.duals import is_primal, is_dual # --- The Coefficient class represents a coefficient in a form --- -class BaseCoefficient(object): +class BaseCoefficient(Counted): """UFL form argument type: Parent Representation of a form coefficient.""" # Slots are disabled here because they cause trouble in PyDOLFIN @@ -33,14 +33,13 @@ class BaseCoefficient(object): # __slots__ = ("_count", "_ufl_function_space", "_repr", "_ufl_shape") _ufl_noslots_ = True __slots__ = () - _globalcount = 0 _ufl_is_abstract_ = True def __getnewargs__(self): return (self._ufl_function_space, self._count) def __init__(self, function_space, count=None): - counted_init(self, count, Coefficient) + Counted.__init__(self, count, Coefficient) if isinstance(function_space, FiniteElementBase): # For legacy support for .ufl files using cells, we map @@ -57,9 +56,6 @@ def __init__(self, function_space, count=None): self._repr = "BaseCoefficient(%s, %s)" % ( repr(self._ufl_function_space), repr(self._count)) - def count(self): - return self._count - @property def ufl_shape(self): "Return the associated UFL shape." @@ -111,6 +107,7 @@ class Cofunction(BaseCoefficient, BaseForm): __slots__ = ( "_count", + "_counted_class", "_arguments", "_ufl_function_space", "ufl_operands", @@ -118,7 +115,6 @@ class Cofunction(BaseCoefficient, BaseForm): "_ufl_shape", "_hash" ) - # _globalcount = 0 _primal = False _dual = True @@ -161,7 +157,6 @@ class Coefficient(FormArgument, BaseCoefficient): """UFL form argument type: Representation of a form coefficient.""" _ufl_noslots_ = True - _globalcount = 0 _primal = True _dual = False diff --git a/ufl/constant.py b/ufl/constant.py index 66e7a8f33..2819647e3 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -11,17 +11,16 @@ from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal from ufl.domain import as_domain -from ufl.utils.counted import counted_init +from ufl.utils.counted import Counted @ufl_type() -class Constant(Terminal): +class Constant(Terminal, Counted): _ufl_noslots_ = True - _globalcount = 0 def __init__(self, domain, shape=(), count=None): Terminal.__init__(self) - counted_init(self, count=count, countedclass=Constant) + Counted.__init__(self, count, Constant) self._ufl_domain = as_domain(domain) self._ufl_shape = shape @@ -31,9 +30,6 @@ def __init__(self, domain, shape=(), count=None): self._repr = "Constant({}, {}, {})".format( repr(self._ufl_domain), repr(self._ufl_shape), repr(self._count)) - def count(self): - return self._count - @property def ufl_shape(self): return self._ufl_shape diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 9b9d15b8d..8d9c5dee6 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -10,7 +10,7 @@ # Modified by Massimiliano Leoni, 2016. -from ufl.utils.counted import counted_init +from ufl.utils.counted import Counted from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal @@ -70,20 +70,15 @@ def __repr__(self): return r -class Index(IndexBase): +class Index(IndexBase, Counted): """UFL value: An index with no value assigned. Used to represent free indices in Einstein indexing notation.""" - __slots__ = ("_count",) - - _globalcount = 0 + __slots__ = ("_count", "_counted_class") def __init__(self, count=None): IndexBase.__init__(self) - counted_init(self, count, Index) - - def count(self): - return self._count + Counted.__init__(self, count, Index) def __hash__(self): return hash(("Index", self._count)) diff --git a/ufl/form.py b/ufl/form.py index 019c7c12f..eeff85020 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -16,12 +16,15 @@ from itertools import chain from ufl.checks import is_scalar_constant_expression +from ufl.constant import Constant from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str from ufl.core.ufl_type import UFLType, ufl_type from ufl.domain import extract_unique_domain, sort_domains from ufl.equation import Equation from ufl.integral import Integral +from ufl.utils.counted import Counted +from ufl.utils.sorting import sorted_by_count # Export list for ufl.classes __all_classes__ = ["Form", "BaseForm", "ZeroBaseForm"] @@ -257,8 +260,9 @@ class Form(BaseForm): "_arguments", "_coefficients", "_coefficient_numbering", - "_constant_numbering", "_constants", + "_constant_numbering", + "_terminal_numbering", "_hash", "_signature", # --- Dict that external frameworks can place framework-specific @@ -289,11 +293,10 @@ def __init__(self, integrals): self._coefficients = None self._coefficient_numbering = None self._constant_numbering = None + self._terminal_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 @@ -406,8 +409,15 @@ def coefficients(self): def coefficient_numbering(self): """Return a contiguous numbering of coefficients in a mapping ``{coefficient:number}``.""" + # cyclic import + from ufl.coefficient import Coefficient + if self._coefficient_numbering is None: - self._analyze_form_arguments() + self._coefficient_numbering = { + expr: num + for expr, num in self.terminal_numbering().items() + if isinstance(expr, Coefficient) + } return self._coefficient_numbering def constants(self): @@ -416,8 +426,38 @@ def constants(self): def constant_numbering(self): """Return a contiguous numbering of constants in a mapping ``{constant:number}``.""" + if self._constant_numbering is None: + self._constant_numbering = { + expr: num + for expr, num in self.terminal_numbering().items() + if isinstance(expr, Constant) + } return self._constant_numbering + def terminal_numbering(self): + """Return a contiguous numbering for all counted objects in the form. + + The returned object is mapping from terminal to its number (an integer). + + The numbering is computed per type so :class:`Coefficient`s, + :class:`Constant`s, etc will each be numbered from zero. + + """ + # cyclic import + from ufl.algorithms.analysis import extract_type + + if self._terminal_numbering is None: + exprs_by_type = defaultdict(set) + for counted_expr in extract_type(self, Counted): + exprs_by_type[counted_expr._counted_class].add(counted_expr) + + numbering = {} + for exprs in exprs_by_type.values(): + for i, expr in enumerate(sorted_by_count(exprs)): + numbering[expr] = i + self._terminal_numbering = numbering + return self._terminal_numbering + def signature(self): "Signature for use with jit cache (independent of incidental numbering of indices etc.)" if self._signature is None: @@ -625,23 +665,19 @@ def _analyze_form_arguments(self): sorted(set(arguments), key=lambda x: x.number())) self._coefficients = tuple( sorted(set(coefficients), key=lambda x: x.count())) - self._coefficient_numbering = dict( - (c, i) for i, c in enumerate(self._coefficients)) def _compute_renumbering(self): # Include integration domains and coefficients in renumbering dn = self.domain_numbering() - cn = self.coefficient_numbering() - cnstn = self.constant_numbering() + tn = self.terminal_numbering() renumbering = {} renumbering.update(dn) - renumbering.update(cn) - renumbering.update(cnstn) + renumbering.update(tn) # Add domains of coefficients, these may include domains not # among integration domains k = len(dn) - for c in cn: + for c in self.coefficients(): d = extract_unique_domain(c) if d is not None and d not in renumbering: renumbering[d] = k diff --git a/ufl/matrix.py b/ufl/matrix.py index bd3a25d91..0b120f414 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -13,24 +13,24 @@ from ufl.core.ufl_type import ufl_type from ufl.argument import Argument from ufl.functionspace import AbstractFunctionSpace -from ufl.utils.counted import counted_init +from ufl.utils.counted import Counted # --- The Matrix class represents a matrix, an assembled two form --- @ufl_type() -class Matrix(BaseForm): +class Matrix(BaseForm, Counted): """An assemble linear operator between two function spaces.""" __slots__ = ( "_count", + "_counted_class", "_ufl_function_spaces", "ufl_operands", "_repr", "_hash", "_ufl_shape", "_arguments") - _globalcount = 0 def __getnewargs__(self): return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], @@ -38,7 +38,7 @@ def __getnewargs__(self): def __init__(self, row_space, column_space, count=None): BaseForm.__init__(self) - counted_init(self, count, Matrix) + Counted.__init__(self, count, Matrix) if not isinstance(row_space, AbstractFunctionSpace): raise ValueError("Expecting a FunctionSpace as the row space.") @@ -52,9 +52,6 @@ def __init__(self, row_space, column_space, count=None): self._hash = None self._repr = f"Matrix({self._ufl_function_spaces[0]!r}, {self._ufl_function_spaces[1]!r}, {self._count!r})" - def count(self): - return self._count - def ufl_function_spaces(self): "Get the tuple of function spaces of this coefficient." return self._ufl_function_spaces diff --git a/ufl/utils/counted.py b/ufl/utils/counted.py index 66d3fd79f..f04bc2aca 100644 --- a/ufl/utils/counted.py +++ b/ufl/utils/counted.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"Utilites for types with a global unique counter attached to each object." +"Mixin class for types with a global unique counter attached to each object." # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -7,37 +7,34 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import itertools -def counted_init(self, count=None, countedclass=None): - "Initialize a counted object, see ExampleCounted below for how to use." - if countedclass is None: - countedclass = type(self) +class Counted: + """Mixin class for globally counted objects.""" - if count is None: - count = countedclass._globalcount + # Mixin classes do not work well with __slots__ so _count must be + # added to the __slots__ of the inheriting class + __slots__ = () - self._count = count + _counter = None - if self._count >= countedclass._globalcount: - countedclass._globalcount = self._count + 1 + def __init__(self, count=None, counted_class=None): + """Initialize the Counted instance. + :arg count: The object count, if ``None`` defaults to the next value + according to the global counter (per type). + :arg counted_class: Class to attach the global counter too. If ``None`` + then ``type(self)`` will be used. -class ExampleCounted(object): - """An example class for classes of objects identified by a global counter. + """ + # create a new counter for each subclass + counted_class = counted_class or type(self) + if counted_class._counter is None: + counted_class._counter = itertools.count() - Mimic this class to create globally counted objects within a single type. - """ - # Store the count for each object - __slots__ = ("_count",) + self._count = count if count is not None else next(counted_class._counter) + self._counted_class = counted_class - # Store a global counter with the class - _globalcount = 0 - - # Call counted_init with an optional constructor argument and the class - def __init__(self, count=None): - counted_init(self, count, ExampleCounted) - - # Make the count accessible def count(self): return self._count diff --git a/ufl/variable.py b/ufl/variable.py index 1b68d6c06..b5a29dc26 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -8,7 +8,7 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.utils.counted import counted_init +from ufl.utils.counted import Counted from ufl.core.expr import Expr from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal @@ -17,17 +17,12 @@ @ufl_type() -class Label(Terminal): - __slots__ = ("_count",) - - _globalcount = 0 +class Label(Terminal, Counted): + __slots__ = ("_count", "_counted_class") def __init__(self, count=None): Terminal.__init__(self) - counted_init(self, count, Label) - - def count(self): - return self._count + Counted.__init__(self, count, Label) def __str__(self): return "Label(%d)" % self._count @@ -55,6 +50,9 @@ def ufl_domains(self): "Return tuple of domains related to this terminal object." return () + def _ufl_signature_data_(self, renumbering): + return ("Label", renumbering[self]) + @ufl_type(is_shaping=True, is_index_free=True, num_ops=1, inherit_shape_from_operand=0) class Variable(Operator): From a49736c7e4372b9bac61d728c223c8b6784b25a3 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 9 Aug 2023 16:13:24 +0100 Subject: [PATCH 037/136] avoid error if Label not in renumbering (#187) --- ufl/variable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ufl/variable.py b/ufl/variable.py index b5a29dc26..935c2f8c8 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -51,6 +51,8 @@ def ufl_domains(self): return () def _ufl_signature_data_(self, renumbering): + if self not in renumbering: + return ("Label", self._count) return ("Label", renumbering[self]) From 9e120c6ace16f13c7b8bf9398d1dd1a71d695f7f Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 10 Aug 2023 07:23:43 +0100 Subject: [PATCH 038/136] Added 4d element types (#188) * [feature][ElementTypes] Added 4d types pentatope (4d simplex) and tesseract (4d cube). (#95) Co-authored-by: Jack S. Hale Co-authored-by: David A. Ham Co-authored-by: Matthew Scroggs * fix merge, add test --------- Co-authored-by: dr-robertk Co-authored-by: Jack S. Hale Co-authored-by: David A. Ham --- test/test_cell.py | 23 ++++++++++++++++++++++- ufl/__init__.py | 6 ++++-- ufl/cell.py | 8 ++++++++ ufl/finiteelement/elementlist.py | 7 ++++--- ufl/objects.py | 2 ++ 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/test/test_cell.py b/test/test_cell.py index 21a5240fa..8ec754622 100644 --- a/test/test_cell.py +++ b/test/test_cell.py @@ -37,6 +37,20 @@ def test_hexahedron(): assert cell.num_faces() == 6 +def test_pentatope(): + cell = ufl.pentatope + assert cell.num_vertices() == 5 + assert cell.num_edges() == 10 + assert cell.num_faces() == 10 + + +def test_tesseract(): + cell = ufl.tesseract + assert cell.num_vertices() == 16 + assert cell.num_edges() == 32 + assert cell.num_faces() == 24 + + @pytest.mark.parametrize("cell", [ufl.interval]) def test_cells_1d(cell): @@ -53,12 +67,19 @@ def test_cells_2d(cell): @pytest.mark.parametrize("cell", [ufl.tetrahedron, ufl.hexahedron, ufl.prism, ufl.pyramid]) -def test_cells_2d(cell): +def test_cells_3d(cell): assert cell.num_facets() == cell.num_faces() assert cell.num_ridges() == cell.num_edges() assert cell.num_peaks() == cell.num_vertices() +@pytest.mark.parametrize("cell", [ufl.tesseract, ufl.pentatope]) +def test_cells_4d(cell): + assert cell.num_facets() == cell.num_sub_entities(3) + assert cell.num_ridges() == cell.num_faces() + assert cell.num_peaks() == cell.num_edges() + + def test_tensorproductcell(): orig = ufl.TensorProductCell(ufl.interval, ufl.interval) cell = orig.reconstruct() diff --git a/ufl/__init__.py b/ufl/__init__.py index 7e7ca3cdb..7cb9cf0e0 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -53,6 +53,8 @@ - hexahedron - prism - pyramid + - pentatope + - tesseract * Domains:: @@ -339,7 +341,7 @@ # Predefined convenience objects from ufl.objects import ( - vertex, interval, triangle, tetrahedron, + vertex, interval, triangle, tetrahedron, pentatope, tesseract, quadrilateral, hexahedron, prism, pyramid, facet, i, j, k, l, p, q, r, s, dx, ds, dS, dP, @@ -400,7 +402,7 @@ 'dc', 'dC', 'dO', 'dI', 'dX', 'ds_b', 'ds_t', 'ds_tb', 'ds_v', 'dS_h', 'dS_v', 'vertex', 'interval', 'triangle', 'tetrahedron', - 'prism', 'pyramid', + 'prism', 'pyramid', 'pentatope', 'tesseract', 'quadrilateral', 'hexahedron', 'facet', 'i', 'j', 'k', 'l', 'p', 'q', 'r', 's', 'e', 'pi', diff --git a/ufl/cell.py b/ufl/cell.py index 9ef8e9975..2d50f6a77 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -196,6 +196,10 @@ def peak_types(self) -> typing.Tuple[AbstractCell, ...]: ("triangle", "quadrilateral", "quadrilateral", "quadrilateral", "triangle"), ("prism", )], "pyramid": [tuple("vertex" for i in range(5)), tuple("interval" for i in range(8)), ("quadrilateral", "triangle", "triangle", "triangle", "triangle"), ("pyramid", )], + "pentatope": [tuple("vertex" for i in range(5)), tuple("interval" for i in range(10)), + tuple("triangle" for i in range(10)), tuple("tetrahedron" for i in range(5)), ("pentatope", )], + "tesseract": [tuple("vertex" for i in range(16)), tuple("interval" for i in range(32)), + tuple("quadrilateral" for i in range(24)), tuple("hexahedron" for i in range(8)), ("tesseract", )], } @@ -422,6 +426,8 @@ def simplex(topological_dimension: int, geometric_dimension: typing.Optional[int return Cell("triangle", geometric_dimension) if topological_dimension == 3: return Cell("tetrahedron", geometric_dimension) + if topological_dimension == 4: + return Cell("pentatope", geometric_dimension) raise ValueError(f"Unsupported topological dimension for simplex: {topological_dimension}") @@ -435,6 +441,8 @@ def hypercube(topological_dimension, geometric_dimension=None): return Cell("quadrilateral", geometric_dimension) if topological_dimension == 3: return Cell("hexahedron", geometric_dimension) + if topological_dimension == 4: + return Cell("tesseract", geometric_dimension) raise ValueError(f"Unsupported topological dimension for hypercube: {topological_dimension}") diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index da3855641..6ea6a6fa2 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -12,6 +12,7 @@ # Modified by Marie E. Rognes , 2010 # Modified by Lizao Li , 2015, 2016 # Modified by Massimiliano Leoni, 2016 +# Modified by Robert Kloefkorn, 2022 import warnings from numpy import asarray @@ -82,12 +83,12 @@ def show_elements(): # the future, add mapping name as another element property. # Cell groups -simplices = ("interval", "triangle", "tetrahedron") -cubes = ("interval", "quadrilateral", "hexahedron") +simplices = ("interval", "triangle", "tetrahedron", "pentatope") +cubes = ("interval", "quadrilateral", "hexahedron", "tesseract") any_cell = (None, "vertex", "interval", "triangle", "tetrahedron", "prism", - "pyramid", "quadrilateral", "hexahedron") + "pyramid", "quadrilateral", "hexahedron", "pentatope", "tesseract") # Elements in the periodic table # TODO: Register these as aliases of # periodic table element description instead of the other way around diff --git a/ufl/objects.py b/ufl/objects.py index ab414091d..84f3f360c 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -37,6 +37,8 @@ pyramid = Cell("pyramid", 3) quadrilateral = Cell("quadrilateral", 2) hexahedron = Cell("hexahedron", 3) +tesseract = Cell("tesseract", 4) +pentatope = Cell("pentatope", 4) # Facet is just a dummy declaration for RestrictedElement facet = "facet" From 5d6df80985dc52f009fe91c3b46991a263fdcef5 Mon Sep 17 00:00:00 2001 From: mrambausek Date: Tue, 12 Sep 2023 11:02:43 +0200 Subject: [PATCH 039/136] Fix issue #36 (#37) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed probably unneeded troublesome lines in VectorElement The removed code overwrites some properties for whatever reason, in particular, quad_scheme. In TensorElement these two lines were not there at all. * added back FiniteElementBase constructor but (re)use sub-element data properly Note: the call to FiniteElementBase.__init__ overrides some properties set by the MixedElement constructor. Essentially the same seems to be done in TensorElement, but more explicitly. --------- Co-authored-by: rambausek Co-authored-by: Jørgen Schartum Dokken Co-authored-by: Matthew Scroggs --- ufl/finiteelement/mixedelement.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index b67fc9f48..f49662122 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -311,8 +311,10 @@ def __init__(self, family, cell=None, degree=None, dim=None, # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) - FiniteElementBase.__init__(self, sub_element.family(), cell, sub_element.degree(), quad_scheme, - value_shape, reference_value_shape) + + FiniteElementBase.__init__(self, sub_element.family(), sub_element.cell(), sub_element.degree(), + sub_element.quadrature_scheme(), value_shape, reference_value_shape) + self._sub_element = sub_element if variant is None: From 551f6735fa238a6edc70a4de48178872190e6074 Mon Sep 17 00:00:00 2001 From: Yang Zongze Date: Tue, 12 Sep 2023 17:03:57 +0800 Subject: [PATCH 040/136] Add function determinant_expr_nxn (#101) * Add function determinant_expr_nxn * Change the order of the assert expression --------- Co-authored-by: Matthew Scroggs --- test/test_apply_algebra_lowering.py | 2 +- ufl/compound_expressions.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test/test_apply_algebra_lowering.py b/test/test_apply_algebra_lowering.py index a9a097b15..a66332313 100755 --- a/test/test_apply_algebra_lowering.py +++ b/test/test_apply_algebra_lowering.py @@ -55,7 +55,7 @@ def test_determinant2(A2): def test_determinant3(A3): assert determinant_expr(A3) == (A3[0, 0]*(A3[1, 1]*A3[2, 2] - A3[1, 2]*A3[2, 1]) - + A3[0, 1]*(A3[1, 2]*A3[2, 0] - A3[1, 0]*A3[2, 2]) + + (A3[1, 0]*A3[2, 2] - A3[1, 2]*A3[2, 0])*(-A3[0, 1]) + A3[0, 2]*(A3[1, 0]*A3[2, 1] - A3[1, 1]*A3[2, 0])) diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index a25307298..af3e05fd9 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -93,6 +93,8 @@ def determinant_expr(A): return determinant_expr_2x2(A) elif sh[0] == 3: return determinant_expr_3x3(A) + else: + return determinant_expr_nxn(A) else: return pseudo_determinant_expr(A) @@ -116,6 +118,12 @@ def determinant_expr_3x3(A): return codeterminant_expr_nxn(A, [0, 1, 2], [0, 1, 2]) +def determinant_expr_nxn(A): + nrow, ncol = A.ufl_shape + assert nrow == ncol + return codeterminant_expr_nxn(A, list(range(nrow)), list(range(ncol))) + + def codeterminant_expr_nxn(A, rows, cols): if len(rows) == 2: return _det_2x2(A, rows[0], rows[1], cols[0], cols[1]) @@ -123,8 +131,8 @@ def codeterminant_expr_nxn(A, rows, cols): r = rows[0] subrows = rows[1:] for i, c in enumerate(cols): - subcols = cols[i + 1:] + cols[:i] - codet += A[r, c] * codeterminant_expr_nxn(A, subrows, subcols) + subcols = cols[:i] + cols[i + 1:] + codet += (-1)**i * A[r, c] * codeterminant_expr_nxn(A, subrows, subcols) return codet From 37ae3531c8e411fef275cfb74712b284a231fc8a Mon Sep 17 00:00:00 2001 From: Cian Wilson Date: Tue, 12 Sep 2023 05:04:23 -0400 Subject: [PATCH 041/136] Possible fix for taking scalar derivatives of vector coefficients using coefficient derivative maps (#124) * A possible fix for taking the derivative of vector coefficients w.r.t. a scalar. Also adding a test. * A slightly neater version that handles scalar derivatives of vectors. --------- Co-authored-by: Matthew Scroggs --- test/test_derivative.py | 24 ++++++++++++++++++++++++ ufl/algorithms/apply_derivatives.py | 6 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/test/test_derivative.py b/test/test_derivative.py index 0259f066e..2a023b9ae 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -483,6 +483,30 @@ def test_coefficient_derivatives(self): self.assertEqual(replace(actual, fd.function_replace_map), expected) +def test_vector_coefficient_scalar_derivatives(self): + V = FiniteElement("Lagrange", triangle, 1) + VV = VectorElement("Lagrange", triangle, 1) + + dv = TestFunction(V) + + df = Coefficient(VV, count=0) + g = Coefficient(VV, count=1) + f = Coefficient(VV, count=2) + u = Coefficient(V, count=3) + cd = {f: df} + + integrand = inner(f, g) + + i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] + expected = as_tensor(df[i1]*dv, (i1,))[i0]*g[i0] + + F = integrand*dx + J = derivative(F, u, dv, cd) + fd = compute_form_data(J) + actual = fd.preprocessed_form.integrals()[0].integrand() + assert (actual*dx).signature() == (expected*dx).signature() + + def test_vector_coefficient_derivatives(self): V = VectorElement("Lagrange", triangle, 1) VV = TensorElement("Lagrange", triangle, 1) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 01c79b8cd..680969426 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -835,9 +835,9 @@ def coefficient(self, o): dosum = Zero(o.ufl_shape) for do, v in zip(dos, self._v): so, oi = as_scalar(do) - rv = len(v.ufl_shape) - oi1 = oi[:-rv] - oi2 = oi[-rv:] + rv = len(oi) - len(v.ufl_shape) + oi1 = oi[:rv] + oi2 = oi[rv:] prod = so * v[oi2] if oi1: dosum += as_tensor(prod, oi1) From f08d7c3b738abdbf9eb93f0d57ed1d6747a65fde Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Sep 2023 10:04:37 +0100 Subject: [PATCH 042/136] Return correct Sobolev space for RestrictedElement (#128) * return correct Sobolev space for FacetElement * InteriorElement in L2 --------- Co-authored-by: Matthew Scroggs --- ufl/finiteelement/restrictedelement.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index 82cd581f4..c1d10b65c 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -39,7 +39,10 @@ def __repr__(self): return f"RestrictedElement({repr(self._element)}, {repr(self._restriction_domain)})" def sobolev_space(self): - return L2 + if self._restriction_domain == "interior": + return L2 + else: + return self._element.sobolev_space() def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially From 5803fcb6dab627a462dd378bcc17f7f058aefd3e Mon Sep 17 00:00:00 2001 From: tommbendall Date: Tue, 12 Sep 2023 10:05:01 +0100 Subject: [PATCH 043/136] Implements the perp operator as a class (#184) * Change perp to being a named operator * PR #184: fix behaviour when perp is Zero and expand testing --------- Co-authored-by: Matthew Scroggs --- test/test_tensoralgebra.py | 21 ++++++++++++++++++ ufl/algorithms/apply_algebra_lowering.py | 3 +++ ufl/operators.py | 4 ++-- ufl/tensoralgebra.py | 28 ++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index 0217311dc..e8831a1d8 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -105,6 +105,27 @@ def test_cross(self): self.assertEqualValues(C, D) +def test_perp(self): + # Test perp is generally doing the correct thing + u = as_vector([3, 1]) + v = perp(u) + w = as_vector([-1, 3]) + self.assertEqualValues(v, w) + + # Test that a perp does the correct thing to Zero + u = zero(2) + v = perp(u) + self.assertEqualValues(u, v) + + # Test that perp throws an error if the wrong thing is provided + u = as_vector([3, 1, -1]) # 3D vector instead of 2D + with pytest.raises(ValueError): + v = perp(u) + u = as_matrix([[1, 3], [0, 4]]) # Matrix instead of vector + with pytest.raises(ValueError): + v = perp(u) + + def xtest_dev(self, A): C = dev(A) D = 0*C # FIXME: Add expected value here diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index 1aec94467..3938ed01f 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -55,6 +55,9 @@ def c(i, j): return Product(a[i], b[j]) - Product(a[j], b[i]) return as_vector((c(1, 2), c(2, 0), c(0, 1))) + def perp(self, o, a): + return as_vector([-a[1], a[0]]) + def dot(self, o, a, b): ai = indices(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 1) diff --git a/ufl/operators.py b/ufl/operators.py index 44dfea0c4..baf66f2bf 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -20,7 +20,7 @@ from ufl.constantvalue import Zero, RealValue, ComplexValue, as_ufl from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv from ufl.tensoralgebra import ( - Transposed, Inner, Outer, Dot, Cross, + Transposed, Inner, Outer, Dot, Cross, Perp, Determinant, Inverse, Cofactor, Trace, Deviatoric, Skew, Sym) from ufl.coefficient import Coefficient from ufl.variable import Variable @@ -175,7 +175,7 @@ def perp(v): v = as_ufl(v) if v.ufl_shape != (2,): raise ValueError("Expecting a 2D vector expression.") - return as_vector((-v[1], v[0])) + return Perp(v) def cross(a, b): diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 374931dc2..e1f40d8e5 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -217,6 +217,34 @@ def __str__(self): parstr(self.ufl_operands[1], self)) +@ufl_type(is_index_free=True, num_ops=1) +class Perp(CompoundTensorOperator): + __slots__ = () + + def __new__(cls, A): + sh = A.ufl_shape + + # Checks + if not len(sh) == 1: + raise ValueError(f"Perp requires arguments of rank 1, got {ufl_err_str(A)}") + if not sh[0] == 2: + raise ValueError(f"Perp can only work on 2D vectors, got {ufl_err_str(A)}") + + # Simplification + if isinstance(A, Zero): + return Zero(sh, A.ufl_free_indices, A.ufl_index_dimensions) + + return CompoundTensorOperator.__new__(cls) + + def __init__(self, A): + CompoundTensorOperator.__init__(self, (A,)) + + ufl_shape = (2,) + + def __str__(self): + return "perp(%s)" % self.ufl_operands[0] + + @ufl_type(num_ops=2) class Cross(CompoundTensorOperator): __slots__ = ("ufl_free_indices", "ufl_index_dimensions") From 0bafaf5bba781d31c2a32f3d8b48ab8e5fb59386 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 12 Sep 2023 10:05:20 +0100 Subject: [PATCH 044/136] Fix coefficients optional kwarg when calling a Form (#193) Co-authored-by: Matthew Scroggs From c01759f1f78b42e217a058c1c2bc8b7a4209bfaf Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Tue, 12 Sep 2023 10:59:09 +0100 Subject: [PATCH 045/136] Add trimmed serendipity to element list. (#195) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: nbouziani Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward --- ufl/finiteelement/elementlist.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index 6ea6a6fa2..73ca9fe90 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -193,6 +193,10 @@ def show_elements(): ("quadrilateral",)) register_element("BDMCF", None, 1, HDiv, "contravariant Piola", (1, None), ("quadrilateral",)) +register_element("SminusE", "SminusE", 1, HCurl, "covariant Piola", (1, None), cubes[1:3]) +register_element("SminusF", "SminusF", 1, HDiv, "contravariant Piola", (1, None), cubes[1:2]) +register_element("SminusDiv", "SminusDiv", 1, HDiv, "contravariant Piola", (1, None), cubes[1:3]) +register_element("SminusCurl", "SminusCurl", 1, HCurl, "covariant Piola", (1, None), cubes[1:3]) register_element("AAE", None, 1, HCurl, "covariant Piola", (1, None), ("hexahedron",)) register_element("AAF", None, 1, HDiv, "contravariant Piola", (1, None), From 8f44275dcf88f8c853487fa832c41098f9d9506c Mon Sep 17 00:00:00 2001 From: Ignacia Fierro-Piccardo Date: Tue, 12 Sep 2023 12:14:25 +0100 Subject: [PATCH 046/136] atan_2 changed to atan2 (#196) --- ufl/__init__.py | 6 +++--- ufl/algorithms/apply_derivatives.py | 2 +- ufl/algorithms/estimate_degrees.py | 2 +- ufl/mathfunctions.py | 4 ++-- ufl/operators.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ufl/__init__.py b/ufl/__init__.py index 7cb9cf0e0..a31d341ee 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -185,7 +185,7 @@ - sqrt - exp, ln, erf - cos, sin, tan - - acos, asin, atan, atan_2 + - acos, asin, atan, atan2 - cosh, sinh, tanh - bessel_J, bessel_Y, bessel_I, bessel_K @@ -320,7 +320,7 @@ from ufl.operators import ( rank, shape, conj, real, imag, outer, inner, dot, cross, perp, det, inv, cofac, transpose, tr, diag, diag_vector, dev, skew, sym, - sqrt, exp, ln, erf, cos, sin, tan, acos, asin, atan, atan_2, cosh, sinh, tanh, + sqrt, exp, ln, erf, cos, sin, tan, acos, asin, atan, atan2, cosh, sinh, tanh, bessel_J, bessel_Y, bessel_I, bessel_K, eq, ne, le, ge, lt, gt, And, Or, Not, conditional, sign, max_value, min_value, variable, diff, Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, @@ -385,7 +385,7 @@ 'transpose', 'tr', 'diag', 'diag_vector', 'dev', 'skew', 'sym', 'sqrt', 'exp', 'ln', 'erf', 'cos', 'sin', 'tan', - 'acos', 'asin', 'atan', 'atan_2', + 'acos', 'asin', 'atan', 'atan2', 'cosh', 'sinh', 'tanh', 'bessel_J', 'bessel_Y', 'bessel_I', 'bessel_K', 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 680969426..befb4067b 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -354,7 +354,7 @@ def atan(self, o, fp): f, = o.ufl_operands return fp / (1.0 + f**2) - def atan_2(self, o, fp, gp): + def atan2(self, o, fp, gp): f, g = o.ufl_operands return (g * fp - f * gp) / (f**2 + g**2) diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 53dae9354..de39d26b3 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -242,7 +242,7 @@ def power(self, v, a, b): # negative integer, Coefficient, etc. return self._add_degrees(v, a, 2) - def atan_2(self, v, a, b): + def atan2(self, v, a, b): """Using the heuristic degree(atan2(const,const)) == 0 degree(atan2(a,b)) == max(degree(a),degree(b))+2 diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index c4bc9aca4..fc0619cd6 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -290,12 +290,12 @@ def evaluate(self, x, mapping, component, index_values): except TypeError: raise ValueError('Atan2 does not support complex numbers.') except ValueError: - warnings.warn('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) + warnings.warn('Value error in evaluation of function atan2 with arguments %s, %s.' % (a, b)) raise return res def __str__(self): - return "atan_2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) + return "atan2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type() diff --git a/ufl/operators.py b/ufl/operators.py index baf66f2bf..dfdc01956 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -605,12 +605,12 @@ def atan(f): return _mathfunction(f, Atan) -def atan_2(f1, f2): +def atan2(f1, f2): "UFL operator: Take the inverse tangent with two the arguments *f1* and *f2*." f1 = as_ufl(f1) f2 = as_ufl(f2) if isinstance(f1, (ComplexValue, complex)) or isinstance(f2, (ComplexValue, complex)): - raise TypeError('atan_2 is incompatible with complex numbers.') + raise TypeError('atan2 is incompatible with complex numbers.') r = Atan2(f1, f2) if isinstance(r, (RealValue, Zero, int, float)): return float(r) From e2632367d14ca25e0246d93c2aaf253fc2fe3ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 12 Sep 2023 14:54:59 +0200 Subject: [PATCH 047/136] Group integrals with common integrand in IntegralData (#92) --------- Co-authored-by: Igor Baratta Co-authored-by: Matthew Scroggs Co-authored-by: David A. Ham --- .github/workflows/fenicsx-tests.yml | 4 +- test/test_domains.py | 57 +++++++++++++++++++++++++---- ufl/algorithms/compute_form_data.py | 13 +++---- ufl/algorithms/domain_analysis.py | 48 ++++++++++++++++-------- ufl/form.py | 12 +++++- 5 files changed, 102 insertions(+), 32 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 30d8094c2..39a2e8191 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -41,6 +41,8 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx + ref: dokken/group_integrals_v2 + - name: Install FFCx run: | cd ffcx @@ -77,7 +79,7 @@ jobs: - name: Install Basix and FFCx run: | python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + python3 -m pip install git+https://github.com/FEniCS/ffcx.git@dokken/group_integrals_v2 - name: Clone DOLFINx uses: actions/checkout@v3 diff --git a/test/test_domains.py b/test/test_domains.py index b072274a6..2422a8822 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -4,6 +4,7 @@ Tests of domain language and attaching domains to forms. """ +from mockobjects import MockMesh, MockMeshFunction import pytest from ufl import * @@ -13,8 +14,6 @@ all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) -from mockobjects import MockMesh, MockMeshFunction - def test_construct_domains_from_cells(): for cell in all_cells: @@ -198,7 +197,8 @@ def test_everywhere_integrals_with_backwards_compatibility(): # Check some integral data assert ida.integral_type == "cell" - assert ida.subdomain_id == "otherwise" + assert(len(ida.subdomain_id) == 1) + assert ida.subdomain_id[0] == "otherwise" assert ida.metadata == {} # Integrands are not equal because of renumbering @@ -208,6 +208,47 @@ def test_everywhere_integrals_with_backwards_compatibility(): assert itg1.ufl_element() == itg2.ufl_element() +def test_merge_sort_integral_data(): + D = Mesh(triangle) + + V = FunctionSpace(D, FiniteElement("CG", triangle, 1)) + + u = Coefficient(V) + c = Constant(D) + a = c * dS((2, 4)) + u * dx + u * ds + 2 * u * dx(3) + 2 * c * dS + 2 * u * dx((1, 4)) + form_data = compute_form_data(a, do_append_everywhere_integrals=False).integral_data + assert(len(form_data) == 5) + + # Check some integral data + assert form_data[0].integral_type == "cell" + assert(len(form_data[0].subdomain_id) == 1) + assert form_data[0].subdomain_id[0] == "otherwise" + assert form_data[0].metadata == {} + + assert form_data[1].integral_type == "cell" + assert(len(form_data[1].subdomain_id) == 3) + assert form_data[1].subdomain_id[0] == 1 + assert form_data[1].subdomain_id[1] == 3 + assert form_data[1].subdomain_id[2] == 4 + assert form_data[1].metadata == {} + + assert form_data[2].integral_type == "exterior_facet" + assert(len(form_data[2].subdomain_id) == 1) + assert form_data[2].subdomain_id[0] == "otherwise" + assert form_data[2].metadata == {} + + assert form_data[3].integral_type == "interior_facet" + assert(len(form_data[3].subdomain_id) == 1) + assert form_data[3].subdomain_id[0] == "otherwise" + assert form_data[3].metadata == {} + + assert form_data[4].integral_type == "interior_facet" + assert(len(form_data[4].subdomain_id) == 2) + assert form_data[4].subdomain_id[0] == 2 + assert form_data[4].subdomain_id[1] == 4 + assert form_data[4].metadata == {} + + def xtest_mixed_elements_on_overlapping_regions(): # Old sketch, not working # Create domain and both disjoint and overlapping regions @@ -366,10 +407,10 @@ def xtest_form_domain_model(): # Old sketch, not working # domains and regions to be part of their integrands dxb = dx('DB') # Get Mesh by name dxbl = dx(Region(DB, (1, 4), 'DBL2')) - # Provide a region with different name but same subdomain ids as - # DBL + # Provide a region with different name but same subdomain ids as + # DBL dxbr = dx((1, 4)) - # Assume unique Mesh and provide subdomain ids explicitly + # Assume unique Mesh and provide subdomain ids explicitly # Not checking measure objects in detail, as long as # they carry information to construct integrals below @@ -466,8 +507,8 @@ def sub_elements_on_subdomains(W): # Disjunctified by UFL: alonly = dot(ul, vl) * dx(D1) - # integral_1 knows that only subelement VL is active + # integral_1 knows that only subelement VL is active am = (dot(ul, vl) + ur * vr) * dx(D2) - # integral_2 knows that both subelements are active + # integral_2 knows that both subelements are active aronly = ur * vr * \ dx(D3) # integral_3 knows that only subelement VR is active diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 842d43d9f..348e27e17 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -102,13 +102,12 @@ def _compute_max_subdomain_ids(integral_data): max_subdomain_ids = {} for itg_data in integral_data: it = itg_data.integral_type - si = itg_data.subdomain_id - if isinstance(si, int): - newmax = si + 1 - else: - newmax = 0 - prevmax = max_subdomain_ids.get(it, 0) - max_subdomain_ids[it] = max(prevmax, newmax) + for integral in itg_data.integrals: + # Convert string for default integral to -1 + sids = (-1 if isinstance(si, str) else si for si in integral.subdomain_id()) + newmax = max(sids) + 1 + prevmax = max_subdomain_ids.get(it, 0) + max_subdomain_ids[it] = max(prevmax, newmax) return max_subdomain_ids diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index e12668ac9..5afec433a 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -12,11 +12,13 @@ import ufl from ufl.integral import Integral from ufl.form import Form +from ufl.protocols import id_or_none from ufl.sorting import cmp_expr, sorted_expr from ufl.utils.sorting import canonicalize_metadata, sorted_by_key from ufl.algorithms.coordinate_derivative_helpers import ( attach_coordinate_derivatives, strip_coordinate_derivatives) import numbers +import typing class IntegralData(object): @@ -134,14 +136,18 @@ def integral_subdomain_ids(integral): raise ValueError(f"Invalid domain id {did}.") -def rearrange_integrals_by_single_subdomains(integrals, do_append_everywhere_integrals): +def rearrange_integrals_by_single_subdomains( + integrals: typing.List[Integral], + do_append_everywhere_integrals: bool) -> typing.Dict[int, typing.List[Integral]]: """Rearrange integrals over multiple subdomains to single subdomain integrals. Input: - integrals: list(Integral) + integrals: List of integrals + do_append_everywhere_integrals: Boolean indicating if integrals defined on the whole domain should + just be restricted to the set of input subdomain ids. Output: - integrals: dict: subdomain_id -> list(Integral) (reconstructed with single subdomain_id) + integrals: The integrals reconstructed with single subdomain_id """ # Split integrals into lists of everywhere and subdomain integrals everywhere_integrals = [] @@ -231,23 +237,37 @@ def build_integral_data(integrals): """ itgs = defaultdict(list) + # --- Merge integral data that has the same integrals, + unique_integrals = defaultdict(tuple) + metadata_table = defaultdict(dict) for integral in integrals: - domain = integral.ufl_domain() + integrand = integral.integrand() integral_type = integral.integral_type() + ufl_domain = integral.ufl_domain() + metadata = integral.metadata() + meta_hash = hash(canonicalize_metadata(metadata)) subdomain_id = integral.subdomain_id() + subdomain_data = id_or_none(integral.subdomain_data()) if subdomain_id == "everywhere": raise ValueError("'everywhere' not a valid subdomain id. Did you forget to call group_form_integrals?") + unique_integrals[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] += (subdomain_id,) + metadata_table[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] = metadata + + for integral_data, subdomain_ids in unique_integrals.items(): + (integral_type, ufl_domain, metadata, integrand, subdomain_data) = integral_data + + integral = Integral(integrand, integral_type, ufl_domain, subdomain_ids, + metadata_table[integral_data], subdomain_data) # Group for integral data (One integral data object for all - # integrals with same domain, itype, subdomain_id (but - # possibly different metadata). - itgs[(domain, integral_type, subdomain_id)].append(integral) + # integrals with same domain, itype, (but possibly different metadata). + itgs[(ufl_domain, integral_type, subdomain_ids)].append(integral) # Build list with canonical ordering, iteration over dicts # is not deterministic across python versions def keyfunc(item): (d, itype, sid), integrals = item - return (d._ufl_sort_key_(), itype, (type(sid).__name__, sid)) - + sid_int = tuple(-1 if i == "otherwise" else i for i in sid) + return (d._ufl_sort_key_(), itype, (type(sid).__name__, ), sid_int) integral_datas = [] for (d, itype, sid), integrals in sorted(itgs.items(), key=keyfunc): integral_datas.append(IntegralData(d, itype, sid, integrals, {})) @@ -262,8 +282,7 @@ def group_form_integrals(form, domains, do_append_everywhere_integrals=True): :returns: A new :class:`~.Form` with gathered integrands. """ # Group integrals by domain and type - integrals_by_domain_and_type = \ - group_integrals_by_domain_and_type(form.integrals(), domains) + integrals_by_domain_and_type = group_integrals_by_domain_and_type(form.integrals(), domains) integrals = [] for domain in domains: @@ -277,8 +296,8 @@ def group_form_integrals(form, domains, do_append_everywhere_integrals=True): # f*dx((1,2)) + g*dx((2,3)) -> f*dx(1) + (f+g)*dx(2) + g*dx(3) # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) - single_subdomain_integrals = \ - rearrange_integrals_by_single_subdomains(ddt_integrals, do_append_everywhere_integrals) + single_subdomain_integrals = rearrange_integrals_by_single_subdomains( + ddt_integrals, do_append_everywhere_integrals) for subdomain_id, ss_integrals in sorted_by_key(single_subdomain_integrals): @@ -307,8 +326,7 @@ def calc_hash(cd): # Accumulate integrands of integrals that share the # same compiler data - integrands_and_cds = \ - accumulate_integrands_with_same_metadata(samecd_integrals[1]) + integrands_and_cds = accumulate_integrands_with_same_metadata(samecd_integrals[1]) for integrand, metadata in integrands_and_cds: integral = Integral(integrand, integral_type, domain, diff --git a/ufl/form.py b/ufl/form.py index eeff85020..d4370b65a 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -10,7 +10,9 @@ # Modified by Anders Logg, 2009-2011. # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. +# Modified by Jørgen S. Dokken 2023. +import numbers import warnings from collections import defaultdict from itertools import chain @@ -51,10 +53,18 @@ def _sorted_integrals(integrals): all_integrals = [] + def keyfunc(item): + if isinstance(item, numbers.Integral): + sid_int = item + else: + # As subdomain ids can be either int or tuples, we need to compare them + sid_int = tuple(-1 if i == "otherwise" else i for i in item) + return (type(item).__name__, sid_int) + # Order integrals canonically to increase signature stability for d in sort_domains(integrals_dict): for it in sorted(integrals_dict[d]): # str is sortable - for si in sorted(integrals_dict[d][it], key=lambda x: (type(x).__name__, x)): # int/str are sortable + for si in sorted(integrals_dict[d][it], key=keyfunc): unsorted_integrals = integrals_dict[d][it][si] # TODO: At this point we could order integrals by # metadata and integrand, or even add the From fc7965401ed33e533d574573bb61016f09bbeb68 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 12 Sep 2023 14:40:38 +0100 Subject: [PATCH 048/136] update formatting (#199) --- ufl/utils/formatting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index f435653f2..24dd5ce10 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -73,7 +73,7 @@ def istr(o): def estr(elements): """Format list of elements for printing.""" - return ", ".join(e.shortstr() for e in elements) + return ", ".join(f"{e}" for e in elements) def _indent_string(n): From 9d0489286c1270989cb3686205ae203cb6f6a828 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 12 Sep 2023 15:02:33 +0100 Subject: [PATCH 049/136] reset branch name (#200) --- .github/workflows/fenicsx-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 39a2e8191..1d253d80a 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -79,7 +79,7 @@ jobs: - name: Install Basix and FFCx run: | python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git@dokken/group_integrals_v2 + python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx uses: actions/checkout@v3 From 1cd9d5567f92919fb466f533c0a920fe55019a6d Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 12 Sep 2023 16:05:04 +0100 Subject: [PATCH 050/136] reset branch --- .github/workflows/fenicsx-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 1d253d80a..2075498b0 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -41,7 +41,7 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx - ref: dokken/group_integrals_v2 + ref: main - name: Install FFCx run: | From 30b22c01022fcb97e85ae3753f3b09273f366b1e Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:04:28 +0100 Subject: [PATCH 051/136] Some updates related to dualspace (#194) *Equip BaseForm objects with a coefficients slot. *Equip BaseForm objects with a ufl_domain as they can be differentiated, which requires inferring the underlying domain. *Cofunctions now have one argument in the primal space as they map from V to R. *Coarguments now have two arguments. In fact, Coarguments map from V* to V*, i.e. V* -> V*, or equivalently V* x V -> R, hence they have one argument in the primal space and one in the dual space. *Add BaseFormCoordinateDerivative and fix the argument analysis for BaseFormDerivative objects. *Update Action differentiation rule *Remove support for Adjoint differentiation * Equip FormSum objects with a ufl_domain * Fix arguments collection for BaseFormDerivative --------- Co-authored-by: David A. Ham Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward --- AUTHORS | 1 + test/test_duals.py | 34 +++++--------- ufl/action.py | 73 ++++++++++++++++++++++------- ufl/adjoint.py | 23 ++++++++- ufl/algorithms/__init__.py | 3 +- ufl/algorithms/ad.py | 8 ---- ufl/algorithms/analysis.py | 14 ++++-- ufl/algorithms/apply_derivatives.py | 12 ++++- ufl/algorithms/compute_form_data.py | 57 ++++++++++++---------- ufl/algorithms/map_integrands.py | 7 ++- ufl/argument.py | 9 +++- ufl/coefficient.py | 6 ++- ufl/differentiation.py | 36 +++++++++++++- ufl/form.py | 72 ++++++++++++++++++++-------- ufl/formoperators.py | 39 ++++++++++----- ufl/matrix.py | 12 ++++- 16 files changed, 287 insertions(+), 119 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8b0fd6507..3ad2dfc66 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,3 +29,4 @@ Contributors: | Jack S. Hale | Tuomas Airaksinen | Reuben W. Hill + | Nacime Bouziani diff --git a/test/test_duals.py b/test/test_duals.py index d29c62639..0f4f46ef2 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -4,7 +4,7 @@ from ufl import (FiniteElement, FunctionSpace, MixedFunctionSpace, Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument, TestFunction, TrialFunction, Adjoint, Action, - action, adjoint, derivative, tetrahedron, triangle, interval, dx) + action, adjoint, derivative, inner, tetrahedron, triangle, interval, dx) from ufl.constantvalue import Zero from ufl.form import ZeroBaseForm @@ -102,8 +102,6 @@ def test_addition(): domain_2d = default_domain(triangle) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) - f_2d_2 = FiniteElement("CG", triangle, 2) - V2 = FunctionSpace(domain_2d, f_2d_2) V_dual = V.dual() u = TrialFunction(V) @@ -137,11 +135,6 @@ def test_addition(): res -= ZeroBaseForm((v,)) assert res == L - with pytest.raises(ValueError): - # Raise error for incompatible arguments - v2 = TestFunction(V2) - res = L + ZeroBaseForm((v2, u)) - def test_scalar_mult(): domain_2d = default_domain(triangle) @@ -256,7 +249,7 @@ def test_differentiation(): w = Cofunction(U.dual()) dwdu = expand_derivatives(derivative(w, u)) assert isinstance(dwdu, ZeroBaseForm) - assert dwdu.arguments() == (Argument(u.ufl_function_space(), 0),) + assert dwdu.arguments() == (Argument(w.ufl_function_space().dual(), 0), Argument(u.ufl_function_space(), 1)) # Check compatibility with int/float assert dwdu == 0 @@ -285,24 +278,21 @@ def test_differentiation(): assert dMdu == 0 # -- Action -- # - Ac = Action(M, u) - dAcdu = expand_derivatives(derivative(Ac, u)) - - # Action(dM/du, u) + Action(M, du/du) = Action(M, uhat) since dM/du = 0. - # Multiply by 1 to get a FormSum (type compatibility). - assert dAcdu == 1 * Action(M, v) + Ac = Action(w, u) + dAcdu = derivative(Ac, u) + assert dAcdu == action(adjoint(derivative(w, u)), u) + action(w, derivative(u, u)) - # -- Adjoint -- # - Ad = Adjoint(M) - dAddu = expand_derivatives(derivative(Ad, u)) - # Push differentiation through Adjoint - assert dAddu == 0 + dAcdu = expand_derivatives(dAcdu) + # Since dw/du = 0 + assert dAcdu == 1 * Action(w, v) # -- Form sum -- # - Fs = M + Ac + uhat = Argument(U, 1) + what = Argument(U, 2) + Fs = M + inner(u * uhat, v) * dx dFsdu = expand_derivatives(derivative(Fs, u)) # Distribute differentiation over FormSum components - assert dFsdu == 1 * Action(M, v) + assert dFsdu == FormSum([inner(what * uhat, v) * dx, 1]) def test_zero_base_form_mult(): diff --git a/ufl/action.py b/ufl/action.py index e7ec6ad5e..d2dc2b03e 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -12,7 +12,8 @@ from ufl.form import BaseForm, FormSum, Form, ZeroBaseForm from ufl.core.ufl_type import ufl_type from ufl.algebra import Sum -from ufl.argument import Argument +from ufl.constantvalue import Zero +from ufl.argument import Argument, Coargument from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction from ufl.differentiation import CoefficientDerivative from ufl.matrix import Matrix @@ -40,6 +41,8 @@ class Action(BaseForm): "ufl_operands", "_repr", "_arguments", + "_coefficients", + "_domains", "_hash") def __new__(cls, *args, **kw): @@ -47,12 +50,17 @@ def __new__(cls, *args, **kw): # Check trivial case if left == 0 or right == 0: - # Check compatibility of function spaces - _check_function_spaces(left, right) # Still need to work out the ZeroBaseForm arguments. - new_arguments = _get_action_form_arguments(left, right) + new_arguments, _ = _get_action_form_arguments(left, right) return ZeroBaseForm(new_arguments) + # Coarguments (resp. Argument) from V* to V* (resp. from V to V) are identity matrices, + # i.e. we have: V* x V -> R (resp. V x V* -> R). + if isinstance(left, (Coargument, Argument)): + return right + if isinstance(right, (Coargument, Argument)): + return left + if isinstance(left, (FormSum, Sum)): # Action distributes over sums on the LHS return FormSum(*[(Action(component, right), 1) @@ -70,6 +78,7 @@ def __init__(self, left, right): self._left = left self._right = right self.ufl_operands = (self._left, self._right) + self._domains = None # Check compatibility of function spaces _check_function_spaces(left, right) @@ -97,14 +106,22 @@ def _analyze_form_arguments(self): The highest number Argument of the left operand and the lowest number Argument of the right operand are consumed by the action. """ - self._arguments = _get_action_form_arguments(self._left, self._right) + self._arguments, self._coefficients = _get_action_form_arguments(self._left, self._right) + + def _analyze_domains(self): + """Analyze which domains can be found in Action.""" + from ufl.domain import join_domains + # Collect unique domains + self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) def equals(self, other): if type(other) is not Action: return False if self is other: return True - return self._left == other._left and self._right == other._right + # Make sure we are returning a boolean as left and right equalities can be `ufl.Equation`s + # if the underlying objects are `ufl.BaseForm`. + return bool(self._left == other._left) and bool(self._right == other._right) def __str__(self): return f"Action({self._left}, {self._right})" @@ -127,29 +144,51 @@ def _check_function_spaces(left, right): # right as a consequence of Leibniz formula. right, *_ = right.ufl_operands + # `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`. + left_arg = left.arguments()[-1] if not isinstance(left, Coefficient) else left if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)): - if left.arguments()[-1].ufl_function_space().dual() != right.arguments()[0].ufl_function_space(): + if left_arg.ufl_function_space().dual() != right.arguments()[0].ufl_function_space(): raise TypeError("Incompatible function spaces in Action") elif isinstance(right, (Coefficient, Cofunction, Argument)): - if left.arguments()[-1].ufl_function_space() != right.ufl_function_space(): + if left_arg.ufl_function_space() != right.ufl_function_space(): raise TypeError("Incompatible function spaces in Action") - else: + # `Zero` doesn't contain any information about the function space. + # -> Not a problem since Action will get simplified with a `ZeroBaseForm` + # which won't take into account the arguments on the right because of argument contraction. + # This occurs for: + # `derivative(Action(A, B), u)` with B is an `Expr` such that dB/du == 0 + # -> `derivative(B, u)` becomes `Zero` when expanding derivatives since B is an Expr. + elif not isinstance(right, Zero): raise TypeError("Incompatible argument in Action: %s" % type(right)) def _get_action_form_arguments(left, right): """Perform argument contraction to work out the arguments of Action""" - if isinstance(right, CoefficientDerivative): + coefficients = () + # `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`. + left_args = left.arguments()[:-1] if not isinstance(left, Coefficient) else () + if isinstance(right, BaseForm): + arguments = left_args + right.arguments()[1:] + coefficients += right.coefficients() + elif isinstance(right, CoefficientDerivative): # Action differentiation pushes differentiation through # right as a consequence of Leibniz formula. - right, *_ = right.ufl_operands - - if isinstance(right, BaseForm): - return left.arguments()[:-1] + right.arguments()[1:] - elif isinstance(right, BaseCoefficient): - return left.arguments()[:-1] + from ufl.algorithms.analysis import extract_arguments_and_coefficients + right_args, right_coeffs = extract_arguments_and_coefficients(right) + arguments = left_args + tuple(right_args) + coefficients += tuple(right_coeffs) + elif isinstance(right, (BaseCoefficient, Zero)): + arguments = left_args + # When right is ufl.Zero, Action gets simplified so updating + # coefficients here doesn't matter + coefficients += (right,) elif isinstance(right, Argument): - return left.arguments()[:-1] + (right,) + arguments = left_args + (right,) else: raise TypeError + + if isinstance(left, BaseForm): + coefficients += left.coefficients() + + return arguments, coefficients diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 29dce30e8..cf76a75e9 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -10,6 +10,7 @@ # Modified by Nacime Bouziani, 2021-2022. from ufl.form import BaseForm, FormSum, ZeroBaseForm +from ufl.argument import Coargument from ufl.core.ufl_type import ufl_type # --- The Adjoint class represents the adjoint of a numerical object that # needs to be computed at assembly time --- @@ -28,6 +29,8 @@ class Adjoint(BaseForm): "_form", "_repr", "_arguments", + "_coefficients", + "_domains", "ufl_operands", "_hash") @@ -44,6 +47,14 @@ def __new__(cls, *args, **kw): # Adjoint distributes over sums return FormSum(*[(Adjoint(component), 1) for component in form.components()]) + elif isinstance(form, Coargument): + # The adjoint of a coargument `c: V* -> V*` is the identity matrix mapping from V to V (i.e. V x V* -> R). + # Equivalently, the adjoint of `c` is its first argument, which is a ufl.Argument defined on the + # primal space of `c`. + primal_arg, _ = form.arguments() + # Returning the primal argument avoids explicit argument reconstruction, making it + # a robust strategy for handling subclasses of `ufl.Coargument`. + return primal_arg return super(Adjoint, cls).__new__(cls) @@ -55,6 +66,7 @@ def __init__(self, form): self._form = form self.ufl_operands = (self._form,) + self._domains = None self._hash = None self._repr = "Adjoint(%s)" % repr(self._form) @@ -68,13 +80,22 @@ def form(self): def _analyze_form_arguments(self): """The arguments of adjoint are the reverse of the form arguments.""" self._arguments = self._form.arguments()[::-1] + self._coefficients = self._form.coefficients() + + def _analyze_domains(self): + """Analyze which domains can be found in Adjoint.""" + from ufl.domain import join_domains + # Collect unique domains + self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) def equals(self, other): if type(other) is not Adjoint: return False if self is other: return True - return self._form == other._form + # Make sure we are returning a boolean as the equality can result in a `ufl.Equation` + # if the underlying objects are `ufl.BaseForm`. + return bool(self._form == other._form) def __str__(self): return f"Adjoint({self._form})" diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 687e23db2..727973437 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -21,6 +21,7 @@ "estimate_total_polynomial_degree", "sort_elements", "compute_form_data", + "preprocess_form", "apply_transformer", "ReuseTransformer", "load_ufl_file", @@ -79,7 +80,7 @@ # Preprocessing a form to extract various meta data # from ufl.algorithms.formdata import FormData -from ufl.algorithms.compute_form_data import compute_form_data +from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form # Utilities for checking properties of forms from ufl.algorithms.signature import compute_form_signature diff --git a/ufl/algorithms/ad.py b/ufl/algorithms/ad.py index 4755070ae..20e102f39 100644 --- a/ufl/algorithms/ad.py +++ b/ufl/algorithms/ad.py @@ -11,7 +11,6 @@ import warnings -from ufl.adjoint import Adjoint from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives @@ -29,13 +28,6 @@ def expand_derivatives(form, **kwargs): if kwargs: warnings("Deprecation: expand_derivatives no longer takes any keyword arguments") - if isinstance(form, Adjoint): - dform = expand_derivatives(form._form) - if dform == 0: - return dform - # Adjoint is taken on a 3-form which can't happen - raise NotImplementedError('Adjoint derivative is not supported.') - # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index 6c2c2190d..b17ec4d2c 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -50,12 +50,16 @@ def extract_type(a, ufl_types): if not isinstance(ufl_types, (list, tuple)): ufl_types = (ufl_types,) - # BaseForms that aren't forms only have arguments + # BaseForms that aren't forms only contain arguments & coefficients if isinstance(a, BaseForm) and not isinstance(a, Form): - if any(issubclass(t, BaseArgument) for t in ufl_types): - return set(a.arguments()) - else: - return set() + objects = set() + arg_types = tuple(t for t in ufl_types if issubclass(t, BaseArgument)) + if arg_types: + objects.update([e for e in a.arguments() if isinstance(e, arg_types)]) + coeff_types = tuple(t for t in ufl_types if issubclass(t, BaseCoefficient)) + if coeff_types: + objects.update([e for e in a.coefficients() if isinstance(e, coeff_types)]) + return objects if all(issubclass(t, Terminal) for t in ufl_types): # Optimization diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index befb4067b..3fced9e3a 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -24,7 +24,7 @@ from ufl.core.terminal import Terminal from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction -from ufl.differentiation import CoordinateDerivative +from ufl.differentiation import CoordinateDerivative, BaseFormCoordinateDerivative from ufl.domain import extract_unique_domain from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, conditional, cos, cosh, exp, facet_avg, ln, sign, @@ -1032,7 +1032,7 @@ def cofunction(self, o): dc = self.coefficient(o) if dc == 0: # Convert ufl.Zero into ZeroBaseForm - return ZeroBaseForm(self._v) + return ZeroBaseForm(o.arguments() + self._v) return dc def coargument(self, o): @@ -1102,6 +1102,14 @@ def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): rcache=self.rcaches[key]), o_[1], o_[2], o_[3]) + def base_form_coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + o_ = o.ufl_operands + key = (BaseFormCoordinateDerivative, o_[0]) + return BaseFormCoordinateDerivative(map_expr_dag(self, o_[0], + vcache=self.vcaches[key], + rcache=self.rcaches[key]), + o_[1], o_[2], o_[3]) + def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules # Reuse if untouched if Ap is o.ufl_operands[0]: diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 348e27e17..afa092e07 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -206,31 +206,7 @@ def attach_estimated_degrees(form): return Form(new_integrals) -def compute_form_data(form, - # Default arguments configured to behave the way old FFC expects it: - do_apply_function_pullbacks=False, - do_apply_integral_scaling=False, - do_apply_geometry_lowering=False, - preserve_geometry_types=(), - do_apply_default_restrictions=True, - do_apply_restrictions=True, - do_estimate_degrees=True, - do_append_everywhere_integrals=True, - complex_mode=False, - ): - - # TODO: Move this to the constructor instead - self = FormData() - - # --- Store untouched form for reference. - # The user of FormData may get original arguments, - # original coefficients, and form signature from this object. - # But be aware that the set of original coefficients are not - # the same as the ones used in the final UFC form. - # See 'reduced_coefficients' below. - self.original_form = form - - # --- Pass form integrands through some symbolic manipulation +def preprocess_form(form, complex_mode): # Note: Default behaviour here will process form the way that is # currently expected by vanilla FFC @@ -258,6 +234,37 @@ def compute_form_data(form, # user-defined coefficient relations it just gets too messy form = apply_derivatives(form) + return form + + +def compute_form_data(form, + # Default arguments configured to behave the way old FFC expects it: + do_apply_function_pullbacks=False, + do_apply_integral_scaling=False, + do_apply_geometry_lowering=False, + preserve_geometry_types=(), + do_apply_default_restrictions=True, + do_apply_restrictions=True, + do_estimate_degrees=True, + do_append_everywhere_integrals=True, + complex_mode=False, + ): + + # TODO: Move this to the constructor instead + self = FormData() + + # --- Store untouched form for reference. + # The user of FormData may get original arguments, + # original coefficients, and form signature from this object. + # But be aware that the set of original coefficients are not + # the same as the ones used in the final UFC form. + # See 'reduced_coefficients' below. + self.original_form = form + + # --- Pass form integrands through some symbolic manipulation + + form = preprocess_form(form, complex_mode) + # --- Group form integrals # TODO: Refactor this, it's rather opaque what this does # TODO: Is self.original_form.ufl_domains() right here? diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 568e65ddb..65ede8f0b 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -39,9 +39,14 @@ def map_integrands(function, form, only_integral_type=None): elif isinstance(form, FormSum): mapped_components = [map_integrands(function, component, only_integral_type) for component in form.components()] - nonzero_components = [(component, 1) for component in mapped_components + nonzero_components = [(component, w) for component, w in zip(mapped_components, form.weights()) # Catch ufl.Zero and ZeroBaseForm if component != 0] + if all(not isinstance(component, BaseForm) for component, _ in nonzero_components): + # Simplification of `BaseForm` objects may turn `FormSum` into a sum of `Expr` objects + # that are not `BaseForm`, i.e. into a `Sum` object. + # Example: `Action(Adjoint(c*), u)` with `c*` a `Coargument` and u a `Coefficient`. + return sum([component for component, _ in nonzero_components]) return FormSum(*nonzero_components) elif isinstance(form, Adjoint): # Zeros are caught inside `Adjoint.__new__` diff --git a/ufl/argument.py b/ufl/argument.py index 74358d503..30dd815b0 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -180,6 +180,7 @@ class Coargument(BaseForm, BaseArgument): "_ufl_function_space", "_ufl_shape", "_arguments", + "_coefficients", "ufl_operands", "_number", "_part", @@ -208,7 +209,13 @@ def __init__(self, function_space, number, part=None): def _analyze_form_arguments(self): "Analyze which Argument and Coefficient objects can be found in the form." # Define canonical numbering of arguments and coefficients - self._arguments = (Argument(self._ufl_function_space, 0),) + # Coarguments map from V* to V*, i.e. V* -> V*, or equivalently V* x V -> R. + # So they have one argument in the primal space and one in the dual space. + self._arguments = (Argument(self.ufl_function_space().dual(), 0), self) + self._coefficients = () + + def ufl_domain(self): + return BaseArgument.ufl_domain(self) def equals(self, other): if type(other) is not Coargument: diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 704c3912d..5ea15ce09 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -14,6 +14,7 @@ from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument +from ufl.argument import Argument from ufl.finiteelement import FiniteElementBase from ufl.domain import default_domain from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace @@ -109,6 +110,7 @@ class Cofunction(BaseCoefficient, BaseForm): "_count", "_counted_class", "_arguments", + "_coefficients", "_ufl_function_space", "ufl_operands", "_repr", @@ -149,7 +151,9 @@ def __hash__(self): def _analyze_form_arguments(self): "Analyze which Argument and Coefficient objects can be found in the form." # Define canonical numbering of arguments and coefficients - self._arguments = () + # Cofunctions have one argument in primal space as they map from V to R. + self._arguments = (Argument(self._ufl_function_space.dual(), 0),) + self._coefficients = (self,) @ufl_type() diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 75c4197db..d8828e37e 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -8,6 +8,7 @@ from ufl.checks import is_cellwise_constant from ufl.coefficient import Coefficient +from ufl.argument import Argument, Coargument from ufl.constantvalue import Zero from ufl.core.expr import Expr from ufl.core.operator import Operator @@ -91,8 +92,39 @@ def __init__(self, base_form, coefficients, arguments, def _analyze_form_arguments(self): """Collect the arguments of the corresponding BaseForm""" - base_form = self.ufl_operands[0] - self._arguments = base_form.arguments() + from ufl.algorithms.analysis import extract_type, extract_coefficients + base_form, _, arguments, _ = self.ufl_operands + + def arg_type(x): + if isinstance(x, BaseForm): + return Coargument + return Argument + # Each derivative arguments can either be a: + # - `ufl.BaseForm`: if it contains a `ufl.Coargument` + # - or a `ufl.Expr`: if it contains a `ufl.Argument` + # When a `Coargument` is encountered, it is treated as an argument (i.e. as V* -> V* and not V* x V -> R) + # and should result in one single argument (in the dual space). + base_form_args = base_form.arguments() + tuple(arg for a in arguments.ufl_operands + for arg in extract_type(a, arg_type(a))) + # BaseFormDerivative's arguments don't necessarily contain BaseArgument objects only + # -> e.g. `derivative(u ** 2, u, u)` with `u` a Coefficient. + base_form_coeffs = base_form.coefficients() + tuple(arg for a in arguments.ufl_operands + for arg in extract_coefficients(a)) + # Reconstruct arguments for correct numbering + self._arguments = tuple(type(arg)(arg.ufl_function_space(), arg.number(), arg.part()) for arg in base_form_args) + self._coefficients = base_form_coeffs + + +@ufl_type(num_ops=4, inherit_shape_from_operand=0, + inherit_indices_from_operand=0) +class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): + """Derivative of a base form w.r.t. the SpatialCoordinates.""" + _ufl_noslots_ = True + + def __init__(self, base_form, coefficients, arguments, + coefficient_derivatives): + BaseFormDerivative.__init__(self, base_form, coefficients, arguments, + coefficient_derivatives) @ufl_type(num_ops=2) diff --git a/ufl/form.py b/ufl/form.py index d4370b65a..601e4d13a 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -86,11 +86,12 @@ class BaseForm(object, metaclass=UFLType): # classes __slots__ = () _ufl_is_abstract_ = True - _ufl_required_methods_ = ('_analyze_form_arguments', "ufl_domains") + _ufl_required_methods_ = ('_analyze_form_arguments', '_analyze_domains', "ufl_domains") def __init__(self): - # Internal variables for caching form argument data + # Internal variables for caching form argument/coefficient data self._arguments = None + self._coefficients = None # --- Accessor interface --- def arguments(self): @@ -99,6 +100,24 @@ def arguments(self): self._analyze_form_arguments() return self._arguments + def coefficients(self): + "Return all ``Coefficient`` objects found in form." + if self._coefficients is None: + self._analyze_form_arguments() + return self._coefficients + + def ufl_domain(self): + """Return the single geometric integration domain occuring in the + base form. Fails if multiple domains are found. + """ + if self._domains is None: + self._analyze_domains() + + if len(self._domains) > 1: + raise ValueError("%s must have exactly one domain." % type(self).__name__) + # Return the single geometric domain + return self._domains[0] + # --- Operator implementations --- def __eq__(self, other): @@ -123,7 +142,6 @@ def __add__(self, other): return self elif isinstance(other, ZeroBaseForm): - self._check_arguments_sum(other) # Simplify addition with ZeroBaseForm return self @@ -131,7 +149,6 @@ def __add__(self, other): # We could overwrite ZeroBaseForm.__add__ but that implies # duplicating cases with `0` and `ufl.Zero`. elif isinstance(self, ZeroBaseForm): - self._check_arguments_sum(other) # Simplify addition with ZeroBaseForm return other @@ -143,18 +160,6 @@ def __add__(self, other): # Let python protocols do their job if we don't handle it return NotImplemented - def _check_arguments_sum(self, other): - # Get component with the highest number of arguments - a = max((self, other), key=lambda x: len(x.arguments())) - b = self if a is other else other - # Components don't necessarily have the exact same arguments - # but the first argument(s) need to match as for `a + L` - # where a and L are a bilinear and linear form respectively. - a_args = sorted(a.arguments(), key=lambda x: x.number()) - b_args = sorted(b.arguments(), key=lambda x: x.number()) - if b_args != a_args[:len(b_args)]: - raise ValueError('Mismatching arguments when summing:\n %s\n and\n %s' % (self, other)) - def __sub__(self, other): "Subtract other form from this one." return self + (-other) @@ -506,7 +511,6 @@ def __add__(self, other): return Form(list(chain(self.integrals(), other.integrals()))) if isinstance(other, ZeroBaseForm): - self._check_arguments_sum(other) # Simplify addition with ZeroBaseForm return self @@ -730,6 +734,7 @@ class FormSum(BaseForm): arg_weights is a list of tuples of component index and weight""" __slots__ = ("_arguments", + "_coefficients", "_weights", "_components", "ufl_operands", @@ -738,20 +743,37 @@ class FormSum(BaseForm): "_hash") _ufl_required_methods_ = ('_analyze_form_arguments') + def __new__(cls, *args, **kwargs): + # All the components are `ZeroBaseForm` + if all(component == 0 for component, _ in args): + # Assume that the arguments of all the components have consistent with each other and select + # the first one to define the arguments of `ZeroBaseForm`. + # This might not always be true but `ZeroBaseForm`'s arguments are not checked anywhere + # because we can't reliably always infer them. + ((arg, _), *_) = args + arguments = arg.arguments() + return ZeroBaseForm(arguments) + + return super(FormSum, cls).__new__(cls) + def __init__(self, *components): BaseForm.__init__(self) + # Remove `ZeroBaseForm` components + filtered_components = [(component, w) for component, w in components if component != 0] + weights = [] full_components = [] - for (component, w) in components: + for (component, w) in filtered_components: if isinstance(component, FormSum): full_components.extend(component.components()) - weights.extend(w * component.weights()) + weights.extend([w * wc for wc in component.weights()]) else: full_components.append(component) weights.append(w) self._arguments = None + self._coefficients = None self._domains = None self._domain_numbering = None self._hash = None @@ -788,9 +810,18 @@ def _sum_variational_components(self): def _analyze_form_arguments(self): "Return all ``Argument`` objects found in form." arguments = [] + coefficients = [] for component in self._components: arguments.extend(component.arguments()) + coefficients.extend(component.coefficients()) self._arguments = tuple(set(arguments)) + self._coefficients = tuple(set(coefficients)) + + def _analyze_domains(self): + """Analyze which domains can be found in FormSum.""" + from ufl.domain import join_domains + # Collect unique domains + self._domains = join_domains([component.ufl_domain() for component in self._components]) def __hash__(self): "Hash code for use in dicts (includes incidental numbering of indices etc.)" @@ -849,7 +880,8 @@ def __init__(self, arguments): self.form = None def _analyze_form_arguments(self): - return self._arguments + # `self._arguments` is already set in `BaseForm.__init__` + self._coefficients = () def __ne__(self, other): # Overwrite BaseForm.__neq__ which relies on `equals` diff --git a/ufl/formoperators.py b/ufl/formoperators.py index 355bc9713..40caeaf68 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -11,7 +11,7 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -from ufl.form import Form, FormSum, BaseForm, as_form +from ufl.form import Form, FormSum, BaseForm, ZeroBaseForm, as_form from ufl.core.expr import Expr, ufl_err_str from ufl.split_functions import split from ufl.exprcontainers import ExprList, ExprMapping @@ -21,7 +21,8 @@ from ufl.coefficient import Coefficient, Cofunction from ufl.adjoint import Adjoint from ufl.action import Action -from ufl.differentiation import CoefficientDerivative, BaseFormDerivative, CoordinateDerivative +from ufl.differentiation import (CoefficientDerivative, BaseFormDerivative, + CoordinateDerivative, BaseFormCoordinateDerivative) from ufl.constantvalue import is_true_ufl_scalar, as_ufl from ufl.indexed import Indexed from ufl.core.multiindex import FixedIndex, MultiIndex @@ -285,15 +286,23 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): return FormSum(*[(derivative(component, coefficient, argument, coefficient_derivatives), 1) for component in form.components()]) elif isinstance(form, Adjoint): - # Push derivative through Adjoint - return adjoint(derivative(form._form, coefficient, argument, coefficient_derivatives)) + # Is `derivative(Adjoint(A), ...)` with A a 2-form even legal ? + # -> If yes, what's the right thing to do here ? + raise NotImplementedError('Adjoint derivative is not supported.') elif isinstance(form, Action): # Push derivative through Action slots left, right = form.ufl_operands - dleft = derivative(left, coefficient, argument, coefficient_derivatives) - dright = derivative(right, coefficient, argument, coefficient_derivatives) - # Leibniz formula - return action(dleft, right) + action(left, dright) + # Eagerly simplify spatial derivatives when Action results in a scalar. + if not len(form.arguments()) and isinstance(coefficient, SpatialCoordinate): + return ZeroBaseForm(()) + + if len(left.arguments()) == 1: + dleft = derivative(left, coefficient, argument, coefficient_derivatives) + dright = derivative(right, coefficient, argument, coefficient_derivatives) + # Leibniz formula + return action(adjoint(dleft), right) + action(left, dright) + else: + raise NotImplementedError('Action derivative not supported when the left argument is not a 1-form.') coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) @@ -309,18 +318,24 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): if isinstance(form, Form): integrals = [] for itg in form.integrals(): - if not isinstance(coefficient, SpatialCoordinate): - fd = CoefficientDerivative(itg.integrand(), coefficients, - arguments, coefficient_derivatives) - else: + if isinstance(coefficient, SpatialCoordinate): fd = CoordinateDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) + elif isinstance(coefficient, BaseForm): + # Make the `ZeroBaseForm` arguments + arguments = form.arguments() + coefficient.arguments() + return ZeroBaseForm(arguments) + else: + fd = CoefficientDerivative(itg.integrand(), coefficients, + arguments, coefficient_derivatives) integrals.append(itg.reconstruct(fd)) return Form(integrals) elif isinstance(form, BaseForm): if not isinstance(coefficient, SpatialCoordinate): return BaseFormDerivative(form, coefficients, arguments, coefficient_derivatives) + else: + return BaseFormCoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) elif isinstance(form, Expr): # What we got was in fact an integrand diff --git a/ufl/matrix.py b/ufl/matrix.py index 0b120f414..23cefcc6f 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -30,7 +30,9 @@ class Matrix(BaseForm, Counted): "_repr", "_hash", "_ufl_shape", - "_arguments") + "_arguments", + "_coefficients", + "_domains") def __getnewargs__(self): return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], @@ -49,6 +51,7 @@ def __init__(self, row_space, column_space, count=None): self._ufl_function_spaces = (row_space, column_space) self.ufl_operands = () + self._domains = None self._hash = None self._repr = f"Matrix({self._ufl_function_spaces[0]!r}, {self._ufl_function_spaces[1]!r}, {self._count!r})" @@ -60,6 +63,13 @@ def _analyze_form_arguments(self): "Define arguments of a matrix when considered as a form." self._arguments = (Argument(self._ufl_function_spaces[0], 0), Argument(self._ufl_function_spaces[1], 1)) + self._coefficients = () + + def _analyze_domains(self): + """Analyze which domains can be found in a Matrix.""" + from ufl.domain import join_domains + # Collect unique domains + self._domains = join_domains([fs.ufl_domain() for fs in self._ufl_function_spaces]) def __str__(self): count = str(self._count) From b6f3bf048c4419ab3752842bd96aa1be72376474 Mon Sep 17 00:00:00 2001 From: Ignacia Fierro-Piccardo Date: Tue, 12 Sep 2023 18:21:28 +0100 Subject: [PATCH 052/136] Deprecation messages for legacy features (#201) * Deprecation messages have been introduced instead * Update ufl/argument.py * Update ufl/coefficient.py --------- Co-authored-by: Matthew Scroggs --- ufl/argument.py | 5 +++++ ufl/coefficient.py | 5 +++++ ufl/domain.py | 3 +++ 3 files changed, 13 insertions(+) diff --git a/ufl/argument.py b/ufl/argument.py index 30dd815b0..5157700b6 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -11,8 +11,11 @@ # Modified by Anders Logg, 2008-2009. # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. +# Modified by Ignacia Fierro-Piccardo 2023. +import warnings import numbers + from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument from ufl.split_functions import split @@ -44,6 +47,8 @@ def __init__(self, function_space, number, part=None): element = function_space domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) + warnings.warn("The use of FiniteElement as an input to Argument will be deprecated by December 2023. " + "Please, use FunctionSpace instead", FutureWarning) elif not isinstance(function_space, AbstractFunctionSpace): raise ValueError("Expecting a FunctionSpace or FiniteElement.") diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 5ea15ce09..394e29e72 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -11,6 +11,8 @@ # Modified by Anders Logg, 2008-2009. # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. +# Modified by Ignacia Fierro-Piccardo 2023. +import warnings from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument @@ -23,6 +25,7 @@ from ufl.utils.counted import Counted from ufl.duals import is_primal, is_dual + # --- The Coefficient class represents a coefficient in a form --- @@ -48,6 +51,8 @@ def __init__(self, function_space, count=None): element = function_space domain = default_domain(element.cell()) function_space = FunctionSpace(domain, element) + warnings.warn("The use of FiniteElement as an input to Coefficient will be deprecated by December 2023. " + "Please, use FunctionSpace instead", FutureWarning) elif not isinstance(function_space, AbstractFunctionSpace): raise ValueError("Expecting a FunctionSpace or FiniteElement.") diff --git a/ufl/domain.py b/ufl/domain.py index b58acdcf4..7bf2feedb 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -7,6 +7,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numbers +import warnings from ufl.cell import AbstractCell, TensorProductCell, as_cell from ufl.core.ufl_id import attach_ufl_id @@ -318,6 +319,8 @@ def join_domains(domains): # Handle legacy domains checking if legacy_domains: + warnings.warn("The use of Legacy domains will be deprecated by December 2023. " + "Please, use FunctionSpace instead", DeprecationWarning) if modern_domains: raise ValueError( "Found both a new-style domain and a legacy default domain. " From 71698e12894123e778ab36a559d410d394d17238 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:45:53 +0100 Subject: [PATCH 053/136] Add Interpolate and BaseFormOperator (#205) * Hash in matrix * Add test for action on action * remove eq in form as redundant * lint + amend tests * Fix Issue with adjoint * small change to arguments method of adjoint class * lint * lint * more style * cleanup Action * cleanup Action * cleanup adjoint * use correct action * sanitise argument creation * Shut up Flake8 W503 is no longer PEP8 best practice. E129 causes spurious problems on long `if` statements. * clean up Matrix * Clean up duals * Clean up functionspace * Remove unneeded signature from Matrix * cleanup form * Check trivial case for Action and Adjoint * Update possible right term for Action * Take into account various BaseForm objects in expand_derivatives * Add BaseForm in __init__ * Add BaseForm in as_ufl * Update action * Update Cofunction and Coargument for when they take in a primal space * Add equals to FormSum * Add __eq__ to Action * Add __eq__ to Adjoint * Add __eq__ and __hash__ to Coargument * Fix typos * Refactor analysis.py * Add BaseFormDerivative * Fix for arguments, coefficients and function spaces * Fix hash for coefficients, arguments and function spaces + some more equality fixes * Draft BaseForm diff * Draft: Refactor UFL types using UFLType * Add BaseFormOperator * Add Interp * Remove assemble from BaseFormOperator * Update Interp interface * Some fixes + Add __str__ to Interp * Fix __eq__ for Coargument * Check trivial cases + some fixes * Add test_interp.py * Add UFLType handler as the default handler + Move UFLType to ufl_type.py * Add ufl_operands and _ufl_compute_hash to BaseForm objects for MultiFunction traversal * Add Matrix/Cofunction/Coargument differentiation + Add some ufl_type handlers * Push arguments analysis through BaseFormDerivative * Add Action differentiation * Add tests for BaseForm differentiation * Add Adjoint(Adjoint(.)) = Id * Fix Action differentiation * Matrix derivative is always 0 (since we can't differentiate wrt a Matrix) * Update _handle_derivative_arguments * Fix _analyze_form_arguments for FormSum * Update FormSum and tests * Fix ExprList * Add Adjoint differentiation * Cleanup Interp + add _ufl_expr_reconstruct_ * Cleanup BaseFormOperator * Add BaseFormOperatorDerivative * Make Interp public * Add Interp differentiation * Update test * Delegate ufl_free_indices and ufl_index_dimensions to Interp expression * Add handlers for Action/Adjoint/FormSum in map_integrands * Use pytest.raises * Add ufl_operands to Interp * Add replace_derivative_nodes (from ExternalOperator implementation) * Add BaseFormOperator differentiation (2 stages mechanism) from ExternalOperator implementation * Change __eq__ to equals for dual objects * Update AUTHORS * Add preprocess_form * Update analysis.py with BaseFormOperator * Add .base_form_operators() in Form * Fix _ufl_expr_reconstruct_ for Interp * Update tests * Grad differentiation + Fix few things in differentiation * BaseFormOperator doesn't have free indices * Cleanup * Update doc * Update traversal.py * Replace expr handler by ufl_type in Replacer * Last minute change * Flake8 * Fix flake8 * Fix few things * Add replacer handler for Interp * Refactor UFL type system * Address comments from the PR * Add coefficients to BaseFormOperator * Updare interp * Fix lint * Update date interp.py * Update test * Fix BaseFormOperatorCoordinateDerivative * Fix lint * Update arguments analysis for FormSum * Fix lint * Extend Action distribution over ufl.Sum * Add test for Action distributivity wrt FormSum and Sum * Enable Matrix on the rhs of Action * Add ZeroBaseForm * Add tests for ZeroBaseForm * Update author info * Enable Matrix on the rhs of Action * Add ZeroBaseForm * Add tests for ZeroBaseForm * Update author info * Fix Cofunction's argument * Update expand_derivatives * Update action * Clean way of getting action arguments and check function spaces * Rename _get_action_arguments * Fix ZeroBaseForm simplification for BaseForm * Handle Zero case for Action * Fix typo * Provide support for caching derivative expansion for action/adjoint * Swap ZeroBaseForm's arguments for adjoint * Check arguments when summing a ZeroBaseForm * Fix argument contraction with CoefficientDerivative * Clean up * Update Interp reconstruct * Fix __str__ for Action/Adjoint * Add/Fix comments * Fix typo * Update warnings * Clean up docstrings + fix flake8 * Update Interp with revisions * Revert "Allow multiple subdomain data (#120)" This reverts commit 677358ab5e9b84f32ff586e6ddb87f74f29a9c76. * Add BaseFormOperatorDerivativeRuleset * Fix lint * Revert "Revert "Allow multiple subdomain data (#120)"" This reverts commit 073579e6a0ec68445776e6d9606827d760e66bd6. * Fix typo * Remove strong typing check for Interp.__eq__ * fix interp equality * remove dead code * Equip Cofunctions with an Argument in the primal space * Equip FormSum objects with a ufl_domain * Fix arguments collection for BaseFormDerivative * Equip BaeForm with coefficients * Equip BaseForm with a ufl_domain * Add BaseFormCoordinateDerivative * Update AUTHORS * Fix weight analysis for FormSum composition * Handle ufl.Zero in rhs * Remove support for adjoint derivative * Remove support for Action derivative when left is a 2-form * Extend form arguments analysis to coefficients for BaseFormDerivative * Update rules for Action arguments * Coarguments have one argument in the primal space and one in the dual space * Add preprocess_form * Fix FormSum reconstruction in map_integrands * DerivativeRuleDispatcher: Add handler for base form coordinate derivatives * Simplify Action/Adjoint of Coarguments * Remove checks on ZeroBaseForms' arguments * Update Action differentiation test * Return primal space argument for Coargument's adjoint * Fix tests * Fix flake8 * Fix flake8 * Add argument_slots to BaseFormOperatorDerivative * Extend Action simplification cases to ufl.Argument * Lift some code from external operator branch * Fix equals for Action/Adjoint * remove spurioius names * Update ufl/argument.py * Update ufl/coefficient.py * Fix some bugs and address PR comments * Fix spurious firedrake UFL code * Fix flake8 * Fix test * Update ufl/algorithms/replace_derivative_nodes.py * Address PR comments * Lift test change from Intepr branch * Fix dForm/dBaseFormOperator * Fix comment * Fix analysis * Fix count * Update replace_derivative_nodes and BaseFormOperator.__eq___ * Rename Interp -> Interpolate and add interpolate helper function * Fix flake8 * Add check on Action's left * Lift new differentiation process from external operator branch --------- Co-authored-by: India Marsden Co-authored-by: David Ham Co-authored-by: Rob Kirby Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward --- AUTHORS | 1 + test/test_duals.py | 2 +- test/test_interpolate.py | 168 ++++++++++++++++ ufl/__init__.py | 6 +- ufl/action.py | 9 +- ufl/algorithms/__init__.py | 2 + ufl/algorithms/analysis.py | 61 +++++- ufl/algorithms/apply_derivatives.py | 224 +++++++++++++++++++-- ufl/algorithms/replace.py | 9 +- ufl/algorithms/replace_derivative_nodes.py | 64 ++++++ ufl/argument.py | 17 +- ufl/core/base_form_operator.py | 154 ++++++++++++++ ufl/core/expr.py | 2 +- ufl/core/interpolate.py | 94 +++++++++ ufl/differentiation.py | 44 ++++ ufl/form.py | 26 ++- ufl/formatting/ufl2unicode.py | 3 + ufl/formoperators.py | 55 +++-- 18 files changed, 890 insertions(+), 51 deletions(-) create mode 100644 test/test_interpolate.py create mode 100644 ufl/algorithms/replace_derivative_nodes.py create mode 100644 ufl/core/base_form_operator.py create mode 100644 ufl/core/interpolate.py diff --git a/AUTHORS b/AUTHORS index 3ad2dfc66..c3c6f37c6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,5 +28,6 @@ Contributors: | Corrado Maurini | Jack S. Hale | Tuomas Airaksinen + | Nacime Bouziani | Reuben W. Hill | Nacime Bouziani diff --git a/test/test_duals.py b/test/test_duals.py index 0f4f46ef2..272294391 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -284,7 +284,7 @@ def test_differentiation(): dAcdu = expand_derivatives(dAcdu) # Since dw/du = 0 - assert dAcdu == 1 * Action(w, v) + assert dAcdu == Action(w, v) # -- Form sum -- # uhat = Argument(U, 1) diff --git a/test/test_interpolate.py b/test/test_interpolate.py new file mode 100644 index 000000000..d7e79dff1 --- /dev/null +++ b/test/test_interpolate.py @@ -0,0 +1,168 @@ +#!/usr/bin/env py.test +# -*- coding: utf-8 -*- + +import pytest +from ufl import * + + +__authors__ = "Nacime Bouziani" +__date__ = "2021-11-19" + +""" +Test Interpolate object +""" + +from ufl.core.interpolate import Interpolate +from ufl.algorithms.ad import expand_derivatives +from ufl.algorithms.analysis import (extract_coefficients, extract_arguments, + extract_arguments_and_coefficients, + extract_base_form_operators) + +from ufl.algorithms.expand_indices import expand_indices +from ufl.domain import default_domain + + +@pytest.fixture +def V1(): + domain_2d = default_domain(triangle) + f1 = FiniteElement("CG", triangle, 1) + return FunctionSpace(domain_2d, f1) + + +@pytest.fixture +def V2(): + domain_2d = default_domain(triangle) + f1 = FiniteElement("CG", triangle, 2) + return FunctionSpace(domain_2d, f1) + + +def test_symbolic(V1, V2): + + # Set dual of V2 + V2_dual = V2.dual() + + u = Coefficient(V1) + vstar = Argument(V2_dual, 0) + Iu = Interpolate(u, vstar) + + assert Iu == Interpolate(u, V2) + assert Iu.ufl_function_space() == V2 + assert Iu.argument_slots() == (vstar, u) + assert Iu.arguments() == (vstar,) + assert Iu.ufl_operands == (u,) + + +def test_action_adjoint(V1, V2): + + # Set dual of V2 + V2_dual = V2.dual() + vstar = Argument(V2_dual, 0) + + u = Coefficient(V1) + Iu = Interpolate(u, vstar) + + v1 = TrialFunction(V1) + Iv = Interpolate(v1, vstar) + + assert Iv.argument_slots() == (vstar, v1) + assert Iv.arguments() == (vstar, v1) + + # -- Action -- # + v = TestFunction(V1) + v2 = TrialFunction(V2) + F = v2 * v * dx + assert action(Iv, u) == Action(Iv, u) + assert action(F, Iv) == Action(F, Iv) + assert action(F, Iu) == Iu * v * dx + + # -- Adjoint -- # + adjoint(Iv) == Adjoint(Iv) + + +def test_differentiation(V1, V2): + + u = Coefficient(V1) + v = TestFunction(V1) + + # Define Interpolate + Iu = Interpolate(u, V2) + + # -- Differentiate: Interpolate(u, V2) -- # + uhat = TrialFunction(V1) + dIu = expand_derivatives(derivative(Iu, u, uhat)) + + # dInterpolate(u, v*)/du[uhat] <==> Interpolate(uhat, v*) + assert dIu == Interpolate(uhat, V2) + + # -- Differentiate: Interpolate(u**2, V2) -- # + g = u**2 + Ig = Interpolate(g, V2) + dIg = expand_derivatives(derivative(Ig, u, uhat)) + assert dIg == Interpolate(2 * uhat * u, V2) + + # -- Differentiate: I(u, V2) * v * dx -- # + F = Iu * v * dx + Ihat = TrialFunction(Iu.ufl_function_space()) + dFdu = expand_derivatives(derivative(F, u, uhat)) + # Compute dFdu = ∂F/∂u + Action(dFdIu, dIu/du) + # = Action(dFdIu, Iu(uhat, v*)) + dFdIu = expand_derivatives(derivative(F, Iu, Ihat)) + assert dFdIu == Ihat * v * dx + assert dFdu == Action(dFdIu, dIu) + + # -- Differentiate: u * I(u, V2) * v * dx -- # + F = u * Iu * v * dx + dFdu = expand_derivatives(derivative(F, u, uhat)) + # Compute dFdu = ∂F/∂u + Action(dFdIu, dIu/du) + # = ∂F/∂u + Action(dFdIu, Iu(uhat, v*)) + dFdu_partial = uhat * Iu * v * dx + dFdIu = Ihat * u * v * dx + assert dFdu == dFdu_partial + Action(dFdIu, dIu) + + # -- Differentiate (wrt Iu): + - + f = Coefficient(V1) + F = inner(Iu, v) * dx + inner(grad(Iu), grad(v)) * dx - inner(f, v) * dx + dFdIu = expand_derivatives(derivative(F, Iu, Ihat)) + + # BaseFormOperators are treated as coefficients when a form is differentiated wrt them. + # -> dFdIu <=> dFdw + w = Coefficient(V2) + F = replace(F, {Iu: w}) + dFdw = expand_derivatives(derivative(F, w, Ihat)) + + # Need to expand indices to be able to match equal (different MultiIndex used for both). + assert expand_indices(dFdIu) == expand_indices(dFdw) + + +def test_extract_base_form_operators(V1, V2): + + u = Coefficient(V1) + uhat = TrialFunction(V1) + vstar = Argument(V2.dual(), 0) + + # -- Interpolate(u, V2) -- # + Iu = Interpolate(u, V2) + assert extract_arguments(Iu) == [vstar] + assert extract_arguments_and_coefficients(Iu) == ([vstar], [u]) + + F = Iu * dx + # Form composition: Iu * dx <=> Action(v * dx, Iu(u; v*)) + assert extract_arguments(F) == [] + assert extract_arguments_and_coefficients(F) == ([], [u]) + + for e in [Iu, F]: + assert extract_coefficients(e) == [u] + assert extract_base_form_operators(e) == [Iu] + + # -- Interpolate(u, V2) -- # + Iv = Interpolate(uhat, V2) + assert extract_arguments(Iv) == [vstar, uhat] + assert extract_arguments_and_coefficients(Iv) == ([vstar, uhat], []) + assert extract_coefficients(Iv) == [] + assert extract_base_form_operators(Iv) == [Iv] + + # -- Action(v * v2 * dx, Iv) -- # + v2 = TrialFunction(V2) + v = TestFunction(V1) + F = Action(v * v2 * dx, Iv) + assert extract_arguments(F) == [v, uhat] diff --git a/ufl/__init__.py b/ufl/__init__.py index a31d341ee..b591fdf4f 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -240,6 +240,7 @@ # Modified by Lawrence Mitchell, 2014 # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 +# Modified by Nacime Bouziani, 2019 import importlib.metadata @@ -299,6 +300,9 @@ # Actions from ufl.action import Action +# Interpolates +from ufl.core.interpolate import Interpolate, interpolate + # Split function from ufl.split_functions import split @@ -372,7 +376,7 @@ 'Argument', 'Coargument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', 'Coefficient', 'Cofunction', 'Coefficients', - 'Matrix', 'Adjoint', 'Action', + 'Matrix', 'Adjoint', 'Action', 'Interpolate', 'interpolate', 'Constant', 'VectorConstant', 'TensorConstant', 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', diff --git a/ufl/action.py b/ufl/action.py index d2dc2b03e..7a18c8962 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -16,6 +16,7 @@ from ufl.argument import Argument, Coargument from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction from ufl.differentiation import CoefficientDerivative +from ufl.core.base_form_operator import BaseFormOperator from ufl.matrix import Matrix # --- The Action class represents the action of a numerical object that needs @@ -50,6 +51,11 @@ def __new__(cls, *args, **kw): # Check trivial case if left == 0 or right == 0: + if isinstance(left, Zero): + # There is no point in checking the action arguments + # if `left` is a `ufl.Zero` as those objects don't have arguments. + # We can also not reliably determine the `ZeroBaseForm` arguments. + return ZeroBaseForm(()) # Still need to work out the ZeroBaseForm arguments. new_arguments, _ = _get_action_form_arguments(left, right) return ZeroBaseForm(new_arguments) @@ -149,8 +155,9 @@ def _check_function_spaces(left, right): if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)): if left_arg.ufl_function_space().dual() != right.arguments()[0].ufl_function_space(): raise TypeError("Incompatible function spaces in Action") - elif isinstance(right, (Coefficient, Cofunction, Argument)): + elif isinstance(right, (Coefficient, Cofunction, Argument, BaseFormOperator)): if left_arg.ufl_function_space() != right.ufl_function_space(): + raise TypeError("Incompatible function spaces in Action") # `Zero` doesn't contain any information about the function space. # -> Not a problem since Action will get simplified with a `ZeroBaseForm` diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 727973437..86d55c4bc 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -35,6 +35,7 @@ "replace", "expand_derivatives", "extract_coefficients", + "extract_base_form_operators", "strip_variables", "strip_terminal_data", "replace_terminal_data", @@ -71,6 +72,7 @@ extract_arguments, extract_coefficients, # extract_arguments_and_coefficients, + extract_base_form_operators, extract_elements, extract_unique_elements, extract_sub_elements, diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index b17ec4d2c..693022860 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -15,7 +15,8 @@ from ufl.utils.sorting import sorted_by_count, topological_sorting from ufl.core.terminal import Terminal -from ufl.argument import BaseArgument +from ufl.core.base_form_operator import BaseFormOperator +from ufl.argument import BaseArgument, Coargument from ufl.coefficient import BaseCoefficient from ufl.constant import Constant from ufl.form import BaseForm, Form @@ -50,8 +51,15 @@ def extract_type(a, ufl_types): if not isinstance(ufl_types, (list, tuple)): ufl_types = (ufl_types,) - # BaseForms that aren't forms only contain arguments & coefficients - if isinstance(a, BaseForm) and not isinstance(a, Form): + if all(t is not BaseFormOperator for t in ufl_types): + remove_base_form_ops = True + ufl_types += (BaseFormOperator,) + else: + remove_base_form_ops = False + + # BaseForms that aren't forms or base form operators + # only contain arguments & coefficients + if isinstance(a, BaseForm) and not isinstance(a, (Form, BaseFormOperator)): objects = set() arg_types = tuple(t for t in ufl_types if issubclass(t, BaseArgument)) if arg_types: @@ -63,13 +71,42 @@ def extract_type(a, ufl_types): if all(issubclass(t, Terminal) for t in ufl_types): # Optimization - return set(o for e in iter_expressions(a) - for o in traverse_unique_terminals(e) - if any(isinstance(o, t) for t in ufl_types)) + objects = set(o for e in iter_expressions(a) + for o in traverse_unique_terminals(e) + if any(isinstance(o, t) for t in ufl_types)) else: - return set(o for e in iter_expressions(a) - for o in unique_pre_traversal(e) - if any(isinstance(o, t) for t in ufl_types)) + objects = set(o for e in iter_expressions(a) + for o in unique_pre_traversal(e) + if any(isinstance(o, t) for t in ufl_types)) + + # Need to extract objects contained in base form operators whose type is in ufl_types + base_form_ops = set(e for e in objects if isinstance(e, BaseFormOperator)) + ufl_types_no_args = tuple(t for t in ufl_types if not issubclass(t, BaseArgument)) + base_form_objects = () + for o in base_form_ops: + # This accounts for having BaseFormOperator in Forms: if N is a BaseFormOperator + # `N(u; v*) * v * dx` <=> `action(v1 * v * dx, N(...; v*))` + # where `v`, `v1` are `Argument`s and `v*` a `Coargument`. + for ai in tuple(arg for arg in o.argument_slots(isinstance(a, Form))): + # Extracting BaseArguments of an object of which a Coargument is an argument, + # then we just return the dual argument of the Coargument and not its primal argument. + if isinstance(ai, Coargument): + new_types = tuple(Coargument if t is BaseArgument else t for t in ufl_types) + base_form_objects += tuple(extract_type(ai, new_types)) + else: + base_form_objects += tuple(extract_type(ai, ufl_types)) + # Look for BaseArguments in BaseFormOperator's argument slots only since that's where they are by definition. + # Don't look into operands, which is convenient for external operator composition, e.g. N1(N2; v*) + # where N2 is seen as an operator and not a form. + slots = o.ufl_operands + for ai in slots: + base_form_objects += tuple(extract_type(ai, ufl_types_no_args)) + objects.update(base_form_objects) + + # `Remove BaseFormOperator` objects if there were initially not in `ufl_types` + if remove_base_form_ops: + objects -= base_form_ops + return objects def has_type(a, ufl_type): @@ -112,6 +149,12 @@ def extract_constants(a): return sorted_by_count(extract_type(a, Constant)) +def extract_base_form_operators(a): + """Build a sorted list of all base form operators (e.g. Interpolate or ExternalOperator)in a, + which can be a Form, Integral or Expr.""" + return sorted_by_count(extract_type(a, BaseFormOperator)) + + def extract_arguments_and_coefficients(a): """Build two sorted lists of all arguments and coefficients in a, which can be BaseForm, Integral or Expr.""" diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 3fced9e3a..2f8f0fe6b 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -11,6 +11,7 @@ from math import pi from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.algorithms.replace_derivative_nodes import replace_derivative_nodes from ufl.checks import is_cellwise_constant from ufl.classes import (Coefficient, ComponentTensor, Conj, ConstantValue, ExprList, ExprMapping, FloatValue, FormArgument, Grad, @@ -24,7 +25,7 @@ from ufl.core.terminal import Terminal from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction -from ufl.differentiation import CoordinateDerivative, BaseFormCoordinateDerivative +from ufl.differentiation import CoordinateDerivative, BaseFormCoordinateDerivative, BaseFormOperatorDerivative from ufl.domain import extract_unique_domain from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, conditional, cos, cosh, exp, facet_avg, ln, sign, @@ -32,7 +33,10 @@ from ufl.tensors import (as_scalar, as_scalars, as_tensor, unit_indexed_tensor, unwrap_list_tensor) -from ufl.form import ZeroBaseForm +from ufl.argument import BaseArgument +from ufl.action import Action +from ufl.form import Form, ZeroBaseForm +from ufl.core.base_form_operator import BaseFormOperator # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset @@ -502,6 +506,12 @@ def cell_coordinate(self, o): # --- Specialized rules for form arguments + def base_form_operator(self, o): + # Push the grad through the operator is not legal in most cases: + # -> Not enouth regularity for chain rule to hold! + # By the time we evaluate `grad(o)`, the operator `o` will have been assembled and substituted by its output. + return Grad(o) + def coefficient(self, o): if is_cellwise_constant(o): return self.independent_terminal(o) @@ -763,7 +773,7 @@ class GateauxDerivativeRuleset(GenericDerivativeRuleset): """ - def __init__(self, coefficients, arguments, coefficient_derivatives): + def __init__(self, coefficients, arguments, coefficient_derivatives, pending_operations): GenericDerivativeRuleset.__init__(self, var_shape=()) # Type checking @@ -785,6 +795,10 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): cd = coefficient_derivatives.ufl_operands self._cd = {cd[2 * i]: cd[2 * i + 1] for i in range(len(cd) // 2)} + # Record the operations delayed to the derivative expansion phase: + # Example: dN(u)/du where `N` is an ExternalOperator and `u` a Coefficient + self.pending_operations = pending_operations + # Explicitly defining dg/dw == 0 geometric_quantity = GenericDerivativeRuleset.independent_terminal @@ -872,7 +886,7 @@ def reference_grad(self, o): def grad(self, g): # If we hit this type, it has already been propagated to a - # coefficient (or grad of a coefficient), # FIXME: Assert + # coefficient (or grad of a coefficient) or a base form operator, # FIXME: Assert # this! so we need to take the gradient of the variation or # return zero. Complications occur when dealing with # derivatives w.r.t. single components... @@ -883,7 +897,8 @@ def grad(self, g): while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 - if not isinstance(o, FormArgument): + # `grad(N)` where N is a BaseFormOperator is treated as if `N` was a Coefficient. + if not isinstance(o, (FormArgument, BaseFormOperator)): raise ValueError(f"Expecting gradient of a FormArgument, not {ufl_err_str(o)}.") def apply_grads(f): @@ -935,8 +950,10 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # components for (w, v) in zip(self._w, self._v): - # Analyse differentiation variable coefficient - if isinstance(w, FormArgument): + # -- Analyse differentiation variable coefficient -- # + + # Can differentiate a Form wrt a BaseFormOperator + if isinstance(w, (FormArgument, BaseFormOperator)): if not w == o: continue wshape = w.ufl_shape @@ -1023,6 +1040,16 @@ def coordinate_derivative(self, o): o = o.ufl_operands return CoordinateDerivative(map_expr_dag(self, o[0]), o[1], o[2], o[3]) + def base_form_operator(self, o, *dfs): + """If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a variable => we call the appropriate handler + Otherwise => differentiation done wrt the BaseFormOperator (dF/dN[Nhat]) => we treat o as a Coefficient + """ + d_coeff = self.coefficient(o) + # It also handles the non-scalar case + if d_coeff == 0: + self.pending_operations += (o,) + return d_coeff + # -- Handlers for BaseForm objects -- # def cofunction(self, o): @@ -1049,6 +1076,44 @@ def matrix(self, M): return ZeroBaseForm(M.arguments() + self._v) +class BaseFormOperatorDerivativeRuleset(GateauxDerivativeRuleset): + """Apply AFD (Automatic Functional Differentiation) to BaseFormOperator. + + Implements rules for the Gateaux derivative D_w[v](...) defined as + + D_w[v](B) = d/dtau B(w+tau v)|tau=0 + + where B is a ufl.BaseFormOperator + """ + + def __init__(self, coefficients, arguments, coefficient_derivatives, pending_operations): + GateauxDerivativeRuleset.__init__(self, coefficients, arguments, coefficient_derivatives, pending_operations) + + def pending_operations_recording(base_form_operator_handler): + def wrapper(self, base_form_op, *dfs): + # Get the outer `BaseFormOperator` expression, i.e. the operator that is being differentiated. + expression = self.pending_operations.expression + # If the base form operator we observe is different from the outer `BaseFormOperator`: + # -> Record that `BaseFormOperator` so that `d(expression)/d(base_form_op)` can then be computed later. + # Else: + # -> Compute the Gateaux derivative of `base_form_ops` by calling the appropriate handler. + if expression != base_form_op: + self.pending_operations += (base_form_op,) + return self.coefficient(base_form_op) + return base_form_operator_handler(self, base_form_op, *dfs) + return wrapper + + @pending_operations_recording + def interpolate(self, i_op, dw): + # Interpolate rule: D_w[v](i_op(w, v*)) = i_op(v, v*), by linearity of Interpolate! + if not dw: + # i_op doesn't depend on w: + # -> It also covers the Hessian case since Interpolate is linear, + # e.g. D_w[v](D_w[v](i_op(w, v*))) = D_w[v](i_op(v, v*)) = 0 (since w not found). + return ZeroBaseForm(i_op.arguments() + self._v) + return i_op._ufl_expr_reconstruct_(expr=dw) + + class DerivativeRuleDispatcher(MultiFunction): def __init__(self): MultiFunction.__init__(self) @@ -1056,6 +1121,10 @@ def __init__(self): self.vcaches = defaultdict(dict) self.rcaches = defaultdict(dict) + # Record the operations delayed to the derivative expansion phase: + # Example: dN(u)/du where `N` is a BaseFormOperator and `u` a Coefficient + self.pending_operations = () + def terminal(self, o): return o @@ -1088,11 +1157,44 @@ def variable_derivative(self, o, f, dummy_v): def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): dummy, w, v, cd = o.ufl_operands - rules = GateauxDerivativeRuleset(w, v, cd) + pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) + rules = GateauxDerivativeRuleset(w, v, cd, pending_operations) key = (GateauxDerivativeRuleset, w, v, cd) - return map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) + # We need to go through the dag first to record the pending operations + mapped_expr = map_expr_dag(rules, f, + vcache=self.vcaches[key], + rcache=self.rcaches[key]) + # Need to account for pending operations that have been stored in other integrands + self.pending_operations += pending_operations + return mapped_expr + + def base_form_operator_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + dummy, w, v, cd = o.ufl_operands + pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) + rules = BaseFormOperatorDerivativeRuleset(w, v, cd, pending_operations=pending_operations) + key = (BaseFormOperatorDerivativeRuleset, w, v, cd) + if isinstance(f, ZeroBaseForm): + arg, = v.ufl_operands + arguments = f.arguments() + # derivative(F, u, du) with `du` a Coefficient + # is equivalent to taking the action of the derivative. + # In that case, we don't add arguments to `ZeroBaseForm`. + if isinstance(arg, BaseArgument): + arguments += (arg,) + return ZeroBaseForm(arguments) + # We need to go through the dag first to record the pending operations + mapped_expr = map_expr_dag(rules, f, + vcache=self.vcaches[key], + rcache=self.rcaches[key]) + + mapped_f = rules.coefficient(f) + if mapped_f != 0: + # If dN/dN needs to return an Argument in N space + # with N a BaseFormOperator. + return mapped_f + # Need to account for pending operations that have been stored in other integrands + self.pending_operations += pending_operations + return mapped_expr def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): o_ = o.ufl_operands @@ -1142,9 +1244,107 @@ def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules return op +class BaseFormOperatorDerivativeRecorder(): + def __init__(self, expression, var, **kwargs): + + base_form_ops = kwargs.pop("base_form_ops", ()) + + if kwargs.keys() != {'arguments', 'coefficient_derivatives'}: + raise ValueError("Only `arguments` and `coefficient_derivatives` are allowed as derivative arguments.") + + self.expression = expression + self.var = var + self.der_kwargs = kwargs + self.base_form_ops = base_form_ops + + def __len__(self): + return len(self.base_form_ops) + + def __bool__(self): + return bool(self.base_form_ops) + + def __add__(self, other): + if isinstance(other, (list, tuple)): + base_form_ops = self.base_form_ops + other + elif isinstance(other, BaseFormOperatorDerivativeRecorder): + if self.der_kwargs != other.der_kwargs: + raise ValueError("Derivative arguments must match when summing %s objects." % type(self).__name__) + base_form_ops = self.base_form_ops + other.base_form_ops + else: + raise NotImplementedError(f"Sum of {type(self)} and {type(other)} objects is not supported.") + + return BaseFormOperatorDerivativeRecorder(self.expression, self.var, + base_form_ops=base_form_ops, + **self.der_kwargs) + + def __radd__(self, other): + # Recording order doesn't matter as collected `BaseFormOperator`s are sorted later on. + return self.__add__(other) + + def __iadd__(self, other): + if isinstance(other, (list, tuple)): + self.base_form_ops += other + elif isinstance(other, BaseFormOperatorDerivativeRecorder): + self.base_form_ops += other.base_form_ops + else: + raise NotImplementedError + return self + + def apply_derivatives(expression): + # Note that `expression` can be a Form, an Expr or a BaseFormOperator. + # Notation: Let `var` be the thing we are differentating with respect to. + rules = DerivativeRuleDispatcher() - return map_integrand_dags(rules, expression) + + # If we hit a base form operator (bfo), then if `var` is: + # - a BaseFormOperator → Return `d(expression)/dw` where `w` is the coefficient produced by the bfo `var`. + # - else → Record the bfo on the MultiFunction object and returns 0. + # Example: + # → If derivative(F(u, N(u); v), u) was taken the following line would compute `∂F/∂u`. + dexpression_dvar = map_integrand_dags(rules, expression) + + # Get the recorded delayed operations + pending_operations = rules.pending_operations + if not pending_operations: + return dexpression_dvar + + # Don't take into account empty Forms + if not (isinstance(dexpression_dvar, Form) and len(dexpression_dvar.integrals()) == 0): + dexpression_dvar = (dexpression_dvar,) + else: + dexpression_dvar = () + + # Retrieve the base form operators, var, and the argument and coefficient_derivatives for `derivative` + var = pending_operations.var + base_form_ops = pending_operations.base_form_ops + der_kwargs = pending_operations.der_kwargs + for N in sorted(set(base_form_ops), key=lambda x: x.count()): + # -- Replace dexpr/dvar by dexpr/dN -- # + # We don't use `apply_derivatives` since the differentiation is done via `\partial` and not `d`. + dexpr_dN = map_integrand_dags(rules, replace_derivative_nodes(expression, {var.ufl_operands[0]: N})) + # -- Add the BaseFormOperatorDerivative node -- # + var_arg, = der_kwargs['arguments'].ufl_operands + cd = der_kwargs['coefficient_derivatives'] + # Not always the case since `derivative`'s syntax enables one to use a Coefficient as the Gateaux direction + if isinstance(var_arg, BaseArgument): + # Construct the argument number based on the BaseFormOperator arguments instead of naively + # using `var_arg`. This is critical when BaseFormOperators are used inside 0-forms. + # + # Example: F = 0.5 * u** 2 * dx + 0.5 * N(u; v*)** 2 * dx + # -> dFdu[vhat] = + Action(, dNdu(u; v1, v*)) + # with `vhat` a 0-numbered argument, and where `v1` and `vhat` have the same function space but + # a different number. Here, applying `vhat` (`var_arg`) naively would result in `dNdu(u; vhat, v*)`, + # i.e. the 2-forms `dNdu` would have two 0-numbered arguments. Instead we increment the argument number + # of `vhat` to form `v1`. + var_arg = type(var_arg)(var_arg.ufl_function_space(), number=len(N.arguments()), part=var_arg.part()) + dN_dvar = apply_derivatives(BaseFormOperatorDerivative(N, var, ExprList(var_arg), cd)) + # -- Sum the Action: dF/du = ∂F/∂u + \sum_{i=1,...} Action(∂F/∂Ni, dNi/du) -- # + if not (isinstance(dexpr_dN, Form) and len(dexpr_dN.integrals()) == 0): + # In this case: Action <=> ufl.action since `dN_var` has 2 arguments. + # We use Action to handle the trivial case `dN_dvar` = 0. + dexpression_dvar += (Action(dexpr_dN, dN_dvar),) + return sum(dexpression_dvar) class CoordinateDerivativeRuleset(GenericDerivativeRuleset): diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 8d44aa442..e794c1a38 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -9,7 +9,7 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.classes import CoefficientDerivative +from ufl.classes import CoefficientDerivative, Interpolate from ufl.constantvalue import as_ufl from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags @@ -29,6 +29,13 @@ def ufl_type(self, o, *args): except KeyError: return self.reuse_if_untouched(o, *args) + def interpolate(self, o): + o = self.mapping.get(o) or o + if isinstance(o, Interpolate): + new_args = tuple(replace(arg, self.mapping) for arg in o.argument_slots()) + return o._ufl_expr_reconstruct_(*reversed(new_args)) + return o + def coefficient_derivative(self, o): raise ValueError("Derivatives should be applied before executing replace.") diff --git a/ufl/algorithms/replace_derivative_nodes.py b/ufl/algorithms/replace_derivative_nodes.py new file mode 100644 index 000000000..dafd873be --- /dev/null +++ b/ufl/algorithms/replace_derivative_nodes.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +"""Algorithm for replacing derivative nodes in a BaseForm or Expr""" + +import ufl +from ufl.corealg.multifunction import MultiFunction +from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.algorithms.analysis import extract_arguments +from ufl.tensors import ListTensor +from ufl.constantvalue import as_ufl + + +class DerivativeNodeReplacer(MultiFunction): + """Replace derivative nodes with new derivative nodes""" + + def __init__(self, mapping, **derivative_kwargs): + super().__init__() + self.mapping = mapping + self.der_kwargs = derivative_kwargs + + expr = MultiFunction.reuse_if_untouched + + def coefficient_derivative(self, cd, o, coefficients, arguments, coefficient_derivatives): + der_kwargs = self.der_kwargs + new_coefficients = tuple(self.mapping[c] if c in self.mapping.keys() else c for c in coefficients.ufl_operands) + + # Ensure type compatibility for arguments! + if 'argument' not in der_kwargs.keys(): + # Argument's number/part can be retrieved from the former coefficient derivative. + arguments = arguments.ufl_operands + new_arguments = () + for c, a in zip(new_coefficients, arguments): + if isinstance(a, ListTensor): + a, = extract_arguments(a) + new_arguments += (type(a)(c.ufl_function_space(), a.number(), a.part()),) + der_kwargs.update({'argument': new_arguments}) + + return ufl.derivative(o, new_coefficients, **der_kwargs) + + +def replace_derivative_nodes(expr, mapping, **derivative_kwargs): + """Replaces derivative nodes, i.e. replaces the variable with respect to which the derivative is taken. + This is called during apply_derivatives to treat delayed derivatives. + + Example: Let u be a Coefficient, N an ExternalOperator independent of u (i.e. N's operands don't depend on u), + and let uhat and Nhat be Arguments. + + F = u ** 2 * N * dx + dFdu = derivative(F, u, uhat) + dFdN = replace_derivative_nodes(dFdu, {u: N}, argument=Nhat) + + Then, by subsequently expanding the derivatives we have: + + dFdu -> 2 * u * uhat * N * dx + dFdN -> u ** 2 * Nhat * dx + + @param e: + An Expr or BaseForm. + @param mapping: + A dict with from:to replacements to perform. + @param derivative_kwargs: + A dict containing the keyword arguments for derivative (i.e. `argument` and `coefficient_derivatives`). + """ + mapping2 = dict((k, as_ufl(v)) for (k, v) in mapping.items()) + return map_integrand_dags(DerivativeNodeReplacer(mapping2, **derivative_kwargs), expr) diff --git a/ufl/argument.py b/ufl/argument.py index 5157700b6..b0a68422c 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -211,13 +211,24 @@ def __init__(self, function_space, number, part=None): self._repr = "Coargument(%s, %s, %s)" % ( repr(self._ufl_function_space), repr(self._number), repr(self._part)) - def _analyze_form_arguments(self): + def arguments(self, outer_form=None): + "Return all ``Argument`` objects found in form." + if self._arguments is None: + self._analyze_form_arguments(outer_form=outer_form) + return self._arguments + + def _analyze_form_arguments(self, outer_form=None): "Analyze which Argument and Coefficient objects can be found in the form." # Define canonical numbering of arguments and coefficients + self._coefficients = () # Coarguments map from V* to V*, i.e. V* -> V*, or equivalently V* x V -> R. # So they have one argument in the primal space and one in the dual space. - self._arguments = (Argument(self.ufl_function_space().dual(), 0), self) - self._coefficients = () + # However, when they are composed with linear forms with dual arguments, such as BaseFormOperators, + # the primal argument is discarded when analysing the argument as Coarguments. + if not outer_form: + self._arguments = (Argument(self.ufl_function_space().dual(), 0), self) + else: + self._arguments = (self,) def ufl_domain(self): return BaseArgument.ufl_domain(self) diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py new file mode 100644 index 000000000..e29979157 --- /dev/null +++ b/ufl/core/base_form_operator.py @@ -0,0 +1,154 @@ + +# -*- coding: utf-8 -*- +"""This module defines the BaseFormOperator class, which is the base class for objects that can be seen as forms + and as operators such as ExternalOperator or Interpolate.""" + +# Copyright (C) 2019 Nacime Bouziani +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2021-2022 + +from collections import OrderedDict + +from ufl.argument import Argument, Coargument +from ufl.core.operator import Operator +from ufl.form import BaseForm +from ufl.core.ufl_type import ufl_type +from ufl.constantvalue import as_ufl +from ufl.functionspace import AbstractFunctionSpace +from ufl.utils.counted import Counted + + +@ufl_type(num_ops="varying", is_differential=True) +class BaseFormOperator(Operator, BaseForm, Counted): + + # Slots are disabled here because they cause trouble in PyDOLFIN + # multiple inheritance pattern: + _ufl_noslots_ = True + + def __init__(self, *operands, function_space, derivatives=None, argument_slots=()): + r""" + :param operands: operands on which acts the operator. + :param function_space: the :class:`.FunctionSpace`, + or :class:`.MixedFunctionSpace` on which to build this :class:`Function`. + :param derivatives: tuple specifiying the derivative multiindex. + :param argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + """ + + BaseForm.__init__(self) + ufl_operands = tuple(map(as_ufl, operands)) + argument_slots = tuple(map(as_ufl, argument_slots)) + Operator.__init__(self, ufl_operands) + Counted.__init__(self, counted_class=BaseFormOperator) + + # -- Function space -- # + if not isinstance(function_space, AbstractFunctionSpace): + raise ValueError("Expecting a FunctionSpace or FiniteElement.") + + # -- Derivatives -- # + # Some BaseFormOperator does have derivatives (e.g. ExternalOperator) + # while other don't since they are fully determined by their + # argument slots (e.g. Interpolate) + self.derivatives = derivatives + + # -- Argument slots -- # + if len(argument_slots) == 0: + # Make v* + v_star = Argument(function_space.dual(), 0) + argument_slots = (v_star,) + self._argument_slots = argument_slots + + # Internal variables for caching coefficient data + self._coefficients = None + + # BaseFormOperators don't have free indices. + ufl_free_indices = () + ufl_index_dimensions = () + + def argument_slots(self, outer_form=False): + r"""Returns a tuple of expressions containing argument and coefficient based expressions. + We get an argument uhat when we take the Gateaux derivative in the direction uhat: + -> d/du N(u; v*) = dNdu(u; uhat, v*) where uhat is a ufl.Argument and v* a ufl.Coargument + Applying the action replace the last argument by coefficient: + -> action(dNdu(u; uhat, v*), w) = dNdu(u; w, v*) where du is a ufl.Coefficient + """ + from ufl.algorithms.analysis import extract_arguments + if not outer_form: + return self._argument_slots + # Takes into account argument contraction when a base form operator is in an outer form: + # For example: + # F = N(u; v*) * v * dx can be seen as Action(v1 * v * dx, N(u; v*)) + # => F.arguments() should return (v,)! + return tuple(a for a in self._argument_slots[1:] if len(extract_arguments(a)) != 0) + + def coefficients(self): + "Return all ``BaseCoefficient`` objects found in base form operator." + if self._coefficients is None: + self._analyze_form_arguments() + return self._coefficients + + def _analyze_form_arguments(self): + "Analyze which Argument and Coefficient objects can be found in the base form." + from ufl.algorithms.analysis import extract_arguments, extract_coefficients, extract_type + dual_arg, *arguments = self.argument_slots() + # When coarguments are treated as BaseForms, they have two arguments (one primal and one dual) + # as they map from V* to V* => V* x V -> R. However, when they are treated as mere "arguments", + # the primal space argument is discarded and we only have the dual space argument (Coargument). + # This is the exact same situation than BaseFormOperator's arguments which are different depending on + # whether the BaseFormOperator is used in an outer form or not. + arguments = (tuple(extract_type(dual_arg, Coargument)) + + tuple(a for arg in arguments for a in extract_arguments(arg))) + coefficients = tuple(c for op in self.ufl_operands for c in extract_coefficients(op)) + # Define canonical numbering of arguments and coefficients + # 1) Need concept of order since we may have arguments with the same number + # because of form composition (`argument_slots(outer_form=True)`): + # Example: Let u \in V1 and N \in V2 and F = N(u; v*) * dx, then + # `derivative(F, u)` will contain dNdu(u; uhat, v*) with v* = Argument(0, V2) + # and uhat = Argument(0, V1) (since F.arguments() = ()) + # 2) Having sorted arguments also makes BaseFormOperator compatible with other + # BaseForm objects for which the highest-numbered argument always comes last. + self._arguments = tuple(sorted(OrderedDict.fromkeys(arguments), key=lambda x: x.number())) + self._coefficients = tuple(sorted(set(coefficients), key=lambda x: x.count())) + + def count(self): + "Returns the count associated to the base form operator" + return self._count + + @property + def ufl_shape(self): + "Returns the UFL shape of the coefficient.produced by the operator" + return self.arguments()[0]._ufl_shape + + def ufl_function_space(self): + "Returns the function space associated to the operator, i.e. the dual of the base form operator's `Coargument`" + return self.arguments()[0]._ufl_function_space.dual() + + def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, argument_slots=None): + "Return a new object of the same type with new operands." + return type(self)(*operands, function_space=function_space or self.ufl_function_space(), + derivatives=derivatives or self.derivatives, + argument_slots=argument_slots or self.argument_slots()) + + def __repr__(self): + "Default repr string construction for base form operators." + r = "%s(%s; %s; %s; derivatives=%s)" % (type(self).__name__, + ", ".join(repr(op) for op in self.ufl_operands), + repr(self.ufl_function_space()), + ", ".join(repr(arg) for arg in self.argument_slots()), + repr(self.derivatives)) + return r + + def __hash__(self): + "Hash code for use in dicts." + hashdata = (type(self), + tuple(hash(op) for op in self.ufl_operands), + tuple(hash(arg) for arg in self._argument_slots), + self.derivatives, + hash(self.ufl_function_space())) + return hash(hashdata) + + def __eq__(self, other): + raise NotImplementedError diff --git a/ufl/core/expr.py b/ufl/core/expr.py index b47ccadca..d6d51e412 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -23,8 +23,8 @@ from ufl.core.ufl_type import UFLType, update_ufl_type_attributes -# --- The base object for all UFL expression tree nodes --- +# --- The base object for all UFL expression tree nodes --- class Expr(object, metaclass=UFLType): """Base class for all UFL expression types. diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py new file mode 100644 index 000000000..f7e912caa --- /dev/null +++ b/ufl/core/interpolate.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +"""This module defines the Interpolate class.""" + +# Copyright (C) 2021 Nacime Bouziani +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2021-2022 + +from ufl.core.ufl_type import ufl_type +from ufl.constantvalue import as_ufl +from ufl.functionspace import AbstractFunctionSpace +from ufl.argument import Coargument, Argument +from ufl.coefficient import Cofunction +from ufl.form import Form +from ufl.core.base_form_operator import BaseFormOperator +from ufl.duals import is_dual + + +@ufl_type(num_ops="varying", is_differential=True) +class Interpolate(BaseFormOperator): + + # Slots are disabled here because they cause trouble in PyDOLFIN + # multiple inheritance pattern: + _ufl_noslots_ = True + + def __init__(self, expr, v): + r""" Symbolic representation of the interpolation operator. + + :arg expr: a UFL expression to interpolate. + :arg v: the :class:`.FunctionSpace` to interpolate into or the :class:`.Coargument` + defined on the dual of the :class:`.FunctionSpace` to interpolate into. + """ + + # This check could be more rigorous. + dual_args = (Coargument, Cofunction, Form) + + if isinstance(v, AbstractFunctionSpace): + if is_dual(v): + raise ValueError('Expecting a primal function space.') + v = Argument(v.dual(), 0) + elif not isinstance(v, dual_args): + raise ValueError("Expecting the second argument to be FunctionSpace, FiniteElement or dual.") + + expr = as_ufl(expr) + if isinstance(expr, dual_args): + raise ValueError("Expecting the first argument to be primal.") + + # Reversed order convention + argument_slots = (v, expr) + # Get the primal space (V** = V) + vv = v if not isinstance(v, Form) else v.arguments()[0] + function_space = vv.ufl_function_space().dual() + # Set the operand as `expr` for DAG traversal purpose. + operand = expr + BaseFormOperator.__init__(self, operand, function_space=function_space, + argument_slots=argument_slots) + + def _ufl_expr_reconstruct_(self, expr, v=None, **add_kwargs): + "Return a new object of the same type with new operands." + v = v or self.argument_slots()[0] + return type(self)(expr, v, **add_kwargs) + + def __repr__(self): + "Default repr string construction for Interpolate." + r = "Interpolate(%s; %s)" % (", ".join(repr(arg) for arg in reversed(self.argument_slots())), + repr(self.ufl_function_space())) + return r + + def __str__(self): + "Default str string construction for Interpolate." + s = "Interpolate(%s; %s)" % (", ".join(str(arg) for arg in reversed(self.argument_slots())), + str(self.ufl_function_space())) + return s + + def __eq__(self, other): + if self is other: + return True + return (type(self) is type(other) and + all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) and + self.ufl_function_space() == other.ufl_function_space()) + + +# Helper function +def interpolate(expr, v): + r""" Symbolic representation of the interpolation operator. + + :arg expr: a UFL expression to interpolate. + :arg v: the :class:`.FunctionSpace` to interpolate into or the :class:`.Coargument` + defined on the dual of the :class:`.FunctionSpace` to interpolate into. + """ + return Interpolate(expr, v) diff --git a/ufl/differentiation.py b/ufl/differentiation.py index d8828e37e..ed4ff2dd8 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -12,6 +12,7 @@ from ufl.constantvalue import Zero from ufl.core.expr import Expr from ufl.core.operator import Operator +from ufl.core.base_form_operator import BaseFormOperator from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import extract_unique_domain, find_geometric_dimension @@ -127,6 +128,49 @@ def __init__(self, base_form, coefficients, arguments, coefficient_derivatives) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, + inherit_indices_from_operand=0) +class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): + """Derivative of a base form operator w.r.t the + degrees of freedom in a discrete Coefficient.""" + _ufl_noslots_ = True + + # BaseFormOperatorDerivative is only needed because of a different + # differentiation procedure for BaseformOperator objects. + def __init__(self, base_form, coefficients, arguments, + coefficient_derivatives): + BaseFormDerivative.__init__(self, base_form, coefficients, arguments, + coefficient_derivatives) + self._argument_slots = base_form._argument_slots + + # Enforce Operator reconstruction as Operator is a parent class of both: BaseFormDerivative and BaseFormOperator. + # Therfore the latter overwrites Operator reconstruction and we would have: + # -> BaseFormOperatorDerivative._ufl_expr_reconstruct_ = BaseFormOperator._ufl_expr_reconstruct_ + _ufl_expr_reconstruct_ = Operator._ufl_expr_reconstruct_ + # Set __repr__ + __repr__ = Operator.__repr__ + + def argument_slots(self, outer_form=False): + """Returns a tuple of expressions containing argument and coefficient based expressions.""" + from ufl.algorithms.analysis import extract_arguments + base_form, _, arguments, _ = self.ufl_operands + argument_slots = (base_form.argument_slots(outer_form) + + tuple(arg for a in arguments for arg in extract_arguments(a))) + return argument_slots + + +@ufl_type(num_ops=4, inherit_shape_from_operand=0, + inherit_indices_from_operand=0) +class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, CoordinateDerivative): + """Derivative of a base form operator w.r.t. the SpatialCoordinates.""" + _ufl_noslots_ = True + + def __init__(self, base_form, coefficients, arguments, + coefficient_derivatives): + BaseFormOperatorDerivative.__init__(self, base_form, coefficients, arguments, + coefficient_derivatives) + + @ufl_type(num_ops=2) class VariableDerivative(Derivative): __slots__ = ( diff --git a/ufl/form.py b/ufl/form.py index 601e4d13a..b146a2d8e 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -10,6 +10,7 @@ # Modified by Anders Logg, 2009-2011. # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. +# Modified by Nacime Bouziani, 2020. # Modified by Jørgen S. Dokken 2023. import numbers @@ -239,7 +240,6 @@ def __call__(self, *args, **kwargs): repdict[f] = coefficients[f] else: warnings("Coefficient %s is not in form." % ufl_err_str(f)) - if repdict: from ufl.formoperators import replace return replace(self, repdict) @@ -273,6 +273,7 @@ class Form(BaseForm): "_domain_numbering", "_subdomain_data", "_arguments", + "_base_form_operators", "_coefficients", "_coefficient_numbering", "_constants", @@ -310,6 +311,9 @@ def __init__(self, integrals): self._constant_numbering = None self._terminal_numbering = None + # Internal variables for caching base form operator data + self._base_form_operators = None + from ufl.algorithms.analysis import extract_constants self._constants = extract_constants(self) @@ -409,18 +413,18 @@ def max_subdomain_ids(self): self._analyze_subdomain_data() return self._max_subdomain_ids - def arguments(self): - "Return all ``Argument`` objects found in form." - if self._arguments is None: - self._analyze_form_arguments() - return self._arguments - def coefficients(self): "Return all ``Coefficient`` objects found in form." if self._coefficients is None: self._analyze_form_arguments() return self._coefficients + def base_form_operators(self): + "Return all ``BaseFormOperator`` objects found in form." + if self._base_form_operators is None: + self._analyze_base_form_operators() + return self._base_form_operators + def coefficient_numbering(self): """Return a contiguous numbering of coefficients in a mapping ``{coefficient:number}``.""" @@ -680,6 +684,12 @@ def _analyze_form_arguments(self): self._coefficients = tuple( sorted(set(coefficients), key=lambda x: x.count())) + def _analyze_base_form_operators(self): + "Analyze which BaseFormOperator objects can be found in the form." + from ufl.algorithms.analysis import extract_base_form_operators + base_form_ops = extract_base_form_operators(self) + self._base_form_operators = tuple(sorted(base_form_ops, key=lambda x: x.count())) + def _compute_renumbering(self): # Include integration domains and coefficients in renumbering dn = self.domain_numbering() @@ -722,7 +732,7 @@ def _compute_signature(self): def as_form(form): "Convert to form if not a form, otherwise return form." - if not isinstance(form, BaseForm): + if not isinstance(form, BaseForm) and form != 0: raise ValueError(f"Unable to convert object to a UFL form: {ufl_err_str(form)}") return form diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 8fda43db7..3a9df00dc 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -443,6 +443,9 @@ def coefficient(self, o): return "%s%s" % (var, subscript_number(i)) return self.coefficient_names[o.count()] + def base_form_operator(self, o): + return "BaseFormOperator" + def constant(self, o): i = o.count() var = "c" diff --git a/ufl/formoperators.py b/ufl/formoperators.py index 40caeaf68..7aa9848aa 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -13,6 +13,7 @@ from ufl.form import Form, FormSum, BaseForm, ZeroBaseForm, as_form from ufl.core.expr import Expr, ufl_err_str +from ufl.core.base_form_operator import BaseFormOperator from ufl.split_functions import split from ufl.exprcontainers import ExprList, ExprMapping from ufl.variable import Variable @@ -21,8 +22,9 @@ from ufl.coefficient import Coefficient, Cofunction from ufl.adjoint import Adjoint from ufl.action import Action -from ufl.differentiation import (CoefficientDerivative, BaseFormDerivative, - CoordinateDerivative, BaseFormCoordinateDerivative) +from ufl.differentiation import (CoefficientDerivative, CoordinateDerivative, + BaseFormDerivative, BaseFormCoordinateDerivative, + BaseFormOperatorDerivative, BaseFormOperatorCoordinateDerivative) from ufl.constantvalue import is_true_ufl_scalar, as_ufl from ufl.indexed import Indexed from ufl.core.multiindex import FixedIndex, MultiIndex @@ -102,18 +104,28 @@ def functional(form): # TODO: Does this make sense for anything other than test return compute_form_functional(form) -def action(form, coefficient=None): +def action(form, coefficient=None, derivatives_expanded=None): """UFL form operator: Given a bilinear form, return a linear form with an additional coefficient, representing the action of the form on the coefficient. This can be used for matrix-free methods. For formbase objects,coefficient can be any object of the correct type, - and this function returns an Action object.""" + and this function returns an Action object. + + When `action` is being called multiple times on the same form, expanding derivatives + become expensive -> `derivatives_expanded` enables to use caching mechanisms to avoid that. + """ form = as_form(form) - if isinstance(form, Form) and not (isinstance(coefficient, BaseForm) and len(coefficient.arguments()) > 1): - form = expand_derivatives(form) - return compute_form_action(form, coefficient) + is_coefficient_valid = (not isinstance(coefficient, BaseForm) or + (isinstance(coefficient, BaseFormOperator) and len(coefficient.arguments()) == 1)) + # Can't expand derivatives on objects that are not Form or Expr (e.g. Matrix) + if isinstance(form, (Form, BaseFormOperator)) and is_coefficient_valid: + if not derivatives_expanded: + # For external operators differentiation may turn a Form into a FormSum + form = expand_derivatives(form) + if isinstance(form, Form): + return compute_form_action(form, coefficient) return Action(form, coefficient) @@ -126,7 +138,7 @@ def energy_norm(form, coefficient=None): return compute_energy_norm(form, coefficient) -def adjoint(form, reordered_arguments=None): +def adjoint(form, reordered_arguments=None, derivatives_expanded=None): """UFL form operator: Given a combined bilinear form, compute the adjoint form by changing the ordering (count) of the test and trial functions, and @@ -139,11 +151,20 @@ def adjoint(form, reordered_arguments=None): If the form is a baseform instance instead of a Form object, we return an Adjoint object instructing the adjoint to be computed at a later point. + + When `adjoint` is being called multiple times on the same form, expanding derivatives + become expensive -> `derivatives_expanded` enables to use caching mechanisms to avoid that. """ form = as_form(form) - if isinstance(form, Form): - form = expand_derivatives(form) - return compute_form_adjoint(form, reordered_arguments) + if isinstance(form, BaseForm): + # Allow BaseForm objects that are not BaseForm such as Adjoint since there are cases + # where we need to expand derivatives: e.g. to get the number of arguments + # => For example: Adjoint(Action(2-form, derivative(u,u))) + if not derivatives_expanded: + # For external operators differentiation may turn a Form into a FormSum + form = expand_derivatives(form) + if isinstance(form, Form): + return compute_form_adjoint(form, reordered_arguments) return Adjoint(form) @@ -174,7 +195,7 @@ def _handle_derivative_arguments(form, coefficient, argument): if argument is None: # Try to create argument if not provided - if not all(isinstance(c, (Coefficient, Cofunction)) for c in coefficients): + if not all(isinstance(c, (Coefficient, Cofunction, BaseFormOperator)) for c in coefficients): raise ValueError("Can only create arguments automatically for non-indexed coefficients.") # Get existing arguments from form and position the new one @@ -227,7 +248,7 @@ def _handle_derivative_arguments(form, coefficient, argument): for (c, a) in zip(coefficients, arguments): if c.ufl_shape != a.ufl_shape: raise ValueError("Coefficient and argument shapes do not match!") - if isinstance(c, (Coefficient, Cofunction, SpatialCoordinate)): + if isinstance(c, (Coefficient, Cofunction, BaseFormOperator, SpatialCoordinate)): m[c] = a else: if not isinstance(c, Indexed): @@ -321,7 +342,7 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): if isinstance(coefficient, SpatialCoordinate): fd = CoordinateDerivative(itg.integrand(), coefficients, arguments, coefficient_derivatives) - elif isinstance(coefficient, BaseForm): + elif isinstance(coefficient, BaseForm) and not isinstance(coefficient, BaseFormOperator): # Make the `ZeroBaseForm` arguments arguments = form.arguments() + coefficient.arguments() return ZeroBaseForm(arguments) @@ -331,6 +352,12 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): integrals.append(itg.reconstruct(fd)) return Form(integrals) + elif isinstance(form, BaseFormOperator): + if not isinstance(coefficient, SpatialCoordinate): + return BaseFormOperatorDerivative(form, coefficients, arguments, coefficient_derivatives) + else: + return BaseFormOperatorCoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) + elif isinstance(form, BaseForm): if not isinstance(coefficient, SpatialCoordinate): return BaseFormDerivative(form, coefficients, arguments, coefficient_derivatives) From d5ebee9dcd82004d892a7c753aacb2b0e8e68f41 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:08:56 +0100 Subject: [PATCH 054/136] External operator dualspace (#206) * Update AUTHORS * Add preprocess_form * Update analysis.py with BaseFormOperator * Add .base_form_operators() in Form * Fix _ufl_expr_reconstruct_ for Interp * Update tests * Grad differentiation + Fix few things in differentiation * BaseFormOperator doesn't have free indices * Cleanup * Update doc * Update traversal.py * Replace expr handler by ufl_type in Replacer * Last minute change * Flake8 * Fix flake8 * Fix few things * Add replacer handler for Interp * Refactor UFL type system * Address comments from the PR * Add coefficients to BaseFormOperator * Updare interp * Some update * Fix lint * Fix lint * Update date interp.py * Update test * Fix BaseFormOperatorCoordinateDerivative * Fix lint * Fix lint * Remove deprecated ExternalOperator manipulations in compute_form_data * Expunge external operator extraction mechanism from BaseForm objects * Fix lint * Rewrite external operator replace mechanism (untested) * Add ufl_element to ExternalOperator for split * Update arguments analysis for FormSum * Fix lint * Extend Action distribution over ufl.Sum * Add test for Action distributivity wrt FormSum and Sum * Enable Matrix on the rhs of Action * Add ZeroBaseForm * Add tests for ZeroBaseForm * Update author info * Enable Matrix on the rhs of Action * Add ZeroBaseForm * Add tests for ZeroBaseForm * Update author info * Fix Cofunction's argument * Update expand_derivatives * Update action * Clean way of getting action arguments and check function spaces * Rename _get_action_arguments * Fix ZeroBaseForm simplification for BaseForm * Handle Zero case for Action * Fix ZeroBaseForm simplification for BaseForm * Handle Zero case for Action * Fix typo * Provide support for caching derivative expansion for action/adjoint * Swap ZeroBaseForm's arguments for adjoint * Check arguments when summing a ZeroBaseForm * Fix argument contraction with CoefficientDerivative * Clean up * Clean up * Update Interp reconstruct * Fix replace_derivative_nodes for ListTensor arguments * Handle empty forms for Adjoint * Fix Action __str__ * Fix __str__ for Action/Adjoint * Set argument number locally when constructing the derivative of a BaseFormOperator * Fix lint * Add/Fix comments * Fix typo * Update warnings * Backup * Clean up docstrings + fix flake8 * Update Interp with revisions * Revert "Allow multiple subdomain data (#120)" This reverts commit 677358ab5e9b84f32ff586e6ddb87f74f29a9c76. * Add BaseFormOperatorDerivativeRuleset * Fix lint * Revert "Revert "Allow multiple subdomain data (#120)"" This reverts commit 073579e6a0ec68445776e6d9606827d760e66bd6. * Fix typo * Remove spurious files * Add BaseFormOperatorDerivativeRecorder for BaseFormOperator differentiation * Remove strong typing check for Interp.__eq__ * fix interp equality * remove dead code * Equip Cofunctions with an Argument in the primal space * Equip FormSum objects with a ufl_domain * Fix arguments collection for BaseFormDerivative * Equip BaeForm with coefficients * Equip BaseForm with a ufl_domain * Add BaseFormCoordinateDerivative * Update AUTHORS * Fix weight analysis for FormSum composition * Handle ufl.Zero in rhs * Revert "Merging" This reverts commit 45bcf27a4b88f3c50a8b1ea194257a4c736f3154, reversing changes made to 8c3f11184a10c391e3ea12abd5dc2f1d9e20c05b. * Remove support for adjoint derivative * Remove support for Action derivative when left is a 2-form * Extend form arguments analysis to coefficients for BaseFormDerivative * Update rules for Action arguments * Coarguments have one argument in the primal space and one in the dual space * Add preprocess_form * Fix FormSum reconstruction in map_integrands * DerivativeRuleDispatcher: Add handler for base form coordinate derivatives * Simplify Action/Adjoint of Coarguments * Remove checks on ZeroBaseForms' arguments * Update Action differentiation test * Return primal space argument for Coargument's adjoint * Fix tests * Fix flake8 * Fix flake8 * Fix flake8 * Fix flake8 * Fix coargument extraction within base form operators * Add argument_slots to BaseFormOperatorDerivative * Extend Action simplification cases to ufl.Argument * Remove spurious code * Remove deprecated code * Remove deprecated __eq__ (previously remplaced by equals) * Remove spurious space * Fix flake8 * Lift some code from external operator branch * Fix test * Fix equals for Action/Adjoint * remove spurioius names * Update ufl/argument.py * Update ufl/coefficient.py * Fix some bugs and address PR comments * Fix spurious firedrake UFL code * Fix flake8 * Fix test * Update ufl/algorithms/replace_derivative_nodes.py * Address PR comments * Lift test change from Intepr branch * Fix dForm/dBaseFormOperator * Fix comment * Fix and clean few things * Fix analysis * Fix analysis * Fix count * Update ufl/core/external_operator.py * Address PR comments * Update ufl/differentiation.py * Update ufl/algorithms/replace_derivative_nodes.py * Update ufl/algorithms/replace_derivative_nodes.py * Update replace_derivative_nodes and BaseFormOperator.__eq___ * Rename Interp -> Interpolate and add interpolate helper function * Fix flake8 * Add __eq__ to ExternalOperator * Fix BaseFormOperator differentiation * Remove out-of-date doc (lifted to another branch) * Add check on Action's left * Lift new differentiation process from external operator branch * Fix comment --------- Co-authored-by: David A. Ham Co-authored-by: Rob Kirby Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward --- test/test_external_operator.py | 446 ++++++++++++++++++++++++++++ ufl/__init__.py | 7 +- ufl/algorithms/apply_derivatives.py | 25 ++ ufl/algorithms/replace.py | 19 +- ufl/core/external_operator.py | 94 ++++++ 5 files changed, 588 insertions(+), 3 deletions(-) create mode 100644 test/test_external_operator.py create mode 100644 ufl/core/external_operator.py diff --git a/test/test_external_operator.py b/test/test_external_operator.py new file mode 100644 index 000000000..d586c04a6 --- /dev/null +++ b/test/test_external_operator.py @@ -0,0 +1,446 @@ +#!/usr/bin/env py.test +# -*- coding: utf-8 -*- + +__authors__ = "Nacime Bouziani" +__date__ = "2019-03-26" + + +""" +Test ExternalOperator object +""" + +import pytest + +# This imports everything external code will see from ufl +from ufl import * +from ufl.core.external_operator import ExternalOperator +from ufl.form import BaseForm +from ufl.algorithms.apply_derivatives import apply_derivatives +from ufl.algorithms import expand_derivatives +from ufl.domain import default_domain + + +@pytest.fixture +def V1(): + domain_2d = default_domain(triangle) + f1 = FiniteElement("CG", triangle, 1) + return FunctionSpace(domain_2d, f1) + + +@pytest.fixture +def V2(): + domain_2d = default_domain(triangle) + f1 = FiniteElement("CG", triangle, 2) + return FunctionSpace(domain_2d, f1) + + +@pytest.fixture +def V3(): + domain_2d = default_domain(triangle) + f1 = FiniteElement("CG", triangle, 3) + return FunctionSpace(domain_2d, f1) + + +def test_properties(V1): + u = Coefficient(V1, count=0) + r = Coefficient(V1, count=1) + + e = ExternalOperator(u, r, function_space=V1) + + assert e.ufl_function_space() == V1 + assert e.ufl_operands[0] == u + assert e.ufl_operands[1] == r + assert e.derivatives == (0, 0) + assert e.ufl_shape == () + + e2 = ExternalOperator(u, r, function_space=V1, derivatives=(3, 4)) + assert e2.derivatives == (3, 4) + assert e2.ufl_shape == () + + # Test __str__ + s = Coefficient(V1, count=2) + t = Coefficient(V1, count=3) + v0 = Argument(V1, 0) + v1 = Argument(V1, 1) + + e = ExternalOperator(u, function_space=V1) + assert str(e) == 'e(w_0; v_0)' + + e = ExternalOperator(u, function_space=V1, derivatives=(1,)) + assert str(e) == '∂e(w_0; v_0)/∂o1' + + e = ExternalOperator(u, r, 2 * s, t, function_space=V1, derivatives=(1, 0, 1, 2), argument_slots=(v0, v1)) + assert str(e) == '∂e(w_0, w_1, 2 * w_2, w_3; v_1, v_0)/∂o1∂o3∂o4∂o4' + + +def test_form(V1, V2): + u = Coefficient(V1) + m = Coefficient(V1) + u_hat = TrialFunction(V1) + v = TestFunction(V1) + + # F = N * v * dx + N = ExternalOperator(u, m, function_space=V2) + F = N * v * dx + actual = derivative(F, u, u_hat) + + vstar, = N.arguments() + Nhat = TrialFunction(N.ufl_function_space()) + + dNdu = N._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=(vstar, u_hat)) + dFdN = Nhat * v * dx + expected = Action(dFdN, dNdu) + + assert apply_derivatives(actual) == expected + + # F = N * u * v * dx + N = ExternalOperator(u, m, function_space=V1) + F = N * u * v * dx + actual = derivative(F, u, u_hat) + + vstar, = N.arguments() + Nhat = TrialFunction(N.ufl_function_space()) + + dNdu = N._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=(vstar, u_hat)) + dFdu_partial = N * u_hat * v * dx + dFdN = Nhat * u * v * dx + expected = dFdu_partial + Action(dFdN, dNdu) + assert apply_derivatives(actual) == expected + + +def test_differentiation_procedure_action(V1, V2): + s = Coefficient(V1) + u = Coefficient(V2) + m = Coefficient(V2) + + # External operators + N1 = ExternalOperator(u, m, function_space=V1) + N2 = ExternalOperator(cos(s), function_space=V1) + + # Check arguments and argument slots + assert len(N1.arguments()) == 1 + assert len(N2.arguments()) == 1 + assert N1.arguments() == N1.argument_slots() + assert N2.arguments() == N2.argument_slots() + + # Check coefficients + assert N1.coefficients() == (u, m) + assert N2.coefficients() == (s,) + + # Get v* + vstar_N1, = N1.arguments() + vstar_N2, = N2.arguments() + assert vstar_N1.ufl_function_space().dual() == V1 + assert vstar_N2.ufl_function_space().dual() == V1 + + u_hat = Argument(V1, 1) + s_hat = Argument(V2, 1) + w = Coefficient(V1) + r = Coefficient(V2) + + # Bilinear forms + a1 = inner(N1, m) * dx + Ja1 = derivative(a1, u, u_hat) + Ja1 = expand_derivatives(Ja1) + + a2 = inner(N2, m) * dx + Ja2 = derivative(a2, s, s_hat) + Ja2 = expand_derivatives(Ja2) + + # Get external operators + assert isinstance(Ja1, Action) + dN1du = Ja1.right() + dN1du_action = Action(dN1du, w) + + assert isinstance(Ja2, Action) + dN2du = Ja2.right() + dN2du_action = Action(dN2du, r) + + # Check shape + assert dN1du.ufl_shape == () + assert dN2du.ufl_shape == () + + # Get v*s + vstar_dN1du, _ = dN1du.arguments() + vstar_dN2du, _ = dN2du.arguments() + assert vstar_dN1du.ufl_function_space().dual() == V1 # shape: (2,) + assert vstar_dN2du.ufl_function_space().dual() == V1 # shape: (2,) + + # Check derivatives + assert dN1du.derivatives == (1, 0) + assert dN2du.derivatives == (1,) + + # Check arguments + assert dN1du.arguments() == (vstar_dN1du, u_hat) + assert dN1du_action.arguments() == (vstar_dN1du,) + + assert dN2du.arguments() == (vstar_dN2du, s_hat) + assert dN2du_action.arguments() == (vstar_dN2du,) + + # Check argument slots + assert dN1du.argument_slots() == (vstar_dN1du, u_hat) + assert dN2du.argument_slots() == (vstar_dN2du, - sin(s) * s_hat) + + +def test_extractions(V1): + from ufl.algorithms.analysis import (extract_coefficients, extract_arguments, + extract_arguments_and_coefficients, extract_base_form_operators, + extract_constants) + + u = Coefficient(V1) + c = Constant(triangle) + + e = ExternalOperator(u, c, function_space=V1) + vstar_e, = e.arguments() + + assert extract_coefficients(e) == [u] + assert extract_arguments(e) == [vstar_e] + assert extract_arguments_and_coefficients(e) == ([vstar_e], [u]) + assert extract_constants(e) == [c] + assert extract_base_form_operators(e) == [e] + + F = e * dx + + assert extract_coefficients(F) == [u] + assert extract_arguments(e) == [vstar_e] + assert extract_arguments_and_coefficients(e) == ([vstar_e], [u]) + assert extract_constants(F) == [c] + assert F.base_form_operators() == (e,) + + u_hat = Argument(V1, 1) + e = ExternalOperator(u, function_space=V1, derivatives=(1,), argument_slots=(vstar_e, u_hat)) + + assert extract_coefficients(e) == [u] + assert extract_arguments(e) == [vstar_e, u_hat] + assert extract_arguments_and_coefficients(e) == ([vstar_e, u_hat], [u]) + assert extract_base_form_operators(e) == [e] + + F = e * dx + + assert extract_coefficients(F) == [u] + assert extract_arguments(e) == [vstar_e, u_hat] + assert extract_arguments_and_coefficients(e) == ([vstar_e, u_hat], [u]) + assert F.base_form_operators() == (e,) + + w = Coefficient(V1) + e2 = ExternalOperator(w, e, function_space=V1) + vstar_e2, = e2.arguments() + + assert extract_coefficients(e2) == [u, w] + assert extract_arguments(e2) == [vstar_e2, u_hat] + assert extract_arguments_and_coefficients(e2) == ([vstar_e2, u_hat], [u, w]) + assert extract_base_form_operators(e2) == [e, e2] + + F = e2 * dx + + assert extract_coefficients(e2) == [u, w] + assert extract_arguments(e2) == [vstar_e2, u_hat] + assert extract_arguments_and_coefficients(e2) == ([vstar_e2, u_hat], [u, w]) + assert F.base_form_operators() == (e, e2) + + +def get_external_operators(form_base): + if isinstance(form_base, ExternalOperator): + return (form_base,) + elif isinstance(form_base, BaseForm): + return form_base.base_form_operators() + else: + raise ValueError('Expecting FormBase argument!') + + +def test_adjoint_action_jacobian(V1, V2, V3): + + u = Coefficient(V1) + m = Coefficient(V2) + + # N(u, m; v*) + N = ExternalOperator(u, m, function_space=V3) + vstar_N, = N.arguments() + + # Arguments for the Gateaux-derivative + u_hat = lambda number: Argument(V1, number) # V1: degree 1 # dFdu.arguments()[-1] + m_hat = lambda number: Argument(V2, number) # V2: degree 2 # dFdm.arguments()[-1] + vstar_N = lambda number: Argument(V3.dual(), number) # V3: degree 3 + + # Coefficients for the action + w = Coefficient(V1) # for u + p = Coefficient(V2) # for m + + v2 = TestFunction(V2) + v3 = TestFunction(V3) + form_base_expressions = (N * dx, N * v2 * dx, N * v3 * dx) # , N) + + for F in form_base_expressions: + + # Get test function + v_F = F.arguments() if isinstance(F, Form) else () + # If we have a 0-form with an ExternalOperator: e.g. F = N * dx + # => F.arguments() = (), because of form composition. + # But we still need to make arguments with number 1 (i.e. n_arg = 1) + # since at the external operator level, argument numbering is based on + # the external operator arguments and not on the outer form arguments. + n_arg = len(v_F) if len(v_F) else 1 + assert n_arg < 2 + + # Differentiate + dFdu = expand_derivatives(derivative(F, u, u_hat(n_arg))) + dFdm = expand_derivatives(derivative(F, m, m_hat(n_arg))) + + assert dFdu.arguments() == v_F + (u_hat(n_arg),) + assert dFdm.arguments() == v_F + (m_hat(n_arg),) + + assert isinstance(dFdu, Action) + + # dNdu(u, m; u_hat, v*) + dNdu = dFdu.right() + # dNdm(u, m; m_hat, v*) + dNdm = dFdm.right() + + assert dNdu.derivatives == (1, 0) + assert dNdm.derivatives == (0, 1) + assert dNdu.arguments() == (vstar_N(0), u_hat(n_arg)) + assert dNdm.arguments() == (vstar_N(0), m_hat(n_arg)) + assert dNdu.argument_slots() == dNdu.arguments() + assert dNdm.argument_slots() == dNdm.arguments() + + # Action + action_dFdu = action(dFdu, w) + action_dFdm = action(dFdm, p) + + assert action_dFdu.arguments() == v_F + () + assert action_dFdm.arguments() == v_F + () + + # If we have 2 arguments + if len(v_F): + # Adjoint + dFdu_adj = adjoint(dFdu) + dFdm_adj = adjoint(dFdm) + + assert dFdu_adj.arguments() == (u_hat(n_arg),) + v_F + assert dFdm_adj.arguments() == (m_hat(n_arg),) + v_F + + # Action of the adjoint + q = Coefficient(v_F[0].ufl_function_space()) + action_dFdu_adj = action(dFdu_adj, q) + action_dFdm_adj = action(dFdm_adj, q) + + assert action_dFdu_adj.arguments() == (u_hat(n_arg),) + assert action_dFdm_adj.arguments() == (m_hat(n_arg),) + + +def test_multiple_external_operators(V1, V2): + + u = Coefficient(V1) + m = Coefficient(V1) + w = Coefficient(V2) + + v = TestFunction(V1) + v_hat = TrialFunction(V1) + w_hat = TrialFunction(V2) + + # N1(u, m; v*) + N1 = ExternalOperator(u, m, function_space=V1) + + # N2(w; v*) + N2 = ExternalOperator(w, function_space=V2) + + # N3(u; v*) + N3 = ExternalOperator(u, function_space=V1) + + # N4(N1, u; v*) + N4 = ExternalOperator(N1, u, function_space=V1) + + # N5(N4(N1, u); v*) + N5 = ExternalOperator(N4, u, function_space=V1) + + # --- F = < N1(u, m; v*), v > + + --- # + + F = (inner(N1, v) + inner(N2, v) + inner(N3, v)) * dx + + # dFdu = Action(dFdN1, dN1du) + Action(dFdN3, dN3du) + dFdu = expand_derivatives(derivative(F, u)) + dFdN1 = inner(v_hat, v) * dx + dFdN2 = inner(w_hat, v) * dx + dFdN3 = inner(v_hat, v) * dx + dN1du = N1._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=N1.arguments() + (v_hat,)) + dN3du = N3._ufl_expr_reconstruct_(u, derivatives=(1,), argument_slots=N3.arguments() + (v_hat,)) + + assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) + + # dFdm = Action(dFdN1, dN1dm) + dFdm = expand_derivatives(derivative(F, m)) + dN1dm = N1._ufl_expr_reconstruct_(u, m, derivatives=(0, 1), argument_slots=N1.arguments() + (v_hat,)) + + assert dFdm == Action(dFdN1, dN1dm) + + # dFdw = Action(dFdN2, dN2dw) + dFdw = expand_derivatives(derivative(F, w)) + dN2dw = N2._ufl_expr_reconstruct_(w, derivatives=(1,), argument_slots=N2.arguments() + (w_hat,)) + + assert dFdw == Action(dFdN2, dN2dw) + + # --- F = < N4(N1(u, m), u; v*), v > --- # + + F = inner(N4, v) * dx + + # dFdu = ∂F/∂u + Action(∂F/∂N1, dN1/du) + Action(∂F/∂N4, dN4/du) + # = Action(∂F/∂N4, dN4/du), since ∂F/∂u = 0 and ∂F/∂N1 = 0 + # + # In addition, we have: + # dN4/du = ∂N4/∂u + Action(∂N4/∂N1, dN1/du) + # + # Using the fact that Action is distributive, we have: + # + # dFdu = Action(∂F/∂N4, ∂N4/∂u) + + # Action(∂F/∂N4, Action(∂N4/∂N1, dN1/du)) + dFdu = expand_derivatives(derivative(F, u)) + dFdN4_partial = inner(v_hat, v) * dx + dN4dN1_partial = N4._ufl_expr_reconstruct_(N1, u, derivatives=(1, 0), argument_slots=N4.arguments() + (v_hat,)) + dN4du_partial = N4._ufl_expr_reconstruct_(N1, u, derivatives=(0, 1), argument_slots=N4.arguments() + (v_hat,)) + + assert dFdu == Action(dFdN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dFdN4_partial, dN4du_partial) + + # dFdm = Action(∂F/∂N4, Action(∂N4/∂N1, dN1/dm)) + dFdm = expand_derivatives(derivative(F, m)) + + assert dFdm == Action(dFdN4_partial, Action(dN4dN1_partial, dN1dm)) + + # --- F = < N1(u, m; v*), v > + + + < N4(N1(u, m), u; v*), v > --- # + + F = (inner(N1, v) + inner(N2, v) + inner(N3, v) + inner(N4, v)) * dx + + dFdu = expand_derivatives(derivative(F, u)) + assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) +\ + Action(dFdN4_partial, Action(dN4dN1_partial, dN1du)) +\ + Action(dFdN4_partial, dN4du_partial) + + dFdm = expand_derivatives(derivative(F, m)) + assert dFdm == Action(dFdN1, dN1dm) + Action(dFdN4_partial, Action(dN4dN1_partial, dN1dm)) + + dFdw = expand_derivatives(derivative(F, w)) + assert dFdw == Action(dFdN2, dN2dw) + + # --- F = < N5(N4(N1(u, m), u), u; v*), v > + < N1(u, m; v*), v > + < u * N5(N4(N1(u, m), u), u; v*), v >--- # + + F = (inner(N5, v) + inner(N1, v) + inner(u * N5, v)) * dx + + # dFdu = ∂F/∂u + Action(∂F/∂N1, dN1/du) + Action(∂F/∂N4, dN4/du) + Action(∂F/∂N5, dN5/du) + # + # where: + # - ∂F/∂u = inner(w * N5, v) * dx + # - ∂F/∂N1 = inner(w, v) * dx + # - ∂F/∂N5 = inner(w, v) * dx + inner(u * w, v) * dx + # - ∂F/∂N4 = 0 + # - dN5/du = ∂N5/∂u + Action(∂N5/∂N4, dN4/du) + # = ∂N5/∂u + Action(∂N5/∂N4, ∂N4/∂u) + Action(∂N5/∂N4, Action(∂N4/∂N1, dN1/du)) + # with w = TrialFunction(V1) + w = TrialFunction(V1) + dFdu_partial = inner(w * N5, v) * dx + dFdN1_partial = inner(w, v) * dx + dFdN5_partial = (inner(w, v) + inner(u * w, v)) * dx + dN5dN4_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(1, 0), argument_slots=N4.arguments() + (w,)) + dN5du_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(0, 1), argument_slots=N4.arguments() + (w,)) + dN5du = Action(dN5dN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dN5dN4_partial, dN4du_partial) + dN5du_partial + + dFdu = expand_derivatives(derivative(F, u)) + assert dFdu == dFdu_partial + Action(dFdN1_partial, dN1du) + Action(dFdN5_partial, dN5du) diff --git a/ufl/__init__.py b/ufl/__init__.py index b591fdf4f..1a275f32a 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -330,6 +330,9 @@ Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, jump, avg, cell_avg, facet_avg, elem_mult, elem_div, elem_pow, elem_op) +# External Operator +from ufl.core.external_operator import ExternalOperator + # Measure classes from ufl.measure import Measure, register_integral_type, integral_types, custom_integral_types @@ -376,7 +379,9 @@ 'Argument', 'Coargument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', 'Coefficient', 'Cofunction', 'Coefficients', - 'Matrix', 'Adjoint', 'Action', 'Interpolate', 'interpolate', + 'Matrix', 'Adjoint', 'Action', + 'Interpolate', 'interpolate', + 'ExternalOperator', 'Constant', 'VectorConstant', 'TensorConstant', 'split', 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 2f8f0fe6b..644453b81 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -10,6 +10,7 @@ from collections import defaultdict from math import pi +from ufl.algorithms.analysis import extract_arguments from ufl.algorithms.map_integrands import map_integrand_dags from ufl.algorithms.replace_derivative_nodes import replace_derivative_nodes from ufl.checks import is_cellwise_constant @@ -1113,6 +1114,30 @@ def interpolate(self, i_op, dw): return ZeroBaseForm(i_op.arguments() + self._v) return i_op._ufl_expr_reconstruct_(expr=dw) + @pending_operations_recording + def external_operator(self, N, *dfs): + result = () + for i, df in enumerate(dfs): + derivatives = tuple(dj + int(i == j) for j, dj in enumerate(N.derivatives)) + if len(extract_arguments(df)) != 0: + # Handle the symbolic differentiation of external operators. + # This bit returns: + # + # `\sum_{i} dNdOi(..., Oi, ...; DOi(u)[v], ..., v*)` + # + # where we differentate wrt u, Oi is the i-th operand, N(..., Oi, ...; ..., v*) an ExternalOperator + # and v the direction (Argument). dNdOi(..., Oi, ...; DOi(u)[v]) is an ExternalOperator + # representing the Gateaux-derivative of N. For example: + # -> From N(u) = u**2, we get `dNdu(u; uhat, v*) = 2 * u * uhat`. + new_args = N.argument_slots() + (df,) + extop = N._ufl_expr_reconstruct_(*N.ufl_operands, derivatives=derivatives, argument_slots=new_args) + elif df == 0: + extop = Zero(N.ufl_shape) + else: + raise NotImplementedError('Frechet derivative of external operators need to be provided!') + result += (extop,) + return sum(result) + class DerivativeRuleDispatcher(MultiFunction): def __init__(self): diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index e794c1a38..0e73a8dff 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -9,7 +9,7 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.classes import CoefficientDerivative, Interpolate +from ufl.classes import CoefficientDerivative, Interpolate, ExternalOperator, Form from ufl.constantvalue import as_ufl from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags @@ -20,7 +20,14 @@ class Replacer(MultiFunction): def __init__(self, mapping): super().__init__() self.mapping = mapping - if not all(k.ufl_shape == v.ufl_shape for k, v in mapping.items()): + + # One can replace Coarguments by 1-Forms + def get_shape(x): + if isinstance(x, Form): + return x.arguments()[0].ufl_shape + return x.ufl_shape + + if not all(get_shape(k) == get_shape(v) for k, v in mapping.items()): raise ValueError("Replacement expressions must have the same shape as what they replace.") def ufl_type(self, o, *args): @@ -29,6 +36,14 @@ def ufl_type(self, o, *args): except KeyError: return self.reuse_if_untouched(o, *args) + def external_operator(self, o): + o = self.mapping.get(o) or o + if isinstance(o, ExternalOperator): + new_ops = tuple(replace(op, self.mapping) for op in o.ufl_operands) + new_args = tuple(replace(arg, self.mapping) for arg in o.argument_slots()) + return o._ufl_expr_reconstruct_(*new_ops, argument_slots=new_args) + return o + def interpolate(self, o): o = self.mapping.get(o) or o if isinstance(o, Interpolate): diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py new file mode 100644 index 000000000..0ffb5c906 --- /dev/null +++ b/ufl/core/external_operator.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +"""This module defines the ``ExternalOperator`` class, which symbolically represents operators that are not + straightforwardly expressible in UFL. Subclasses of ``ExternalOperator`` must define + how this operator should be evaluated as well as its derivatives from a given set of operands. +""" + +# Copyright (C) 2019 Nacime Bouziani +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Nacime Bouziani, 2023 + +from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.ufl_type import ufl_type + + +@ufl_type(num_ops="varying", is_differential=True) +class ExternalOperator(BaseFormOperator): + + # Slots are disabled here because they cause trouble in PyDOLFIN + # multiple inheritance pattern: + _ufl_noslots_ = True + + def __init__(self, *operands, function_space, derivatives=None, argument_slots=()): + r""" + :param operands: operands on which acts the :class:`ExternalOperator`. + :param function_space: the :class:`.FunctionSpace`, + or :class:`.MixedFunctionSpace` on which to build this :class:`Function`. + :param derivatives: tuple specifiying the derivative multiindex. + :param argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + """ + + # -- Derivatives -- # + if derivatives is not None: + if not isinstance(derivatives, tuple): + raise TypeError("Expecting a tuple for derivatives and not %s" % derivatives) + if not len(derivatives) == len(operands): + raise ValueError("Expecting a size of %s for %s" % (len(operands), derivatives)) + if not all(isinstance(d, int) for d in derivatives) or any(d < 0 for d in derivatives): + raise ValueError("Expecting a derivative multi-index with nonnegative indices and not %s" + % str(derivatives)) + else: + derivatives = (0,) * len(operands) + + BaseFormOperator.__init__(self, *operands, + function_space=function_space, + derivatives=derivatives, + argument_slots=argument_slots) + + def ufl_element(self): + "Shortcut to get the finite element of the function space of the external operator" + # Useful when applying split on an ExternalOperator + return self.arguments()[0].ufl_element() + + def grad(self): + """Returns the symbolic grad of the external operator""" + # By default, differential rules produce `grad(assembled_o)` `where assembled_o` + # is the `Coefficient` resulting from assembling the external operator since + # the external operator may not be smooth enough for chain rule to hold. + # Symbolic gradient (`grad(ExternalOperator)`) depends on the operator considered + # and its implementation may be needed in some cases (e.g. convolution operator). + raise NotImplementedError('Symbolic gradient not defined for the external operator considered!') + + def assemble(self, *args, **kwargs): + """Assemble the external operator""" + raise NotImplementedError("Symbolic evaluation of %s not available." % self._ufl_class_.__name__) + + def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, + argument_slots=None, add_kwargs={}): + "Return a new object of the same type with new operands." + return type(self)(*operands, function_space=function_space or self.ufl_function_space(), + derivatives=derivatives or self.derivatives, + argument_slots=argument_slots or self.argument_slots(), + **add_kwargs) + + def __str__(self): + "Default str string for ExternalOperator operators." + d = '\N{PARTIAL DIFFERENTIAL}' + derivatives = self.derivatives + d_ops = "".join(d + "o" + str(i + 1) for i, di in enumerate(derivatives) for j in range(di)) + e = "e(%s; %s)" % (", ".join(str(op) for op in self.ufl_operands), + ", ".join(str(arg) for arg in reversed(self.argument_slots()))) + return d + e + "/" + d_ops if sum(derivatives) > 0 else e + + def __eq__(self, other): + if self is other: + return True + return (type(self) is type(other) and + all(a == b for a, b in zip(self.ufl_operands, other.ufl_operands)) and + all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) and + self.derivatives == other.derivatives and + self.ufl_function_space() == other.ufl_function_space()) From adcc9abbb59c418c2ab6273117f5a2b57e3dba46 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 19 Sep 2023 08:57:01 +0100 Subject: [PATCH 055/136] Format docstrings (#208) * ufl2unicode * tidy docstrings in algorithms * use fstrings instead of % * Move pydocstyle to Google convention (#207) * docstrings in core/ * newlines after "Returns:" * docstrings in corealg and utils * Make pydocstyle tests pass in finiteelement/ * docstrings in files starting a-c * files starting d to f * __init__ * files starting g-m * files starting o-r * make pydocstyle pass on remaining files * only run pydocstyle checks on ufl/ --- .github/workflows/pythonapp.yml | 2 +- setup.cfg | 9 +- ufl/__init__.py | 4 +- ufl/action.py | 18 +- ufl/adjoint.py | 11 +- ufl/algebra.py | 56 ++- ufl/algorithms/__init__.py | 7 +- ufl/algorithms/ad.py | 1 - ufl/algorithms/analysis.py | 81 ++-- ufl/algorithms/apply_algebra_lowering.py | 29 +- ufl/algorithms/apply_derivatives.py | 236 +++++++++-- ufl/algorithms/apply_function_pullbacks.py | 33 +- ufl/algorithms/apply_geometry_lowering.py | 40 +- ufl/algorithms/apply_integral_scaling.py | 5 +- ufl/algorithms/apply_restrictions.py | 53 ++- ufl/algorithms/balancing.py | 8 + ufl/algorithms/change_to_reference.py | 13 +- ufl/algorithms/check_arities.py | 26 +- ufl/algorithms/check_restrictions.py | 12 +- ufl/algorithms/checks.py | 6 +- ufl/algorithms/comparison_checker.py | 27 +- ufl/algorithms/compute_form_data.py | 64 +-- .../coordinate_derivative_helpers.py | 32 +- ufl/algorithms/domain_analysis.py | 82 ++-- ufl/algorithms/estimate_degrees.py | 154 +++++-- ufl/algorithms/expand_compounds.py | 9 +- ufl/algorithms/expand_indices.py | 27 +- ufl/algorithms/formdata.py | 18 +- ufl/algorithms/formfiles.py | 17 +- ufl/algorithms/formsplitter.py | 8 +- ufl/algorithms/formtransformations.py | 65 ++- ufl/algorithms/map_integrands.py | 6 +- ufl/algorithms/multifunction.py | 5 - ufl/algorithms/remove_complex_nodes.py | 15 +- ufl/algorithms/renumbering.py | 18 +- ufl/algorithms/replace.py | 19 +- ufl/algorithms/replace_derivative_nodes.py | 22 +- ufl/algorithms/signature.py | 8 +- ufl/algorithms/strip_terminal_data.py | 51 ++- ufl/algorithms/transformer.py | 54 ++- ufl/algorithms/traversal.py | 5 +- ufl/argument.py | 62 ++- ufl/averaging.py | 20 +- ufl/cell.py | 21 + ufl/checks.py | 18 +- ufl/classes.py | 11 +- ufl/coefficient.py | 41 +- ufl/compound_expressions.py | 29 +- ufl/conditional.py | 74 +++- ufl/constant.py | 18 +- ufl/constantvalue.py | 115 ++++-- ufl/core/__init__.py | 1 + ufl/core/base_form_operator.py | 65 +-- ufl/core/compute_expr_hash.py | 1 - ufl/core/expr.py | 68 +-- ufl/core/external_operator.py | 53 +-- ufl/core/interpolate.py | 38 +- ufl/core/multiindex.py | 84 ++-- ufl/core/operator.py | 18 +- ufl/core/terminal.py | 30 +- ufl/core/ufl_id.py | 7 +- ufl/core/ufl_type.py | 66 ++- ufl/corealg/__init__.py | 1 + ufl/corealg/map_dag.py | 55 +-- ufl/corealg/multifunction.py | 11 +- ufl/corealg/traversal.py | 12 +- ufl/differentiation.py | 96 ++++- ufl/domain.py | 59 ++- ufl/duals.py | 18 +- ufl/equation.py | 17 +- ufl/exprcontainers.py | 28 +- ufl/exprequals.py | 9 +- ufl/exproperators.py | 51 ++- ufl/finiteelement/__init__.py | 3 +- ufl/finiteelement/brokenelement.py | 6 + ufl/finiteelement/elementlist.py | 29 +- ufl/finiteelement/enrichedelement.py | 50 +-- ufl/finiteelement/finiteelement.py | 40 +- ufl/finiteelement/finiteelementbase.py | 77 ++-- ufl/finiteelement/hdivcurl.py | 35 +- ufl/finiteelement/mixedelement.py | 130 +++--- ufl/finiteelement/restrictedelement.py | 42 +- ufl/finiteelement/tensorproductelement.py | 21 +- ufl/form.py | 161 ++++---- ufl/formatting/__init__.py | 1 + ufl/formatting/ufl2unicode.py | 389 +++++++++++------- ufl/formoperators.py | 50 +-- ufl/functionspace.py | 58 ++- ufl/geometry.py | 286 +++++++------ ufl/index_combination_utils.py | 17 +- ufl/indexed.py | 8 +- ufl/indexsum.py | 14 +- ufl/integral.py | 51 +-- ufl/mathfunctions.py | 86 +++- ufl/matrix.py | 13 +- ufl/measure.py | 94 ++--- ufl/objects.py | 4 +- ufl/operators.py | 220 +++++----- ufl/permutation.py | 13 +- ufl/precedence.py | 8 +- ufl/protocols.py | 2 +- ufl/referencevalue.py | 12 +- ufl/restriction.py | 12 +- ufl/sobolevspace.py | 58 +-- ufl/sorting.py | 19 +- ufl/split_functions.py | 10 +- ufl/tensoralgebra.py | 75 +++- ufl/tensors.py | 58 ++- ufl/utils/__init__.py | 1 + ufl/utils/counted.py | 13 +- ufl/utils/formatting.py | 5 +- ufl/utils/indexflattening.py | 1 - ufl/utils/sequences.py | 1 - ufl/utils/sorting.py | 15 +- ufl/utils/stacks.py | 7 +- ufl/variable.py | 26 +- 116 files changed, 3008 insertions(+), 1766 deletions(-) delete mode 100644 ufl/algorithms/multifunction.py diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 695aad6c7..1d916b7b5 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -35,7 +35,7 @@ jobs: - name: Check documentation style run: | python -m pip install pydocstyle - python -m pydocstyle . + python -m pydocstyle ufl/ - name: Install UFL run: python -m pip install .[ci] - name: Run unit tests diff --git a/setup.cfg b/setup.cfg index 4a58031e9..55704a7bb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,11 +62,4 @@ max-line-length = 120 exclude = doc/sphinx/source/conf.py,test [pydocstyle] -# Work on removing these ignores -ignore = D100,D101,D102,D103,D104,D105,D107, - D200,D202, - # the skipping of D203 should be removed - D203, - D204,D205,D208,D209,D210,D212,D213, - D300,D301, - D400,D401,D402,D404,D415,D416 +convention = google diff --git a/ufl/__init__.py b/ufl/__init__.py index 1a275f32a..40821e8b7 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -1,4 +1,6 @@ -"""The Unified Form Language is an embedded domain specific language +"""UFL: The Unified Form Language. + +The Unified Form Language is an embedded domain specific language for definition of variational forms intended for finite element discretization. More precisely, it defines a fixed interface for choosing finite element spaces and defining expressions for weak forms in a diff --git a/ufl/action.py b/ufl/action.py index 7a18c8962..6051b4b7c 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """This module defines the Action class.""" - # Copyright (C) 2021 India Marsden # # This file is part of UFL (https://www.fenicsproject.org) @@ -26,6 +24,7 @@ @ufl_type() class Action(BaseForm): """UFL base form type: respresents the action of an object on another. + For example: res = Ax A would be the first argument, left and x would be the second argument, @@ -47,6 +46,7 @@ class Action(BaseForm): "_hash") def __new__(cls, *args, **kw): + """Create a new Action.""" left, right = args # Check trivial case @@ -79,6 +79,7 @@ def __new__(cls, *args, **kw): return super(Action, cls).__new__(cls) def __init__(self, left, right): + """Initialise.""" BaseForm.__init__(self) self._left = left @@ -93,7 +94,7 @@ def __init__(self, left, right): self._hash = None def ufl_function_spaces(self): - "Get the tuple of function spaces of the underlying form" + """Get the tuple of function spaces of the underlying form.""" if isinstance(self._right, Form): return self._left.ufl_function_spaces()[:-1] \ + self._right.ufl_function_spaces()[1:] @@ -101,9 +102,11 @@ def ufl_function_spaces(self): return self._left.ufl_function_spaces()[:-1] def left(self): + """Get left.""" return self._left def right(self): + """Get right.""" return self._right def _analyze_form_arguments(self): @@ -121,6 +124,7 @@ def _analyze_domains(self): self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) def equals(self, other): + """Check if two Actions are equal.""" if type(other) is not Action: return False if self is other: @@ -130,13 +134,15 @@ def equals(self, other): return bool(self._left == other._left) and bool(self._right == other._right) def __str__(self): + """Format as a string.""" return f"Action({self._left}, {self._right})" def __repr__(self): + """Representation.""" return self._repr def __hash__(self): - "Hash code for use in dicts " + """Hash.""" if self._hash is None: self._hash = hash(("Action", hash(self._right), hash(self._left))) return self._hash @@ -144,7 +150,6 @@ def __hash__(self): def _check_function_spaces(left, right): """Check if the function spaces of left and right match.""" - if isinstance(right, CoefficientDerivative): # Action differentiation pushes differentiation through # right as a consequence of Leibniz formula. @@ -170,8 +175,7 @@ def _check_function_spaces(left, right): def _get_action_form_arguments(left, right): - """Perform argument contraction to work out the arguments of Action""" - + """Perform argument contraction to work out the arguments of Action.""" coefficients = () # `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`. left_args = left.arguments()[:-1] if not isinstance(left, Coefficient) else () diff --git a/ufl/adjoint.py b/ufl/adjoint.py index cf76a75e9..e0f86ec22 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module defines the Adjoint class.""" # Copyright (C) 2021 India Marsden @@ -35,6 +34,7 @@ class Adjoint(BaseForm): "_hash") def __new__(cls, *args, **kw): + """Create a new Adjoint.""" form = args[0] # Check trivial case: This is not a ufl.Zero but a ZeroBaseForm! if form == 0: @@ -59,6 +59,7 @@ def __new__(cls, *args, **kw): return super(Adjoint, cls).__new__(cls) def __init__(self, form): + """Initialise.""" BaseForm.__init__(self) if len(form.arguments()) != 2: @@ -71,10 +72,11 @@ def __init__(self, form): self._repr = "Adjoint(%s)" % repr(self._form) def ufl_function_spaces(self): - "Get the tuple of function spaces of the underlying form" + """Get the tuple of function spaces of the underlying form.""" return self._form.ufl_function_spaces() def form(self): + """Return the form.""" return self._form def _analyze_form_arguments(self): @@ -89,6 +91,7 @@ def _analyze_domains(self): self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) def equals(self, other): + """Check if two Adjoints are equal.""" if type(other) is not Adjoint: return False if self is other: @@ -98,13 +101,15 @@ def equals(self, other): return bool(self._form == other._form) def __str__(self): + """Format as a string.""" return f"Adjoint({self._form})" def __repr__(self): + """Representation.""" return self._repr def __hash__(self): - """Hash code for use in dicts.""" + """Hash.""" if self._hash is None: self._hash = hash(("Adjoint", hash(self._form))) return self._hash diff --git a/ufl/algebra.py b/ufl/algebra.py index 20624f5e7..1bd1493be 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Basic algebra operations." - +"""Basic algebra operations.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -25,9 +23,12 @@ inherit_shape_from_operand=0, inherit_indices_from_operand=0, binop="__add__", rbinop="__radd__") class Sum(Operator): + """Sum.""" + __slots__ = () def __new__(cls, a, b): + """Create a new Sum.""" # Make sure everything is an Expr a = as_ufl(a) b = as_ufl(b) @@ -77,16 +78,20 @@ def __new__(cls, a, b): return self def _init(self, a, b): + """Initialise.""" self.ufl_operands = (a, b) def __init__(self, a, b): + """Initialise.""" Operator.__init__(self) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" return sum(o.evaluate(x, mapping, component, index_values) for o in self.ufl_operands) def __str__(self): + """Format as a string.""" return " + ".join([parstr(o, self) for o in self.ufl_operands]) @@ -94,9 +99,11 @@ def __str__(self): binop="__mul__", rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" + __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): + """Create a new product.""" # Conversion a = as_ufl(a) b = as_ufl(b) @@ -143,7 +150,7 @@ def __new__(cls, a, b): return self def _init(self, a, b): - "Constructor, called by __new__ with already checked arguments." + """Constructor, called by __new__ with already checked arguments.""" self.ufl_operands = (a, b) # Extract indices @@ -155,11 +162,13 @@ def _init(self, a, b): self.ufl_index_dimensions = fid def __init__(self, a, b): + """Initialise.""" Operator.__init__(self) ufl_shape = () def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" ops = self.ufl_operands sh = self.ufl_shape if sh: @@ -174,6 +183,7 @@ def evaluate(self, x, mapping, component, index_values): return tmp def __str__(self): + """Format as a string.""" a, b = self.ufl_operands return " * ".join((parstr(a, self), parstr(b, self))) @@ -182,9 +192,12 @@ def __str__(self): inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") class Division(Operator): + """Division.""" + __slots__ = () def __new__(cls, a, b): + """Create a new Division.""" # Conversion a = as_ufl(a) b = as_ufl(b) @@ -221,14 +234,17 @@ def __new__(cls, a, b): return self def _init(self, a, b): + """Initialise.""" self.ufl_operands = (a, b) def __init__(self, a, b): + """Initialise.""" Operator.__init__(self) ufl_shape = () # self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) @@ -240,6 +256,7 @@ def evaluate(self, x, mapping, component, index_values): return e def __str__(self): + """Format as a string.""" return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" @@ -247,9 +264,12 @@ def __str__(self): inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") class Power(Operator): + """Power.""" + __slots__ = () def __new__(cls, a, b): + """Create new Power.""" # Conversion a = as_ufl(a) b = as_ufl(b) @@ -282,20 +302,24 @@ def __new__(cls, a, b): return self def _init(self, a, b): + """Initialise.""" self.ufl_operands = (a, b) def __init__(self, a, b): + """Initialise.""" Operator.__init__(self) ufl_shape = () def evaluate(self, x, mapping, component, index_values): + """Evalute.""" a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) return a**b def __str__(self): + """Format as a string.""" a, b = self.ufl_operands return f"{parstr(a, self)} ** {parstr(b, self)}" @@ -304,9 +328,12 @@ def __str__(self): inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") class Abs(Operator): + """Absolute value.""" + __slots__ = () def __new__(cls, a): + """Create a new Abs.""" a = as_ufl(a) # Simplification @@ -320,13 +347,16 @@ def __new__(cls, a): return Operator.__new__(cls) def __init__(self, a): + """Initialise.""" Operator.__init__(self, (a,)) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return abs(a) def __str__(self): + """Format as a string.""" a, = self.ufl_operands return f"|{parstr(a, self)}|" @@ -334,9 +364,12 @@ def __str__(self): @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Conj(Operator): + """Complex conjugate.""" + __slots__ = () def __new__(cls, a): + """Creatr a new Conj.""" a = as_ufl(a) # Simplification @@ -350,13 +383,16 @@ def __new__(cls, a): return Operator.__new__(cls) def __init__(self, a): + """Initialise.""" Operator.__init__(self, (a,)) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return a.conjugate() def __str__(self): + """Format as a string.""" a, = self.ufl_operands return f"conj({parstr(a, self)})" @@ -364,9 +400,12 @@ def __str__(self): @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Real(Operator): + """Real part.""" + __slots__ = () def __new__(cls, a): + """Create a new Real.""" a = as_ufl(a) # Simplification @@ -382,13 +421,16 @@ def __new__(cls, a): return Operator.__new__(cls) def __init__(self, a): + """Initialise.""" Operator.__init__(self, (a,)) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return a.real def __str__(self): + """Format as a string.""" a, = self.ufl_operands return f"Re[{parstr(a, self)}]" @@ -396,9 +438,12 @@ def __str__(self): @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Imag(Operator): + """Imaginary part.""" + __slots__ = () def __new__(cls, a): + """Create a new Imag.""" a = as_ufl(a) # Simplification @@ -412,12 +457,15 @@ def __new__(cls, a): return Operator.__new__(cls) def __init__(self, a): + """Initialise.""" Operator.__init__(self, (a,)) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return a.imag def __str__(self): + """Format as a string.""" a, = self.ufl_operands return f"Im[{parstr(a, self)}]" diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 86d55c4bc..690b237f8 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -# flake8: noqa -"This module collects algorithms and utility functions operating on UFL objects." +"""This module collects algorithms and utility functions operating on UFL objects.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -52,7 +50,10 @@ "compute_form_rhs", "compute_form_functional", "compute_form_signature", + "compute_form_arities", "tree_format", + "read_ufl_file", + "load_forms", ] # Utilities for traversing over expression trees in different ways diff --git a/ufl/algorithms/ad.py b/ufl/algorithms/ad.py index 20e102f39..cc1e04224 100644 --- a/ufl/algorithms/ad.py +++ b/ufl/algorithms/ad.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Front-end for AD routines.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index 693022860..a758351ce 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Utility algorithms for inspection of and information extraction from UFL objects in various ways.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -28,11 +27,12 @@ # inlined stack based traversal algorithms def _sorted_by_number_and_part(seq): + """Sort items by number and part.""" return sorted(seq, key=lambda x: (x.number(), x.part())) def unique_tuple(objects): - "Return tuple of unique objects, preserving initial ordering." + """Return tuple of unique objects, preserving initial ordering.""" unique_objects = [] handled = set() for obj in objects: @@ -46,8 +46,14 @@ def unique_tuple(objects): def extract_type(a, ufl_types): """Build a set of all objects found in a whose class is in ufl_types. - The argument a can be a BaseForm, Integral or Expr.""" + Args: + a: A BaseForm, Integral or Expr + ufl_types: A list of UFL types + + Returns: + All objects found in a whose class is in ufl_type + """ if not isinstance(ufl_types, (list, tuple)): ufl_types = (ufl_types,) @@ -110,8 +116,15 @@ def extract_type(a, ufl_types): def has_type(a, ufl_type): - """Return if an object of class ufl_type can be found in a. - The argument a can be a BaseForm, Integral or Expr.""" + """Return if an object of class ufl_type or a subclass can be found in a. + + Args: + a: A BaseForm, Integral or Expr + ufl_type: A UFL type + + Returns: + Whether an object of class ufl_type can be found in a + """ if issubclass(ufl_type, Terminal): # Optimization traversal = traverse_unique_terminals @@ -122,7 +135,14 @@ def has_type(a, ufl_type): def has_exact_type(a, ufl_type): """Return if an object of class ufl_type can be found in a. - The argument a can be a BaseForm, Integral or Expr.""" + + Args: + a: A BaseForm, Integral or Expr + ufl_type: A UFL type + + Returns: + Whether an object of class ufl_type can be found in a + """ tc = ufl_type._ufl_typecode_ if issubclass(ufl_type, Terminal): # Optimization @@ -133,35 +153,50 @@ def has_exact_type(a, ufl_type): def extract_arguments(a): - """Build a sorted list of all arguments in a, - which can be a BaseForm, Integral or Expr.""" + """Build a sorted list of all arguments in a. + + Args: + a: A BaseForm, Integral or Expr + """ return _sorted_by_number_and_part(extract_type(a, BaseArgument)) def extract_coefficients(a): - """Build a sorted list of all coefficients in a, - which can be a BaseForm, Integral or Expr.""" + """Build a sorted list of all coefficients in a. + + Args: + a: A BaseForm, Integral or Expr + """ return sorted_by_count(extract_type(a, BaseCoefficient)) def extract_constants(a): - """Build a sorted list of all constants in a""" + """Build a sorted list of all constants in a. + + Args: + a: A BaseForm, Integral or Expr + """ return sorted_by_count(extract_type(a, Constant)) def extract_base_form_operators(a): - """Build a sorted list of all base form operators (e.g. Interpolate or ExternalOperator)in a, - which can be a Form, Integral or Expr.""" + """Build a sorted list of all base form operators in a. + + Args: + a: A BaseForm, Integral or Expr + """ return sorted_by_count(extract_type(a, BaseFormOperator)) def extract_arguments_and_coefficients(a): - """Build two sorted lists of all arguments and coefficients - in a, which can be BaseForm, Integral or Expr.""" + """Build two sorted lists of all arguments and coefficients in a. - # This function is faster than extract_arguments + extract_coefficients - # for large forms, and has more validation built in. + This function is faster than extract_arguments + extract_coefficients + for large forms, and has more validation built in. + Args: + a: A BaseForm, Integral or Expr + """ # Extract lists of all BaseArgument and BaseCoefficient instances base_coeff_and_args = extract_type(a, (BaseArgument, BaseCoefficient)) arguments = [f for f in base_coeff_and_args if isinstance(f, BaseArgument)] @@ -190,18 +225,18 @@ def extract_arguments_and_coefficients(a): def extract_elements(form): - "Build sorted tuple of all elements used in form." + """Build sorted tuple of all elements used in form.""" args = chain(*extract_arguments_and_coefficients(form)) return tuple(f.ufl_element() for f in args) def extract_unique_elements(form): - "Build sorted tuple of all unique elements used in form." + """Build sorted tuple of all unique elements used in form.""" return unique_tuple(extract_elements(form)) def extract_sub_elements(elements): - "Build sorted tuple of all sub elements (including parent element)." + """Build sorted tuple of all sub elements (including parent element).""" sub_elements = tuple(chain(*[e.sub_elements() for e in elements])) if not sub_elements: return tuple(elements) @@ -209,14 +244,14 @@ def extract_sub_elements(elements): def sort_elements(elements): - """ - Sort elements so that any sub elements appear before the + """Sort elements. + + A sort is performed so that any sub elements appear before the corresponding mixed elements. This is useful when sub elements need to be defined before the corresponding mixed elements. The ordering is based on sorting a directed acyclic graph. """ - # Set nodes nodes = sorted(elements) diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index 3938ed01f..998a139e6 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""Algorithm for expanding compound expressions into -equivalent representations using basic operators.""" +"""Algorithm for expanding compound expressions into equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # @@ -21,10 +19,10 @@ class LowerCompoundAlgebra(MultiFunction): - """Expands high level compound operators (e.g. inner) to equivalent - representations using basic operators (e.g. index notation).""" + """Expands high level compound operators to equivalent representations using basic operators.""" def __init__(self): + """Initialize.""" MultiFunction.__init__(self) ufl_type = MultiFunction.reuse_if_untouched @@ -32,33 +30,41 @@ def __init__(self): # ------------ Compound tensor operators def trace(self, o, A): + """Lower a trace.""" i = Index() return A[i, i] def transposed(self, o, A): + """Lower a transposed.""" i, j = indices(2) return as_tensor(A[i, j], (j, i)) def deviatoric(self, o, A): + """Lower a deviatoric.""" return deviatoric_expr(A) def skew(self, o, A): + """Lower a skew.""" i, j = indices(2) return as_matrix((A[i, j] - A[j, i]) / 2, (i, j)) def sym(self, o, A): + """Lower a sym.""" i, j = indices(2) return as_matrix((A[i, j] + A[j, i]) / 2, (i, j)) def cross(self, o, a, b): + """Lower a cross.""" def c(i, j): return Product(a[i], b[j]) - Product(a[j], b[i]) return as_vector((c(1, 2), c(2, 0), c(0, 1))) def perp(self, o, a): + """Lower a perp.""" return as_vector([-a[1], a[0]]) def dot(self, o, a, b): + """Lower a dot.""" ai = indices(len(a.ufl_shape) - 1) bi = indices(len(b.ufl_shape) - 1) k = (Index(),) @@ -67,6 +73,7 @@ def dot(self, o, a, b): return as_tensor(s, ai + bi) def inner(self, o, a, b): + """Lower an inner.""" ash = a.ufl_shape bsh = b.ufl_shape if ash != bsh: @@ -77,6 +84,7 @@ def inner(self, o, a, b): return s def outer(self, o, a, b): + """Lower an outer.""" ii = indices(len(a.ufl_shape)) jj = indices(len(b.ufl_shape)) # Create a Product with no shared indices @@ -84,25 +92,31 @@ def outer(self, o, a, b): return as_tensor(s, ii + jj) def determinant(self, o, A): + """Lower a determinant.""" return determinant_expr(A) def cofactor(self, o, A): + """Lower a cofactor.""" return cofactor_expr(A) def inverse(self, o, A): + """Lower an inverse.""" return inverse_expr(A) # ------------ Compound differential operators def div(self, o, a): + """Lower a div.""" i = Index() return a[..., i].dx(i) def nabla_div(self, o, a): + """Lower a nabla_div.""" i = Index() return a[i, ...].dx(i) def nabla_grad(self, o, a): + """Lower a nabla_grad.""" sh = a.ufl_shape if sh == (): return Grad(a) @@ -112,10 +126,12 @@ def nabla_grad(self, o, a): return as_tensor(a[ii].dx(j), (j,) + ii) def curl(self, o, a): + """Lower a curl.""" # o = curl a = "[a.dx(1), -a.dx(0)]" if a.ufl_shape == () # o = curl a = "cross(nabla, (a0, a1, 0))[2]" if a.ufl_shape == (2,) # o = curl a = "cross(nabla, a)" if a.ufl_shape == (3,) def c(i, j): + """A component of curl.""" return a[j].dx(i) - a[i].dx(j) sh = a.ufl_shape if sh == (): @@ -128,6 +144,5 @@ def c(i, j): def apply_algebra_lowering(expr): - """Expands high level compound operators (e.g. inner) to equivalent - representations using basic operators (e.g. index notation).""" + """Expands high level compound operators to equivalent representations using basic operators.""" return map_integrand_dags(LowerCompoundAlgebra(), expr) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 644453b81..d34540904 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -46,30 +46,40 @@ class GenericDerivativeRuleset(MultiFunction): + """A generic derivative.""" + def __init__(self, var_shape): + """Initialise.""" MultiFunction.__init__(self) self._var_shape = var_shape # --- Error checking for missing handlers and unexpected types def expr(self, o): + """Raise error.""" raise ValueError(f"Missing differentiation handler for type {o._ufl_class_.__name__}. " "Have you added a new type?") def unexpected(self, o): + """Raise error about unexpected type.""" raise ValueError(f"Unexpected type {o._ufl_class_.__name__} in AD rules.") def override(self, o): + """Raise error about overriding.""" raise ValueError(f"Type {o._ufl_class_.__name__} must be overridden in specialized AD rule set.") def derivative(self, o): + """Raise error.""" raise ValueError(f"Unhandled derivative type {o._ufl_class_.__name__}, nested differentiation has failed.") # --- Some types just don't have any derivative, this is just to # --- make algorithm structure generic def non_differentiable_terminal(self, o): - "Labels and indices are not differentiable. It's convenient to return the non-differentiated object." + """Return the non-differentiated object. + + Labels and indices are not differentiable: it's convenient to return the non-differentiated object. + """ return o label = non_differentiable_terminal multi_index = non_differentiable_terminal @@ -77,11 +87,11 @@ def non_differentiable_terminal(self, o): # --- Helper functions for creating zeros with the right shapes def independent_terminal(self, o): - "Return a zero with the right shape for terminals independent of differentiation variable." + """Return a zero with the right shape for terminals independent of differentiation variable.""" return Zero(o.ufl_shape + self._var_shape) def independent_operator(self, o): - "Return a zero with the right shape and indices for operators independent of differentiation variable." + """Return a zero with the right shape and indices for operators independent of differentiation variable.""" return Zero(o.ufl_shape + self._var_shape, o.ufl_free_indices, o.ufl_index_dimensions) # --- All derivatives need to define grad and averaging @@ -156,11 +166,13 @@ def independent_operator(self, o): # --- Default rules for operators def variable(self, o, df, unused_l): + """Differentiate a variable.""" return df # --- Indexing and component handling def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in nesting rules + """Differentiate an indexed.""" # Propagate zeros if isinstance(Ap, Zero): return self.independent_operator(o) @@ -191,9 +203,11 @@ def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in nesting rules return op def list_tensor(self, o, *dops): + """Differentiate a list_tensor.""" return ListTensor(*dops) def component_tensor(self, o, Ap, ii): + """Differentiate a component_tensor.""" if isinstance(Ap, Zero): op = self.independent_operator(o) else: @@ -204,12 +218,15 @@ def component_tensor(self, o, Ap, ii): # --- Algebra operators def index_sum(self, o, Ap, i): + """Differentiate an index_sum.""" return IndexSum(Ap, i) def sum(self, o, da, db): + """Differentiate a sum.""" return da + db def product(self, o, da, db): + """Differentiate a product.""" # Even though arguments to o are scalar, da and db may be # tensor valued a, b = o.ufl_operands @@ -222,6 +239,7 @@ def product(self, o, da, db): return s def division(self, o, fp, gp): + """Differentiate a division.""" f, g = o.ufl_operands if not is_ufl_scalar(f): @@ -246,6 +264,7 @@ def division(self, o, fp, gp): return op def power(self, o, fp, gp): + """Differentiate a power.""" f, g = o.ufl_operands if not is_true_ufl_scalar(f): @@ -280,6 +299,7 @@ def power(self, o, fp, gp): return op def abs(self, o, df): + """Differentiate an abs.""" f, = o.ufl_operands # return conditional(eq(f, 0), 0, Product(sign(f), df)) abs is # not complex differentiable, so we workaround the case of a @@ -290,17 +310,21 @@ def abs(self, o, df): # --- Complex algebra def conj(self, o, df): + """Differentiate a conj.""" return Conj(df) def real(self, o, df): + """Differentiate a real.""" return Real(df) def imag(self, o, df): + """Differentiate a imag.""" return Imag(df) # --- Mathfunctions def math_function(self, o, df): + """Differentiate a math_function.""" # FIXME: Introduce a UserOperator type instead of this hack # and define user derivative() function properly if hasattr(o, 'derivative'): @@ -309,38 +333,47 @@ def math_function(self, o, df): raise ValueError("Unknown math function.") def sqrt(self, o, fp): + """Differentiate a sqrt.""" return fp / (2 * o) def exp(self, o, fp): + """Differentiate an exp.""" return fp * o def ln(self, o, fp): + """Differentiate a ln.""" f, = o.ufl_operands if isinstance(f, Zero): raise ZeroDivisionError() return fp / f def cos(self, o, fp): + """Differentiate a cos.""" f, = o.ufl_operands return fp * -sin(f) def sin(self, o, fp): + """Differentiate a sin.""" f, = o.ufl_operands return fp * cos(f) def tan(self, o, fp): + """Differentiate a tan.""" f, = o.ufl_operands return 2.0 * fp / (cos(2.0 * f) + 1.0) def cosh(self, o, fp): + """Differentiate a cosh.""" f, = o.ufl_operands return fp * sinh(f) def sinh(self, o, fp): + """Differentiate a sinh.""" f, = o.ufl_operands return fp * cosh(f) def tanh(self, o, fp): + """Differentiate a tanh.""" f, = o.ufl_operands def sech(y): @@ -348,28 +381,34 @@ def sech(y): return fp * sech(f)**2 def acos(self, o, fp): + """Differentiate an acos.""" f, = o.ufl_operands return -fp / sqrt(1.0 - f**2) def asin(self, o, fp): + """Differentiate an asin.""" f, = o.ufl_operands return fp / sqrt(1.0 - f**2) def atan(self, o, fp): + """Differentiate an atan.""" f, = o.ufl_operands return fp / (1.0 + f**2) def atan2(self, o, fp, gp): + """Differentiate an atan2.""" f, g = o.ufl_operands return (g * fp - f * gp) / (f**2 + g**2) def erf(self, o, fp): + """Differentiate an erf.""" f, = o.ufl_operands return fp * (2.0 / sqrt(pi) * exp(-f**2)) # --- Bessel functions def bessel_j(self, o, nup, fp): + """Differentiate a bessel_j.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") @@ -381,6 +420,7 @@ def bessel_j(self, o, nup, fp): return op * fp def bessel_y(self, o, nup, fp): + """Differentiate a bessel_y.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") @@ -392,6 +432,7 @@ def bessel_y(self, o, nup, fp): return op * fp def bessel_i(self, o, nup, fp): + """Differentiate a bessel_i.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") @@ -403,6 +444,7 @@ def bessel_i(self, o, nup, fp): return op * fp def bessel_k(self, o, nup, fp): + """Differentiate a bessel_k.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") @@ -416,6 +458,7 @@ def bessel_k(self, o, nup, fp): # --- Restrictions def restricted(self, o, fp): + """Differentiate a restricted.""" # Restriction and differentiation commutes if isinstance(fp, ConstantValue): return fp # TODO: Add simplification to Restricted instead? @@ -425,14 +468,17 @@ def restricted(self, o, fp): # --- Conditionals def binary_condition(self, o, dl, dr): + """Differentiate a binary_condition.""" # Should not be used anywhere... return None def not_condition(self, o, c): + """Differentiate a not_condition.""" # Should not be used anywhere... return None def conditional(self, o, unused_dc, dt, df): + """Differentiate a conditional.""" if isinstance(dt, Zero) and isinstance(df, Zero): # Assuming dt and df have the same indices here, which # should be the case @@ -445,6 +491,7 @@ def conditional(self, o, unused_dc, dt, df): return conditional(c, dt, df) def max_value(self, o, df, dg): + """Differentiate a max_value.""" # d/dx max(f, g) = # f > g: df/dx # f < g: dg/dx @@ -455,6 +502,7 @@ def max_value(self, o, df, dg): return dc * df + (1.0 - dc) * dg def min_value(self, o, df, dg): + """Differentiate a min_value.""" # d/dx min(f, g) = # f < g: df/dx # else: dg/dx @@ -466,16 +514,22 @@ def min_value(self, o, df, dg): class GradRuleset(GenericDerivativeRuleset): + """Take the grad derivative.""" + def __init__(self, geometric_dimension): + """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=(geometric_dimension,)) self._Id = Identity(geometric_dimension) # --- Specialized rules for geometric quantities def geometric_quantity(self, o): - """Default for geometric quantities is do/dx = 0 if piecewise constant, + """Differentiate a geometric_quantity. + + 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.""" + Override for specific types if other behaviour is needed. + """ if is_cellwise_constant(o): return self.independent_terminal(o) else: @@ -485,6 +539,7 @@ def geometric_quantity(self, o): return Do def jacobian_inverse(self, o): + """Differentiate a jacobian_inverse.""" # grad(K) == K_ji rgrad(K)_rj if is_cellwise_constant(o): return self.independent_terminal(o) @@ -497,28 +552,37 @@ def jacobian_inverse(self, o): # non-affine domains several should be non-zero. def spatial_coordinate(self, o): - "dx/dx = I" + """Differentiate a spatial_coordinate. + + dx/dx = I. + """ return self._Id def cell_coordinate(self, o): - "dX/dx = inv(dx/dX) = inv(J) = K" + """Differentiate a cell_coordinate. + + dX/dx = inv(dx/dX) = inv(J) = K. + """ # FIXME: Is this true for manifolds? What about orientation? return JacobianInverse(extract_unique_domain(o)) # --- Specialized rules for form arguments def base_form_operator(self, o): + """Differentiate a base_form_operator.""" # Push the grad through the operator is not legal in most cases: # -> Not enouth regularity for chain rule to hold! # By the time we evaluate `grad(o)`, the operator `o` will have been assembled and substituted by its output. return Grad(o) def coefficient(self, o): + """Differentiate a coefficient.""" if is_cellwise_constant(o): return self.independent_terminal(o) return Grad(o) def argument(self, o): + """Differentiate an argument.""" # TODO: Enable this after fixing issue#13, unless we move # simplificat ion to a separate stage? # if is_cellwise_constant(o): @@ -530,6 +594,7 @@ def argument(self, o): # --- Rules for values or derivatives in reference frame def reference_value(self, o): + """Differentiate a reference_value.""" # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] if f.ufl_element().mapping() == "physical": @@ -544,6 +609,7 @@ def reference_value(self, o): return Do def reference_grad(self, o): + """Differentiate a reference_grad.""" # grad(o) == grad(rgrad(rv(f))) -> K_ji*rgrad(rgrad(rv(f)))_rj f = o.ufl_operands[0] @@ -558,8 +624,10 @@ def reference_grad(self, o): # --- Nesting of gradients def grad(self, o): - "Represent grad(grad(f)) as Grad(Grad(f))." + """Differentiate a grad. + Represent grad(grad(f)) as Grad(Grad(f)). + """ # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): raise ValueError("Expecting only grads applied to a terminal.") @@ -567,6 +635,7 @@ def grad(self, o): return Grad(o) def _grad(self, o): + """Differentiate a _grad.""" pass # TODO: Not sure how to detect that gradient of f is cellwise constant. # Can we trust element degrees? @@ -584,15 +653,11 @@ def _grad(self, o): def grad_to_reference_grad(o, K): """Relates grad(o) to reference_grad(o) using the Jacobian inverse. - Args - ---- - o: Operand - K: Jacobian inverse - - Returns - ------- - Do: grad(o) written in terms of reference_grad(o) and K - + Args: + o: Operand + K: Jacobian inverse + Returns: + grad(o) written in terms of reference_grad(o) and K """ r = indices(len(o.ufl_shape)) i, j = indices(2) @@ -602,7 +667,9 @@ def grad_to_reference_grad(o, K): class ReferenceGradRuleset(GenericDerivativeRuleset): + """Apply the reference grad derivative.""" def __init__(self, topological_dimension): + """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=(topological_dimension,)) self._Id = Identity(topological_dimension) @@ -610,7 +677,10 @@ def __init__(self, topological_dimension): # --- Specialized rules for geometric quantities def geometric_quantity(self, o): - "dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g)" + """Differentiate a geometric_quantity. + + dg/dX = 0 if piecewise constant, otherwise ReferenceGrad(g). + """ if is_cellwise_constant(o): return self.independent_terminal(o) else: @@ -619,12 +689,18 @@ def geometric_quantity(self, o): return ReferenceGrad(o) def spatial_coordinate(self, o): - "dx/dX = J" + """Differentiate a spatial_coordinate. + + dx/dX = J. + """ # Don't convert back to J, otherwise we get in a loop return ReferenceGrad(o) def cell_coordinate(self, o): - "dX/dX = I" + """Differentiate a cell_coordinate. + + dX/dX = I. + """ return self._Id # TODO: Add more geometry types here, with non-affine domains @@ -633,23 +709,30 @@ def cell_coordinate(self, o): # --- Specialized rules for form arguments def reference_value(self, o): + """Differentiate a reference_value.""" if not o.ufl_operands[0]._ufl_is_terminal_: raise ValueError("ReferenceValue can only wrap a terminal") return ReferenceGrad(o) def coefficient(self, o): + """Differentiate a coefficient.""" raise ValueError("Coefficient should be wrapped in ReferenceValue by now") def argument(self, o): + """Differentiate an argument.""" raise ValueError("Argument should be wrapped in ReferenceValue by now") # --- Nesting of gradients def grad(self, o): + """Differentiate a grad.""" raise ValueError(f"Grad should have been transformed by this point, but got {type(o).__name__}.") def reference_grad(self, o): - "Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f))." + """Differentiate a reference_grad. + + Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f)). + """ # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue, Terminal)): @@ -661,7 +744,9 @@ def reference_grad(self, o): class VariableRuleset(GenericDerivativeRuleset): + """Differentiate with respect to a variable.""" def __init__(self, var): + """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=var.ufl_shape) if var.ufl_free_indices: raise ValueError("Differentiation variable cannot have free indices.") @@ -669,7 +754,10 @@ def __init__(self, var): self._Id = self._make_identity(self._var_shape) def _make_identity(self, sh): - "Create a higher order identity tensor to represent dv/dv." + """Differentiate a _make_identity. + + Creates a higher order identity tensor to represent dv/dv. + """ res = None if sh == (): # Scalar dv/dv is scalar @@ -705,7 +793,9 @@ def _make_identity(self, sh): # return AnnotatedZero(o.ufl_shape + self._var_shape, arguments=(o,)) # TODO: Missing this type def coefficient(self, o): - """df/dv = Id if v is f else 0. + """Differentiate a coefficient. + + df/dv = Id if v is f else 0. Note that if v = variable(f), df/dv is still 0, but if v == f, i.e. isinstance(v, Coefficient) == True, @@ -720,6 +810,7 @@ def coefficient(self, o): return self.independent_terminal(o) def variable(self, o, df, a): + """Differentiate a variable.""" v = self._variable if isinstance(v, Variable) and v.label() == a: # dv/dv = identity of rank 2*rank(v) @@ -729,7 +820,10 @@ def variable(self, o, df, a): return df def grad(self, o): - "Variable derivative of a gradient of a terminal must be 0." + """Differentiate a grad. + + Variable derivative of a gradient of a terminal must be 0. + """ # Check that o is a "differential terminal" if not isinstance(o.ufl_operands[0], (Grad, Terminal)): raise ValueError("Expecting only grads applied to a terminal.") @@ -738,6 +832,7 @@ def grad(self, o): # --- Rules for values or derivatives in reference frame def reference_value(self, o): + """Differentiate a reference_value.""" # d/dv(o) == d/dv(rv(f)) = 0 if v is not f, or rv(dv/df) v = self._variable if isinstance(v, Coefficient) and o.ufl_operands[0] == v: @@ -755,7 +850,10 @@ def reference_value(self, o): return self.independent_terminal(o) def reference_grad(self, o): - "Variable derivative of a gradient of a terminal must be 0." + """Differentiate a reference_grad. + + Variable derivative of a gradient of a terminal must be 0. + """ if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue)): raise ValueError("Unexpected argument to reference_grad.") @@ -769,12 +867,11 @@ class GateauxDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. Implements rules for the Gateaux derivative D_w[v](...) defined as - - D_w[v](e) = d/dtau e(w+tau v)|tau=0 - + D_w[v](e) = d/dtau e(w+tau v)|tau=0. """ def __init__(self, coefficients, arguments, coefficient_derivatives, pending_operations): + """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=()) # Type checking @@ -804,11 +901,13 @@ def __init__(self, coefficients, arguments, coefficient_derivatives, pending_ope geometric_quantity = GenericDerivativeRuleset.independent_terminal def cell_avg(self, o, fp): + """Differentiate a cell_avg.""" # Cell average of a single function and differentiation # commutes, D_f[v](cell_avg(f)) = cell_avg(v) return cell_avg(fp) def facet_avg(self, o, fp): + """Differentiate a facet_avg.""" # Facet average of a single function and differentiation # commutes, D_f[v](facet_avg(f)) = facet_avg(v) return facet_avg(fp) @@ -817,6 +916,7 @@ def facet_avg(self, o, fp): argument = GenericDerivativeRuleset.independent_terminal def coefficient(self, o): + """Differentiate a coefficient.""" # Define dw/dw := d/ds [w + s v] = v # Return corresponding argument if we can find o among w @@ -861,6 +961,7 @@ def coefficient(self, o): return dosum def reference_value(self, o): + """Differentiate a reference_value.""" raise NotImplementedError("Currently no support for ReferenceValue in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative @@ -878,6 +979,7 @@ def reference_value(self, o): # return self.independent_terminal(o) def reference_grad(self, o): + """Differentiate a reference_grad.""" raise NotImplementedError("Currently no support for ReferenceGrad in CoefficientDerivative.") # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative @@ -886,6 +988,7 @@ def reference_grad(self, o): # derivative(...ReferenceValue...,...). def grad(self, g): + """Differentiate a grad.""" # If we hit this type, it has already been propagated to a # coefficient (or grad of a coefficient) or a base form operator, # FIXME: Assert # this! so we need to take the gradient of the variation or @@ -1008,7 +1111,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # TODO: Make it possible to silence this message # in particular? It may be good to have for # debugging... - warnings.warn("Assuming d{%s}/d{%s} = 0." % (o, self._w)) + warnings.warn(f"Assuming d{{{0}}}/d{{{self._w}}} = 0.") else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): @@ -1038,12 +1141,15 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): return gprimesum def coordinate_derivative(self, o): + """Differentiate a coordinate_derivative.""" o = o.ufl_operands return CoordinateDerivative(map_expr_dag(self, o[0]), o[1], o[2], o[3]) def base_form_operator(self, o, *dfs): - """If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a variable => we call the appropriate handler - Otherwise => differentiation done wrt the BaseFormOperator (dF/dN[Nhat]) => we treat o as a Coefficient + """Differentiate a base_form_operator. + + If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a variable => we call the appropriate handler. + Otherwise => differentiation done wrt the BaseFormOperator (dF/dN[Nhat]) => we treat o as a Coefficient. """ d_coeff = self.coefficient(o) # It also handles the non-scalar case @@ -1054,6 +1160,7 @@ def base_form_operator(self, o, *dfs): # -- Handlers for BaseForm objects -- # def cofunction(self, o): + """Differentiate a cofunction.""" # Same rule than for Coefficient except that we use a Coargument. # The coargument is already attached to the class (self._v) # which `self.coefficient` relies on. @@ -1064,6 +1171,7 @@ def cofunction(self, o): return dc def coargument(self, o): + """Differentiate a coargument.""" # Same rule than for Argument (da/dw == 0). dc = self.argument(o) if dc == 0: @@ -1072,6 +1180,7 @@ def coargument(self, o): return dc def matrix(self, M): + """Differentiate a matrix.""" # Matrix rule: D_w[v](M) = v if M == w else 0 # We can't differentiate wrt a matrix so always return zero in the appropriate space return ZeroBaseForm(M.arguments() + self._v) @@ -1081,17 +1190,17 @@ class BaseFormOperatorDerivativeRuleset(GateauxDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to BaseFormOperator. Implements rules for the Gateaux derivative D_w[v](...) defined as - - D_w[v](B) = d/dtau B(w+tau v)|tau=0 - - where B is a ufl.BaseFormOperator + D_w[v](B) = d/dtau B(w+tau v)|tau=0 where B is a ufl.BaseFormOperator. """ def __init__(self, coefficients, arguments, coefficient_derivatives, pending_operations): + """Initialise.""" GateauxDerivativeRuleset.__init__(self, coefficients, arguments, coefficient_derivatives, pending_operations) def pending_operations_recording(base_form_operator_handler): + """Decorate a function to record pending operations.""" def wrapper(self, base_form_op, *dfs): + """Decorate.""" # Get the outer `BaseFormOperator` expression, i.e. the operator that is being differentiated. expression = self.pending_operations.expression # If the base form operator we observe is different from the outer `BaseFormOperator`: @@ -1106,6 +1215,7 @@ def wrapper(self, base_form_op, *dfs): @pending_operations_recording def interpolate(self, i_op, dw): + """Differentiate an interpolate.""" # Interpolate rule: D_w[v](i_op(w, v*)) = i_op(v, v*), by linearity of Interpolate! if not dw: # i_op doesn't depend on w: @@ -1116,6 +1226,7 @@ def interpolate(self, i_op, dw): @pending_operations_recording def external_operator(self, N, *dfs): + """Differentiate an external_operator.""" result = () for i, df in enumerate(dfs): derivatives = tuple(dj + int(i == j) for j, dj in enumerate(N.derivatives)) @@ -1140,7 +1251,10 @@ def external_operator(self, N, *dfs): class DerivativeRuleDispatcher(MultiFunction): + """Dispatch a derivative rule.""" + def __init__(self): + """Initialise.""" MultiFunction.__init__(self) # caches for reuse in the dispatched transformers self.vcaches = defaultdict(dict) @@ -1151,14 +1265,17 @@ def __init__(self): self.pending_operations = () def terminal(self, o): + """Apply to a terminal.""" return o def derivative(self, o): + """Apply to a derivative.""" raise NotImplementedError(f"Missing derivative handler for {type(o).__name__}.") ufl_type = MultiFunction.reuse_if_untouched def grad(self, o, f): + """Apply to a grad.""" rules = GradRuleset(o.ufl_shape[-1]) key = (GradRuleset, o.ufl_shape[-1]) return map_expr_dag(rules, f, @@ -1166,6 +1283,7 @@ def grad(self, o, f): rcache=self.rcaches[key]) def reference_grad(self, o, f): + """Apply to a reference_grad.""" rules = ReferenceGradRuleset(o.ufl_shape[-1]) # FIXME: Look over this and test better. key = (ReferenceGradRuleset, o.ufl_shape[-1]) return map_expr_dag(rules, f, @@ -1173,6 +1291,7 @@ def reference_grad(self, o, f): rcache=self.rcaches[key]) def variable_derivative(self, o, f, dummy_v): + """Apply to a variable_derivative.""" op = o.ufl_operands[1] rules = VariableRuleset(op) key = (VariableRuleset, op) @@ -1181,6 +1300,7 @@ def variable_derivative(self, o, f, dummy_v): rcache=self.rcaches[key]) def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + """Apply to a coefficient_derivative.""" dummy, w, v, cd = o.ufl_operands pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) rules = GateauxDerivativeRuleset(w, v, cd, pending_operations) @@ -1194,6 +1314,7 @@ def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): return mapped_expr def base_form_operator_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + """Apply to a base_form_operator_derivative.""" dummy, w, v, cd = o.ufl_operands pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) rules = BaseFormOperatorDerivativeRuleset(w, v, cd, pending_operations=pending_operations) @@ -1222,6 +1343,7 @@ def base_form_operator_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): return mapped_expr def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + """Apply to a coordinate_derivative.""" o_ = o.ufl_operands key = (CoordinateDerivative, o_[0]) return CoordinateDerivative(map_expr_dag(self, o_[0], @@ -1230,6 +1352,7 @@ def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): o_[1], o_[2], o_[3]) def base_form_coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): + """Apply to a base_form_coordinate_derivative.""" o_ = o.ufl_operands key = (BaseFormCoordinateDerivative, o_[0]) return BaseFormCoordinateDerivative(map_expr_dag(self, o_[0], @@ -1238,6 +1361,7 @@ def base_form_coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): o_[1], o_[2], o_[3]) def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules + """Apply to an indexed.""" # Reuse if untouched if Ap is o.ufl_operands[0]: return o @@ -1270,8 +1394,10 @@ def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules class BaseFormOperatorDerivativeRecorder(): - def __init__(self, expression, var, **kwargs): + """A derivative recorded for a base form operator.""" + def __init__(self, expression, var, **kwargs): + """Initialise.""" base_form_ops = kwargs.pop("base_form_ops", ()) if kwargs.keys() != {'arguments', 'coefficient_derivatives'}: @@ -1283,17 +1409,20 @@ def __init__(self, expression, var, **kwargs): self.base_form_ops = base_form_ops def __len__(self): + """Get the length.""" return len(self.base_form_ops) def __bool__(self): + """Convert to a bool.""" return bool(self.base_form_ops) def __add__(self, other): + """Add.""" if isinstance(other, (list, tuple)): base_form_ops = self.base_form_ops + other elif isinstance(other, BaseFormOperatorDerivativeRecorder): if self.der_kwargs != other.der_kwargs: - raise ValueError("Derivative arguments must match when summing %s objects." % type(self).__name__) + raise ValueError(f"Derivative arguments must match when summing {type(self).__name__} objects.") base_form_ops = self.base_form_ops + other.base_form_ops else: raise NotImplementedError(f"Sum of {type(self)} and {type(other)} objects is not supported.") @@ -1303,10 +1432,12 @@ def __add__(self, other): **self.der_kwargs) def __radd__(self, other): + """Add.""" # Recording order doesn't matter as collected `BaseFormOperator`s are sorted later on. return self.__add__(other) def __iadd__(self, other): + """Add.""" if isinstance(other, (list, tuple)): self.base_form_ops += other elif isinstance(other, BaseFormOperatorDerivativeRecorder): @@ -1317,7 +1448,14 @@ def __iadd__(self, other): def apply_derivatives(expression): - # Note that `expression` can be a Form, an Expr or a BaseFormOperator. + """Apply derivatives to an expression. + + Args: + expression: A Form, an Expr or a BaseFormOperator to be differentiated + + Returns: + A differentiated expression + """ # Notation: Let `var` be the thing we are differentating with respect to. rules = DerivativeRuleDispatcher() @@ -1376,14 +1514,12 @@ class CoordinateDerivativeRuleset(GenericDerivativeRuleset): """Apply AFD (Automatic Functional Differentiation) to expression. Implements rules for the Gateaux derivative D_w[v](...) defined as - - D_w[v](e) = d/dtau e(w+tau v)|tau=0 - + D_w[v](e) = d/dtau e(w+tau v)|tau=0 where 'e' is a ufl form after pullback and w is a SpatialCoordinate. - """ def __init__(self, coefficients, arguments, coefficient_derivatives): + """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=()) # Type checking @@ -1412,12 +1548,15 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): argument = GenericDerivativeRuleset.independent_terminal def coefficient(self, o): + """Differentiate a coefficient.""" raise NotImplementedError("CoordinateDerivative of coefficient in physical space is not implemented.") def grad(self, o): + """Differentiate a grad.""" raise NotImplementedError("CoordinateDerivative grad in physical space is not implemented.") def spatial_coordinate(self, o): + """Differentiate a spatial_coordinate.""" do = self._w2v.get(o) # d x /d x => Argument(x.function_space()) if do is not None: @@ -1427,6 +1566,7 @@ def spatial_coordinate(self, o): "from the one being differentiated.") def reference_value(self, o): + """Differentiate a reference_value.""" do = self._cd.get(o) if do is not None: return do @@ -1434,6 +1574,7 @@ def reference_value(self, o): return self.independent_terminal(o) def reference_grad(self, g): + """Differentiate a reference_grad.""" # d (grad_X(...(x)) / dx => grad_X(...(Argument(x.function_space())) o = g ngrads = 0 @@ -1457,6 +1598,7 @@ def apply_grads(f): return self.independent_terminal(o) def jacobian(self, o): + """Differentiate a jacobian.""" # d (grad_X(x))/d x => grad_X(Argument(x.function_space()) for (w, v) in zip(self._w, self._v): if extract_unique_domain(o) == extract_unique_domain(w) and isinstance(v.ufl_operands[0], FormArgument): @@ -1465,29 +1607,38 @@ def jacobian(self, o): class CoordinateDerivativeRuleDispatcher(MultiFunction): + """Dispatcher.""" + def __init__(self): + """Initialise.""" MultiFunction.__init__(self) self.vcache = defaultdict(dict) self.rcache = defaultdict(dict) def terminal(self, o): + """Apply to a terminal.""" return o def derivative(self, o): + """Apply to a derivative.""" raise NotImplementedError(f"Missing derivative handler for {type(o).__name__}.") expr = MultiFunction.reuse_if_untouched def grad(self, o): + """Apply to a grad.""" return o def reference_grad(self, o): + """Apply to a reference_grad.""" return o def coefficient_derivative(self, o): + """Apply to a coefficient_derivative.""" return o def coordinate_derivative(self, o, f, w, v, cd): + """Apply to a coordinate_derivative.""" from ufl.algorithms import extract_unique_elements for space in extract_unique_elements(o): if space.mapping() == "custom": @@ -1501,5 +1652,6 @@ def coordinate_derivative(self, o, f, w, v, cd): def apply_coordinate_derivatives(expression): + """Apply coordinate derivatives to an expression.""" rules = CoordinateDerivativeRuleDispatcher() return map_integrand_dags(rules, expression) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index b7790c7fb..ebd66866d 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -21,7 +21,7 @@ def sub_elements_with_mappings(element): - "Return an ordered list of the largest subelements that have a defined mapping." + """Return an ordered list of the largest subelements that have a defined mapping.""" if element.mapping() != "undefined": return [element] elements = [] @@ -36,8 +36,9 @@ def sub_elements_with_mappings(element): def apply_known_single_pullback(r, element): """Apply pullback with given mapping. - :arg r: Expression wrapped in ReferenceValue - :arg element: The element defining the mapping + Args: + r: Expression wrapped in ReferenceValue + element: The element defining the mapping """ # Need to pass in r rather than the physical space thing, because # the latter may be a ListTensor or similar, rather than a @@ -89,11 +90,15 @@ def apply_known_single_pullback(r, element): def apply_single_function_pullbacks(r, element): - """Apply an appropriate pullback to something in physical space + """Apply an appropriate pullback to something in physical space. - :arg r: An expression wrapped in ReferenceValue. - :arg element: The element this expression lives in. - :returns: a pulled back expression.""" + Args: + r: An expression wrapped in ReferenceValue. + element: The element this expression lives in. + + Returns: + a pulled back expression. + """ mapping = element.mapping() if r.ufl_shape != element.reference_value_shape(): raise ValueError( @@ -150,16 +155,21 @@ def apply_single_function_pullbacks(r, element): class FunctionPullbackApplier(MultiFunction): + """A pull back applier.""" + def __init__(self): + """Initalise.""" MultiFunction.__init__(self) expr = MultiFunction.reuse_if_untouched def terminal(self, t): + """Apply to a terminal.""" return t @memoized_handler def form_argument(self, o): + """Apply to a form_argument.""" # Represent 0-derivatives of form arguments on reference # element f = apply_single_function_pullbacks(ReferenceValue(o), o.ufl_element()) @@ -168,11 +178,12 @@ def form_argument(self, o): def apply_function_pullbacks(expr): - """Change representation of coefficients and arguments in expression - by applying Piola mappings where applicable and representing all + """Change representation of coefficients and arguments in an expression. + + Applies Piola mappings where applicable and represents all form arguments in reference value. - @param expr: - An Expr. + Args: + expr: An Expression """ return map_integrand_dags(FunctionPullbackApplier(), expr) diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 5317f4415..21ea99f3c 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -26,8 +26,6 @@ from ufl.core.multiindex import Index, indices from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction, memoized_handler -# FacetJacobianInverse, -# FacetOrientation, QuadratureWeight, from ufl.domain import extract_unique_domain from ufl.measure import custom_integral_types, point_integral_types from ufl.operators import conj, max_value, min_value, real, sqrt @@ -35,7 +33,9 @@ class GeometryLoweringApplier(MultiFunction): + """Geometry lowering.""" def __init__(self, preserve_types=()): + """Initialise.""" MultiFunction.__init__(self) # Store preserve_types as boolean lookup table self._preserve_types = [False] * Expr._ufl_num_typecodes_ @@ -45,10 +45,12 @@ def __init__(self, preserve_types=()): expr = MultiFunction.reuse_if_untouched def terminal(self, t): + """Apply to terminal.""" return t @memoized_handler def jacobian(self, o): + """Apply to jacobian.""" if self._preserve_types[o._ufl_typecode_]: return o domain = extract_unique_domain(o) @@ -63,6 +65,7 @@ def jacobian(self, o): @memoized_handler def _future_jacobian(self, o): + """Apply to _future_jacobian.""" # If we're not using Coefficient to represent the spatial # coordinate, we can just as well just return o here too # unless we add representation of basis functions and dofs to @@ -71,6 +74,7 @@ def _future_jacobian(self, o): @memoized_handler def jacobian_inverse(self, o): + """Apply to jacobian_inverse.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -83,6 +87,7 @@ def jacobian_inverse(self, o): @memoized_handler def jacobian_determinant(self, o): + """Apply to jacobian_determinant.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -101,6 +106,7 @@ def jacobian_determinant(self, o): @memoized_handler def facet_jacobian(self, o): + """Apply to facet_jacobian.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -112,6 +118,7 @@ def facet_jacobian(self, o): @memoized_handler def facet_jacobian_inverse(self, o): + """Apply to facet_jacobian_inverse.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -123,6 +130,7 @@ def facet_jacobian_inverse(self, o): @memoized_handler def facet_jacobian_determinant(self, o): + """Apply to facet_jacobian_determinant.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -141,7 +149,10 @@ def facet_jacobian_determinant(self, o): @memoized_handler def spatial_coordinate(self, o): - "Fall through to coordinate field of domain if it exists." + """Apply to spatial_coordinate. + + Fall through to coordinate field of domain if it exists. + """ if self._preserve_types[o._ufl_typecode_]: return o if extract_unique_domain(o).ufl_coordinate_element().mapping() != "identity": @@ -152,7 +163,10 @@ def spatial_coordinate(self, o): @memoized_handler def cell_coordinate(self, o): - "Compute from physical coordinates if they are known, using the appropriate mappings." + """Apply to cell_coordinate. + + Compute from physical coordinates if they are known, using the appropriate mappings. + """ if self._preserve_types[o._ufl_typecode_]: return o @@ -166,6 +180,7 @@ def cell_coordinate(self, o): @memoized_handler def facet_cell_coordinate(self, o): + """Apply to facet_cell_coordinate.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -174,6 +189,7 @@ def facet_cell_coordinate(self, o): @memoized_handler def cell_volume(self, o): + """Apply to cell_volume.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -190,6 +206,7 @@ def cell_volume(self, o): @memoized_handler def facet_area(self, o): + """Apply to facet_area.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -211,6 +228,7 @@ def facet_area(self, o): @memoized_handler def circumradius(self, o): + """Apply to circumradius.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -251,13 +269,16 @@ def circumradius(self, o): @memoized_handler def max_cell_edge_length(self, o): + """Apply to max_cell_edge_length.""" return self._reduce_cell_edge_length(o, max_value) @memoized_handler def min_cell_edge_length(self, o): + """Apply to min_cell_edge_length.""" return self._reduce_cell_edge_length(o, min_value) def _reduce_cell_edge_length(self, o, reduction_op): + """Apply to _reduce_cell_edge_length.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -282,6 +303,7 @@ def _reduce_cell_edge_length(self, o, reduction_op): @memoized_handler def cell_diameter(self, o): + """Apply to cell_diameter.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -306,13 +328,16 @@ def cell_diameter(self, o): @memoized_handler def max_facet_edge_length(self, o): + """Apply to max_facet_edge_length.""" return self._reduce_facet_edge_length(o, max_value) @memoized_handler def min_facet_edge_length(self, o): + """Apply to min_facet_edge_length.""" return self._reduce_facet_edge_length(o, min_value) def _reduce_facet_edge_length(self, o, reduction_op): + """Apply to _reduce_facet_edge_length.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -336,6 +361,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): @memoized_handler def cell_normal(self, o): + """Apply to cell_normal.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -368,6 +394,7 @@ def cell_normal(self, o): @memoized_handler def facet_normal(self, o): + """Apply to facet_normal.""" if self._preserve_types[o._ufl_typecode_]: return o @@ -417,8 +444,9 @@ def apply_geometry_lowering(form, preserve_types=()): Assumes the expression is preprocessed or at least that derivatives have been expanded. - @param form: - An Expr or Form. + Args: + form: An Expr or Form. + preserve_types: Preserved types """ if isinstance(form, Form): newintegrals = [apply_geometry_lowering(integral, preserve_types) diff --git a/ufl/algorithms/apply_integral_scaling.py b/ufl/algorithms/apply_integral_scaling.py index 111bf4444..de5cc9fa5 100644 --- a/ufl/algorithms/apply_integral_scaling.py +++ b/ufl/algorithms/apply_integral_scaling.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2013-2016 Martin Sandve Alnæs @@ -16,7 +15,6 @@ 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) @@ -75,7 +73,7 @@ def compute_integrand_scaling_factor(integral): def apply_integral_scaling(form): - "Multiply integrands by a factor to scale the integral to reference frame." + """Multiply integrands by a factor to scale the integral to reference frame.""" # TODO: Consider adding an in_reference_frame property to Integral # and checking it here and setting it in the returned form if isinstance(form, Form): @@ -106,6 +104,7 @@ def apply_integral_scaling(form): md["estimated_polynomial_degree"] = new_degree def scale_coordinate_derivative(o, scale): + """Scale the coordinate derivative.""" o_ = o.ufl_operands if isinstance(o, CoordinateDerivative): return CoordinateDerivative(scale_coordinate_derivative(o_[0], scale), o_[1], o_[2], o_[3]) diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 0cd3417c2..cd98315ae 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -1,5 +1,8 @@ -"""This module contains the apply_restrictions algorithm which propagates -restrictions in a form towards the terminals.""" +"""Apply restrictions. + +This module contains the apply_restrictions algorithm which propagates restrictions in a form +towards the terminals. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -18,7 +21,10 @@ class RestrictionPropagator(MultiFunction): + """Restriction propagator.""" + def __init__(self, side=None): + """Initialise.""" MultiFunction.__init__(self) self.current_restriction = side self.default_restriction = "+" @@ -30,7 +36,7 @@ def __init__(self, side=None): "-": RestrictionPropagator("-")} def restricted(self, o): - "When hitting a restricted quantity, visit child with a separate restriction algorithm." + """When hitting a restricted quantity, visit child with a separate restriction algorithm.""" # Assure that we have only two levels here, inside or outside # the Restricted node if self.current_restriction is not None: @@ -44,25 +50,27 @@ def restricted(self, o): # --- Reusable rules def _ignore_restriction(self, o): - "Ignore current restriction, quantity is independent of side also from a computational point of view." + """Ignore current restriction, quantity is independent of side also from a computational point of view.""" return o def _require_restriction(self, o): - "Restrict a discontinuous quantity to current side, require a side to be set." + """Restrict a discontinuous quantity to current side, require a side to be set.""" if self.current_restriction is None: raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") return o(self.current_restriction) def _default_restricted(self, o): - "Restrict a continuous quantity to default side if no current restriction is set." + """Restrict a continuous quantity to default side if no current restriction is set.""" r = self.current_restriction if r is None: r = self.default_restriction return o(r) def _opposite(self, o): - """Restrict a quantity to default side, if the current restriction - is different swap the sign, require a side to be set.""" + """Restrict a quantity to default side. + + If the current restriction is different swap the sign, require a side to be set. + """ if self.current_restriction is None: raise ValueError(f"Discontinuous type {o._ufl_class_.__name__} must be restricted.") elif self.current_restriction == self.default_restriction: @@ -71,6 +79,7 @@ def _opposite(self, o): return -o(self.default_restriction) def _missing_rule(self, o): + """Raise an error.""" raise ValueError(f"Missing rule for {o._ufl_class_.__name__}") # --- Rules for operators @@ -85,11 +94,11 @@ def _missing_rule(self, o): grad = _require_restriction def variable(self, o, op, label): - "Strip variable." + """Strip variable.""" return op def reference_value(self, o): - "Reference value of something follows same restriction rule as the underlying object." + """Reference value of something follows same restriction rule as the underlying object.""" f, = o.ufl_operands assert f._ufl_is_terminal_ g = self(f) @@ -129,15 +138,20 @@ def reference_value(self, o): reference_facet_volume = _ignore_restriction def coefficient(self, o): - """Allow coefficients to be unrestricted (apply default if so) if the values are - fully continuous across the facet.""" + """Restrict a coefficient. + + Allow coefficients to be unrestricted (apply default if so) if the values are fully continuous + across the facet. + """ 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 + # It must still be computed from one of the sides, we just don't care which + return self._default_restricted(o) else: return self._require_restriction(o) def facet_normal(self, o): + """Restrict a facet_normal.""" D = extract_unique_domain(o) e = D.ufl_coordinate_element() gd = D.geometric_dimension() @@ -159,7 +173,7 @@ def facet_normal(self, o): def apply_restrictions(expression): - "Propagate restriction nodes to wrap differential terminals directly." + """Propagate restriction nodes to wrap differential terminals directly.""" integral_types = [k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet")] rules = RestrictionPropagator() @@ -168,7 +182,10 @@ def apply_restrictions(expression): class DefaultRestrictionApplier(MultiFunction): + """Default restriction applier.""" + def __init__(self, side=None): + """Initialise.""" MultiFunction.__init__(self) self.current_restriction = side self.default_restriction = "+" @@ -177,6 +194,7 @@ def __init__(self, side=None): "-": DefaultRestrictionApplier("-")} def terminal(self, o): + """Apply to terminal.""" # Most terminals are unchanged return o @@ -184,17 +202,19 @@ def terminal(self, o): operator = MultiFunction.reuse_if_untouched def restricted(self, o): + """Apply to restricted.""" # Don't restrict twice return o def derivative(self, o): + """Apply to derivative.""" # I don't think it's safe to just apply default restriction # to the argument of any derivative, i.e. grad(cg1_function) # is not continuous across cells even if cg1_function is. return o def _default_restricted(self, o): - "Restrict a continuous quantity to default side if no current restriction is set." + """Restrict a continuous quantity to default side if no current restriction is set.""" r = self.current_restriction if r is None: r = self.default_restriction @@ -219,7 +239,8 @@ def _default_restricted(self, o): def apply_default_restrictions(expression): """Some terminals can be restricted from either side. - This applies a default restriction to such terminals if unrestricted.""" + This applies a default restriction to such terminals if unrestricted. + """ integral_types = [k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet")] rules = DefaultRestrictionApplier() diff --git a/ufl/algorithms/balancing.py b/ufl/algorithms/balancing.py index 9e0a6b741..fdb6682c5 100644 --- a/ufl/algorithms/balancing.py +++ b/ufl/algorithms/balancing.py @@ -1,3 +1,4 @@ +"""Balancing.""" # -*- coding: utf-8 -*- # Copyright (C) 2011-2017 Martin Sandve Alnæs # @@ -22,6 +23,7 @@ def balance_modified_terminal(expr): + """Balance modified terminal.""" # NB! Assuming e.g. grad(cell_avg(expr)) does not occur, # i.e. it is simplified to 0 immediately. @@ -53,13 +55,18 @@ def balance_modified_terminal(expr): class BalanceModifiers(MultiFunction): + """Balance modifiers.""" + def expr(self, expr, *ops): + """Apply to expr.""" return expr._ufl_expr_reconstruct_(*ops) def terminal(self, expr): + """Apply to terminal.""" return expr def _modifier(self, expr, *ops): + """Apply to _modifier.""" return balance_modified_terminal(expr) reference_value = _modifier @@ -72,5 +79,6 @@ def _modifier(self, expr, *ops): def balance_modifiers(expr): + """Balance modifiers.""" mf = BalanceModifiers() return map_expr_dag(mf, expr) diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index 88ad22d12..452f0d896 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -108,15 +107,20 @@ class ChangeToReferenceGrad(MultiFunction): + """Change to reference grad.""" + def __init__(self): + """Initalise.""" MultiFunction.__init__(self) expr = MultiFunction.reuse_if_untouched def terminal(self, o): + """Apply to terminal.""" return o def grad(self, o): + """Apply to grad.""" # Peel off the Grads and count them, and get restriction if # it's between the grad and the terminal ngrads = 0 @@ -193,9 +197,11 @@ def grad(self, o): return jinv_lgrad_f def reference_grad(self, o): + """Apply to reference_grad.""" raise ValueError("Not expecting reference grad while applying change to reference grad.") def coefficient_derivative(self, o): + """Apply to coefficient_derivative.""" raise ValueError("Coefficient derivatives should be expanded before applying change to reference grad.") @@ -204,8 +210,8 @@ def change_to_reference_grad(e): Assumes the expression is preprocessed or at least that derivatives have been expanded. - @param e: - An Expr or Form. + Args: + e: An Expr or Form. """ mf = ChangeToReferenceGrad() return map_expr_dag(mf, e) @@ -213,7 +219,6 @@ def change_to_reference_grad(e): def change_integrand_geometry_representation(integrand, scale, integral_type): """Change integrand geometry to the right representations.""" - integrand = apply_function_pullbacks(integrand) integrand = change_to_reference_grad(integrand) diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 3f2f64a7d..80a917e03 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - - +"""Check arities.""" from itertools import chain from ufl.corealg.traversal import traverse_unique_terminals @@ -10,27 +8,34 @@ class ArityMismatch(BaseException): + """Arity mismatch exception.""" pass -# String representation of an arity tuple: def _afmt(atuple): + """Return a string representation of an arity tuple.""" return tuple(f"conj({arg})" if conj else str(arg) for arg, conj in atuple) class ArityChecker(MultiFunction): + """Arity checker.""" + def __init__(self, arguments): + """Initialise.""" MultiFunction.__init__(self) self.arguments = arguments self._et = () def terminal(self, o): + """Apply to terminal.""" return self._et def argument(self, o): + """Apply to argument.""" return ((o, False),) def nonlinear_operator(self, o): + """Apply to nonlinear_operator.""" # Cutoff traversal by not having *ops in argument list of this # handler. Traverse only the terminals under here the fastest # way we know of: @@ -43,16 +48,19 @@ def nonlinear_operator(self, o): expr = nonlinear_operator def sum(self, o, a, b): + """Apply to sum.""" if a != b: raise ArityMismatch(f"Adding expressions with non-matching form arguments {_afmt(a)} vs {_afmt(b)}.") return a def division(self, o, a, b): + """Apply to division.""" if b: raise ArityMismatch(f"Cannot divide by form argument {b}.") return a def product(self, o, a, b): + """Apply to product.""" if a and b: # Check that we don't have test*test, trial*trial, even # for different parts in a block system @@ -77,14 +85,17 @@ def product(self, o, a, b): # inner, outer and dot all behave as product but for conjugates def inner(self, o, a, b): + """Apply to inner.""" return self.product(o, a, self.conj(None, b)) dot = inner def outer(self, o, a, b): + """Apply to outer.""" return self.product(o, self.conj(None, a), b) def linear_operator(self, o, a): + """Apply to linear_operator.""" return a # Positive and negative restrictions behave as linear operators @@ -102,15 +113,18 @@ def linear_operator(self, o, a): # Conj, is a sesquilinear operator def conj(self, o, a): + """Apply to conj.""" return tuple((a_[0], not a_[1]) for a_ in a) # Does it make sense to have a Variable(Argument)? I see no # problem. def variable(self, o, f, a): + """Apply to variable.""" return f # Conditional is linear on each side of the condition def conditional(self, o, c, a, b): + """Apply to conditional.""" if c: raise ArityMismatch(f"Condition cannot depend on form arguments ({_afmt(a)}).") if a and isinstance(o.ufl_operands[2], Zero): @@ -129,6 +143,7 @@ def conditional(self, o, c, a, b): f"{_afmt(a)} vs {_afmt(b)}.") def linear_indexed_type(self, o, a, i): + """Apply to linear_indexed_type.""" return a # All of these indexed thingies behave as a linear_indexed_type @@ -137,6 +152,7 @@ def linear_indexed_type(self, o, a, i): component_tensor = linear_indexed_type def list_tensor(self, o, *ops): + """Apply to list_tensor.""" args = set(chain(*ops)) if args: # Check that each list tensor component has the same @@ -156,6 +172,7 @@ def list_tensor(self, o, *ops): def check_integrand_arity(expr, arguments, complex_mode=False): + """Check the arity of an integrand.""" arguments = tuple(sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) @@ -176,5 +193,6 @@ def check_integrand_arity(expr, arguments, complex_mode=False): def check_form_arity(form, arguments, complex_mode=False): + """Check the arity of a form.""" for itg in form.integrals(): check_integrand_arity(itg.integrand(), arguments, complex_mode) diff --git a/ufl/algorithms/check_restrictions.py b/ufl/algorithms/check_restrictions.py index 3aaca1786..d2c4ecd7e 100644 --- a/ufl/algorithms/check_restrictions.py +++ b/ufl/algorithms/check_restrictions.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Algorithms related to restrictions." +"""Algorithms related to restrictions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -12,15 +11,20 @@ class RestrictionChecker(MultiFunction): + """Restiction checker.""" + def __init__(self, require_restriction): + """Initialise.""" MultiFunction.__init__(self) self.current_restriction = None self.require_restriction = require_restriction def expr(self, o): + """Apply to expr.""" pass def restricted(self, o): + """Apply to restricted.""" if self.current_restriction is not None: raise ValueError("Not expecting twice restricted expression.") self.current_restriction = o._side @@ -29,6 +33,7 @@ def restricted(self, o): self.current_restriction = None def facet_normal(self, o): + """Apply to facet_normal.""" if self.require_restriction: if self.current_restriction is None: raise ValueError("Facet normal must be restricted in interior facet integrals.") @@ -37,6 +42,7 @@ def facet_normal(self, o): raise ValueError("Restrictions are only allowed for interior facet integrals.") def form_argument(self, o): + """Apply to form_argument.""" if self.require_restriction: if self.current_restriction is None: raise ValueError("Form argument must be restricted in interior facet integrals.") @@ -46,6 +52,6 @@ def form_argument(self, o): def check_restrictions(expression, require_restriction): - "Check that types that must be restricted are restricted in expression." + """Check that types that must be restricted are restricted in expression.""" rules = RestrictionChecker(require_restriction) return map_expr_dag(rules, expression) diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index a850f5ff1..b5f6aa109 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Functions to check the validity of forms.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -80,7 +79,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste msg = "TrialFunctions" else: msg = "Arguments with same number and part" - msg = "Found different %s: %s and %s." % (msg, repr(f), repr(g)) + msg = f"Found different {msg}: {f!r} and {g!r}." errors.append(msg) else: arguments[(n, p)] = f @@ -88,8 +87,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste # Check that all integrands are scalar for expression in iter_expressions(form): if not is_true_ufl_scalar(expression): - errors.append("Found non-scalar integrand expression: %s\n" % - ufl_err_str(expression)) + errors.append("Found non-scalar integrand expression: {ufl_err_str(expression)}\n") # Check that restrictions are permissible for integral in form.integrals(): diff --git a/ufl/algorithms/comparison_checker.py b/ufl/algorithms/comparison_checker.py index 3b7347190..32a9098fc 100644 --- a/ufl/algorithms/comparison_checker.py +++ b/ufl/algorithms/comparison_checker.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""Algorithm to check for 'comparison' nodes -in a form when the user is in 'complex mode'""" +"""Algorithm to check for 'comparison' nodes in a form when the user is in 'complex mode'.""" from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags @@ -23,16 +21,15 @@ class CheckComparisons(MultiFunction): """ def __init__(self): + """Initialise.""" MultiFunction.__init__(self) self.nodetype = {} def expr(self, o, *ops): - """Defaults expressions to complex unless they only - act on real quantities. Overridden for specific operators. + """Defaults expressions to complex unless they only act on real quantities. - Rebuilds objects if necessary. + Overridden for specific operators. Rebuilds objects if necessary. """ - types = {self.nodetype[op] for op in ops} if types: @@ -45,6 +42,7 @@ def expr(self, o, *ops): return o def compare(self, o, *ops): + """Compare.""" types = {self.nodetype[op] for op in ops} if "complex" in types: @@ -61,6 +59,7 @@ def compare(self, o, *ops): sign = compare def max_value(self, o, *ops): + """Apply to max_value.""" types = {self.nodetype[op] for op in ops} if "complex" in types: @@ -71,6 +70,7 @@ def max_value(self, o, *ops): return o def min_value(self, o, *ops): + """Apply to min_value.""" types = {self.nodetype[op] for op in ops} if "complex" in types: @@ -81,21 +81,25 @@ def min_value(self, o, *ops): return o def real(self, o, *ops): + """Apply to real.""" o = self.reuse_if_untouched(o, *ops) self.nodetype[o] = 'real' return o def imag(self, o, *ops): + """Apply to imag.""" o = self.reuse_if_untouched(o, *ops) self.nodetype[o] = 'real' return o def sqrt(self, o, *ops): + """Apply to sqrt.""" o = self.reuse_if_untouched(o, *ops) self.nodetype[o] = 'complex' return o def power(self, o, base, exponent): + """Apply to power.""" o = self.reuse_if_untouched(o, base, exponent) try: # Attempt to diagnose circumstances in which the result must be real. @@ -110,11 +114,13 @@ def power(self, o, base, exponent): return o def abs(self, o, *ops): + """Apply to abs.""" o = self.reuse_if_untouched(o, *ops) self.nodetype[o] = 'real' return o def terminal(self, term, *ops): + """Apply to terminal.""" # default terminals to complex, except the ones we *know* are real if isinstance(term, (RealValue, Zero, Argument, GeometricQuantity)): self.nodetype[term] = 'real' @@ -123,15 +129,16 @@ def terminal(self, term, *ops): return term def indexed(self, o, expr, multiindex): + """Apply to indexed.""" o = self.reuse_if_untouched(o, expr, multiindex) self.nodetype[o] = self.nodetype[expr] return o def do_comparison_check(form): - """Raises an error if invalid comparison nodes exist""" + """Raises an error if invalid comparison nodes exist.""" return map_integrand_dags(CheckComparisons(), form) -class ComplexComparisonError(Exception): - pass +class ComplexComparisonError(BaseException): + """Complex compariseon exception.""" diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index afa092e07..5a393987a 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- -"""This module provides the compute_form_data function which form compilers -will typically call prior to code generation to preprocess/simplify a -raw input form given by a user.""" +"""This module provides the compute_form_data function. +Form compilers will typically call compute_form_dataprior to code generation to preprocess/simplify a +raw input form given by a user. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -38,10 +38,10 @@ def _auto_select_degree(elements): - """ - Automatically select degree for all elements of the form in cases - where this has not been specified by the user. This feature is - used by DOLFIN to allow the specification of Expressions with + """Automatically select degree for all elements of the form. + + This is be used in cases where the degree has not been specified by the user. + This feature is used by DOLFIN to allow the specification of Expressions with undefined degrees. """ # Use max degree of all elements, at least 1 (to work with @@ -50,7 +50,7 @@ def _auto_select_degree(elements): def _compute_element_mapping(form): - "Compute element mapping for element replacement" + """Compute element mapping for element replacement.""" # The element mapping is a slightly messy concept with two use # cases: # - Expression with missing cell or element TODO: Implement proper @@ -99,6 +99,7 @@ def _compute_element_mapping(form): def _compute_max_subdomain_ids(integral_data): + """Compute the maximum subdomain ids.""" max_subdomain_ids = {} for itg_data in integral_data: it = itg_data.integral_type @@ -112,6 +113,7 @@ def _compute_max_subdomain_ids(integral_data): def _compute_form_data_elements(self, arguments, coefficients, domains): + """Compute form data elements.""" self.argument_elements = tuple(f.ufl_element() for f in arguments) self.coefficient_elements = tuple(f.ufl_element() for f in coefficients) self.coordinate_elements = tuple(domain.ufl_coordinate_element() for domain in domains) @@ -133,6 +135,7 @@ def _compute_form_data_elements(self, arguments, coefficients, domains): def _check_elements(form_data): + """Check elements.""" for element in chain(form_data.unique_elements, form_data.unique_sub_elements): if element.cell() is None: @@ -140,6 +143,7 @@ def _check_elements(form_data): def _check_facet_geometry(integral_data): + """Check facet geometry.""" for itg_data in integral_data: for itg in itg_data.integrals: it = itg_data.integral_type @@ -155,8 +159,7 @@ def _check_facet_geometry(integral_data): def _check_form_arity(preprocessed_form): - # Check that we don't have a mixed linear/bilinear form or - # anything like that + """Check that we don't have a mixed linear/bilinear form or anything like that.""" # FIXME: This is slooow and should be moved to form compiler # and/or replaced with something faster if 1 != len(compute_form_arities(preprocessed_form)): @@ -164,9 +167,11 @@ def _check_form_arity(preprocessed_form): def _build_coefficient_replace_map(coefficients, element_mapping=None): - """Create new Coefficient objects - with count starting at 0. Return mapping from old - to new objects, and lists of the new objects.""" + """Create new Coefficient objects with count starting at 0. + + Returns: + mapping from old to new objects, and lists of the new objects + """ if element_mapping is None: element_mapping = {} @@ -191,8 +196,11 @@ def _build_coefficient_replace_map(coefficients, element_mapping=None): def attach_estimated_degrees(form): """Attach estimated polynomial degree to a form's integrals. - :arg form: The :class:`~.Form` to inspect. - :returns: A new Form with estimate degrees attached. + Args: + form: The Form` to inspect. + + Returns: + A new Form with estimate degrees attached. """ integrals = form.integrals() @@ -207,7 +215,7 @@ def attach_estimated_degrees(form): def preprocess_form(form, complex_mode): - + """Preprocess a form.""" # Note: Default behaviour here will process form the way that is # currently expected by vanilla FFC @@ -237,19 +245,17 @@ def preprocess_form(form, complex_mode): return form -def compute_form_data(form, - # Default arguments configured to behave the way old FFC expects it: - do_apply_function_pullbacks=False, - do_apply_integral_scaling=False, - do_apply_geometry_lowering=False, - preserve_geometry_types=(), - do_apply_default_restrictions=True, - do_apply_restrictions=True, - do_estimate_degrees=True, - do_append_everywhere_integrals=True, - complex_mode=False, - ): +def compute_form_data( + form, do_apply_function_pullbacks=False, do_apply_integral_scaling=False, + do_apply_geometry_lowering=False, preserve_geometry_types=(), + do_apply_default_restrictions=True, do_apply_restrictions=True, + do_estimate_degrees=True, do_append_everywhere_integrals=True, + complex_mode=False, +): + """Compute form data. + The default arguments configured to behave the way old FFC expects. + """ # TODO: Move this to the constructor instead self = FormData() diff --git a/ufl/algorithms/coordinate_derivative_helpers.py b/ufl/algorithms/coordinate_derivative_helpers.py index 938e59a3f..d94a8eed1 100644 --- a/ufl/algorithms/coordinate_derivative_helpers.py +++ b/ufl/algorithms/coordinate_derivative_helpers.py @@ -1,6 +1,7 @@ -# -*- coding: utf-8 -*- -"""This module provides the necessary tools to strip away and then reattach the -coordinate derivatives at the right time point in compute_form_data.""" +"""This module provides the necessary tools to strip away and reattach coordinate derivatives. + +This is used in compute_form_data. +""" # Copyright (C) 2018 Florian Wechsung # @@ -9,36 +10,43 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from ufl.differentiation import CoordinateDerivative -from ufl.algorithms.multifunction import MultiFunction +from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dags from ufl.classes import Integral class CoordinateDerivativeIsOutermostChecker(MultiFunction): - """ Traverses the tree to make sure that CoordinateDerivatives are only on - the outside. The visitor returns False as long as no CoordinateDerivative - has been seen. """ + """Traverses the tree to make sure that CoordinateDerivatives are only on the outside. + + The visitor returns False as long as no CoordinateDerivative has been seen. + """ def multi_index(self, o): + """Apply to multi_index.""" return False def terminal(self, o): + """Apply to terminal.""" return False def expr(self, o, *operands): - """ If we have already seen a CoordinateDerivative, then no other + """Apply to expr. + + If we have already seen a CoordinateDerivative, then no other expressions apart from more CoordinateDerivatives are allowed to wrap - around it. """ + around it. + """ if any(operands): raise ValueError("CoordinateDerivative(s) must be outermost") return False def coordinate_derivative(self, o, expr, *_): + """Apply to coordinate derivative.""" return True def strip_coordinate_derivatives(integrals): - + """Strip coordinate derivatives.""" if isinstance(integrals, list): if len(integrals) == 0: return integrals, None @@ -55,9 +63,8 @@ def strip_coordinate_derivatives(integrals): map_expr_dags(checker, [integrand]) coordinate_derivatives = [] - # grab all coordinate derivatives and store them, so that we can apply - # them later again def take_top_coordinate_derivatives(o): + """Grab all coordinate derivatives and store them, so that we can apply them later again.""" o_ = o.ufl_operands if isinstance(o, CoordinateDerivative): coordinate_derivatives.append((o_[1], o_[2], o_[3])) @@ -73,6 +80,7 @@ def take_top_coordinate_derivatives(o): def attach_coordinate_derivatives(integral, coordinate_derivatives): + """Attach coordinate derivatives.""" if coordinate_derivatives is None: return integral diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 5afec433a..86fc603d8 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithms for building canonical data structure for integrals over subdomains.""" # Copyright (C) 2009-2016 Anders Logg and Martin Sandve Alnæs @@ -22,12 +21,11 @@ class IntegralData(object): - """Utility class with the members (domain, integral_type, - subdomain_id, integrals, metadata) + """Utility class. + This class has members (domain, integral_type, subdomain_id, integrals, metadata), where metadata is an empty dictionary that may be used for associating metadata with each object. - """ __slots__ = ('domain', 'integral_type', 'subdomain_id', 'integrals', 'metadata', @@ -36,6 +34,7 @@ class IntegralData(object): def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): + """Initialise.""" if 1 != len(set(itg.ufl_domain() for itg in integrals)): raise ValueError("Multiple domains mismatch in integral data.") if not all(integral_type == itg.integral_type() for itg in integrals): @@ -59,6 +58,7 @@ def __init__(self, domain, integral_type, subdomain_id, integrals, self.metadata = metadata def __lt__(self, other): + """Check if self is less than other.""" # To preserve behaviour of extract_integral_data: return ( self.integral_type, self.subdomain_id, self.integrals, self.metadata @@ -67,25 +67,29 @@ def __lt__(self, other): ) def __eq__(self, other): + """Check for equality.""" # Currently only used for tests: return (self.integral_type == other.integral_type and self.subdomain_id == other.subdomain_id and # noqa: W504 self.integrals == other.integrals and self.metadata == other.metadata) def __str__(self): - s = "IntegralData over domain(%s, %s), with integrals:\n%s\nand metadata:\n%s" % ( - self.integral_type, self.subdomain_id, - '\n\n'.join(map(str, self.integrals)), self.metadata) + """Format as a string.""" + s = f"IntegralData over domain({self.integral_type}, {self.subdomain_id}), with integrals:\n" + s += "\n\n".join(map(str, self.integrals)) + s += "\nand metadata:\n{metadata}" return s -# Tuple comparison helper class ExprTupleKey(object): + """Tuple comparison helper.""" __slots__ = ('x',) def __init__(self, x): + """Initialise.""" self.x = x def __lt__(self, other): + """Check if self is less than other.""" # Comparing expression first c = cmp_expr(self.x[0], other.x[0]) if c < 0: @@ -100,13 +104,14 @@ def __lt__(self, other): def group_integrals_by_domain_and_type(integrals, domains): - """ - Input: + """Group integrals by domain and type. + + Args: integrals: list of Integral objects domains: list of AbstractDomain objects from the parent Form - Output: - integrals_by_domain_and_type: dict: (domain, integral_type) -> list(Integral) + Returns: + Dictionary mapping (domain, integral_type) to list(Integral) """ integrals_by_domain_and_type = defaultdict(list) for itg in integrals: @@ -121,7 +126,7 @@ def group_integrals_by_domain_and_type(integrals, domains): def integral_subdomain_ids(integral): - "Get a tuple of integer subdomains or a valid string subdomain from integral." + """Get a tuple of integer subdomains or a valid string subdomain from integral.""" did = integral.subdomain_id() if isinstance(did, numbers.Integral): return (did,) @@ -137,17 +142,18 @@ def integral_subdomain_ids(integral): def rearrange_integrals_by_single_subdomains( - integrals: typing.List[Integral], - do_append_everywhere_integrals: bool) -> typing.Dict[int, typing.List[Integral]]: + integrals: typing.List[Integral], + do_append_everywhere_integrals: bool +) -> typing.Dict[int, typing.List[Integral]]: """Rearrange integrals over multiple subdomains to single subdomain integrals. - Input: + Args: integrals: List of integrals do_append_everywhere_integrals: Boolean indicating if integrals defined on the whole domain should - just be restricted to the set of input subdomain ids. + just be restricted to the set of input subdomain ids. - Output: - integrals: The integrals reconstructed with single subdomain_id + Returns: + The integrals reconstructed with single subdomain_id """ # Split integrals into lists of everywhere and subdomain integrals everywhere_integrals = [] @@ -192,15 +198,14 @@ def rearrange_integrals_by_single_subdomains( def accumulate_integrands_with_same_metadata(integrals): - """ - Taking input on the form: - integrals = [integral0, integral1, ...] + """Accumulate integrands with the same metedata. - Return result on the form: - integrands_by_id = [(integrand0, metadata0), - (integrand1, metadata1), ...] + Args: + integrals: a list of integrals - where integrand0 < integrand1 by the canonical ufl expression ordering criteria. + Returns: + A list of the form [(integrand0, metadata0), (integrand1, metadata1), ...] + where integrand0 < integrand1 by the canonical ufl expression ordering criteria. """ # Group integrals by compiler data hash by_cdid = {} @@ -228,12 +233,15 @@ def accumulate_integrands_with_same_metadata(integrals): def build_integral_data(integrals): """Build integral data given a list of integrals. - :arg integrals: An iterable of :class:`~.Integral` objects. - :returns: A tuple of :class:`IntegralData` objects. - The integrals you pass in here must have been rearranged and - gathered (removing the "everywhere" subdomain_id. To do this, you - should call :func:`group_form_integrals`. + gathered (removing the "everywhere" subdomain_id). To do this, you + should call group_form_integrals. + + Args: + integrals: An iterable of Integral objects. + + Returns: + A tuple of IntegralData objects. """ itgs = defaultdict(list) @@ -277,9 +285,14 @@ def keyfunc(item): def group_form_integrals(form, domains, do_append_everywhere_integrals=True): """Group integrals by domain and type, performing canonical simplification. - :arg form: the :class:`~.Form` to group the integrals of. - :arg domains: an iterable of :class:`~.Domain`s. - :returns: A new :class:`~.Form` with gathered integrands. + Args: + form: the Form to group the integrals of. + domains: an iterable of Domains. + do_append_everywhere_integrals: Boolean indicating if integrals defined on the whole domain should + just be restricted to the set of input subdomain ids. + + Returns: + A new Form with gathered integrands. """ # Group integrals by domain and type integrals_by_domain_and_type = group_integrals_by_domain_and_type(form.integrals(), domains) @@ -337,6 +350,7 @@ def calc_hash(cd): def reconstruct_form_from_integral_data(integral_data): + """Reconstruct a form from integral data.""" integrals = [] for ida in integral_data: integrals.extend(ida.integrals) diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index de39d26b3..24b77f789 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -11,7 +11,7 @@ import warnings -from ufl.algorithms.multifunction import MultiFunction +from ufl.corealg.multifunction import MultiFunction from ufl.checks import is_cellwise_constant from ufl.constantvalue import IntValue from ufl.corealg.map_dag import map_expr_dags @@ -29,22 +29,33 @@ class IrreducibleInt(int): class SumDegreeEstimator(MultiFunction): - "This algorithm is exact for a few operators and heuristic for many." + """Sum degree estimator. + + This algorithm is exact for a few operators and heuristic for many. + """ def __init__(self, default_degree, element_replace_map): + """Initialise.""" MultiFunction.__init__(self) self.default_degree = default_degree self.element_replace_map = element_replace_map def constant_value(self, v): - "Constant values are constant." + """Apply to constant_value. + + Constant values are constant. + """ return 0 def constant(self, v): + """Apply to constant.""" return 0 def geometric_quantity(self, v): - "Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate." + """Apply to geometric_quantity. + + Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate. + """ if is_cellwise_constant(v): return 0 else: @@ -52,21 +63,33 @@ def geometric_quantity(self, v): return extract_unique_domain(v).ufl_coordinate_element().degree() def spatial_coordinate(self, v): - "A coordinate provides additional degrees depending on coordinate field of domain." + """Apply to spatial_coordinate. + + A coordinate provides additional degrees depending on coordinate field of domain. + """ return extract_unique_domain(v).ufl_coordinate_element().degree() def cell_coordinate(self, v): - "A coordinate provides one additional degree." + """Apply to cell_coordinate. + + A coordinate provides one additional degree. + """ return 1 def argument(self, v): - """A form argument provides a degree depending on the element, - or the default degree if the element has no degree.""" + """Apply to argument. + + A form argument provides a degree depending on the element, + or the default degree if the element has no degree. + """ return v.ufl_element().degree() # FIXME: Use component to improve accuracy for mixed elements def coefficient(self, v): - """A form argument provides a degree depending on the element, - or the default degree if the element has no degree.""" + """Apply to coefficient. + + A form argument provides a degree depending on the element, + or the default degree if the element has no degree. + """ e = v.ufl_element() e = self.element_replace_map.get(e, e) d = e.degree() # FIXME: Use component to improve accuracy for mixed elements @@ -75,9 +98,12 @@ def coefficient(self, v): return d def _reduce_degree(self, v, f): - """Reduces the estimated degree by one; used when derivatives + """Apply to _reduce_degree. + + Reduces the estimated degree by one; used when derivatives are taken. Does not reduce the degree when TensorProduct elements - or quadrilateral elements are involved.""" + or quadrilateral elements are involved. + """ if isinstance(f, int) and not isinstance(f, IrreducibleInt): return max(f - 1, 0) else: @@ -85,6 +111,7 @@ def _reduce_degree(self, v, f): return f def _add_degrees(self, v, *ops): + """Apply to _add_degrees.""" def add_single(ops): if any(isinstance(o, IrreducibleInt) for o in ops): return IrreducibleInt(sum(ops)) @@ -101,6 +128,7 @@ def add_single(ops): return add_single(ops) def _max_degrees(self, v, *ops): + """Apply to _max_degrees.""" def max_single(ops): if any(isinstance(o, IrreducibleInt) for o in ops): return IrreducibleInt(max(ops)) @@ -114,54 +142,71 @@ def max_single(ops): return max_single(ops + (0,)) def _not_handled(self, v, *args): + """Apply to _not_handled.""" raise ValueError(f"Missing degree handler for type {v._ufl_class_.__name__}") def expr(self, v, *ops): - "For most operators we take the max degree of its operands." + """Apply to expr. + + For most operators we take the max degree of its operands. + """ warnings.warn(f"Missing degree estimation handler for type {v._ufl_class_.__name__}") return self._add_degrees(v, *ops) # Utility types with no degree concept def multi_index(self, v): + """Apply to multi_index.""" return None def label(self, v): + """Apply to label.""" return None # Fall-through, indexing and similar types def reference_value(self, rv, f): + """Apply to reference_value.""" return f def variable(self, v, e, a): + """Apply to variable.""" return e def transposed(self, v, A): + """Apply to transposed.""" return A def index_sum(self, v, A, ii): + """Apply to index_sum.""" return A def indexed(self, v, A, ii): + """Apply to indexed.""" return A def component_tensor(self, v, A, ii): + """Apply to component_tensor.""" return A list_tensor = _max_degrees def positive_restricted(self, v, a): + """Apply to positive_restricted.""" return a def negative_restricted(self, v, a): + """Apply to negative_restricted.""" return a def conj(self, v, a): + """Apply to conj.""" return a def real(self, v, a): + """Apply to real.""" return a def imag(self, v, a): + """Apply to imag.""" return a # A sum takes the max degree of its operands: @@ -182,11 +227,17 @@ def imag(self, v, a): reference_curl = _reduce_degree def cell_avg(self, v, a): - "Cell average of a function is always cellwise constant." + """Apply to cell_avg. + + Cell average of a function is always cellwise constant. + """ return 0 def facet_avg(self, v, a): - "Facet average of a function is always cellwise constant." + """Apply to facet_avg. + + Facet average of a function is always cellwise constant. + """ return 0 # A product accumulates the degrees of its operands: @@ -213,21 +264,28 @@ def facet_avg(self, v, a): sym = _not_handled def abs(self, v, a): - "This is a heuristic, correct if there is no " + """Apply to abs. + + This is a heuristic, correct if there is no. + """ if a == 0: return a else: return a def division(self, v, *ops): - "Using the sum here is a heuristic. Consider e.g. (x+1)/(x-1)." + """Apply to division. + + Using the sum here is a heuristic. Consider e.g. (x+1)/(x-1). + """ return self._add_degrees(v, *ops) def power(self, v, a, b): - """If b is a positive integer: - degree(a**b) == degree(a)*b - otherwise use the heuristic - degree(a**b) == degree(a) + 2""" + """Apply to power. + + If b is a positive integer: degree(a**b) == degree(a)*b + otherwise use the heuristic: degree(a**b) == degree(a) + 2. + """ f, g = v.ufl_operands if isinstance(g, IntValue): @@ -243,11 +301,12 @@ def power(self, v, a, b): return self._add_degrees(v, a, 2) def atan2(self, v, a, b): - """Using the heuristic + """Apply to atan2. + + Using the heuristic: degree(atan2(const,const)) == 0 degree(atan2(a,b)) == max(degree(a),degree(b))+2 - which can be wildly inaccurate but at least - gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat high integration degree. """ if a or b: return self._add_degrees(v, self._max_degrees(v, a, b), 2) @@ -255,11 +314,12 @@ def atan2(self, v, a, b): return self._max_degrees(v, a, b) def math_function(self, v, a): - """Using the heuristic + """Apply to math_function. + + Using the heuristic: degree(sin(const)) == 0 degree(sin(a)) == degree(a)+2 - which can be wildly inaccurate but at least - gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat high integration degree. """ if a: return self._add_degrees(v, a, 2) @@ -267,11 +327,12 @@ def math_function(self, v, a): return a def bessel_function(self, v, nu, x): - """Using the heuristic + """Apply to bessel_function. + + Using the heuristic degree(bessel_*(const)) == 0 degree(bessel_*(x)) == degree(x)+2 - which can be wildly inaccurate but at least - gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat high integration degree. """ if x: return self._add_degrees(v, x, 2) @@ -279,35 +340,42 @@ def bessel_function(self, v, nu, x): return x def condition(self, v, *args): + """Apply to condition.""" return None def conditional(self, v, c, t, f): - """Degree of condition does not - influence degree of values which - conditional takes. So heuristicaly - taking max of true degree and false - degree. This will be exact in cells - where condition takes single value. - For improving accuracy of quadrature - near condition transition surface - quadrature order must be adjusted manually.""" + """Apply to conditional. + + Degree of condition does not influence degree of values which conditional takes. So + heuristicaly taking max of true degree and false degree. This will be exact in cells + where condition takes single value. For improving accuracy of quadrature near + condition transition surface quadrature order must be adjusted manually. + """ return self._max_degrees(v, t, f) def min_value(self, v, a, r): - """Same as conditional.""" + """Apply to min_value. + + Same as conditional. + """ return self._max_degrees(v, a, r) max_value = min_value def coordinate_derivative(self, v, integrand_degree, b, direction_degree, d): - """ We use the heuristic that a shape derivative in direction V + """Apply to coordinate_derivative. + + We use the heuristic that a shape derivative in direction V introduces terms V and grad(V) into the integrand. Hence we add the - degree of the deformation to the estimate. """ + degree of the deformation to the estimate. + """ return self._add_degrees(v, integrand_degree, direction_degree) def expr_list(self, v, *o): + """Apply to expr_list.""" return self._max_degrees(v, *o) def expr_mapping(self, v, *o): + """Apply to expr_mapping.""" return self._max_degrees(v, *o) @@ -315,7 +383,7 @@ def estimate_total_polynomial_degree(e, default_degree=1, element_replace_map={}): """Estimate total polynomial degree of integrand. - NB! Although some compound types are supported here, + NB: Although some compound types are supported here, some derivatives and compounds must be preprocessed prior to degree estimation. In generic code, this algorithm should only be applied after preprocessing. diff --git a/ufl/algorithms/expand_compounds.py b/ufl/algorithms/expand_compounds.py index 59e08cf5a..04290729b 100644 --- a/ufl/algorithms/expand_compounds.py +++ b/ufl/algorithms/expand_compounds.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""Algorithm for expanding compound expressions into -equivalent representations using basic operators.""" +"""Algorithm for expanding compound expressions into equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # @@ -10,8 +8,13 @@ # # Modified by Anders Logg, 2009-2010 +import warnings from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering def expand_compounds(e): + """Expand compounds.""" + warnings.warn("The use of expand_compounds is deprecated and will be removed after December 2023. " + "Please, use apply_algebra_lowering directly instead", FutureWarning) + return apply_algebra_lowering(e) diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index 7b937bf51..b36923ac5 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -"""This module defines expression transformation utilities, -for expanding free indices in expressions to explicit fixed -indices only.""" +"""This module defines expression transformation utilities. + +These utilities are for expanding free indices in expressions to explicit fixed indices only. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -20,20 +20,22 @@ class IndexExpander(ReuseTransformer): - """...""" + """Index expander.""" def __init__(self): + """Initialise.""" ReuseTransformer.__init__(self) self._components = Stack() self._index2value = StackDict() def component(self): - "Return current component tuple." + """Return current component tuple.""" if self._components: return self._components.peek() return () def terminal(self, x): + """Apply to terminal.""" if x.ufl_shape: c = self.component() if len(x.ufl_shape) != len(c): @@ -42,6 +44,7 @@ def terminal(self, x): return x def form_argument(self, x): + """Apply to form_argument.""" sh = x.ufl_shape if sh == (): return x @@ -63,6 +66,7 @@ def form_argument(self, x): return x[c] def zero(self, x): + """Apply to zero.""" if len(x.ufl_shape) != len(self.component()): raise ValueError("Component size mismatch.") @@ -74,6 +78,7 @@ def zero(self, x): return Zero() def scalar_value(self, x): + """Apply to scalar_value.""" if len(x.ufl_shape) != len(self.component()): self.print_visit_stack() if len(x.ufl_shape) != len(self.component()): @@ -86,6 +91,7 @@ def scalar_value(self, x): return x._ufl_class_(x.value()) def conditional(self, x): + """Apply to conditional.""" c, t, f = x.ufl_operands # Not accepting nonscalars in condition @@ -104,6 +110,7 @@ def conditional(self, x): return self.reuse_if_possible(x, c, t, f) def division(self, x): + """Apply to division.""" a, b = x.ufl_operands # Not accepting nonscalars in division anymore @@ -123,6 +130,7 @@ def division(self, x): return self.reuse_if_possible(x, a, b) def index_sum(self, x): + """Apply to index_sum.""" ops = [] summand, multiindex = x.ufl_operands index, = multiindex @@ -138,6 +146,7 @@ def index_sum(self, x): return sum(ops) def _multi_index_values(self, x): + """Apply to _multi_index_values.""" comp = [] for i in x._indices: if isinstance(i, FixedIndex): @@ -147,10 +156,12 @@ def _multi_index_values(self, x): return tuple(comp) def multi_index(self, x): + """Apply to multi_index.""" comp = self._multi_index_values(x) return MultiIndex(tuple(FixedIndex(i) for i in comp)) def indexed(self, x): + """Apply to indexed.""" A, ii = x.ufl_operands # Push new component built from index value map @@ -173,6 +184,7 @@ def indexed(self, x): return result def component_tensor(self, x): + """Apply to component_tensor.""" # This function evaluates the tensor expression # with indices equal to the current component tuple expression, indices = x.ufl_operands @@ -197,6 +209,7 @@ def component_tensor(self, x): return result def list_tensor(self, x): + """Apply to list_tensor.""" # Pick the right subtensor and subcomponent c = self.component() c0, c1 = c[0], c[1:] @@ -208,6 +221,7 @@ def list_tensor(self, x): return r def grad(self, x): + """Apply to grad.""" f, = x.ufl_operands if not isinstance(f, (Terminal, Grad)): raise ValueError("Expecting expand_derivatives to have been applied.") @@ -216,4 +230,5 @@ def grad(self, x): def expand_indices(e): + """Expand indices.""" return apply_transformer(e, IndexExpander()) diff --git a/ufl/algorithms/formdata.py b/ufl/algorithms/formdata.py index c6d66e729..c5162c140 100644 --- a/ufl/algorithms/formdata.py +++ b/ufl/algorithms/formdata.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """FormData class easy for collecting of various data about a form.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -13,22 +12,17 @@ class FormData(object): - """Class collecting various information extracted from a Form by - calling preprocess. - - """ + """Class collecting various information extracted from a Form by calling preprocess.""" def __init__(self): - "Create empty form data for given form." + """Create empty form data for given form.""" def __str__(self): - "Return formatted summary of form data" + """Return formatted summary of form data.""" types = sorted(self.max_subdomain_ids.keys()) - geometry = ( - ("Geometric dimension", self.geometric_dimension), - ) - subdomains = tuple(("Number of %s subdomains" % integral_type, - self.max_subdomain_ids[integral_type]) for integral_type in types) + geometry = (("Geometric dimension", self.geometric_dimension), ) + subdomains = tuple((f"Number of {integral_type} subdomains", self.max_subdomain_ids[integral_type]) + for integral_type in types) functions = ( # Arguments ("Rank", self.rank), diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index 622461580..0bb4f38a5 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """A collection of utility algorithms for handling UFL files.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -23,7 +22,10 @@ class FileData(object): + """File data.""" + def __init__(self): + """Initialise.""" self.elements = [] self.coefficients = [] self.expressions = [] @@ -33,6 +35,7 @@ def __init__(self): self.reserved_objects = {} def __bool__(self): + """Convert to a bool.""" return bool(self.elements or self.coefficients or self.forms or self.expressions or # noqa: W504 self.object_names or self.object_by_name or self.reserved_objects) @@ -40,9 +43,11 @@ def __bool__(self): def read_lines_decoded(fn): + """Read decoded lines of a UFL file.""" r = re.compile(b".*coding: *([^ ]+)") def match(line): + """Match.""" return r.match(line, re.ASCII) # First read lines as bytes @@ -68,7 +73,7 @@ def match(line): def read_ufl_file(filename): - "Read a UFL file." + """Read a UFL file.""" if not os.path.exists(filename): raise ValueError(f"File '{filename}' doesn't exist.") lines = read_lines_decoded(filename) @@ -77,14 +82,14 @@ def read_ufl_file(filename): def execute_ufl_code(uflcode): - # Execute code + """Execute code.""" namespace = {} exec(uflcode, namespace) return namespace def interpret_ufl_namespace(namespace): - "Takes a namespace dict from an executed ufl file and converts it to a FileData object." + """Take a namespace dict from an executed ufl file and convert it to a FileData object.""" # Object to hold all returned data ufd = FileData() @@ -171,7 +176,7 @@ def get_form(name): def load_ufl_file(filename): - "Load a UFL file with elements, coefficients, expressions and forms." + """Load a UFL file with elements, coefficients, expressions and forms.""" # Read code from file and execute it uflcode = read_ufl_file(filename) namespace = execute_ufl_code(uflcode) @@ -179,6 +184,6 @@ def load_ufl_file(filename): def load_forms(filename): - "Return a list of all forms in a file." + """Return a list of all forms in a file.""" ufd = load_ufl_file(filename) return ufd.forms diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index 032352b92..755b479df 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Extract part of a form in a mixed FunctionSpace." +"""Extract part of a form in a mixed FunctionSpace.""" # Copyright (C) 2016 Chris Richardson and Lawrence Mitchell # @@ -18,13 +17,16 @@ class FormSplitter(MultiFunction): + """Form splitter.""" def split(self, form, ix, iy=0): + """Split.""" # Remember which block to extract self.idx = [ix, iy] return map_integrand_dags(self, form) def argument(self, obj): + """Apply to argument.""" if (obj.part() is not None): # Mixed element built from MixedFunctionSpace, # whose sub-function spaces are indexed by obj.part() @@ -70,12 +72,14 @@ def argument(self, obj): return as_vector(args) def multi_index(self, obj): + """Apply to multi_index.""" return obj expr = MultiFunction.reuse_if_untouched def extract_blocks(form, i=None, j=None): + """Extract blocks.""" fs = FormSplitter() arguments = form.arguments() forms = [] diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 84757999a..6a473c71a 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""This module defines utilities for transforming -complete Forms into new related Forms.""" +"""This module defines utilities for transforming complete Forms into new related Forms.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -30,6 +28,7 @@ # FIXME: Don't use this below, it makes partextracter more expensive than necessary def _expr_has_terminal_types(expr, ufl_types): + """Check if an expression has terminal types.""" input = [expr] while input: e = input.pop() @@ -42,22 +41,23 @@ def _expr_has_terminal_types(expr, ufl_types): def zero_expr(e): + """Create a zero expression.""" return Zero(e.ufl_shape, e.ufl_free_indices, e.ufl_index_dimensions) class PartExtracter(Transformer): - """ - PartExtracter extracts those parts of a form that contain the - given argument(s). - """ + """PartExtracter extracts those parts of a form that contain the given argument(s).""" def __init__(self, arguments): + """Initialise.""" Transformer.__init__(self) self._want = set(arguments) def expr(self, x): - """The default is a nonlinear operator not accepting any - Arguments among its children.""" + """Apply to expr. + + The default is a nonlinear operator not accepting any Arguments among its children. + """ if _expr_has_terminal_types(x, Argument): raise ValueError(f"Found Argument in {ufl_err_str(x)}, this is an invalid expression.") return (x, set()) @@ -67,8 +67,7 @@ def expr(self, x): terminal = expr def variable(self, x): - "Return relevant parts of this variable." - + """Return relevant parts of this variable.""" # Extract parts/provides from this variable's expression expression, label = x.ufl_operands part, provides = self.visit(expression) @@ -84,8 +83,7 @@ def variable(self, x): return (x, provides) def argument(self, x): - "Return itself unless itself provides too much." - + """Return itself unless itself provides too much.""" # An argument provides itself provides = {x} @@ -96,9 +94,7 @@ def argument(self, x): return (x, provides) def sum(self, x): - """ - Return the terms that might eventually yield the correct - parts(!) + """Return the terms that might eventually yield the correct parts(!). The logic required for sums is a bit elaborate: @@ -120,9 +116,8 @@ def sum(self, x): 3) Bottom-line: if there are terms with providing different arguments -- provide terms that contain the most arguments. If there are terms providing different sets of same size -> throw - error (e.g. Argument(-1) + Argument(-2)) + error (e.g. Argument(-1) + Argument(-2)). """ - parts_that_provide = {} # 1. Skip terms that provide too much @@ -169,9 +164,11 @@ def sum(self, x): return (x, most_provided) def product(self, x, *ops): - """ Note: Product is a visit-children-first handler. ops are - the visited factors.""" + """Apply to product. + Note: Product is a visit-children-first handler. ops are + the visited factors. + """ provides = set() factors = [] @@ -200,8 +197,7 @@ def product(self, x, *ops): dot = product def division(self, x): - "Return parts_of_numerator/denominator." - + """Return parts_of_numerator/denominator.""" # Get numerator and denominator numerator, denominator = x.ufl_operands @@ -224,9 +220,11 @@ def division(self, x): return (x, provides) def linear_operator(self, x, arg): - """A linear operator with a single operand accepting arity > 0, - providing whatever Argument its operand does.""" + """Apply to linear_operator. + A linear operator with a single operand accepting arity > 0, + providing whatever Argument its operand does. + """ # linear_operator is a visit-children-first handler. Handled # arguments are in arg. part, provides = arg @@ -257,9 +255,7 @@ def linear_operator(self, x, arg): imag = linear_operator def linear_indexed_type(self, x): - """Return parts of expression belonging to this indexed - expression.""" - + """Return parts of expression belonging to this indexed expression.""" expression, index = x.ufl_operands part, provides = self.visit(expression) @@ -279,6 +275,7 @@ def linear_indexed_type(self, x): component_tensor = linear_indexed_type def list_tensor(self, x, *ops): + """Apply to list_tensor.""" # list_tensor is a visit-children-first handler. ops contains # the visited operands with their provides. (It follows that # none of the visited operands provide more than wanted.) @@ -307,7 +304,6 @@ def list_tensor(self, x, *ops): def compute_form_with_arity(form, arity, arguments=None): """Compute parts of form of given arity.""" - # Extract all arguments in form if arguments is None: arguments = form.arguments() @@ -317,7 +313,7 @@ def compute_form_with_arity(form, arity, arguments=None): raise ValueError("compute_form_with_arity cannot handle parts.") if len(arguments) < arity: - warnings.warn("Form has no parts with arity %d." % arity) + warnings.warn(f"Form has no parts with arity {arity}.") return 0 * form # Assuming that the form is not a sum of terms @@ -337,7 +333,6 @@ def _transform(e): def compute_form_arities(form): """Return set of arities of terms present in form.""" - # Extract all arguments present in form arguments = form.arguments() @@ -362,10 +357,8 @@ def compute_form_lhs(form): """Compute the left hand side of a form. Example: - ------- a = u*v*dx + f*v*dx a = lhs(a) -> u*v*dx - """ return compute_form_with_arity(form, 2) @@ -374,19 +367,17 @@ def compute_form_rhs(form): """Compute the right hand side of a form. Example: - ------- a = u*v*dx + f*v*dx L = rhs(a) -> -f*v*dx - """ return -compute_form_with_arity(form, 1) def compute_form_functional(form): - """Compute the functional part of a form, that - is the terms independent of Arguments. + """Compute the functional part of a form, that is the terms independent of Arguments. - (Used for testing, not sure if it's useful for anything?)""" + (Used for testing, not sure if it's useful for anything?) + """ return compute_form_with_arity(form, 0) diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 65ede8f0b..e1884b525 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Basic algorithms for applying functions to subexpressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -21,9 +20,7 @@ def map_integrands(function, form, only_integral_type=None): - """Apply transform(expression) to each integrand - expression in form, or to form if it is an Expr. - """ + """Apply transform(expression) to each integrand expression in form, or to form if it is an Expr.""" if isinstance(form, Form): mapped_integrals = [map_integrands(function, itg, only_integral_type) for itg in form.integrals()] @@ -67,5 +64,6 @@ def map_integrands(function, form, only_integral_type=None): def map_integrand_dags(function, form, only_integral_type=None, compress=True): + """Map integrand dags.""" return map_integrands(lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type) diff --git a/ufl/algorithms/multifunction.py b/ufl/algorithms/multifunction.py deleted file mode 100644 index b903f4434..000000000 --- a/ufl/algorithms/multifunction.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- -# Moved here to be usable in ufl.* files without depending on -# ufl.algorithms.*... - -from ufl.corealg.multifunction import MultiFunction # noqa: F401 diff --git a/ufl/algorithms/remove_complex_nodes.py b/ufl/algorithms/remove_complex_nodes.py index 061956b20..12a5b34e1 100644 --- a/ufl/algorithms/remove_complex_nodes.py +++ b/ufl/algorithms/remove_complex_nodes.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""Algorithm for removing conj, real, and imag nodes -from a form for when the user is in 'real mode'""" +"""Algorithm for removing conj, real, and imag nodes from a form for when the user is in 'real mode'.""" from ufl.corealg.multifunction import MultiFunction from ufl.constantvalue import ComplexValue @@ -8,19 +6,23 @@ class ComplexNodeRemoval(MultiFunction): - """Replaces complex operator nodes with their children""" + """Replaces complex operator nodes with their children.""" expr = MultiFunction.reuse_if_untouched def conj(self, o, a): + """Apply to conj.""" return a def real(self, o, a): + """Apply to real.""" return a def imag(self, o, a): + """Apply to imag.""" raise ValueError("Unexpected imag in real expression.") def terminal(self, t, *ops): + """Apply to terminal.""" if isinstance(t, ComplexValue): raise ValueError('Unexpected complex value in real expression.') else: @@ -28,8 +30,9 @@ def terminal(self, t, *ops): def remove_complex_nodes(expr): - """Replaces complex operator nodes with their children. This is called - during compute_form_data if the compiler wishes to compile + """Replaces complex operator nodes with their children. + + This is called during compute_form_data if the compiler wishes to compile real-valued forms. In essence this strips all trace of complex support from the preprocessed form. """ diff --git a/ufl/algorithms/renumbering.py b/ufl/algorithms/renumbering.py index f46d6b2e9..2f3d94a21 100644 --- a/ufl/algorithms/renumbering.py +++ b/ufl/algorithms/renumbering.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Algorithms for renumbering of counted objects, currently variables and indices." - +"""Algorithms for renumbering of counted objects, currently variables and indices.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -15,11 +13,15 @@ class VariableRenumberingTransformer(ReuseTransformer): + """Variable renumbering transformer.""" + def __init__(self): + """Initialise.""" ReuseTransformer.__init__(self) self.variable_map = {} def variable(self, o): + """Apply to variable.""" e, l = o.ufl_operands # noqa: E741 v = self.variable_map.get(l) if v is None: @@ -31,13 +33,18 @@ def variable(self, o): class IndexRenumberingTransformer(VariableRenumberingTransformer): - "This is a poorly designed algorithm. It is used in some tests, please do not use for anything else." + """Index renumbering transformer. + + This is a poorly designed algorithm. It is used in some tests, please do not use for anything else. + """ def __init__(self): + """Initialise.""" VariableRenumberingTransformer.__init__(self) self.index_map = {} def zero(self, o): + """Apply to zero.""" fi = o.ufl_free_indices fid = o.ufl_index_dimensions mapped_fi = tuple(self.index(Index(count=i)) for i in fi) @@ -46,6 +53,7 @@ def zero(self, o): return Zero(o.ufl_shape, new_fi, new_fid) def index(self, o): + """Apply to index.""" if isinstance(o, FixedIndex): return o else: @@ -57,11 +65,13 @@ def index(self, o): return i def multi_index(self, o): + """Apply to multi_index.""" new_indices = tuple(self.index(i) for i in o.indices()) return MultiIndex(new_indices) def renumber_indices(expr): + """Renumber indices.""" if isinstance(expr, Expr): num_free_indices = len(expr.ufl_free_indices) diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 0e73a8dff..fb62095cd 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Algorithm for replacing terminals in an expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg @@ -17,12 +16,16 @@ class Replacer(MultiFunction): + """Replacer.""" + def __init__(self, mapping): + """Initialize.""" super().__init__() self.mapping = mapping # One can replace Coarguments by 1-Forms def get_shape(x): + """Get the shape of an object.""" if isinstance(x, Form): return x.arguments()[0].ufl_shape return x.ufl_shape @@ -31,12 +34,14 @@ def get_shape(x): raise ValueError("Replacement expressions must have the same shape as what they replace.") def ufl_type(self, o, *args): + """Replace a ufl_type.""" try: return self.mapping[o] except KeyError: return self.reuse_if_untouched(o, *args) def external_operator(self, o): + """Replace an external_operator.""" o = self.mapping.get(o) or o if isinstance(o, ExternalOperator): new_ops = tuple(replace(op, self.mapping) for op in o.ufl_operands) @@ -45,6 +50,7 @@ def external_operator(self, o): return o def interpolate(self, o): + """Replace an interpolate.""" o = self.mapping.get(o) or o if isinstance(o, Interpolate): new_args = tuple(replace(arg, self.mapping) for arg in o.argument_slots()) @@ -52,16 +58,19 @@ def interpolate(self, o): return o def coefficient_derivative(self, o): + """Replace a coefficient derivative.""" raise ValueError("Derivatives should be applied before executing replace.") def replace(e, mapping): """Replace subexpressions in expression. - @param e: - An Expr or Form. - @param mapping: - A dict with from:to replacements to perform. + Params: + e: An Expr or Form + mapping: A dict with from:to replacements to perform + + Returns: + The expression with replacements performed """ mapping2 = dict((k, as_ufl(v)) for (k, v) in mapping.items()) diff --git a/ufl/algorithms/replace_derivative_nodes.py b/ufl/algorithms/replace_derivative_nodes.py index dafd873be..d779a790f 100644 --- a/ufl/algorithms/replace_derivative_nodes.py +++ b/ufl/algorithms/replace_derivative_nodes.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"""Algorithm for replacing derivative nodes in a BaseForm or Expr""" +"""Algorithm for replacing derivative nodes in a BaseForm or Expr.""" import ufl from ufl.corealg.multifunction import MultiFunction @@ -10,9 +9,10 @@ class DerivativeNodeReplacer(MultiFunction): - """Replace derivative nodes with new derivative nodes""" + """Replace derivative nodes with new derivative nodes.""" def __init__(self, mapping, **derivative_kwargs): + """Initialise.""" super().__init__() self.mapping = mapping self.der_kwargs = derivative_kwargs @@ -20,6 +20,7 @@ def __init__(self, mapping, **derivative_kwargs): expr = MultiFunction.reuse_if_untouched def coefficient_derivative(self, cd, o, coefficients, arguments, coefficient_derivatives): + """Apply to coefficient_derivative.""" der_kwargs = self.der_kwargs new_coefficients = tuple(self.mapping[c] if c in self.mapping.keys() else c for c in coefficients.ufl_operands) @@ -38,7 +39,9 @@ def coefficient_derivative(self, cd, o, coefficients, arguments, coefficient_der def replace_derivative_nodes(expr, mapping, **derivative_kwargs): - """Replaces derivative nodes, i.e. replaces the variable with respect to which the derivative is taken. + """Replaces derivative nodes. + + Replaces the variable with respect to which the derivative is taken. This is called during apply_derivatives to treat delayed derivatives. Example: Let u be a Coefficient, N an ExternalOperator independent of u (i.e. N's operands don't depend on u), @@ -53,12 +56,11 @@ def replace_derivative_nodes(expr, mapping, **derivative_kwargs): dFdu -> 2 * u * uhat * N * dx dFdN -> u ** 2 * Nhat * dx - @param e: - An Expr or BaseForm. - @param mapping: - A dict with from:to replacements to perform. - @param derivative_kwargs: - A dict containing the keyword arguments for derivative (i.e. `argument` and `coefficient_derivatives`). + Args: + expr: An Expr or BaseForm. + mapping: A dict with from:to replacements to perform. + derivative_kwargs: A dict containing the keyword arguments for derivative + (i.e. `argument` and `coefficient_derivatives`). """ mapping2 = dict((k, as_ufl(v)) for (k, v) in mapping.items()) return map_integrand_dags(DerivativeNodeReplacer(mapping2, **derivative_kwargs), expr) diff --git a/ufl/algorithms/signature.py b/ufl/algorithms/signature.py index 4650818c1..da3c516dc 100644 --- a/ufl/algorithms/signature.py +++ b/ufl/algorithms/signature.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Signature computation for forms.""" - # Copyright (C) 2012-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -18,6 +16,7 @@ def compute_multiindex_hashdata(expr, index_numbering): + """Compute multiindex hashdata.""" data = [] for i in expr: if isinstance(i, Index): @@ -34,7 +33,7 @@ def compute_multiindex_hashdata(expr, index_numbering): def compute_terminal_hashdata(expressions, renumbering): - + """Compute terminal hashdata.""" if not isinstance(expressions, list): expressions = [expressions] assert renumbering is not None @@ -87,6 +86,7 @@ def compute_terminal_hashdata(expressions, renumbering): def compute_expression_hashdata(expression, terminal_hashdata) -> bytes: + """Compute expression hashdata.""" cache = {} for expr in unique_post_traversal(expression): @@ -105,6 +105,7 @@ def compute_expression_hashdata(expression, terminal_hashdata) -> bytes: def compute_expression_signature(expr, renumbering): # FIXME: Fix callers + """Compute expression signature.""" # FIXME: Rewrite in terms of compute_form_signature? # Build hashdata for all terminals first @@ -119,6 +120,7 @@ def compute_expression_signature(expr, renumbering): # FIXME: Fix callers def compute_form_signature(form, renumbering): # FIXME: Fix callers + """Compute form signature.""" # Extract integrands integrals = form.integrals() integrands = [integral.integrand() for integral in integrals] diff --git a/ufl/algorithms/strip_terminal_data.py b/ufl/algorithms/strip_terminal_data.py index 076db443c..04e48336d 100644 --- a/ufl/algorithms/strip_terminal_data.py +++ b/ufl/algorithms/strip_terminal_data.py @@ -1,6 +1,7 @@ -# -*- coding: utf-8 -*- -"""Algorithm for replacing form arguments with 'stripped' versions where any -data-carrying objects have been extracted to a mapping.""" +"""Algorithm for replacing form arguments with 'stripped' versions. + +In the stripped version, any data-carrying objects have been extracted to a mapping. +""" from ufl.classes import Form, Integral from ufl.classes import Argument, Coefficient, Constant @@ -12,21 +13,27 @@ class TerminalStripper(MultiFunction): + """Terminal stripper.""" + def __init__(self): + """Initialise.""" super().__init__() self.mapping = {} def argument(self, o): + """Apply to argument.""" o_new = Argument(strip_function_space(o.ufl_function_space()), o.number(), o.part()) return self.mapping.setdefault(o, o_new) def coefficient(self, o): + """Apply to coefficient.""" o_new = Coefficient(strip_function_space(o.ufl_function_space()), o.count()) return self.mapping.setdefault(o, o_new) def constant(self, o): + """Apply to constant.""" o_new = Constant(strip_domain(o.ufl_domain()), o.ufl_shape, o.count()) return self.mapping.setdefault(o, o_new) @@ -35,18 +42,19 @@ def constant(self, o): def strip_terminal_data(o): - """Return a new form where all terminals have been replaced by UFL-only - equivalents. - - :arg o: The object to be stripped. This must either be a :class:`~.Form` - or :class:`~.Integral`. - :returns: A 2-tuple containing an equivalent UFL-only object and a mapping - allowing the original form to be reconstructed using - :func:`replace_terminal_data`. + """Return a new form where all terminals have been replaced by UFL-only equivalents. This function is useful for forms containing augmented UFL objects that hold references to large data structures. These objects are be extracted into the mapping allowing the form to be cached without leaking memory. + + Args: + o: The object to be stripped. This must either be a Form or Integral + + Returns: + A 2-tuple containing an equivalent UFL-only object and a mapping + allowing the original form to be reconstructed using replace_terminal_data + """ # We need to keep track of two maps because integrals store references to the # domain and ``replace`` expects only a mapping containing ``Expr`` objects. @@ -73,14 +81,15 @@ def strip_terminal_data(o): def replace_terminal_data(o, mapping): - """Return a new form where the terminals have been replaced using the - provided mapping. - - :arg o: The object to have its terminals replaced. This must either be a - :class:`~.Form` or :class:`~.Integral`. - :arg mapping: A mapping suitable for reconstructing the form such as the one - returned by :func:`strip_terminal_data`. - :returns: The new form. + """Return a new form where the terminals have been replaced using the provided mapping. + + Args: + o: The object to have its terminals replaced. This must either be a Form or Integral. + mapping: A mapping suitable for reconstructing the form such as the one + returned by strip_terminal_data. + + Returns: + The new form. """ if isinstance(o, Form): return Form([replace_terminal_data(itg, mapping) for itg in o.integrals()]) @@ -93,7 +102,7 @@ def replace_terminal_data(o, mapping): def strip_function_space(function_space): - "Return a new function space with all non-UFL information removed." + """Return a new function space with all non-UFL information removed.""" if isinstance(function_space, FunctionSpace): return FunctionSpace(strip_domain(function_space.ufl_domain()), function_space.ufl_element()) @@ -108,7 +117,7 @@ def strip_function_space(function_space): def strip_domain(domain): - "Return a new domain with all non-UFL information removed." + """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): diff --git a/ufl/algorithms/transformer.py b/ufl/algorithms/transformer.py index dbbc96f69..e6eed85c3 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -1,9 +1,10 @@ -# -*- coding: utf-8 -*- -"""This module defines the Transformer base class and some +"""Transformer. + +This module defines the Transformer base class and some basic specializations to further base other algorithms upon, as well as some utilities for easier application of such -algorithms.""" - +algorithms. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -20,7 +21,7 @@ def is_post_handler(function): - "Is this a handler that expects transformed children as input?" + """Check if function is a handler that expects transformed children as input.""" insp = inspect.getfullargspec(function) num_args = len(insp[0]) + int(insp[1] is not None) visit_children_first = num_args > 2 @@ -28,11 +29,15 @@ def is_post_handler(function): class Transformer(object): - """Base class for a visitor-like algorithm design pattern used to - transform expression trees from one representation to another.""" + """Transformer. + + Base class for a visitor-like algorithm design pattern used to + transform expression trees from one representation to another. + """ _handlers_cache = {} def __init__(self, variable_cache=None): + """Initialise.""" if variable_cache is None: variable_cache = {} self._variable_cache = variable_cache @@ -75,10 +80,12 @@ def __init__(self, variable_cache=None): self._visit_stack = [] def print_visit_stack(self): + """Print visit stack.""" print("/" * 80) print("Visit stack in Transformer:") def sstr(s): + """Format.""" ss = str(type(s)) + " ; " n = 160 - len(ss) return ss + str(s)[:n] @@ -87,6 +94,7 @@ def sstr(s): print("\\" * 80) def visit(self, o): + """Visit.""" # Update stack self._visit_stack.append(o) @@ -94,10 +102,6 @@ def visit(self, o): # external subclass of the actual UFL class) h, visit_children_first = self._handlers[o._ufl_typecode_] - # if not h: - # # Failed to find a handler! Should never happen, but will happen if a non-Expr object is visited. - # raise ValueError("Can't handle objects of type %s" % str(type(o))) - # Is this a handler that expects transformed children as # input? if visit_children_first: @@ -113,20 +117,17 @@ def visit(self, o): return r def undefined(self, o): - "Trigger error." + """Trigger error.""" raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") def reuse(self, o): - "Always reuse Expr (ignore children)" + """Reuse Expr (ignore children).""" return o def reuse_if_untouched(self, o, *ops): """Reuse object if operands are the same objects. - Use in your own subclass by setting e.g. - - expr = MultiFunction.reuse_if_untouched - + Use in your own subclass by setting e.g. `expr = MultiFunction.reuse_if_untouched` as a default rule. """ if all(a is b for a, b in zip(o.ufl_operands, ops)): @@ -138,7 +139,7 @@ def reuse_if_untouched(self, o, *ops): reuse_if_possible = reuse_if_untouched def always_reconstruct(self, o, *operands): - "Always reconstruct expr." + """Reconstruct expr.""" return o._ufl_expr_reconstruct_(*operands) # Set default behaviour for any UFLType @@ -148,6 +149,7 @@ def always_reconstruct(self, o, *operands): terminal = reuse def reuse_variable(self, o): + """Reuse variable.""" # Check variable cache to reuse previously transformed # variable if possible e, l = o.ufl_operands # noqa: E741 @@ -170,6 +172,7 @@ def reuse_variable(self, o): return v def reconstruct_variable(self, o): + """Reconstruct variable.""" # Check variable cache to reuse previously transformed # variable if possible e, l = o.ufl_operands # noqa: E741 @@ -187,7 +190,10 @@ def reconstruct_variable(self, o): class ReuseTransformer(Transformer): + """Reuse transformer.""" + def __init__(self, variable_cache=None): + """Initialise.""" Transformer.__init__(self, variable_cache) # Set default behaviour for any Expr @@ -201,7 +207,10 @@ def __init__(self, variable_cache=None): class CopyTransformer(Transformer): + """Copy transformer.""" + def __init__(self, variable_cache=None): + """Initialise.""" Transformer.__init__(self, variable_cache) # Set default behaviour for any Expr @@ -215,20 +224,23 @@ def __init__(self, variable_cache=None): class VariableStripper(ReuseTransformer): + """Variable stripper.""" + def __init__(self): + """Initialise.""" ReuseTransformer.__init__(self) def variable(self, o): + """Visit a variable.""" return self.visit(o.ufl_operands[0]) def apply_transformer(e, transformer, integral_type=None): - """Apply transformer.visit(expression) to each integrand - expression in form, or to form if it is an Expr.""" + """Apply transformer.visit(expression) to each integrand expression in form, or to form if it is an Expr.""" return map_integrands(lambda expr: transformer.visit(expr), e, integral_type) def strip_variables(e): - "Replace all Variable instances with the expression they represent." + """Replace all Variable instances with the expression they represent.""" return apply_transformer(e, VariableStripper()) diff --git a/ufl/algorithms/traversal.py b/ufl/algorithms/traversal.py index aab1573aa..7a5c39918 100644 --- a/ufl/algorithms/traversal.py +++ b/ufl/algorithms/traversal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module contains algorithms for traversing expression trees in different ways.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg @@ -19,8 +18,8 @@ # --- Traversal utilities --- def iter_expressions(a): - """Utility function to handle Form, Integral and any Expr - the same way when inspecting expressions. + """Utility function to handle Form, Integral and any Expr the same way when inspecting expressions. + Returns an iterable over Expr instances: - a is an Expr: (a,) - a is an Integral: the integrand expression of a diff --git a/ufl/argument.py b/ufl/argument.py index b0a68422c..876354301 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- -"""This module defines the class Argument and a number of related -classes (functions), including TestFunction and TrialFunction.""" +"""Argument. +This module defines the class Argument and a number of related +classes (functions), including TestFunction and TrialFunction. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -33,14 +34,16 @@ class BaseArgument(object): """UFL value: Representation of an argument to a form.""" + __slots__ = () _ufl_is_abstract_ = True def __getnewargs__(self): + """Get new args.""" return (self._ufl_function_space, self._number, self._part) def __init__(self, function_space, number, part=None): - + """initialise.""" if isinstance(function_space, FiniteElementBase): # For legacy support for UFL files using cells, we map the cell to # the default Mesh @@ -66,11 +69,11 @@ def __init__(self, function_space, number, part=None): @property def ufl_shape(self): - "Return the associated UFL shape." + """Return the associated UFL shape.""" return self._ufl_shape def ufl_function_space(self): - "Get the function space of this Argument." + """Get the function space of this Argument.""" return self._ufl_function_space def ufl_domain(self): @@ -82,14 +85,15 @@ def ufl_element(self): return self._ufl_function_space.ufl_element() def number(self): - "Return the Argument number." + """Return the Argument number.""" return self._number def part(self): + """Return the part.""" return self._part def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # TODO: Should in principle do like with Coefficient, # but that may currently simplify away some arguments # we want to keep, or? See issue#13. @@ -101,11 +105,12 @@ def ufl_domains(self): return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): - "Signature data for form arguments depend on the global numbering of the form arguments and domains." + """Signature data for form arguments depend on the global numbering of the form arguments and domains.""" fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Argument", self._number, self._part, fsdata) def __str__(self): + """Format as a string.""" number = str(self._number) if len(number) == 1: s = "v_%s" % number @@ -120,10 +125,13 @@ def __str__(self): return s def __repr__(self): + """Representation.""" return self._repr def __eq__(self, other): - """Deliberately comparing exact type and not using isinstance here, + """Check equality. + + Deliberately comparing exact type and not using isinstance here, meaning eventual subclasses must reimplement this function to work correctly, and instances of this class will compare not equal to instances of eventual subclasses. The overloading allows @@ -143,6 +151,7 @@ def __eq__(self, other): @ufl_type() class Argument(FormArgument, BaseArgument): """UFL value: Representation of an argument to a form.""" + __slots__ = ( "_ufl_function_space", "_ufl_shape", @@ -160,11 +169,13 @@ class Argument(FormArgument, BaseArgument): __eq__ = BaseArgument.__eq__ def __new__(cls, *args, **kw): + """Create new Argument.""" if args[0] and is_dual(args[0]): return Coargument(*args, **kw) return super().__new__(cls) def __init__(self, function_space, number, part=None): + """Initialise.""" FormArgument.__init__(self) BaseArgument.__init__(self, function_space, number, part) @@ -172,15 +183,18 @@ def __init__(self, function_space, number, part=None): repr(self._ufl_function_space), repr(self._number), repr(self._part)) def ufl_domains(self): + """Return UFL domains.""" return BaseArgument.ufl_domains(self) def __repr__(self): + """Representation.""" return self._repr @ufl_type() class Coargument(BaseForm, BaseArgument): """UFL value: Representation of an argument to a form in a dual space.""" + __slots__ = ( "_ufl_function_space", "_ufl_shape", @@ -197,12 +211,14 @@ class Coargument(BaseForm, BaseArgument): _dual = True def __new__(cls, *args, **kw): + """Create a new Coargument.""" if args[0] and is_primal(args[0]): raise ValueError("ufl.Coargument takes in a dual space! If you want to define an argument " "in the primal space you should use ufl.Argument.") return super().__new__(cls) def __init__(self, function_space, number, part=None): + """Initialise.""" BaseArgument.__init__(self, function_space, number, part) BaseForm.__init__(self) @@ -212,13 +228,13 @@ def __init__(self, function_space, number, part=None): repr(self._ufl_function_space), repr(self._number), repr(self._part)) def arguments(self, outer_form=None): - "Return all ``Argument`` objects found in form." + """Return all Argument objects found in form.""" if self._arguments is None: self._analyze_form_arguments(outer_form=outer_form) return self._arguments def _analyze_form_arguments(self, outer_form=None): - "Analyze which Argument and Coefficient objects can be found in the form." + """Analyze which Argument and Coefficient objects can be found in the form.""" # Define canonical numbering of arguments and coefficients self._coefficients = () # Coarguments map from V* to V*, i.e. V* -> V*, or equivalently V* x V -> R. @@ -231,9 +247,11 @@ def _analyze_form_arguments(self, outer_form=None): self._arguments = (self,) def ufl_domain(self): + """Return the UFL domain.""" return BaseArgument.ufl_domain(self) def equals(self, other): + """Check equality.""" if type(other) is not Coargument: return False if self is other: @@ -242,7 +260,7 @@ def equals(self, other): self._number == other._number and self._part == other._part) def __hash__(self): - """Hash code for use in dicts.""" + """Hash.""" return hash(("Coargument", hash(self._ufl_function_space), self._number, @@ -264,8 +282,10 @@ def TrialFunction(function_space, part=None): # --- Helper functions for creating subfunctions on mixed elements --- def Arguments(function_space, number): - """UFL value: Create an Argument in a mixed space, and return a - tuple with the function components corresponding to the subelements.""" + """Create an Argument in a mixed space. + + Returns a tuple with the function components corresponding to the subelements. + """ if isinstance(function_space, MixedFunctionSpace): return [Argument(function_space.ufl_sub_space(i), number, i) for i in range(function_space.num_sub_spaces())] @@ -274,12 +294,16 @@ def Arguments(function_space, number): def TestFunctions(function_space): - """UFL value: Create a TestFunction in a mixed space, and return a - tuple with the function components corresponding to the subelements.""" + """Create a TestFunction in a mixed space. + + Returns a tuple with the function components corresponding to the subelements. + """ return Arguments(function_space, 0) def TrialFunctions(function_space): - """UFL value: Create a TrialFunction in a mixed space, and return a - tuple with the function components corresponding to the subelements.""" + """Create a TrialFunction in a mixed space. + + Returns a tuple with the function components corresponding to the subelements. + """ return Arguments(function_space, 1) diff --git a/ufl/averaging.py b/ufl/averaging.py index 055695ed3..dc6801cc6 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -16,27 +16,33 @@ num_ops=1, is_evaluation=True) class CellAvg(Operator): + """Cell average.""" + __slots__ = () def __new__(cls, f): + """Create a new CellAvg.""" if isinstance(f, ConstantValue): return f return super(CellAvg, cls).__new__(cls) def __init__(self, f): + """Initialise.""" Operator.__init__(self, (f,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): - "Performs an approximate symbolic evaluation, since we dont have a cell." + """Performs an approximate symbolic evaluation, since we don't have a cell.""" return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): - return "cell_avg(%s)" % (self.ufl_operands[0],) + """Format as a string.""" + return f"cell_avg({self.ufl_operands[0]})" @ufl_type(inherit_shape_from_operand=0, @@ -44,23 +50,29 @@ def __str__(self): num_ops=1, is_evaluation=True) class FacetAvg(Operator): + """Facet average.""" + __slots__ = () def __new__(cls, f): + """Create a new FacetAvg.""" if isinstance(f, ConstantValue): return f return super(FacetAvg, cls).__new__(cls) def __init__(self, f): + """Initialise.""" Operator.__init__(self, (f,)) @property def ufl_shape(self): + """Return the UFL shape.""" return self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): - "Performs an approximate symbolic evaluation, since we dont have a cell." + """Performs an approximate symbolic evaluation, since we dont have a cell.""" return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): - return "facet_avg(%s)" % (self.ufl_operands[0],) + """Format as a string.""" + return f"facet_avg({self.ufl_operands[0]})" diff --git a/ufl/cell.py b/ufl/cell.py index 2d50f6a77..85a556474 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -21,6 +21,7 @@ class AbstractCell(UFLObject): """A base class for all cells.""" + @abstractmethod def topological_dimension(self) -> int: """Return the dimension of the topology of this cell.""" @@ -209,6 +210,12 @@ class Cell(AbstractCell): "_sub_entities", "_sub_entity_types") def __init__(self, cellname: str, geometric_dimension: typing.Optional[int] = None): + """Initialise. + + Args: + cellname: Name of the cell + geometric_dimension: Geometric dimension + """ if cellname not in _sub_entity_celltypes: raise ValueError(f"Unsupported cell type: {cellname}") @@ -283,18 +290,21 @@ def cellname(self) -> str: return self._cellname def __str__(self) -> str: + """Format as a string.""" if self._gdim == self._tdim: return self._cellname else: return f"{self._cellname}{self._gdim}D" def __repr__(self) -> str: + """Representation.""" if self._gdim == self._tdim: return self._cellname else: return f"Cell({self._cellname}, {self._gdim})" def _ufl_hash_data_(self) -> typing.Hashable: + """UFL hash data.""" return (self._cellname, self._gdim) def reconstruct(self, **kwargs: typing.Any) -> Cell: @@ -309,9 +319,17 @@ def reconstruct(self, **kwargs: typing.Any) -> Cell: class TensorProductCell(AbstractCell): + """Tensor product cell.""" + __slots__ = ("_cells", "_tdim", "_gdim") def __init__(self, *cells: Cell, geometric_dimension: typing.Optional[int] = None): + """Initialise. + + Args: + cells: Cells to take the tensor product of + geometric_dimension: Geometric dimension + """ self._cells = tuple(as_cell(cell) for cell in cells) self._tdim = sum([cell.topological_dimension() for cell in self._cells]) @@ -392,6 +410,7 @@ def cellname(self) -> str: return " * ".join([cell.cellname() for cell in self._cells]) def __str__(self) -> str: + """Format as a string.""" s = "TensorProductCell(" s += ", ".join(f"{c!r}" for c in self._cells) if self._tdim != self._gdim: @@ -400,9 +419,11 @@ def __str__(self) -> str: return s def __repr__(self) -> str: + """Representation.""" return str(self) def _ufl_hash_data_(self) -> typing.Hashable: + """UFL hash data.""" return tuple(c._ufl_hash_data_() for c in self._cells) + (self._gdim,) def reconstruct(self, **kwargs: typing.Any) -> Cell: diff --git a/ufl/checks.py b/ufl/checks.py index ca84d7274..cd47a272c 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -13,32 +13,32 @@ def is_python_scalar(expression): - "Return True iff expression is of a Python scalar type." + """Return True iff expression is of a Python scalar type.""" return isinstance(expression, (int, float, complex)) def is_ufl_scalar(expression): - """Return True iff expression is scalar-valued, - but possibly containing free indices.""" + """Return True iff expression is scalar-valued, but possibly containing free indices.""" return isinstance(expression, Expr) and not expression.ufl_shape def is_true_ufl_scalar(expression): - """Return True iff expression is scalar-valued, - with no free indices.""" + """Return True iff expression is scalar-valued, with no free indices.""" return isinstance(expression, Expr) and not (expression.ufl_shape or expression.ufl_free_indices) def is_cellwise_constant(expr): - "Return whether expression is constant over a single cell." + """Return whether expression is constant over a single cell.""" # TODO: Implement more accurately considering e.g. derivatives? return all(t.is_cellwise_constant() for t in traverse_unique_terminals(expr)) def is_globally_constant(expr): - """Check if an expression is globally constant, which - includes spatially independent constant coefficients that - are not known before assembly time.""" + """Check if an expression is globally constant. + + This includes spatially independent constant coefficients that + are not known before assembly time. + """ # TODO: This does not consider gradients of coefficients, so false # negatives are possible. # from ufl.argument import Argument diff --git a/ufl/classes.py b/ufl/classes.py index 7bef75358..64d12284e 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -1,11 +1,11 @@ -# -*- coding: utf-8 -*- -# flake8: noqa -"""This file is useful for external code like tests and form compilers, +"""Classes. + +This file is useful for external code like tests and form compilers, since it enables the syntax "from ufl.classes import CellFacetooBar" for getting implementation details not exposed through the default ufl namespace. It also contains functionality used by algorithms for dealing with groups -of classes, and for mapping types to different handler functions.""" - +of classes, and for mapping types to different handler functions. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -68,6 +68,7 @@ "ufl_classes", "terminal_classes", "nonterminal_classes", + "__exproperators", ] diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 394e29e72..c5ba8dac7 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""This module defines the Coefficient class and a number -of related classes, including Constant.""" +"""This module defines the Coefficient class and a number of related classes, including Constant.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -40,9 +38,11 @@ class BaseCoefficient(Counted): _ufl_is_abstract_ = True def __getnewargs__(self): + """Get new args.""" return (self._ufl_function_space, self._count) def __init__(self, function_space, count=None): + """Initalise.""" Counted.__init__(self, count, Coefficient) if isinstance(function_space, FiniteElementBase): @@ -64,42 +64,45 @@ def __init__(self, function_space, count=None): @property def ufl_shape(self): - "Return the associated UFL shape." + """Return the associated UFL shape.""" return self._ufl_shape def ufl_function_space(self): - "Get the function space of this coefficient." + """Get the function space of this coefficient.""" return self._ufl_function_space def ufl_domain(self): - "Shortcut to get the domain of the function space of this coefficient." + """Shortcut to get the domain of the function space of this coefficient.""" return self._ufl_function_space.ufl_domain() def ufl_element(self): - "Shortcut to get the finite element of the function space of this coefficient." + """Shortcut to get the finite element of the function space of this coefficient.""" return self._ufl_function_space.ufl_element() def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" return self.ufl_element().is_cellwise_constant() def ufl_domains(self): - "Return tuple of domains related to this terminal object." + """Return tuple of domains related to this terminal object.""" return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): - "Signature data for form arguments depend on the global numbering of the form arguments and domains." + """Signature data for form arguments depend on the global numbering of the form arguments and domains.""" count = renumbering[self] fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Coefficient", count, fsdata) def __str__(self): + """Format as a string.""" return f"w_{self._count}" def __repr__(self): + """Representation.""" return self._repr def __eq__(self, other): + """Check equality.""" if not isinstance(other, BaseCoefficient): return False if self is other: @@ -126,12 +129,14 @@ class Cofunction(BaseCoefficient, BaseForm): _dual = True def __new__(cls, *args, **kw): + """Create a new Cofunction.""" if args[0] and is_primal(args[0]): raise ValueError("ufl.Cofunction takes in a dual space. If you want to define a coefficient " "in the primal space you should use ufl.Coefficient.") return super().__new__(cls) def __init__(self, function_space, count=None): + """Initialise.""" BaseCoefficient.__init__(self, function_space, count) BaseForm.__init__(self) @@ -141,6 +146,7 @@ def __init__(self, function_space, count=None): repr(self._ufl_function_space), repr(self._count)) def equals(self, other): + """Check equality.""" if type(other) is not Cofunction: return False if self is other: @@ -148,13 +154,13 @@ def equals(self, other): return self._count == other._count and self._ufl_function_space == other._ufl_function_space def __hash__(self): - """Hash code for use in dicts.""" + """Hash.""" return hash(("Cofunction", hash(self._ufl_function_space), self._count)) def _analyze_form_arguments(self): - "Analyze which Argument and Coefficient objects can be found in the form." + """Analyze which Argument and Coefficient objects can be found in the form.""" # Define canonical numbering of arguments and coefficients # Cofunctions have one argument in primal space as they map from V to R. self._arguments = (Argument(self._ufl_function_space.dual(), 0),) @@ -174,11 +180,13 @@ class Coefficient(FormArgument, BaseCoefficient): _ufl_signature_data_ = BaseCoefficient._ufl_signature_data_ def __new__(cls, *args, **kw): + """Create a new Coefficient.""" if args[0] and is_dual(args[0]): return Cofunction(*args, **kw) return super().__new__(cls) def __init__(self, function_space, count=None): + """Initialise.""" FormArgument.__init__(self) BaseCoefficient.__init__(self, function_space, count) @@ -186,9 +194,11 @@ def __init__(self, function_space, count=None): repr(self._ufl_function_space), repr(self._count)) def ufl_domains(self): + """Get the UFL domains.""" return BaseCoefficient.ufl_domains(self) def __eq__(self, other): + """Check equality.""" if not isinstance(other, Coefficient): return False if self is other: @@ -196,14 +206,17 @@ def __eq__(self, other): return self._count == other._count and self._ufl_function_space == other._ufl_function_space def __repr__(self): + """Representation.""" return self._repr # --- Helper functions for subfunctions on mixed elements --- def Coefficients(function_space): - """UFL value: Create a Coefficient in a mixed space, and return a - tuple with the function components corresponding to the subelements.""" + """Create a Coefficient in a mixed space. + + Returns a tuple with the function components corresponding to the subelements. + """ if isinstance(function_space, MixedFunctionSpace): return [Coefficient(fs) if is_primal(fs) else Cofunction(fs) for fs in function_space.num_sub_spaces()] diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index af3e05fd9..246ce765f 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Functions implementing compound expressions as equivalent representations using basic operators.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -9,12 +7,13 @@ # # Modified by Anders Logg, 2009-2010 +import warnings + from ufl.core.multiindex import indices, Index from ufl.tensors import as_tensor, as_matrix, as_vector from ufl.operators import sqrt from ufl.constantvalue import Zero, zero - # Note: To avoid typing errors, the expressions for cofactor and # deviatoric parts below were created with the script # tensoralgebrastrings.py under sandbox/scripts/ @@ -26,6 +25,7 @@ def cross_expr(a, b): + """Symbolic cross product.""" assert len(a) == 3 assert len(b) == 3 @@ -80,7 +80,7 @@ def pseudo_inverse_expr(A): def determinant_expr(A): - "Compute the (pseudo-)determinant of A." + """Compute the (pseudo-)determinant of A.""" sh = A.ufl_shape if isinstance(A, Zero): return zero() @@ -103,28 +103,36 @@ def determinant_expr(A): def _det_2x2(B, i, j, k, l): # noqa: E741 + """Determinant of a 2 by 2 matrix.""" return B[i, k] * B[j, l] - B[i, l] * B[j, k] def determinant_expr_2x2(B): + """Determinant of a 2 by 2 matrix.""" return _det_2x2(B, 0, 1, 0, 1) def old_determinant_expr_3x3(A): + """Determinant of a 3 by 3 matrix.""" + warnings.warn("The use of old_determinant_expr_3x3 is deprecated and will be removed after December 2023. " + "Please, use determinant_expr_3x3 instead", FutureWarning) return A[0, 0] * _det_2x2(A, 1, 2, 1, 2) + A[0, 1] * _det_2x2(A, 1, 2, 2, 0) + A[0, 2] * _det_2x2(A, 1, 2, 0, 1) def determinant_expr_3x3(A): + """Determinant of a 3 by 3 matrix.""" return codeterminant_expr_nxn(A, [0, 1, 2], [0, 1, 2]) def determinant_expr_nxn(A): + """Determinant of a n by n matrix.""" nrow, ncol = A.ufl_shape assert nrow == ncol return codeterminant_expr_nxn(A, list(range(nrow)), list(range(ncol))) def codeterminant_expr_nxn(A, rows, cols): + """Determinant of a n by n matrix.""" if len(rows) == 2: return _det_2x2(A, rows[0], rows[1], cols[0], cols[1]) codet = 0.0 @@ -137,7 +145,7 @@ def codeterminant_expr_nxn(A, rows, cols): def inverse_expr(A): - "Compute the inverse of A." + """Compute the inverse of A.""" sh = A.ufl_shape if sh == (): return 1.0 / A @@ -151,6 +159,7 @@ def inverse_expr(A): def adj_expr(A): + """Adjoint of a matrix.""" sh = A.ufl_shape if sh[0] != sh[1]: raise ValueError("Expecting square matrix.") @@ -166,11 +175,13 @@ def adj_expr(A): def adj_expr_2x2(A): + """Adjoint of a 2 by 2 matrix.""" return as_matrix([[A[1, 1], -A[0, 1]], [-A[1, 0], A[0, 0]]]) def adj_expr_3x3(A): + """Adjoint of a 3 by 3 matrix.""" return as_matrix([[ A[2, 2] * A[1, 1] - A[1, 2] * A[2, 1], -A[0, 1] * A[2, 2] + A[0, 2] * A[2, 1], @@ -187,6 +198,7 @@ def adj_expr_3x3(A): def adj_expr_4x4(A): + """Adjoint of a 4 by 4 matrix.""" return as_matrix([[ -A[3, 3] * A[2, 1] * A[1, 2] + A[1, 2] * A[3, 1] * A[2, 3] + A[1, 1] * A[3, 3] * A[2, 2] - A[3, 1] * A[2, 2] * A[1, 3] + A[2, 1] * A[1, 3] * A[3, 2] - A[1, 1] * A[3, 2] * A[2, 3], # noqa: E501 -A[3, 1] * A[0, 2] * A[2, 3] + A[0, 1] * A[3, 2] * A[2, 3] - A[0, 3] * A[2, 1] * A[3, 2] + A[3, 3] * A[2, 1] * A[0, 2] - A[3, 3] * A[0, 1] * A[2, 2] + A[0, 3] * A[3, 1] * A[2, 2], # noqa: E501 @@ -211,6 +223,7 @@ def adj_expr_4x4(A): def cofactor_expr(A): + """Cofactor of a matrix.""" sh = A.ufl_shape if sh[0] != sh[1]: raise ValueError("Expecting square matrix.") @@ -226,11 +239,13 @@ def cofactor_expr(A): def cofactor_expr_2x2(A): + """Cofactor of a 2 by 2 matrix.""" return as_matrix([[A[1, 1], -A[1, 0]], [-A[0, 1], A[0, 0]]]) def cofactor_expr_3x3(A): + """Cofactor of a 3 by 3 matrix.""" return as_matrix([[ A[1, 1] * A[2, 2] - A[2, 1] * A[1, 2], A[2, 0] * A[1, 2] - A[1, 0] * A[2, 2], @@ -247,6 +262,7 @@ def cofactor_expr_3x3(A): def cofactor_expr_4x4(A): + """Cofactor of a 4 by 4 matrix.""" return as_matrix([[ -A[3, 1] * A[2, 2] * A[1, 3] - A[3, 2] * A[2, 3] * A[1, 1] + A[1, 3] * A[3, 2] * A[2, 1] + A[3, 1] * A[2, 3] * A[1, 2] + A[2, 2] * A[1, 1] * A[3, 3] - A[3, 3] * A[2, 1] * A[1, 2], # noqa: E501 -A[1, 0] * A[2, 2] * A[3, 3] + A[2, 0] * A[3, 3] * A[1, 2] + A[2, 2] * A[1, 3] * A[3, 0] - A[2, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[3, 2] * A[2, 3] - A[1, 3] * A[3, 2] * A[2, 0], # noqa: E501 @@ -271,6 +287,7 @@ def cofactor_expr_4x4(A): def deviatoric_expr(A): + """Deviatoric of a matrix.""" sh = A.ufl_shape if sh[0] != sh[1]: raise ValueError("Expecting square matrix.") @@ -284,11 +301,13 @@ def deviatoric_expr(A): def deviatoric_expr_2x2(A): + """Deviatoric of a 2 by 2 matrix.""" return as_matrix([[-1. / 2 * A[1, 1] + 1. / 2 * A[0, 0], A[0, 1]], [A[1, 0], 1. / 2 * A[1, 1] - 1. / 2 * A[0, 0]]]) def deviatoric_expr_3x3(A): + """Deviatoric of a 3 by 3 matrix.""" return as_matrix([[-1. / 3 * A[1, 1] - 1. / 3 * A[2, 2] + 2. / 3 * A[0, 0], A[0, 1], A[0, 2]], [A[1, 0], 2. / 3 * A[1, 1] - 1. / 3 * A[2, 2] - 1. / 3 * A[0, 0], A[1, 2]], [A[2, 0], A[2, 1], -1. / 3 * A[1, 1] + 2. / 3 * A[2, 2] - 1. / 3 * A[0, 0]]]) diff --git a/ufl/conditional.py b/ufl/conditional.py index 8b0253538..422be6ff2 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """This module defines classes for conditional expressions.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -25,22 +23,30 @@ @ufl_type(is_abstract=True, is_scalar=True) class Condition(Operator): + """Condition.""" + __slots__ = () def __init__(self, operands): + """Initialise.""" Operator.__init__(self, operands) def __bool__(self): + """Convert to a bool.""" # Showing explicit error here to protect against misuse raise ValueError("UFL conditions cannot be evaluated as bool in a Python context.") + __nonzero__ = __bool__ @ufl_type(is_abstract=True, num_ops=2) class BinaryCondition(Condition): + """Binary condition.""" + __slots__ = ('_name',) def __init__(self, name, left, right): + """Initialise.""" left = as_ufl(left) right = as_ufl(right) @@ -69,6 +75,7 @@ def __init__(self, name, left, right): raise ValueError("Expecting scalar arguments.") def __str__(self): + """Format as a string.""" return "%s %s %s" % (parstr(self.ufl_operands[0], self), self._name, parstr(self.ufl_operands[1], self)) @@ -77,18 +84,24 @@ def __str__(self): # reserved for object equivalence for use in set and dict. @ufl_type() class EQ(BinaryCondition): + """Equality condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "==", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a == b) def __bool__(self): + """Convert to a bool.""" return expr_equals(self.ufl_operands[0], self.ufl_operands[1]) + __nonzero__ = __bool__ @@ -96,29 +109,39 @@ def __bool__(self): # reserved for object equivalence for use in set and dict. @ufl_type() class NE(BinaryCondition): + """Not equal condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "!=", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a != b) def __bool__(self): + """Convert to a bool.""" return not expr_equals(self.ufl_operands[0], self.ufl_operands[1]) + __nonzero__ = __bool__ @ufl_type(binop="__le__") class LE(BinaryCondition): + """Less than or equal condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "<=", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a <= b) @@ -126,12 +149,16 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type(binop="__ge__") class GE(BinaryCondition): + """Greater than or equal to condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, ">=", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a >= b) @@ -139,12 +166,16 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type(binop="__lt__") class LT(BinaryCondition): + """Less than condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "<", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a < b) @@ -152,12 +183,16 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type(binop="__gt__") class GT(BinaryCondition): + """Greater than condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, ">", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a > b) @@ -165,12 +200,16 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type() class AndCondition(BinaryCondition): + """And condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "&&", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a and b) @@ -178,12 +217,16 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type() class OrCondition(BinaryCondition): + """Or condition.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" BinaryCondition.__init__(self, "||", left, right) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) return bool(a or b) @@ -191,29 +234,38 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type(num_ops=1) class NotCondition(Condition): + """Not condition.""" + __slots__ = () def __init__(self, condition): + """Initialise.""" Condition.__init__(self, (condition,)) if not isinstance(condition, Condition): raise ValueError("Expecting a condition.") def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return bool(not a) def __str__(self): + """Format as a string.""" return "!(%s)" % (str(self.ufl_operands[0]),) -# --- Conditional expression (condition ? true_value : false_value) --- - @ufl_type(num_ops=3, inherit_shape_from_operand=1, inherit_indices_from_operand=1) class Conditional(Operator): + """Conditional expression. + + In C++ these take the format `(condition ? true_value : false_value)`. + """ + __slots__ = () def __init__(self, condition, true_value, false_value): + """Initialise.""" if not isinstance(condition, Condition): raise ValueError("Expectiong condition as first argument.") true_value = as_ufl(true_value) @@ -236,6 +288,7 @@ def __init__(self, condition, true_value, false_value): Operator.__init__(self, (condition, true_value, false_value)) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" c = self.ufl_operands[0].evaluate(x, mapping, component, index_values) if c: a = self.ufl_operands[1] @@ -244,6 +297,7 @@ def evaluate(self, x, mapping, component, index_values): return a.evaluate(x, mapping, component, index_values) def __str__(self): + """Format as a string.""" return "%s ? %s : %s" % tuple(parstr(o, self) for o in self.ufl_operands) @@ -251,15 +305,18 @@ def __str__(self): @ufl_type(is_scalar=True, num_ops=1) class MinValue(Operator): - "UFL operator: Take the minimum of two values." + """Take the minimum of two values.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" Operator.__init__(self, (left, right)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): raise ValueError("Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) @@ -271,20 +328,24 @@ def evaluate(self, x, mapping, component, index_values): return res def __str__(self): + """Format as a string.""" return "min_value(%s, %s)" % self.ufl_operands @ufl_type(is_scalar=True, num_ops=1) class MaxValue(Operator): - "UFL operator: Take the maximum of two values." + """Take the maximum of two values.""" + __slots__ = () def __init__(self, left, right): + """Initialise.""" Operator.__init__(self, (left, right)) if not (is_true_ufl_scalar(left) and is_true_ufl_scalar(right)): raise ValueError("Expecting scalar arguments.") def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) @@ -296,4 +357,5 @@ def evaluate(self, x, mapping, component, index_values): return res def __str__(self): + """Format as a string.""" return "max_value(%s, %s)" % self.ufl_operands diff --git a/ufl/constant.py b/ufl/constant.py index 2819647e3..2edb21c8e 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""This module defines classes representing non-literal values -which are constant with respect to a domain.""" +"""This module defines classes representing non-literal values which are constant with respect to a domain.""" # Copyright (C) 2019 Michal Habera # @@ -16,9 +14,12 @@ @ufl_type() class Constant(Terminal, Counted): + """Constant.""" + _ufl_noslots_ = True def __init__(self, domain, shape=(), count=None): + """Initalise.""" Terminal.__init__(self) Counted.__init__(self, count, Constant) @@ -32,24 +33,31 @@ def __init__(self, domain, shape=(), count=None): @property def ufl_shape(self): + """Get the UFL shape.""" return self._ufl_shape def ufl_domain(self): + """Get the UFL domain.""" return self._ufl_domain def ufl_domains(self): + """Get the UFL domains.""" return (self.ufl_domain(), ) def is_cellwise_constant(self): + """Return True if the function is cellwise constant.""" return True def __str__(self): + """Format as a string.""" return f"c_{self._count}" def __repr__(self): + """Representation.""" return self._repr def __eq__(self, other): + """Check equality.""" if not isinstance(other, Constant): return False if self is other: @@ -58,17 +66,19 @@ def __eq__(self, other): self._ufl_shape == self._ufl_shape) def _ufl_signature_data_(self, renumbering): - "Signature data for constant depends on 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): + """Vector constant.""" domain = as_domain(domain) 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) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 1367e6036..64a44276a 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines classes representing constant values." +"""This module defines classes representing constant values.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -27,7 +26,7 @@ def format_float(x): - "Format float value based on global UFL precision." + """Format float value based on global UFL precision.""" if precision: return "{:.{prec}}".format(float(x), prec=precision) else: @@ -38,35 +37,41 @@ def format_float(x): @ufl_type(is_abstract=True) class ConstantValue(Terminal): + """Constant value.""" + __slots__ = () def __init__(self): + """Initialise.""" Terminal.__init__(self) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" return True def ufl_domains(self): - "Return tuple of domains related to this terminal object." + """Return tuple of domains related to this terminal object.""" return () -# --- Class for representing zero tensors of different shapes --- - -# TODO: Add geometric dimension/domain and Argument dependencies to -# Zero? +# TODO: Add geometric dimension/domain and Argument dependencies to Zero? @ufl_type(is_literal=True) class Zero(ConstantValue): - "UFL literal type: Representation of a zero valued expression." + """Representation of a zero valued expression. + + Class for representing zero tensors of different shapes. + """ + __slots__ = ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions") _cache = {} def __getnewargs__(self): + """Get new args.""" return (self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) def __new__(cls, shape=(), free_indices=(), index_dimensions=None): + """Create new Zero.""" if free_indices: self = ConstantValue.__new__(cls) else: @@ -79,9 +84,11 @@ def __new__(cls, shape=(), free_indices=(), index_dimensions=None): return self def __init__(self, shape=(), free_indices=(), index_dimensions=None): + """Initialise.""" pass def _init(self, shape=(), free_indices=(), index_dimensions=None): + """Initialise.""" ConstantValue.__init__(self) if not all(isinstance(i, int) for i in shape): @@ -113,9 +120,11 @@ def _init(self, shape=(), free_indices=(), index_dimensions=None): self.ufl_index_dimensions = index_dimensions def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" return 0.0 def __str__(self): + """Format as a string.""" if self.ufl_shape == () and self.ufl_free_indices == (): return "0" if self.ufl_free_indices == (): @@ -125,6 +134,7 @@ def __str__(self): return "0 (shape %s, index labels %s)" % (self.ufl_shape, self.ufl_free_indices) def __repr__(self): + """Representation.""" r = "Zero(%s, %s, %s)" % ( repr(self.ufl_shape), repr(self.ufl_free_indices), @@ -132,6 +142,7 @@ def __repr__(self): return r def __eq__(self, other): + """Check equalty.""" if isinstance(other, Zero): if self is other: return True @@ -144,27 +155,34 @@ def __eq__(self, other): return False def __neg__(self): + """Negate.""" return self def __abs__(self): + """Absolute value.""" return self def __bool__(self): + """Convert to a bool.""" return False + __nonzero__ = __bool__ def __float__(self): + """Convert to a float.""" return 0.0 def __int__(self): + """Convert to an int.""" return 0 def __complex__(self): + """Convert to a complex number.""" return 0 + 0j def zero(*shape): - "UFL literal constant: Return a zero tensor with the given shape." + """UFL literal constant: Return a zero tensor with the given shape.""" if len(shape) == 1 and isinstance(shape[0], tuple): return Zero(shape[0]) else: @@ -175,21 +193,27 @@ def zero(*shape): @ufl_type(is_abstract=True, is_scalar=True) class ScalarValue(ConstantValue): - "A constant scalar value." + """A constant scalar value.""" + __slots__ = ("_value",) def __init__(self, value): + """Initialise.""" ConstantValue.__init__(self) self._value = value def value(self): + """Get the value.""" return self._value def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" return self._value def __eq__(self, other): - """This is implemented to allow comparison with python scalars. + """Check equalty. + + This is implemented to allow comparison with python scalars. Note that this will make IntValue(1) != FloatValue(1.0), but ufl-python comparisons like @@ -208,39 +232,50 @@ def __eq__(self, other): return False def __str__(self): + """Format as a string.""" return str(self._value) def __float__(self): + """Convert to a float.""" return float(self._value) def __int__(self): + """Convert to an int.""" return int(self._value) def __complex__(self): + """Convert to a complex number.""" return complex(self._value) def __neg__(self): + """Negate.""" return type(self)(-self._value) def __abs__(self): + """Absolute value.""" return type(self)(abs(self._value)) def real(self): + """Real part.""" return self._value.real def imag(self): + """Imaginary part.""" return self._value.imag @ufl_type(wraps_type=complex, is_literal=True) class ComplexValue(ScalarValue): - "UFL literal type: Representation of a constant, complex scalar" + """Representation of a constant, complex scalar.""" + __slots__ = () def __getnewargs__(self): + """Get new args.""" return (self._value,) def __new__(cls, value): + """Create a new ComplexValue.""" if value.imag == 0: if value.real == 0: return Zero() @@ -250,64 +285,78 @@ def __new__(cls, value): return ConstantValue.__new__(cls) def __init__(self, value): + """Initialise.""" ScalarValue.__init__(self, complex(value)) def modulus(self): + """Get the modulus.""" return abs(self.value()) def argument(self): + """Get the argument.""" return atan2(self.value().imag, self.value().real) def __repr__(self): + """Representation.""" r = "%s(%s)" % (type(self).__name__, repr(self._value)) return r def __float__(self): + """Convert to a float.""" raise TypeError("ComplexValues cannot be cast to float") def __int__(self): + """Convert to an int.""" raise TypeError("ComplexValues cannot be cast to int") @ufl_type(is_abstract=True, is_scalar=True) class RealValue(ScalarValue): - "Abstract class used to differentiate real values from complex ones" + """Abstract class used to differentiate real values from complex ones.""" + __slots__ = () @ufl_type(wraps_type=float, is_literal=True) class FloatValue(RealValue): - "UFL literal type: Representation of a constant scalar floating point value." + """Representation of a constant scalar floating point value.""" + __slots__ = () def __getnewargs__(self): + """Get new args.""" return (self._value,) def __new__(cls, value): + """Create a new FloatValue.""" if value == 0.0: # Always represent zero with Zero return Zero() return ConstantValue.__new__(cls) def __init__(self, value): + """Initialise.""" super(FloatValue, self).__init__(float(value)) def __repr__(self): + """Representation.""" r = "%s(%s)" % (type(self).__name__, format_float(self._value)) return r @ufl_type(wraps_type=int, is_literal=True) class IntValue(RealValue): - "UFL literal type: Representation of a constant scalar integer value." + """Representation of a constant scalar integer value.""" __slots__ = () _cache = {} def __getnewargs__(self): + """Get new args.""" return (self._value,) def __new__(cls, value): + """Create a new IntValue.""" if value == 0: # Always represent zero with Zero return Zero() @@ -325,12 +374,15 @@ def __new__(cls, value): return self def _init(self, value): + """Initialise.""" super(IntValue, self).__init__(int(value)) def __init__(self, value): + """Initialise.""" pass def __repr__(self): + """Representation.""" r = "%s(%s)" % (type(self).__name__, repr(self._value)) return r @@ -339,20 +391,22 @@ def __repr__(self): @ufl_type() class Identity(ConstantValue): - "UFL literal type: Representation of an identity matrix." + """Representation of an identity matrix.""" __slots__ = ("_dim", "ufl_shape") def __init__(self, dim): + """Initialise.""" ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim, dim) def evaluate(self, x, mapping, component, index_values): - "Evaluates the identity matrix on the given components." + """Evaluate.""" a, b = component return 1 if a == b else 0 def __getitem__(self, key): + """Get an item.""" if len(key) != 2: raise ValueError("Size mismatch for Identity.") if all(isinstance(k, (int, FixedIndex)) for k in key): @@ -360,13 +414,16 @@ def __getitem__(self, key): return Expr.__getitem__(self, key) def __str__(self): + """Format as a string.""" return "I" def __repr__(self): + """Representation.""" r = "Identity(%d)" % self._dim return r def __eq__(self, other): + """Check equalty.""" return isinstance(other, Identity) and self._dim == other._dim @@ -374,22 +431,26 @@ def __eq__(self, other): @ufl_type() class PermutationSymbol(ConstantValue): - """UFL literal type: Representation of a permutation symbol. + """Representation of a permutation symbol. This is also known as the Levi-Civita symbol, antisymmetric symbol, - or alternating symbol.""" + or alternating symbol. + """ + __slots__ = ("ufl_shape", "_dim") def __init__(self, dim): + """Initialise.""" ConstantValue.__init__(self) self._dim = dim self.ufl_shape = (dim,) * dim def evaluate(self, x, mapping, component, index_values): - "Evaluates the permutation symbol." + """Evaluate.""" return self.__eps(component) def __getitem__(self, key): + """Get an item.""" if len(key) != self._dim: raise ValueError("Size mismatch for PermutationSymbol.") if all(isinstance(k, (int, FixedIndex)) for k in key): @@ -397,19 +458,23 @@ def __getitem__(self, key): return Expr.__getitem__(self, key) def __str__(self): + """Format as a string.""" return "eps" def __repr__(self): + """Representation.""" r = "PermutationSymbol(%d)" % self._dim return r def __eq__(self, other): + """Check equalty.""" return isinstance(other, PermutationSymbol) and self._dim == other._dim def __eps(self, x): - """This function body is taken from - http://www.mathkb.com/Uwe/Forum.aspx/math/29865/N-integer-Levi-Civita + """Get eps. + This function body is taken from + http://www.mathkb.com/Uwe/Forum.aspx/math/29865/N-integer-Levi-Civita """ result = IntValue(1) for i, x1 in enumerate(x): @@ -423,7 +488,7 @@ def __eps(self, x): def as_ufl(expression): - "Converts expression to an Expr if possible." + """Converts expression to an Expr if possible.""" if isinstance(expression, (Expr, ufl.BaseForm)): return expression elif isinstance(expression, complex): diff --git a/ufl/core/__init__.py b/ufl/core/__init__.py index e69de29bb..06b883b35 100644 --- a/ufl/core/__init__.py +++ b/ufl/core/__init__.py @@ -0,0 +1 @@ +"""UFL core.""" diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index e29979157..e2aa2475d 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -1,7 +1,8 @@ +"""Base form operator. -# -*- coding: utf-8 -*- -"""This module defines the BaseFormOperator class, which is the base class for objects that can be seen as forms - and as operators such as ExternalOperator or Interpolate.""" +This module defines the BaseFormOperator class, which is the base class for objects that can be seen as forms +and as operators such as ExternalOperator or Interpolate. +""" # Copyright (C) 2019 Nacime Bouziani # @@ -24,20 +25,21 @@ @ufl_type(num_ops="varying", is_differential=True) class BaseFormOperator(Operator, BaseForm, Counted): + """Base form operator.""" # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True def __init__(self, *operands, function_space, derivatives=None, argument_slots=()): - r""" - :param operands: operands on which acts the operator. - :param function_space: the :class:`.FunctionSpace`, - or :class:`.MixedFunctionSpace` on which to build this :class:`Function`. - :param derivatives: tuple specifiying the derivative multiindex. - :param argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. - """ + """Initialise. + Args: + operands: operands on which acts the operator. + function_space: the FunctionSpace or MixedFunctionSpace on which to build this Function. + derivatives: tuple specifiying the derivative multiindex. + argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + """ BaseForm.__init__(self) ufl_operands = tuple(map(as_ufl, operands)) argument_slots = tuple(map(as_ufl, argument_slots)) @@ -69,11 +71,12 @@ def __init__(self, *operands, function_space, derivatives=None, argument_slots=( ufl_index_dimensions = () def argument_slots(self, outer_form=False): - r"""Returns a tuple of expressions containing argument and coefficient based expressions. - We get an argument uhat when we take the Gateaux derivative in the direction uhat: - -> d/du N(u; v*) = dNdu(u; uhat, v*) where uhat is a ufl.Argument and v* a ufl.Coargument - Applying the action replace the last argument by coefficient: - -> action(dNdu(u; uhat, v*), w) = dNdu(u; w, v*) where du is a ufl.Coefficient + """Returns a tuple of expressions containing argument and coefficient based expressions. + + We get an argument uhat when we take the Gateaux derivative in the direction uhat: + d/du N(u; v*) = dNdu(u; uhat, v*) where uhat is a ufl.Argument and v* a ufl.Coargument + Applying the action replace the last argument by coefficient: + action(dNdu(u; uhat, v*), w) = dNdu(u; w, v*) where du is a ufl.Coefficient. """ from ufl.algorithms.analysis import extract_arguments if not outer_form: @@ -85,13 +88,13 @@ def argument_slots(self, outer_form=False): return tuple(a for a in self._argument_slots[1:] if len(extract_arguments(a)) != 0) def coefficients(self): - "Return all ``BaseCoefficient`` objects found in base form operator." + """Return all BaseCoefficient objects found in base form operator.""" if self._coefficients is None: self._analyze_form_arguments() return self._coefficients def _analyze_form_arguments(self): - "Analyze which Argument and Coefficient objects can be found in the base form." + """Analyze which Argument and Coefficient objects can be found in the base form.""" from ufl.algorithms.analysis import extract_arguments, extract_coefficients, extract_type dual_arg, *arguments = self.argument_slots() # When coarguments are treated as BaseForms, they have two arguments (one primal and one dual) @@ -114,35 +117,38 @@ def _analyze_form_arguments(self): self._coefficients = tuple(sorted(set(coefficients), key=lambda x: x.count())) def count(self): - "Returns the count associated to the base form operator" + """Return the count associated to the base form operator.""" return self._count @property def ufl_shape(self): - "Returns the UFL shape of the coefficient.produced by the operator" + """Return the UFL shape of the coefficient.produced by the operator.""" return self.arguments()[0]._ufl_shape def ufl_function_space(self): - "Returns the function space associated to the operator, i.e. the dual of the base form operator's `Coargument`" + """Return the function space associated to the operator. + + I.e. return the dual of the base form operator's Coargument. + """ return self.arguments()[0]._ufl_function_space.dual() def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, argument_slots=None): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" return type(self)(*operands, function_space=function_space or self.ufl_function_space(), derivatives=derivatives or self.derivatives, argument_slots=argument_slots or self.argument_slots()) def __repr__(self): - "Default repr string construction for base form operators." - r = "%s(%s; %s; %s; derivatives=%s)" % (type(self).__name__, - ", ".join(repr(op) for op in self.ufl_operands), - repr(self.ufl_function_space()), - ", ".join(repr(arg) for arg in self.argument_slots()), - repr(self.derivatives)) + """Default repr string construction for base form operators.""" + r = f"{type(self).__name__}(" + r += ", ".join(repr(op) for op in self.ufl_operands) + r += "; {self.ufl_function_space()!r}; " + r += ", ".join(repr(arg) for arg in self.argument_slots()) + r += f"; derivatives={self.derivatives!r})" return r def __hash__(self): - "Hash code for use in dicts." + """Hash code for use in dicts.""" hashdata = (type(self), tuple(hash(op) for op in self.ufl_operands), tuple(hash(arg) for arg in self._argument_slots), @@ -151,4 +157,5 @@ def __hash__(self): return hash(hashdata) def __eq__(self, other): - raise NotImplementedError + """Check for equality.""" + raise NotImplementedError() diff --git a/ufl/core/compute_expr_hash.py b/ufl/core/compute_expr_hash.py index d48669262..e0b6f9cf0 100644 --- a/ufl/core/compute_expr_hash.py +++ b/ufl/core/compute_expr_hash.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Non-recursive traversal-based hash computation algorithm. Fast iteration over nodes in an ``Expr`` DAG to compute diff --git a/ufl/core/expr.py b/ufl/core/expr.py index d6d51e412..9f1851643 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -1,15 +1,12 @@ -# -*- coding: utf-8 -*- -"""This module defines the ``Expr`` class, the superclass -for all expression tree node types in UFL. +"""This module defines the ``Expr`` class, the superclass for all expression tree node types in UFL. -NB! A note about other operators not implemented here: +NB: A note about other operators not implemented here: More operators (special functions) on ``Expr`` instances are defined in ``exproperators.py``, as well as the transpose ``A.T`` and spatial derivative ``a.dx(i)``. This is to avoid circular dependencies between ``Expr`` and its subclasses. """ - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -88,14 +85,15 @@ class MyOperator(Operator): # This is to freeze member variables for objects of this class and # save memory by skipping the per-instance dict. - __slots__ = ("_hash", - "__weakref__") + __slots__ = ("_hash", "__weakref__") # _ufl_noslots_ = True # --- Basic object behaviour --- def __getnewargs__(self): - """The tuple returned here is passed to as args to cls.__new__(cls, *args). + """Get newargs tuple. + + The tuple returned here is passed to as args to cls.__new__(cls, *args). This implementation passes the operands, which is () for terminals. @@ -104,6 +102,7 @@ def __getnewargs__(self): return self.ufl_operands def __init__(self): + """Initialise.""" self._hash = None # This shows the principal behaviour of the hash function attached @@ -230,17 +229,17 @@ def __init__(self): _ufl_regular__init__ = __init__ def _ufl_profiling__init__(self): - "Replacement constructor with object counting." + """Replacement constructor with object counting.""" Expr._ufl_regular__init__(self) Expr._ufl_obj_init_counts_[self._ufl_typecode_] += 1 def _ufl_profiling__del__(self): - "Replacement destructor with object counting." + """Replacement destructor with object counting.""" Expr._ufl_obj_del_counts_[self._ufl_typecode_] -= 1 @staticmethod def ufl_enable_profiling(): - "Turn on the object counting mechanism and reset counts to zero." + """Turn on the object counting mechanism and reset counts to zero.""" Expr.__init__ = Expr._ufl_profiling__init__ setattr(Expr, "__del__", Expr._ufl_profiling__del__) for i in range(len(Expr._ufl_obj_init_counts_)): @@ -249,7 +248,7 @@ def ufl_enable_profiling(): @staticmethod def ufl_disable_profiling(): - "Turn off the object counting mechanism. Return object init and del counts." + """Turn off the object counting mechanism. Return object init and del counts.""" Expr.__init__ = Expr._ufl_regular__init__ delattr(Expr, "__del__") return (Expr._ufl_obj_init_counts_, Expr._ufl_obj_del_counts_) @@ -259,20 +258,20 @@ def ufl_disable_profiling(): # --- Functions for reconstructing expression --- def _ufl_expr_reconstruct_(self, *operands): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" raise NotImplementedError(self.__class__._ufl_expr_reconstruct_) # --- Functions for geometric properties of expression --- def ufl_domains(self): - "Return all domains this expression is defined on." + """Return all domains this expression is defined on.""" warnings.warn("Expr.ufl_domains() is deprecated, please " "use extract_domains(expr) instead.", DeprecationWarning) from ufl.domain import extract_domains return extract_domains(self) def ufl_domain(self): - "Return the single unique domain this expression is defined on, or throw an error." + """Return the single unique domain this expression is defined on, or throw an error.""" warnings.warn("Expr.ufl_domain() is deprecated, please " "use extract_unique_domain(expr) instead.", DeprecationWarning) from ufl.domain import extract_unique_domain @@ -290,7 +289,7 @@ def _ufl_evaluate_scalar_(self): return self(()) # No known x def __float__(self): - "Try to evaluate as scalar and cast to float." + """Try to evaluate as scalar and cast to float.""" try: v = float(self._ufl_evaluate_scalar_()) except Exception: @@ -298,7 +297,7 @@ def __float__(self): return v def __complex__(self): - "Try to evaluate as scalar and cast to complex." + """Try to evaluate as scalar and cast to complex.""" try: v = complex(self._ufl_evaluate_scalar_()) except TypeError: @@ -306,16 +305,16 @@ def __complex__(self): return v def __bool__(self): - "By default, all Expr are nonzero/False." + """By default, all Expr are nonzero/False.""" return True def __nonzero__(self): - "By default, all Expr are nonzero/False." + """By default, all Expr are nonzero/False.""" return self.__bool__() @staticmethod def _ufl_coerce_(value): - "Convert any value to a UFL type." + """Convert any value to a UFL type.""" # Quick skip for most types if isinstance(value, Expr): return value @@ -334,53 +333,55 @@ def _ufl_coerce_(value): # All subclasses must implement _ufl_signature_data_ def _ufl_signature_data_(self, renumbering): - "Return data that uniquely identifies form compiler relevant aspects of this object." + """Return data that uniquely identifies form compiler relevant aspects of this object.""" raise NotImplementedError(self.__class__._ufl_signature_data_) # All subclasses must implement __repr__ def __repr__(self): - "Return string representation this object can be reconstructed from." + """Return string representation this object can be reconstructed from.""" raise NotImplementedError(self.__class__.__repr__) # All subclasses must implement __str__ def __str__(self): - "Return pretty print string representation of this object." + """Return pretty print string representation of this object.""" raise NotImplementedError(self.__class__.__str__) def _ufl_err_str_(self): - "Return a short string to represent this Expr in an error message." - return "<%s id=%d>" % (self._ufl_class_.__name__, id(self)) + """Return a short string to represent this Expr in an error message.""" + return f"<{self._ufl_class_.__name__} id={id(self)}>" # --- Special functions used for processing expressions --- def __eq__(self, other): - """Checks whether the two expressions are represented the - exact same way. This does not check if the expressions are - mathematically equal or equivalent! Used by sets and dicts.""" + """Checks whether the two expressions are represented the exact same way. + + This does not check if the expressions are + mathematically equal or equivalent! Used by sets and dicts. + """ raise NotImplementedError(self.__class__.__eq__) def __len__(self): - "Length of expression. Used for iteration over vector expressions." + """Length of expression. Used for iteration over vector expressions.""" s = self.ufl_shape if len(s) == 1: return s[0] raise NotImplementedError("Cannot take length of non-vector expression.") def __iter__(self): - "Iteration over vector expressions." + """Iteration over vector expressions.""" for i in range(len(self)): yield self[i] def __floordiv__(self, other): - "UFL does not support integer division." + """UFL does not support integer division.""" raise NotImplementedError(self.__class__.__floordiv__) def __pos__(self): - "Unary + is a no-op." + """Unary + is a no-op.""" return self def __round__(self, n=None): - "Round to nearest integer or to nearest nth decimal." + """Round to nearest integer or to nearest nth decimal.""" try: val = float(self._ufl_evaluate_scalar_()) val = round(val, n) @@ -402,6 +403,7 @@ def __round__(self, n=None): def ufl_err_str(expr): + """Return a UFL error string.""" if hasattr(expr, "_ufl_err_str_"): return expr._ufl_err_str_() else: diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index 0ffb5c906..ecd736391 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -1,9 +1,9 @@ -# -*- coding: utf-8 -*- -"""This module defines the ``ExternalOperator`` class, which symbolically represents operators that are not - straightforwardly expressible in UFL. Subclasses of ``ExternalOperator`` must define - how this operator should be evaluated as well as its derivatives from a given set of operands. -""" +"""External operator. +This module defines the ``ExternalOperator`` class, which symbolically represents operators that are not +straightforwardly expressible in UFL. Subclasses of ``ExternalOperator`` must define +how this operator should be evaluated as well as its derivatives from a given set of operands. +""" # Copyright (C) 2019 Nacime Bouziani # # This file is part of UFL (https://www.fenicsproject.org) @@ -18,29 +18,30 @@ @ufl_type(num_ops="varying", is_differential=True) class ExternalOperator(BaseFormOperator): + """External operator.""" # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True def __init__(self, *operands, function_space, derivatives=None, argument_slots=()): - r""" - :param operands: operands on which acts the :class:`ExternalOperator`. - :param function_space: the :class:`.FunctionSpace`, - or :class:`.MixedFunctionSpace` on which to build this :class:`Function`. - :param derivatives: tuple specifiying the derivative multiindex. - :param argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. - """ + """Initialise. + Args: + operands: operands on which acts the ExternalOperator. + function_space: the FunctionSpace, or MixedFunctionSpace on which to build this Function. + derivatives: tuple specifiying the derivative multiindex. + argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + """ # -- Derivatives -- # if derivatives is not None: if not isinstance(derivatives, tuple): - raise TypeError("Expecting a tuple for derivatives and not %s" % derivatives) + raise TypeError(f"Expecting a tuple for derivatives and not {derivatives}") if not len(derivatives) == len(operands): - raise ValueError("Expecting a size of %s for %s" % (len(operands), derivatives)) + raise ValueError(f"Expecting a size of {len(operands)} for {derivatives}") if not all(isinstance(d, int) for d in derivatives) or any(d < 0 for d in derivatives): - raise ValueError("Expecting a derivative multi-index with nonnegative indices and not %s" - % str(derivatives)) + raise ValueError( + f"Expecting a derivative multi-index with nonnegative indices and not {str(derivatives)}") else: derivatives = (0,) * len(operands) @@ -50,12 +51,12 @@ def __init__(self, *operands, function_space, derivatives=None, argument_slots=( argument_slots=argument_slots) def ufl_element(self): - "Shortcut to get the finite element of the function space of the external operator" + """Shortcut to get the finite element of the function space of the external operator.""" # Useful when applying split on an ExternalOperator return self.arguments()[0].ufl_element() def grad(self): - """Returns the symbolic grad of the external operator""" + """Returns the symbolic grad of the external operator.""" # By default, differential rules produce `grad(assembled_o)` `where assembled_o` # is the `Coefficient` resulting from assembling the external operator since # the external operator may not be smooth enough for chain rule to hold. @@ -64,27 +65,31 @@ def grad(self): raise NotImplementedError('Symbolic gradient not defined for the external operator considered!') def assemble(self, *args, **kwargs): - """Assemble the external operator""" - raise NotImplementedError("Symbolic evaluation of %s not available." % self._ufl_class_.__name__) + """Assemble the external operator.""" + raise NotImplementedError(f"Symbolic evaluation of {self._ufl_class_.__name__} not available.") def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, argument_slots=None, add_kwargs={}): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" return type(self)(*operands, function_space=function_space or self.ufl_function_space(), derivatives=derivatives or self.derivatives, argument_slots=argument_slots or self.argument_slots(), **add_kwargs) def __str__(self): - "Default str string for ExternalOperator operators." + """Default str string for ExternalOperator operators.""" d = '\N{PARTIAL DIFFERENTIAL}' derivatives = self.derivatives d_ops = "".join(d + "o" + str(i + 1) for i, di in enumerate(derivatives) for j in range(di)) - e = "e(%s; %s)" % (", ".join(str(op) for op in self.ufl_operands), - ", ".join(str(arg) for arg in reversed(self.argument_slots()))) + e = "e(" + e += ", ".join(str(op) for op in self.ufl_operands) + e += "; " + e += ", ".join(str(arg) for arg in reversed(self.argument_slots())) + e += ")" return d + e + "/" + d_ops if sum(derivatives) > 0 else e def __eq__(self, other): + """Check for equality.""" if self is other: return True return (type(self) is type(other) and diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index f7e912caa..e751c66c9 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module defines the Interpolate class.""" # Copyright (C) 2021 Nacime Bouziani @@ -21,19 +20,20 @@ @ufl_type(num_ops="varying", is_differential=True) class Interpolate(BaseFormOperator): + """Symbolic representation of the interpolation operator.""" # Slots are disabled here because they cause trouble in PyDOLFIN # multiple inheritance pattern: _ufl_noslots_ = True def __init__(self, expr, v): - r""" Symbolic representation of the interpolation operator. + """Initialise. - :arg expr: a UFL expression to interpolate. - :arg v: the :class:`.FunctionSpace` to interpolate into or the :class:`.Coargument` - defined on the dual of the :class:`.FunctionSpace` to interpolate into. + Args: + expr: a UFL expression to interpolate. + v: the FunctionSpace to interpolate into or the Coargument + defined on the dual of the FunctionSpace to interpolate into. """ - # This check could be more rigorous. dual_args = (Coargument, Cofunction, Form) @@ -59,23 +59,26 @@ def __init__(self, expr, v): argument_slots=argument_slots) def _ufl_expr_reconstruct_(self, expr, v=None, **add_kwargs): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" v = v or self.argument_slots()[0] return type(self)(expr, v, **add_kwargs) def __repr__(self): - "Default repr string construction for Interpolate." - r = "Interpolate(%s; %s)" % (", ".join(repr(arg) for arg in reversed(self.argument_slots())), - repr(self.ufl_function_space())) + """Default repr string construction for Interpolate.""" + r = "Interpolate(" + r += ", ".join(repr(arg) for arg in reversed(self.argument_slots())) + r += f"; {self.ufl_function_space()!r})" return r def __str__(self): - "Default str string construction for Interpolate." - s = "Interpolate(%s; %s)" % (", ".join(str(arg) for arg in reversed(self.argument_slots())), - str(self.ufl_function_space())) + """Default str string construction for Interpolate.""" + s = "Interpolate(" + s += ", ".join(str(arg) for arg in reversed(self.argument_slots())) + s += f"; {self.ufl_function_space()})" return s def __eq__(self, other): + """Check for equality.""" if self is other: return True return (type(self) is type(other) and @@ -85,10 +88,11 @@ def __eq__(self, other): # Helper function def interpolate(expr, v): - r""" Symbolic representation of the interpolation operator. + """Create symbolic representation of the interpolation operator. - :arg expr: a UFL expression to interpolate. - :arg v: the :class:`.FunctionSpace` to interpolate into or the :class:`.Coargument` - defined on the dual of the :class:`.FunctionSpace` to interpolate into. + Args: + expr: a UFL expression to interpolate. + v: the FunctionSpace to interpolate into or the Coargument + defined on the dual of the FunctionSpace to interpolate into. """ return Interpolate(expr, v) diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 8d9c5dee6..ca66a619a 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module defines the single index types and some internal index utilities.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg @@ -23,7 +22,7 @@ class IndexBase(object): __slots__ = () def __init__(self): - pass + """Initialise.""" class FixedIndex(IndexBase): @@ -33,9 +32,11 @@ class FixedIndex(IndexBase): _cache = {} def __getnewargs__(self): + """Get new args.""" return (self._value,) def __new__(cls, value): + """Create new FixedIndex.""" self = FixedIndex._cache.get(value) if self is None: if not isinstance(value, int): @@ -46,68 +47,81 @@ def __new__(cls, value): return self def _init(self, value): + """Initialise.""" IndexBase.__init__(self) self._value = value self._hash = hash(("FixedIndex", self._value)) def __init__(self, value): - pass + """Initialise.""" def __hash__(self): + """Hash.""" return self._hash def __eq__(self, other): + """Check equality.""" return isinstance(other, FixedIndex) and (self._value == other._value) def __int__(self): + """Convert to int.""" return self._value def __str__(self): - return "%d" % self._value + """Represent with a string.""" + return f"{self._value}" def __repr__(self): - r = "FixedIndex(%d)" % self._value - return r + """Return representation.""" + return f"FixedIndex({self._value})" class Index(IndexBase, Counted): """UFL value: An index with no value assigned. - Used to represent free indices in Einstein indexing notation.""" + Used to represent free indices in Einstein indexing notation. + """ + __slots__ = ("_count", "_counted_class") def __init__(self, count=None): + """Initialise.""" IndexBase.__init__(self) Counted.__init__(self, count, Index) def __hash__(self): + """Hash.""" return hash(("Index", self._count)) def __eq__(self, other): + """Check equality.""" return isinstance(other, Index) and (self._count == other._count) def __str__(self): - c = str(self._count) + """Represent as a string.""" + c = f"{self._count}" if len(c) > 1: - c = "{%s}" % c - return "i_%s" % c + c = f"{{{c}}}" + return f"i_{c}" def __repr__(self): - r = "Index(%d)" % self._count - return r + """Return representation.""" + return f"Index({self._count})" @ufl_type() class MultiIndex(Terminal): - "Represents a sequence of indices, either fixed or free." + """Represents a sequence of indices, either fixed or free.""" __slots__ = ("_indices",) _cache = {} def __getnewargs__(self): + """Get new args.""" return (self._indices,) def __new__(cls, indices): + """Create new MultiIndex.""" if not isinstance(indices, tuple): raise ValueError("Expecting a tuple of indices.") @@ -133,25 +147,28 @@ def __new__(cls, indices): return self def __init__(self, indices): - pass + """Initialise.""" def _init(self, indices): + """Initialise.""" Terminal.__init__(self) self._indices = indices def indices(self): - "Return tuple of indices." + """Return tuple of indices.""" return self._indices def _ufl_compute_hash_(self): + """Compute UFL hash.""" return hash(("MultiIndex",) + tuple(hash(ind) for ind in self._indices)) def __eq__(self, other): + """Check equality.""" return isinstance(other, MultiIndex) and \ self._indices == other._indices def evaluate(self, x, mapping, component, index_values): - "Evaluate index." + """Evaluate index.""" # Build component from index values component = [] for i in self._indices: @@ -163,30 +180,43 @@ def evaluate(self, x, mapping, component, index_values): @property def ufl_shape(self): - "This shall not be used." + """Get the UFL shape. + + This should not be used. + """ raise ValueError("Multiindex has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): - "This shall not be used." + """Get the UFL free indices. + + This should not be used. + """ raise ValueError("Multiindex has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): - "This shall not be used." + """Get the UFL index dimensions. + + This should not be used. + """ raise ValueError("Multiindex has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): - "Always True." + """Check if cellwise constant. + + Always True. + """ return True def ufl_domains(self): - "Return tuple of domains related to this terminal object." + """Return tuple of domains related to this terminal object.""" return () # --- Adding multiindices --- def __add__(self, other): + """Add.""" if isinstance(other, tuple): return MultiIndex(self._indices + other) elif isinstance(other, MultiIndex): @@ -194,6 +224,7 @@ def __add__(self, other): return NotImplemented def __radd__(self, other): + """Add.""" if isinstance(other, tuple): return MultiIndex(other + self._indices) elif isinstance(other, MultiIndex): @@ -203,24 +234,27 @@ def __radd__(self, other): # --- String formatting --- def __str__(self): + """Format as a string.""" return ", ".join(str(i) for i in self._indices) def __repr__(self): - r = "MultiIndex(%s)" % repr(self._indices) - return r + """Return representation.""" + return f"MultiIndex({self._indices!r})" # --- Iteration protocol --- - def __len__(self): + """Get length.""" return len(self._indices) def __getitem__(self, i): + """Get an item.""" return self._indices[i] def __iter__(self): + """Return iteratable.""" return iter(self._indices) def indices(n): - "UFL value: Return a tuple of :math:`n` new Index objects." + """Return a tuple of n new Index objects.""" return tuple(Index() for i in range(n)) diff --git a/ufl/core/operator.py b/ufl/core/operator.py index 07e8e856f..7a8de1cc1 100644 --- a/ufl/core/operator.py +++ b/ufl/core/operator.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- - +"""Operator.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -17,10 +16,12 @@ @ufl_type(is_abstract=True, is_terminal=False) class Operator(Expr): - "Base class for all operators, i.e. non-terminal expression types." + """Base class for all operators, i.e. non-terminal expression types.""" + __slots__ = ("ufl_operands",) def __init__(self, operands=None): + """Initialise.""" Expr.__init__(self) # If operands is None, the type sets this itself. This is to @@ -31,19 +32,18 @@ def __init__(self, operands=None): self.ufl_operands = operands def _ufl_expr_reconstruct_(self, *operands): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" return self._ufl_class_(*operands) def _ufl_signature_data_(self): + """Get UFL signature data.""" return self._ufl_typecode_ def _ufl_compute_hash_(self): - "Compute a hash code for this expression. Used by sets and dicts." + """Compute a hash code for this expression. Used by sets and dicts.""" return hash((self._ufl_typecode_,) + tuple(hash(o) for o in self.ufl_operands)) def __repr__(self): - "Default repr string construction for operators." + """Default repr string construction for operators.""" # This should work for most cases - r = "%s(%s)" % (self._ufl_class_.__name__, - ", ".join(repr(op) for op in self.ufl_operands)) - return r + return f"{self._ufl_class_.__name__}({', '.join(repr(op) for op in self.ufl_operands)})" diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index 8f7ee653b..f258a1c04 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -"""This module defines the ``Terminal`` class, the superclass -for all types that are terminal nodes in an expression tree.""" +"""This module defines the Terminal class. +Terminal the superclass for all types that are terminal nodes in an expression tree. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -17,14 +17,17 @@ from ufl.core.ufl_type import ufl_type -# --- Base class for terminal objects --- - @ufl_type(is_abstract=True, is_terminal=True) class Terminal(Expr): - "A terminal node in the UFL expression tree." + """Base class for terminal objects. + + A terminal node in the UFL expression tree. + """ + __slots__ = () def __init__(self): + """Initialise the terminal.""" Expr.__init__(self) ufl_operands = () @@ -32,11 +35,11 @@ def __init__(self): ufl_index_dimensions = () def ufl_domains(self): - "Return tuple of domains related to this terminal object." + """Return tuple of domains related to this terminal object.""" raise NotImplementedError("Missing implementation of domains().") def evaluate(self, x, mapping, component, index_values, derivatives=()): - "Get *self* from *mapping* and return the component asked for." + """Get *self* from *mapping* and return the component asked for.""" f = mapping.get(self) # No mapping, trying to evaluate self as a constant if f is None: @@ -54,7 +57,7 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): if hasattr(self, 'ufl_evaluate'): return self.ufl_evaluate(x, component, derivatives) # Take component if any - warnings.warn("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) + warnings.warn(f"Couldn't map '{self}' to a float, returning ufl object without evaluation.") f = self if component: f = f[component] @@ -76,15 +79,15 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): return f def _ufl_signature_data_(self, renumbering): - "Default signature data for of terminals just return the repr string." + """Default signature data for of terminals just return the repr string.""" return repr(self) def _ufl_compute_hash_(self): - "Default hash of terminals just hash the repr string." + """Default hash of terminals just hash the repr string.""" return hash(repr(self)) def __eq__(self, other): - "Default comparison of terminals just compare repr strings." + """Default comparison of terminals just compare repr strings.""" return repr(self) == repr(other) @@ -92,8 +95,9 @@ def __eq__(self, other): @ufl_type(is_abstract=True) class FormArgument(Terminal): - "An abstract class for a form argument (a thing in a primal finite element space)." + """An abstract class for a form argument (a thing in a primal finite element space).""" __slots__ = () def __init__(self): + """Initialise the form argument.""" Terminal.__init__(self) diff --git a/ufl/core/ufl_id.py b/ufl/core/ufl_id.py index 26dd3e675..1af849ba1 100644 --- a/ufl/core/ufl_id.py +++ b/ufl/core/ufl_id.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Utilites for types with a globally counted unique id attached to each object." +"""Utilites for types with a globally counted unique id attached to each object.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -40,11 +39,11 @@ def __init__(self, *args, ufl_id=None): """ def _get_ufl_id(self): - "Return the ufl_id of this object." + """Return the ufl_id of this object.""" return self._ufl_id def _init_ufl_id(cls): - "Initialize new ufl_id for the object under construction." + """Initialize new ufl_id for the object under construction.""" # Bind cls with closure here def init_ufl_id(self, ufl_id): diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index da86467d6..b83d30094 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- - +"""UFL type.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -36,12 +35,15 @@ def __repr__(self) -> str: """Return a string representation of the object.""" def __hash__(self) -> int: + """Hash the object.""" return hash(self._ufl_hash_data_()) def __eq__(self, other): + """Check if two objects are equal.""" return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() def __ne__(self, other): + """Check inequality.""" return not self.__eq__(other) @@ -55,17 +57,17 @@ def attach_operators_from_hash_data(cls): assert hasattr(cls, "_ufl_hash_data_") def __hash__(self): - "__hash__ implementation attached in attach_operators_from_hash_data" + """__hash__ implementation attached in attach_operators_from_hash_data.""" return hash(self._ufl_hash_data_()) cls.__hash__ = __hash__ def __eq__(self, other): - "__eq__ implementation attached in attach_operators_from_hash_data" + """__eq__ implementation attached in attach_operators_from_hash_data.""" return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() cls.__eq__ = __eq__ def __ne__(self, other): - "__ne__ implementation attached in attach_operators_from_hash_data" + """__ne__ implementation attached in attach_operators_from_hash_data.""" return not self.__eq__(other) cls.__ne__ = __ne__ @@ -73,7 +75,7 @@ def __ne__(self, other): def get_base_attr(cls, name): - "Return first non-``None`` attribute of given name among base classes." + """Return first non-``None`` attribute of given name among base classes.""" for base in cls.mro(): if hasattr(base, name): attr = getattr(base, name) @@ -94,7 +96,7 @@ def set_trait(cls, basename, value, inherit=False): def determine_num_ops(cls, num_ops, unop, binop, rbinop): - "Determine number of operands for this type." + """Determine number of operands for this type.""" # Try to determine num_ops from other traits or baseclass, or # require num_ops to be set for non-abstract classes if it cannot # be determined automatically @@ -112,7 +114,7 @@ def determine_num_ops(cls, num_ops, unop, binop, rbinop): def check_is_terminal_consistency(cls): - "Check for consistency in ``is_terminal`` trait among superclasses." + """Check for consistency in ``is_terminal`` trait among superclasses.""" if cls._ufl_is_terminal_ is None: msg = (f"Class {cls.__name__} has not specified the is_terminal trait." " Did you forget to inherit from Terminal or Operator?") @@ -126,7 +128,7 @@ def check_is_terminal_consistency(cls): def check_abstract_trait_consistency(cls): - "Check that the first base classes up to ``Expr`` are other UFL types." + """Check that the first base classes up to ``Expr`` are other UFL types.""" for base in cls.mro(): if base is core.expr.Expr: break @@ -137,8 +139,7 @@ def check_abstract_trait_consistency(cls): def check_has_slots(cls): - """Check if type has ``__slots__`` unless it is marked as exception with - ``_ufl_noslots_``.""" + """Check if type has __slots__ unless it is marked as exception with _ufl_noslots_.""" if "_ufl_noslots_" in cls.__dict__: return @@ -156,8 +157,7 @@ def check_has_slots(cls): def check_type_traits_consistency(cls): - "Execute a variety of consistency checks on the ufl type traits." - + """Execute a variety of consistency checks on the ufl type traits.""" # Check for consistency in global type collection sizes Expr = core.expr.Expr assert Expr._ufl_num_typecodes_ == len(Expr._ufl_all_handler_names_) @@ -202,7 +202,7 @@ def check_implements_required_methods(cls): def check_implements_required_properties(cls): - "Check if type implements the required properties." + """Check if type implements the required properties.""" if not cls._ufl_is_abstract_: for attr in core.expr.Expr._ufl_required_properties_: if not hasattr(cls, attr): @@ -213,9 +213,10 @@ def check_implements_required_properties(cls): raise TypeError(msg.format(cls, attr)) -def attach_implementations_of_indexing_interface(cls, - inherit_shape_from_operand, - inherit_indices_from_operand): +def attach_implementations_of_indexing_interface( + cls, inherit_shape_from_operand, inherit_indices_from_operand +): + """Attach implementations of indexing interface.""" # Scalar or index-free? Then we can simplify the implementation of # tensor properties by attaching them here. if cls._ufl_is_scalar_: @@ -244,7 +245,7 @@ def _inherited_ufl_index_dimensions(self): def update_global_expr_attributes(cls): - "Update global ``Expr`` attributes, mainly by adding *cls* to global collections of ufl types." + """Update global ``Expr`` attributes, mainly by adding *cls* to global collections of ufl types.""" if cls._ufl_is_terminal_modifier_: core.expr.Expr._ufl_terminal_modifiers_.append(cls) @@ -258,6 +259,7 @@ def update_global_expr_attributes(cls): def update_ufl_type_attributes(cls): + """Update UFL type attributes.""" # Determine integer typecode by incrementally counting all types cls._ufl_typecode_ = UFLType._ufl_num_typecodes_ UFLType._ufl_num_typecodes_ += 1 @@ -274,25 +276,13 @@ def update_ufl_type_attributes(cls): UFLType._ufl_obj_del_counts_.append(0) -def ufl_type(is_abstract=False, - is_terminal=None, - is_scalar=False, - is_index_free=False, - is_shaping=False, - is_literal=False, - is_terminal_modifier=False, - is_in_reference_frame=False, - is_restriction=False, - is_evaluation=False, - is_differential=None, - use_default_hash=True, - num_ops=None, - inherit_shape_from_operand=None, - inherit_indices_from_operand=None, - wraps_type=None, - unop=None, - binop=None, - rbinop=None): +def ufl_type( + is_abstract=False, is_terminal=None, is_scalar=False, is_index_free=False, is_shaping=False, + is_literal=False, is_terminal_modifier=False, is_in_reference_frame=False, is_restriction=False, + is_evaluation=False, is_differential=None, use_default_hash=True, num_ops=None, + inherit_shape_from_operand=None, inherit_indices_from_operand=None, wraps_type=None, unop=None, + binop=None, rbinop=None +): """This decorator is to be applied to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. This decorator contains a number of checks that are @@ -304,7 +294,7 @@ def ufl_type(is_abstract=False, """ def _ufl_type_decorator_(cls): - + """UFL type decorator.""" # Update attributes for UFLType instances (BaseForm and Expr objects) update_ufl_type_attributes(cls) if not issubclass(cls, core.expr.Expr): diff --git a/ufl/corealg/__init__.py b/ufl/corealg/__init__.py index e69de29bb..ad28b971b 100644 --- a/ufl/corealg/__init__.py +++ b/ufl/corealg/__init__.py @@ -0,0 +1 @@ +"""Core algorithms.""" diff --git a/ufl/corealg/map_dag.py b/ufl/corealg/map_dag.py index 6b20c4179..946a86ff8 100644 --- a/ufl/corealg/map_dag.py +++ b/ufl/corealg/map_dag.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Basic algorithms for applying functions to subexpressions.""" - # Copyright (C) 2014-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -14,24 +12,24 @@ from ufl.corealg.multifunction import MultiFunction -def map_expr_dag(function, expression, - compress=True, - vcache=None, - rcache=None): +def map_expr_dag(function, expression, compress=True, vcache=None, rcache=None): """Apply a function to each subexpression node in an expression DAG. - If *compress* is ``True`` (default) the output object from - the function is cached in a ``dict`` and reused such that the - resulting expression DAG does not contain duplicate objects. - If the same funtion is called multiple times in a transformation (as for example in apply_derivatives), then to reuse caches across - the call, provide these two arguments: - - :arg vcache: Optional dict for caching results of intermediate transformations - :arg rcache: Optional dict for caching results for compression. - - Return the result of the final function call. + the call, use the arguments vcache and rcache. + + Args: + function: The function + expression: An expression + compress: If True (default), the output object from + the function is cached in a dict and reused such that the + resulting expression DAG does not contain duplicate objects + vcache: Optional dict for caching results of intermediate transformations + rcache: Optional dict for caching results for compression + + Returns: + The result of the final function call """ result, = map_expr_dags(function, [expression], compress=compress, vcache=vcache, @@ -39,10 +37,7 @@ def map_expr_dag(function, expression, return result -def map_expr_dags(function, expressions, - compress=True, - vcache=None, - rcache=None): +def map_expr_dags(function, expressions, compress=True, vcache=None, rcache=None): """Apply a function to each subexpression node in an expression DAG. If *compress* is ``True`` (default) the output object from @@ -51,14 +46,20 @@ def map_expr_dags(function, expressions, If the same funtion is called multiple times in a transformation (as for example in apply_derivatives), then to reuse caches across - the call, provide these two arguments: - - :arg vcache: Optional dict for caching results of intermediate transformations - :arg rcache: Optional dict for caching results for compression. - - Return a list with the result of the final function call for each expression. + the call, use the arguments vcache and rcache. + + Args: + function: The function + expression: An expression + compress: If True (default), the output object from + the function is cached in a dict and reused such that the + resulting expression DAG does not contain duplicate objects + vcache: Optional dict for caching results of intermediate transformations + rcache: Optional dict for caching results for compression + + Returns: + a list with the result of the final function call for each expression """ - # Temporary data structures # expr -> r = function(expr,...), cache of intermediate results vcache = {} if vcache is None else vcache diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index e0ee5e8f3..1c57c2928 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Base class for multifunctions with UFL ``Expr`` type dispatch.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -16,13 +14,13 @@ def get_num_args(function): - "Return the number of arguments accepted by *function*." + """Return the number of arguments accepted by *function*.""" sig = inspect.signature(function) return len(sig.parameters) + 1 def memoized_handler(handler): - "Function decorator to memoize ``MultiFunction`` handlers." + """Function decorator to memoize ``MultiFunction`` handlers.""" def _memoized_handler(self, o): c = getattr(self, "_memoized_handler_cache") @@ -50,6 +48,7 @@ class MultiFunction(object): _handlers_cache = {} def __init__(self): + """Initialise.""" # Analyse class properties and cache handler data the # first time this is run for a particular class # (cached for each algorithm for performance) @@ -92,11 +91,11 @@ def __init__(self): self._memoized_handler_cache = {} def __call__(self, o, *args): - "Delegate to handler function based on typecode of first argument." + """Delegate to handler function based on typecode of first argument.""" return self._handlers[o._ufl_typecode_](o, *args) def undefined(self, o, *args): - "Trigger error for types with missing handlers." + """Trigger error for types with missing handlers.""" raise ValueError(f"No handler defined for {o._ufl_class_.__name__}.") def reuse_if_untouched(self, o, *ops): diff --git a/ufl/corealg/traversal.py b/ufl/corealg/traversal.py index 7b582e918..782e16999 100644 --- a/ufl/corealg/traversal.py +++ b/ufl/corealg/traversal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Various expression traversal utilities. The algorithms here are non-recursive, which is faster than recursion @@ -40,8 +39,7 @@ def post_traversal(expr): def cutoff_post_traversal(expr, cutofftypes): - """Yield ``o`` for each node ``o`` in *expr*, child before parent, but - skipping subtrees of the cutofftypes.""" + """Yield ``o`` for each node ``o`` in *expr*, child before parent, but skipping subtrees of the cutofftypes.""" lifo = [(expr, list(reversed(expr.ufl_operands)))] while lifo: expr, deps = lifo[-1] @@ -81,7 +79,8 @@ def unique_pre_traversal(expr, visited=None): def unique_post_traversal(expr, visited=None): """Yield ``o`` for each node ``o`` in *expr*, child before parent. - Never visit a node twice.""" + Never visit a node twice. + """ lifo = [(expr, list(expr.ufl_operands))] if visited is None: visited = set() @@ -102,7 +101,8 @@ def unique_post_traversal(expr, visited=None): def cutoff_unique_post_traversal(expr, cutofftypes, visited=None): """Yield ``o`` for each node ``o`` in *expr*, child before parent. - Never visit a node twice.""" + Never visit a node twice. + """ lifo = [(expr, list(reversed(expr.ufl_operands)))] if visited is None: visited = set() @@ -125,12 +125,14 @@ def cutoff_unique_post_traversal(expr, cutofftypes, visited=None): def traverse_terminals(expr): + """Traverse terminals.""" for op in pre_traversal(expr): if op._ufl_is_terminal_: yield op def traverse_unique_terminals(expr, visited=None): + """Traverse unique terminals.""" for op in unique_pre_traversal(expr, visited=visited): if op._ufl_is_terminal_: yield op diff --git a/ufl/differentiation.py b/ufl/differentiation.py index ed4ff2dd8..e3820603a 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -27,22 +27,25 @@ @ufl_type(is_abstract=True, is_differential=True) class Derivative(Operator): - "Base class for all derivative types." + """Base class for all derivative types.""" + __slots__ = () def __init__(self, operands): + """Initalise.""" Operator.__init__(self, operands) @ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoefficientDerivative(Derivative): - """Derivative of the integrand of a form w.r.t. the - degrees of freedom in a discrete Coefficient.""" + """Derivative of the integrand of a form w.r.t. the degrees of freedom in a discrete Coefficient.""" + __slots__ = () def __new__(cls, integrand, coefficients, arguments, coefficient_derivatives): + """Create a new CoefficientDerivative.""" if not isinstance(coefficients, ExprList): raise ValueError("Expecting ExprList instance with Coefficients.") if not isinstance(arguments, ExprList): @@ -55,12 +58,14 @@ def __new__(cls, integrand, coefficients, arguments, def __init__(self, integrand, coefficients, arguments, coefficient_derivatives): + """Initalise.""" if not isinstance(coefficient_derivatives, ExprMapping): coefficient_derivatives = ExprMapping(coefficient_derivatives) Derivative.__init__(self, (integrand, coefficients, arguments, coefficient_derivatives)) def __str__(self): + """Format as a string.""" return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coefficient derivatives %s"\ % (self.ufl_operands[0], self.ufl_operands[1], self.ufl_operands[2], self.ufl_operands[3]) @@ -70,9 +75,11 @@ def __str__(self): inherit_indices_from_operand=0) class CoordinateDerivative(CoefficientDerivative): """Derivative of the integrand of a form w.r.t. the SpatialCoordinates.""" + __slots__ = () def __str__(self): + """Format as a string.""" return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coordinate derivatives %s"\ % (self.ufl_operands[0], self.ufl_operands[1], self.ufl_operands[2], self.ufl_operands[3]) @@ -81,18 +88,19 @@ def __str__(self): @ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormDerivative(CoefficientDerivative, BaseForm): - """Derivative of a base form w.r.t the - degrees of freedom in a discrete Coefficient.""" + """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" + _ufl_noslots_ = True def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): + """Initalise.""" CoefficientDerivative.__init__(self, base_form, coefficients, arguments, coefficient_derivatives) BaseForm.__init__(self) def _analyze_form_arguments(self): - """Collect the arguments of the corresponding BaseForm""" + """Collect the arguments of the corresponding BaseForm.""" from ufl.algorithms.analysis import extract_type, extract_coefficients base_form, _, arguments, _ = self.ufl_operands @@ -120,10 +128,12 @@ def arg_type(x): inherit_indices_from_operand=0) class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): """Derivative of a base form w.r.t. the SpatialCoordinates.""" + _ufl_noslots_ = True def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): + """Initalise.""" BaseFormDerivative.__init__(self, base_form, coefficients, arguments, coefficient_derivatives) @@ -131,14 +141,14 @@ def __init__(self, base_form, coefficients, arguments, @ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): - """Derivative of a base form operator w.r.t the - degrees of freedom in a discrete Coefficient.""" + """Derivative of a base form operator w.r.t the degrees of freedom in a discrete Coefficient.""" _ufl_noslots_ = True # BaseFormOperatorDerivative is only needed because of a different # differentiation procedure for BaseformOperator objects. def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): + """Initalise.""" BaseFormDerivative.__init__(self, base_form, coefficients, arguments, coefficient_derivatives) self._argument_slots = base_form._argument_slots @@ -151,7 +161,7 @@ def __init__(self, base_form, coefficients, arguments, __repr__ = Operator.__repr__ def argument_slots(self, outer_form=False): - """Returns a tuple of expressions containing argument and coefficient based expressions.""" + """Return a tuple of expressions containing argument and coefficient based expressions.""" from ufl.algorithms.analysis import extract_arguments base_form, _, arguments, _ = self.ufl_operands argument_slots = (base_form.argument_slots(outer_form) @@ -167,12 +177,15 @@ class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, Coordinat def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): + """Initalise.""" BaseFormOperatorDerivative.__init__(self, base_form, coefficients, arguments, coefficient_derivatives) @ufl_type(num_ops=2) class VariableDerivative(Derivative): + """Variable Derivative.""" + __slots__ = ( "ufl_shape", "ufl_free_indices", @@ -180,6 +193,7 @@ class VariableDerivative(Derivative): ) def __new__(cls, f, v): + """Create a new VariableDerivative.""" # Checks if not isinstance(f, Expr): raise ValueError("Expecting an Expr in VariableDerivative.") @@ -198,12 +212,14 @@ def __new__(cls, f, v): return Derivative.__new__(cls) def __init__(self, f, v): + """Initalise.""" Derivative.__init__(self, (f, v)) self.ufl_free_indices = f.ufl_free_indices self.ufl_index_dimensions = f.ufl_index_dimensions self.ufl_shape = f.ufl_shape + v.ufl_shape def __str__(self): + """Format as a string.""" if isinstance(self.ufl_operands[0], Terminal): return "d%s/d[%s]" % (self.ufl_operands[0], self.ufl_operands[1]) return "d/d[%s] %s" % (self.ufl_operands[1], @@ -214,18 +230,23 @@ def __str__(self): @ufl_type(is_abstract=True) class CompoundDerivative(Derivative): - "Base class for all compound derivative types." + """Base class for all compound derivative types.""" + __slots__ = () def __init__(self, operands): + """Initalise.""" Derivative.__init__(self, operands) @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Grad(CompoundDerivative): + """Grad.""" + __slots__ = ("_dim",) def __new__(cls, f): + """Create a new Grad.""" # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) @@ -234,11 +255,12 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) self._dim = find_geometric_dimension(f) def _ufl_expr_reconstruct_(self, op): - "Return a new object of the same type with new operands." + """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: raise ValueError("Operand shape mismatch in Grad reconstruct.") @@ -249,7 +271,7 @@ def _ufl_expr_reconstruct_(self, op): return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): - "Get child from mapping and return the component asked for." + """Get child from mapping and return the component asked for.""" component, i = component[:-1], component[-1] derivatives = derivatives + (i,) result = self.ufl_operands[0].evaluate(x, mapping, component, @@ -259,18 +281,23 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape + (self._dim,) def __str__(self): + """Format as a string.""" return "grad(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceGrad(CompoundDerivative): - __slots__ = ("_dim",) + """Reference grad.""" + + __slots__ = ("_dim", ) def __new__(cls, f): + """Create a new ReferenceGrad.""" # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = extract_unique_domain(f).topological_dimension() @@ -279,11 +306,12 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) self._dim = extract_unique_domain(f).topological_dimension() def _ufl_expr_reconstruct_(self, op): - "Return a new object of the same type with new operands." + """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: raise ValueError("Operand shape mismatch in ReferenceGrad reconstruct.") @@ -294,7 +322,7 @@ def _ufl_expr_reconstruct_(self, op): return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): - "Get child from mapping and return the component asked for." + """Get child from mapping and return the component asked for.""" component, i = component[:-1], component[-1] derivatives = derivatives + (i,) result = self.ufl_operands[0].evaluate(x, mapping, component, @@ -304,17 +332,22 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape + (self._dim,) def __str__(self): + """Format as a string.""" return "reference_grad(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Div(CompoundDerivative): + """Div.""" + __slots__ = () def __new__(cls, f): + """Create a new Div.""" if f.ufl_free_indices: raise ValueError("Free indices in the divergence argument is not allowed.") @@ -325,22 +358,28 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape[:-1] def __str__(self): + """Format as a string.""" return "div(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceDiv(CompoundDerivative): + """Reference divergence.""" + __slots__ = () def __new__(cls, f): + """Create a new ReferenceDiv.""" if f.ufl_free_indices: raise ValueError("Free indices in the divergence argument is not allowed.") @@ -351,21 +390,27 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape[:-1] def __str__(self): + """Format as a string.""" return "reference_div(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaGrad(CompoundDerivative): + """Nabla grad.""" + __slots__ = ("_dim",) def __new__(cls, f): + """Create a new NablaGrad.""" # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) @@ -374,11 +419,12 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) self._dim = find_geometric_dimension(f) def _ufl_expr_reconstruct_(self, op): - "Return a new object of the same type with new operands." + """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: raise ValueError("Operand shape mismatch in NablaGrad reconstruct.") @@ -390,17 +436,22 @@ def _ufl_expr_reconstruct_(self, op): @property def ufl_shape(self): + """Get the UFL shape.""" return (self._dim,) + self.ufl_operands[0].ufl_shape def __str__(self): + """Format as a string.""" return "nabla_grad(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0) class NablaDiv(CompoundDerivative): + """Nabla div.""" + __slots__ = () def __new__(cls, f): + """Create a new NablaDiv.""" if f.ufl_free_indices: raise ValueError("Free indices in the divergence argument is not allowed.") @@ -411,13 +462,16 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape[1:] def __str__(self): + """Format as a string.""" return "nabla_div(%s)" % self.ufl_operands[0] @@ -426,9 +480,12 @@ def __str__(self): @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True) class Curl(CompoundDerivative): + """Compound derivative.""" + __slots__ = ("ufl_shape",) def __new__(cls, f): + """Create a new CompoundDerivative.""" # Validate input sh = f.ufl_shape if f.ufl_shape not in ((), (2,), (3,)): @@ -443,19 +500,24 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] def __str__(self): + """Format as a string.""" return "curl(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceCurl(CompoundDerivative): + """Reference curl.""" + __slots__ = ("ufl_shape",) def __new__(cls, f): + """Create a new ReferenceCurl.""" # Validate input sh = f.ufl_shape if f.ufl_shape not in ((), (2,), (3,)): @@ -470,8 +532,10 @@ def __new__(cls, f): return CompoundDerivative.__new__(cls) def __init__(self, f): + """Initalise.""" CompoundDerivative.__init__(self, (f,)) self.ufl_shape = _curl_shapes[f.ufl_shape] def __str__(self): + """Format as a string.""" return "reference_curl(%s)" % self.ufl_operands[0] diff --git a/ufl/domain.py b/ufl/domain.py index 7bf2feedb..e3b9502ee 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -20,12 +20,10 @@ class AbstractDomain(object): - """Symbolic representation of a geometric domain with only a geometric - and topological dimension. - - """ + """Symbolic representation of a geometric domain with only a geometric and topological dimension.""" def __init__(self, topological_dimension, geometric_dimension): + """Initialise.""" # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): raise ValueError(f"Expecting integer geometric dimension, not {geometric_dimension.__class__}") @@ -39,11 +37,11 @@ def __init__(self, topological_dimension, geometric_dimension): self._geometric_dimension = geometric_dimension def geometric_dimension(self): - "Return the dimension of the space this domain is embedded in." + """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 the dimension of the topology of this domain.""" return self._topological_dimension @@ -60,6 +58,7 @@ class Mesh(AbstractDomain): """Symbolic representation of a mesh.""" def __init__(self, coordinate_element, ufl_id=None, cargo=None): + """Initialise.""" self._ufl_id = self._init_ufl_id(ufl_id) # Store reference to object that will not be used by UFL @@ -88,34 +87,42 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): AbstractDomain.__init__(self, tdim, gdim) def ufl_cargo(self): - "Return carried object that will not be used by UFL." + """Return carried object that will not be used by UFL.""" return self._ufl_cargo def ufl_coordinate_element(self): + """Get the coordinate element.""" return self._ufl_coordinate_element def ufl_cell(self): + """Get the cell.""" return self._ufl_coordinate_element.cell() def is_piecewise_linear_simplex_domain(self): + """Check if the domain is a piecewise linear simplex.""" return (self._ufl_coordinate_element.degree() == 1) and self.ufl_cell().is_simplex() def __repr__(self): + """Representation.""" r = "Mesh(%s, %s)" % (repr(self._ufl_coordinate_element), repr(self._ufl_id)) return r def __str__(self): + """Format as a string.""" return "" % (self._ufl_id,) def _ufl_hash_data_(self): + """UFL hash data.""" return (self._ufl_id, self._ufl_coordinate_element) def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return ("Mesh", renumbering[self], self._ufl_coordinate_element) # 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) @@ -127,6 +134,7 @@ class MeshView(AbstractDomain): """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 @@ -139,33 +147,41 @@ def __init__(self, mesh, topological_dimension, ufl_id=None): 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 = "MeshView(%s, %s, %s)" % (repr(self._ufl_mesh), repr(tdim), repr(self._ufl_id)) return r def __str__(self): + """Format as a string.""" return "" % ( self._ufl_id, self.topological_dimension(), self._ufl_mesh) 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) @@ -177,6 +193,7 @@ class TensorProductMesh(AbstractDomain): """Symbolic representation of a mesh.""" def __init__(self, meshes, ufl_id=None): + """Initialise.""" self._ufl_id = self._init_ufl_id(ufl_id) # TODO: Error checking of meshes @@ -197,34 +214,43 @@ def __init__(self, meshes, ufl_id=None): AbstractDomain.__init__(self, tdim, gdim) def ufl_coordinate_element(self): + """Get the coordinate element.""" return self._ufl_coordinate_element def ufl_cell(self): + """Get the cell.""" return self._ufl_cell def ufl_meshes(self): + """Get the UFL meshes.""" return self._ufl_meshes def is_piecewise_linear_simplex_domain(self): + """Check if the domain is a piecewise linear simplex.""" return False # TODO: Any cases this is True def __repr__(self): + """Representation.""" r = "TensorProductMesh(%s, %s)" % (repr(self._ufl_meshes), repr(self._ufl_id)) return r def __str__(self): + """Format as a string.""" return "" % ( self._ufl_id, self._ufl_meshes) def _ufl_hash_data_(self): + """UFL hash data.""" return (self._ufl_id,) + tuple(mesh._ufl_hash_data_() for mesh in self._ufl_meshes) def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return ("TensorProductMesh",) + tuple(mesh._ufl_signature_data_(renumbering) for mesh in self._ufl_meshes) # NB! Dropped __lt__ here, don't want users to write 'mesh1 < # mesh2'. def _ufl_sort_key_(self): + """UFL sort key.""" typespecific = (self._ufl_id, tuple(mesh._ufl_sort_key_() for mesh in self._ufl_meshes)) return (self.geometric_dimension(), self.topological_dimension(), "TensorProductMesh", typespecific) @@ -233,7 +259,7 @@ def _ufl_sort_key_(self): # --- Utility conversion functions def affine_mesh(cell, ufl_id=None): - "Create a Mesh over a given cell type with an affine geometric parameterization." + """Create a Mesh over a given cell type with an affine geometric parameterization.""" from ufl.finiteelement import VectorElement cell = as_cell(cell) gdim = cell.geometric_dimension() @@ -246,10 +272,7 @@ def affine_mesh(cell, ufl_id=None): def default_domain(cell): - """Create a singular default Mesh from a cell, always returning the - same Mesh object for the same cell. - - """ + """Create a singular default Mesh from a cell, always returning the same Mesh object for the same cell.""" global _default_domains assert isinstance(cell, AbstractCell) domain = _default_domains.get(cell) @@ -283,16 +306,14 @@ def as_domain(domain): def sort_domains(domains): - "Sort domains in a canonical ordering." + """Sort domains in a canonical ordering.""" return tuple(sorted(domains, key=lambda domain: domain._ufl_sort_key_())) def join_domains(domains): - """Take a list of domains and return a tuple with only unique domain - objects. + """Take a list of domains and return a tuple with only unique domain objects. Checks that domains with the same id are compatible. - """ # Use hashing to join domains, ignore None domains = set(domains) - set((None,)) @@ -336,7 +357,7 @@ def join_domains(domains): # TODO: Move these to an analysis module? def extract_domains(expr): - "Return all domains expression is defined on." + """Return all domains expression is defined on.""" domainlist = [] for t in traverse_unique_terminals(expr): domainlist.extend(t.ufl_domains()) @@ -344,7 +365,7 @@ def extract_domains(expr): def extract_unique_domain(expr): - "Return the single unique domain expression is defined on or throw an error." + """Return the single unique domain expression is defined on or throw an error.""" domains = extract_domains(expr) if len(domains) == 1: return domains[0] @@ -355,7 +376,7 @@ def extract_unique_domain(expr): def find_geometric_dimension(expr): - "Find the geometric dimension of an expression." + """Find the geometric dimension of an expression.""" gdims = set() for t in traverse_unique_terminals(expr): domain = extract_unique_domain(t) diff --git a/ufl/duals.py b/ufl/duals.py index c0f1b15dc..1e017a547 100644 --- a/ufl/duals.py +++ b/ufl/duals.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"""Predicates for recognising duals""" - +"""Predicates for recognising duals.""" # Copyright (C) 2021 India Marsden # # This file is part of UFL (https://www.fenicsproject.org) @@ -10,18 +8,20 @@ def is_primal(object): - """Determine if the object belongs to a primal space + """Determine if the object belongs to a primal space. - This is not simply the negation of :func:`is_dual`, + This is not simply the negation of is_dual, because a mixed function space containing both primal - and dual components is neither primal nor dual.""" + and dual components is neither primal nor dual. + """ return hasattr(object, '_primal') and object._primal def is_dual(object): - """Determine if the object belongs to a dual space + """Determine if the object belongs to a dual space. - This is not simply the negation of :func:`is_primal`, + This is not simply the negation of is_primal, because a mixed function space containing both primal - and dual components is neither primal nor dual.""" + and dual components is neither primal nor dual. + """ return hasattr(object, '_dual') and object._dual diff --git a/ufl/equation.py b/ufl/equation.py index 2800da880..f36089abb 100644 --- a/ufl/equation.py +++ b/ufl/equation.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"The Equation class, used to express equations like a == L." - +"""The Equation class, used to express equations like a == L.""" # Copyright (C) 2012-2016 Anders Logg and Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -12,12 +10,15 @@ class Equation(object): - """This class is used to represent equations expressed by the "==" + """Equation. + + This class is used to represent equations expressed by the "==" operator. Examples include a == L and F == 0 where a, L and F are - Form objects.""" + Form objects. + """ def __init__(self, lhs, rhs): - "Create equation lhs == rhs" + """Create equation lhs == rhs.""" self.lhs = lhs self.rhs = rhs @@ -43,11 +44,13 @@ def __bool__(self): __nonzero__ = __bool__ def __eq__(self, other): - "Compare two equations by comparing lhs and rhs." + """Compare two equations by comparing lhs and rhs.""" return isinstance(other, Equation) and self.lhs == other.lhs and self.rhs == other.rhs def __hash__(self): + """Hash.""" return hash((hash(self.lhs), hash(self.rhs))) def __repr__(self): + """Representation.""" return f"Equation({self.lhs!r}, {self.rhs!r})" diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index f7e717061..60ae13f6f 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """This module defines special types for representing mapping of expressions to expressions.""" - # Copyright (C) 2014 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -18,61 +16,76 @@ @ufl_type(num_ops="varying") class ExprList(Operator): - "List of Expr objects. For internal use, never to be created by end users." + """List of Expr objects. For internal use, never to be created by end users.""" + __slots__ = () def __init__(self, *operands): + """Initialise.""" Operator.__init__(self, operands) # Enable Cofunction/Coargument for BaseForm differentiation if not all(isinstance(i, (Expr, Cofunction, Coargument)) for i in operands): raise ValueError("Expecting Expr, Cofunction or Coargument in ExprList.") def __getitem__(self, i): + """Get an item.""" return self.ufl_operands[i] def __len__(self): + """Get the length.""" return len(self.ufl_operands) def __iter__(self): + """Return iterable.""" return iter(self.ufl_operands) def __str__(self): + """Format as a string.""" return "ExprList(*(%s,))" % ", ".join(str(i) for i in self.ufl_operands) def __repr__(self): + """Representation.""" r = "ExprList(*%s)" % repr(self.ufl_operands) return r @property def ufl_shape(self): + """Get the UFL shape.""" raise ValueError("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): + """Get the UFL free indices.""" raise ValueError("A non-tensor type has no ufl_free_indices.") def free_indices(self): + """Get the free indices.""" raise ValueError("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): + """Get the UFL index dimensions.""" raise ValueError("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): + """Get the index dimensions.""" raise ValueError("A non-tensor type has no index_dimensions.") @ufl_type(num_ops="varying") class ExprMapping(Operator): - "Mapping of Expr objects. For internal use, never to be created by end users." + """Mapping of Expr objects. For internal use, never to be created by end users.""" + __slots__ = () def __init__(self, *operands): + """Initialise.""" Operator.__init__(self, operands) if not all(isinstance(e, Expr) for e in operands): raise ValueError("Expecting Expr in ExprMapping.") def ufl_domains(self): + """Get the UFL domains.""" # Because this type can act like a terminal if it has no # operands, we need to override some recursive operations if self.ufl_operands: @@ -81,26 +94,33 @@ def ufl_domains(self): return [] def __str__(self): + """Format as a string.""" return "ExprMapping(*%s)" % repr(self.ufl_operands) def __repr__(self): + """Representation.""" r = "ExprMapping(*%s)" % repr(self.ufl_operands) return r @property def ufl_shape(self): + """Get the UFL shape.""" raise ValueError("A non-tensor type has no ufl_shape.") @property def ufl_free_indices(self): + """Get the UFL free indices.""" raise ValueError("A non-tensor type has no ufl_free_indices.") def free_indices(self): + """Get the free indices.""" raise ValueError("A non-tensor type has no free_indices.") @property def ufl_index_dimensions(self): + """Get the UFL index dimensions.""" raise ValueError("A non-tensor type has no ufl_index_dimensions.") def index_dimensions(self): + """Get the index dimensions.""" raise ValueError("A non-tensor type has no index_dimensions.") diff --git a/ufl/exprequals.py b/ufl/exprequals.py index dc00ae4fb..cf0359d31 100644 --- a/ufl/exprequals.py +++ b/ufl/exprequals.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +"""Expr equals.""" from collections import defaultdict @@ -9,10 +9,11 @@ def expr_equals(self, other): - """Checks whether the two expressions are represented the - exact same way. This does not check if the expressions are - mathematically equal or equivalent! Used by sets and dicts.""" + """Checks whether the two expressions are represented the exact same way. + This does not check if the expressions are + mathematically equal or equivalent! Used by sets and dicts. + """ # Fast cutoffs for common cases, type difference or hash # difference will cutoff more or less all nonequal types if type(self) is not type(other) or hash(self) != hash(other): diff --git a/ufl/exproperators.py b/ufl/exproperators.py index 8715f18de..f0b38ec79 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -1,8 +1,9 @@ -# -*- coding: utf-8 -*- -"""This module attaches special functions to Expr. -This way we avoid circular dependencies between e.g. -Sum and its superclass Expr.""" +"""Expr operators. +This module attaches special functions to Expr. +This way we avoid circular dependencies between e.g. +Sum and its superclass Expr. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -34,22 +35,22 @@ def _le(left, right): - "UFL operator: A boolean expresion (left <= right) for use with conditional." + """A boolean expresion (left <= right) for use with conditional.""" return LE(left, right) def _ge(left, right): - "UFL operator: A boolean expresion (left >= right) for use with conditional." + """A boolean expresion (left >= right) for use with conditional.""" return GE(left, right) def _lt(left, right): - "UFL operator: A boolean expresion (left < right) for use with conditional." + """A boolean expresion (left < right) for use with conditional.""" return LT(left, right) def _gt(left, right): - "UFL operator: A boolean expresion (left > right) for use with conditional." + """A boolean expresion (left > right) for use with conditional.""" return GT(left, right) @@ -84,7 +85,7 @@ def _ne(self, other): def _as_tensor(self, indices): - "UFL operator: A^indices := as_tensor(A, indices)." + """A^indices := as_tensor(A, indices).""" if not isinstance(indices, tuple): raise ValueError("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") if not all(isinstance(i, Index) for i in indices): @@ -98,6 +99,7 @@ def _as_tensor(self, indices): # --- Helper functions for product handling --- def _mult(a, b): + """Multiply.""" # Discover repeated indices, which results in index sums afi = a.ufl_free_indices bfi = b.ufl_free_indices @@ -174,6 +176,7 @@ def _mult(a, b): def _mul(self, o): + """Multiply.""" if not isinstance(o, _valid_types): return NotImplemented o = as_ufl(o) @@ -184,6 +187,7 @@ def _mul(self, o): def _rmul(self, o): + """Multiply.""" if not isinstance(o, _valid_types): return NotImplemented o = as_ufl(o) @@ -194,6 +198,7 @@ def _rmul(self, o): def _add(self, o): + """Add.""" if not isinstance(o, _valid_types): return NotImplemented return Sum(self, o) @@ -203,6 +208,7 @@ def _add(self, o): def _radd(self, o): + """Add.""" if not isinstance(o, _valid_types): return NotImplemented if isinstance(o, numbers.Number) and o == 0: @@ -216,6 +222,7 @@ def _radd(self, o): def _sub(self, o): + """Subtract.""" if not isinstance(o, _valid_types): return NotImplemented return Sum(self, -o) @@ -225,6 +232,7 @@ def _sub(self, o): def _rsub(self, o): + """Subtract.""" if not isinstance(o, _valid_types): return NotImplemented return Sum(o, -self) @@ -234,6 +242,7 @@ def _rsub(self, o): def _div(self, o): + """Divide.""" if not isinstance(o, _valid_types): return NotImplemented sh = self.ufl_shape @@ -249,6 +258,7 @@ def _div(self, o): def _rdiv(self, o): + """Divide.""" if not isinstance(o, _valid_types): return NotImplemented return Division(o, self) @@ -259,6 +269,7 @@ def _rdiv(self, o): def _pow(self, o): + """Raise to a power.""" if not isinstance(o, _valid_types): return NotImplemented if o == 2 and self.ufl_shape: @@ -270,6 +281,7 @@ def _pow(self, o): def _rpow(self, o): + """Raise to a power.""" if not isinstance(o, _valid_types): return NotImplemented return Power(o, self) @@ -280,6 +292,7 @@ def _rpow(self, o): # TODO: Add Negated class for this? Might simplify reductions in Add. def _neg(self): + """Negate.""" return -1 * self @@ -287,6 +300,7 @@ def _neg(self): def _abs(self): + """Absolute value.""" return Abs(self) @@ -296,6 +310,7 @@ def _abs(self): # --- Extend Expr with restiction operators a("+"), a("-") --- def _restrict(self, side): + """Restrict.""" if side == "+": return PositiveRestricted(self) if side == "-": @@ -304,9 +319,11 @@ def _restrict(self, side): def _eval(self, coord, mapping=None, component=()): - # Evaluate expression at this particular coordinate, with provided - # values for other terminals in mapping + """Evaluate. + Evaluate expression at this particular coordinate, with provided + values for other terminals in mapping. + """ # Evaluate derivatives first from ufl.algorithms import expand_derivatives f = expand_derivatives(self) @@ -319,7 +336,7 @@ def _eval(self, coord, mapping=None, component=()): def _call(self, arg, mapping=None, component=()): - # Taking the restriction or evaluating depending on argument + """Take the restriction or evaluate depending on argument.""" if arg in ("+", "-"): if mapping is not None: raise ValueError("Not expecting a mapping when taking restriction.") @@ -334,8 +351,10 @@ def _call(self, arg, mapping=None, component=()): # --- Extend Expr with the transpose operation A.T --- def _transpose(self): - """Transpose a rank-2 tensor expression. For more general transpose - operations of higher order tensor expressions, use indexing and Tensor.""" + """Transpose a rank-2 tensor expression. + + For more general transpose operations of higher order tensor expressions, use indexing and Tensor. + """ return Transposed(self) @@ -345,7 +364,7 @@ def _transpose(self): # --- Extend Expr with indexing operator a[i] --- def _getitem(self, component): - + """Get an item.""" # Treat component consistently as tuple below if not isinstance(component, tuple): component = (component,) @@ -406,7 +425,7 @@ def _getitem(self, component): # --- Extend Expr with spatial differentiation operator a.dx(i) --- def _dx(self, *ii): - "Return the partial derivative with respect to spatial variable number *ii*." + """Return the partial derivative with respect to spatial variable number *ii*.""" d = self # Unwrap ii to allow .dx(i,j) and .dx((i,j)) if len(ii) == 1 and isinstance(ii[0], tuple): diff --git a/ufl/finiteelement/__init__.py b/ufl/finiteelement/__init__.py index 9a24584ad..f7a7c94a3 100644 --- a/ufl/finiteelement/__init__.py +++ b/ufl/finiteelement/__init__.py @@ -1,6 +1,5 @@ -# -*- coding: utf-8 -*- # flake8: noqa -"This module defines the UFL finite element classes." +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # diff --git a/ufl/finiteelement/brokenelement.py b/ufl/finiteelement/brokenelement.py index 4bd49155f..2467e9d0b 100644 --- a/ufl/finiteelement/brokenelement.py +++ b/ufl/finiteelement/brokenelement.py @@ -1,3 +1,4 @@ +"""Element.""" # -*- coding: utf-8 -*- # Copyright (C) 2014 Andrew T. T. McRae # @@ -14,6 +15,7 @@ class BrokenElement(FiniteElementBase): """The discontinuous version of an existing Finite Element space.""" def __init__(self, element): + """Init.""" self._element = element family = "BrokenElement" @@ -26,9 +28,11 @@ def __init__(self, element): quad_scheme, value_shape, reference_value_shape) def __repr__(self): + """Doc.""" return f"BrokenElement({repr(self._element)})" def mapping(self): + """Doc.""" return self._element.mapping() def sobolev_space(self): @@ -36,9 +40,11 @@ def sobolev_space(self): return L2 def reconstruct(self, **kwargs): + """Doc.""" return BrokenElement(self._element.reconstruct(**kwargs)) def __str__(self): + """Doc.""" return f"BrokenElement({repr(self._element)})" def shortstr(self): diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py index 73ca9fe90..90c783d4f 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/finiteelement/elementlist.py @@ -1,8 +1,9 @@ -# -*- coding: utf-8 -*- -"""This module provides an extensive list of predefined finite element -families. Users or, more likely, form compilers, may register new -elements by calling the function register_element.""" +"""Element. +This module provides an extensive list of predefined finite element +families. Users or, more likely, form compilers, may register new +elements by calling the function register_element. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -32,7 +33,7 @@ # Function for registering new elements def register_element(family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames): - "Register new finite element family." + """Register new finite element family.""" if family in ufl_elements: raise ValueError(f"Finite element '{family}%s' has already been registered.") ufl_elements[family] = (family, short_name, value_rank, sobolev_space, @@ -43,11 +44,12 @@ def register_element(family, short_name, value_rank, sobolev_space, mapping, def register_alias(alias, to): + """Doc.""" aliases[alias] = to def show_elements(): - "Shows all registered elements." + """Shows all registered elements.""" print("Showing all registered elements:") print("================================") shown = set() @@ -275,11 +277,12 @@ def show_elements(): # variant='mse' in the appropriate places def feec_element(family, n, r, k): - """Finite element exterior calculus notation + """Finite element exterior calculus notation. + n = topological dimension of domain r = polynomial order - k = form_degree""" - + k = form_degree + """ # Note: We always map to edge elements in 2D, don't know how to # differentiate otherwise? @@ -321,11 +324,12 @@ def feec_element(family, n, r, k): def feec_element_l2(family, n, r, k): - """Finite element exterior calculus notation + """Finite element exterior calculus notation. + n = topological dimension of domain r = polynomial order - k = form_degree""" - + k = form_degree + """ # Note: We always map to edge elements in 2D, don't know how to # differentiate otherwise? @@ -393,7 +397,6 @@ def canonical_element_description(family, cell, order, form_degree): This is used by the FiniteElement constructor to ved input data against the element list and aliases defined in ufl. """ - # Get domain dimensions if cell is not None: tdim = cell.topological_dimension() diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/finiteelement/enrichedelement.py index e2b191a13..81e2d88cc 100644 --- a/ufl/finiteelement/enrichedelement.py +++ b/ufl/finiteelement/enrichedelement.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -15,11 +14,10 @@ class EnrichedElementBase(FiniteElementBase): - """The vector sum of several finite element spaces: + """The vector sum of several finite element spaces.""" - .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. - """ def __init__(self, *elements): + """Doc.""" self._elements = elements cell = elements[0].cell() @@ -61,6 +59,7 @@ def __init__(self, *elements): reference_value_shape) def mapping(self): + """Doc.""" return self._elements[0].mapping() def sobolev_space(self): @@ -82,6 +81,7 @@ def sobolev_space(self): return sobolev_space def variant(self): + """Doc.""" try: variant, = {e.variant() for e in self._elements} return variant @@ -89,57 +89,59 @@ def variant(self): return None def reconstruct(self, **kwargs): + """Doc.""" return type(self)(*[e.reconstruct(**kwargs) for e in self._elements]) class EnrichedElement(EnrichedElementBase): - """The vector sum of several finite element spaces: + r"""The vector sum of several finite element spaces. - .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. + .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. - Dual basis is a concatenation of subelements dual bases; - primal basis is a concatenation of subelements primal bases; - resulting element is not nodal even when subelements are. - Structured basis may be exploited in form compilers. + Dual basis is a concatenation of subelements dual bases; + primal basis is a concatenation of subelements primal bases; + resulting element is not nodal even when subelements are. + Structured basis may be exploited in form compilers. """ + def is_cellwise_constant(self): - """Return whether the basis functions of this - element is spatially constant over each cell.""" + """Return whether the basis functions of this element is spatially constant over each cell.""" return all(e.is_cellwise_constant() for e in self._elements) def __repr__(self): + """Doc.""" return "EnrichedElement(" + ", ".join(repr(e) for e in self._elements) + ")" def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "<%s>" % " + ".join(str(e) for e in self._elements) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "<%s>" % " + ".join(e.shortstr() for e in self._elements) class NodalEnrichedElement(EnrichedElementBase): - """The vector sum of several finite element spaces: + r"""The vector sum of several finite element spaces. - .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. + .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. - Primal basis is reorthogonalized to dual basis which is - a concatenation of subelements dual bases; resulting - element is nodal. + Primal basis is reorthogonalized to dual basis which is + a concatenation of subelements dual bases; resulting + element is nodal. """ def is_cellwise_constant(self): - """Return whether the basis functions of this - element is spatially constant over each cell.""" + """Return whether the basis functions of this element is spatially constant over each cell.""" return False def __repr__(self): + """Doc.""" return "NodalEnrichedElement(" + ", ".join(repr(e) for e in self._elements) + ")" def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "" % ", ".join(str(e) for e in self._elements) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "NodalEnriched(%s)" % ", ".join(e.shortstr() for e in self._elements) diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index f603e12dc..790ed107e 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." - +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -21,7 +19,7 @@ class FiniteElement(FiniteElementBase): - "The basic finite element class for all simple finite elements." + """The basic finite element class for all simple finite elements.""" # TODO: Move these to base? __slots__ = ("_short_name", "_sobolev_space", "_mapping", "_variant", "_repr") @@ -33,8 +31,7 @@ def __new__(cls, form_degree=None, quad_scheme=None, variant=None): - """Intercepts construction to expand CG, DG, RTCE and RTCF - spaces on TensorProductCells.""" + """Intercepts construction to expand CG, DG, RTCE and RTCF spaces on TensorProductCells.""" if cell is not None: cell = as_cell(cell) @@ -104,6 +101,7 @@ def __new__(cls, elif family == "DQ": def dq_family(cell): + """Doc.""" return "DG" if cell.cellname() in simplices else "DQ" return TensorProductElement(*[FiniteElement(dq_family(c), c, degree, variant=variant) for c in cell.sub_cells()], @@ -111,6 +109,7 @@ def dq_family(cell): elif family == "DQ L2": def dq_family_l2(cell): + """Doc.""" return "DG L2" if cell.cellname() in simplices else "DQ L2" return TensorProductElement(*[FiniteElement(dq_family_l2(c), c, degree, variant=variant) for c in cell.sub_cells()], @@ -127,20 +126,14 @@ def __init__(self, variant=None): """Create finite element. - *Arguments* - family (string) - The finite element family - cell - The geometric cell - degree (int) - The polynomial degree (optional) - form_degree (int) - The form degree (FEEC notation, used when field is + Args: + family: The finite element family + cell: The geometric cell + degree: The polynomial degree (optional) + form_degree: The form degree (FEEC notation, used when field is viewed as k-form) - quad_scheme - The quadrature scheme (optional) - variant - Hint for the local basis function variant (optional) + quad_scheme: The quadrature scheme (optional) + variant: Hint for the local basis function variant (optional) """ # Note: Unfortunately, dolfin sometimes passes None for # cell. Until this is fixed, allow it: @@ -192,9 +185,11 @@ def __repr__(self): return self._repr def _is_globally_constant(self): + """Doc.""" return self.family() == "Real" def _is_linear(self): + """Doc.""" return self.family() == "Lagrange" and self.degree() == 1 def mapping(self): @@ -210,8 +205,7 @@ def variant(self): return self._variant def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, variant=None): - """Construct a new FiniteElement object with some properties - replaced with new values.""" + """Construct a new FiniteElement object with some properties replaced with new values.""" if family is None: family = self.family() if cell is None: @@ -225,7 +219,7 @@ def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, var return FiniteElement(family, cell, degree, quad_scheme=quad_scheme, variant=variant) def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" qs = self.quadrature_scheme() qs = "" if qs is None else "(%s)" % qs v = self.variant() @@ -234,7 +228,7 @@ def __str__(self): qs, v, self.cell()) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return f"{self._short_name}{istr(self.degree())}({self.quadrature_scheme()},{istr(self.variant())})" def __getnewargs__(self): diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py index c92178e6f..7f34252fc 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/finiteelement/finiteelementbase.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -17,7 +16,7 @@ class FiniteElementBase(ABC): - "Base class for all finite elements." + """Base class for all finite elements.""" __slots__ = ("_family", "_cell", "_degree", "_quad_scheme", "_value_shape", "_reference_value_shape", "__weakref__") @@ -72,29 +71,31 @@ def _is_linear(self): return False def _ufl_hash_data_(self): + """Doc.""" return repr(self) def _ufl_signature_data_(self): + """Doc.""" return repr(self) def __hash__(self): - "Compute hash code for insertion in hashmaps." + """Compute hash code for insertion in hashmaps.""" return hash(self._ufl_hash_data_()) def __eq__(self, other): - "Compute element equality for insertion in hashmaps." + """Compute element equality for insertion in hashmaps.""" return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() def __ne__(self, other): - "Compute element inequality for insertion in hashmaps." + """Compute element inequality for insertion in hashmaps.""" return not self.__eq__(other) def __lt__(self, other): - "Compare elements by repr, to give a natural stable sorting." + """Compare elements by repr, to give a natural stable sorting.""" return repr(self) < repr(other) def family(self): # FIXME: Undefined for base? - "Return finite element family." + """Return finite element family.""" return self._family def variant(self): @@ -102,49 +103,51 @@ def variant(self): return None def degree(self, component=None): - "Return polynomial degree of finite element." + """Return polynomial degree of finite element.""" # FIXME: Consider embedded_degree concept for more accurate # degree, see blueprint return self._degree def quadrature_scheme(self): - "Return quadrature scheme of finite element." + """Return quadrature scheme of finite element.""" return self._quad_scheme def cell(self): - "Return cell of finite element." + """Return cell of finite element.""" return self._cell def is_cellwise_constant(self, component=None): - """Return whether the basis functions of this - element is spatially constant over each cell.""" + """Return whether the basis functions of this element is spatially constant over each cell.""" return self._is_globally_constant() or self.degree() == 0 def value_shape(self): - "Return the shape of the value space on the global domain." + """Return the shape of the value space on the global domain.""" return self._value_shape def reference_value_shape(self): - "Return the shape of the value space on the reference cell." + """Return the shape of the value space on the reference cell.""" return self._reference_value_shape def value_size(self): - "Return the integer product of the value shape." + """Return the integer product of the value shape.""" return product(self.value_shape()) def reference_value_size(self): - "Return the integer product of the reference value shape." + """Return the integer product of the reference value shape.""" return product(self.reference_value_shape()) def symmetry(self): # FIXME: different approach - """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` + r"""Return the symmetry dict. + + This is a mapping :math:`c_0 \\to c_1` meaning that component :math:`c_0` is represented by component :math:`c_1`. - A component is a tuple of one or more ints.""" + A component is a tuple of one or more ints. + """ return {} def _check_component(self, i): - "Check that component index i is valid" + """Check that component index i is valid.""" sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): @@ -153,23 +156,24 @@ def _check_component(self, i): f"for element (value rank {r}).") def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative - component index for a given component index.""" + """Extract direct subelement index and subelement relative component index for a given component index.""" if isinstance(i, int): i = (i,) self._check_component(i) return (None, i) def extract_component(self, i): - """Recursively extract component index relative to a (simple) element - and that element for given value component index.""" + """Recursively extract component index relative to a (simple) element. + + and that element for given value component index. + """ if isinstance(i, int): i = (i,) self._check_component(i) return (i, self) def _check_reference_component(self, i): - "Check that reference component index i is valid." + """Check that reference component index i is valid.""" sh = self.value_shape() r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): @@ -178,45 +182,49 @@ def _check_reference_component(self, i): f"for element (value rank {r}).") def extract_subelement_reference_component(self, i): - """Extract direct subelement index and subelement relative - reference component index for a given reference component index.""" + """Extract direct subelement index and subelement relative. + + reference component index for a given reference component index. + """ if isinstance(i, int): i = (i,) self._check_reference_component(i) return (None, i) def extract_reference_component(self, i): - """Recursively extract reference component index relative to a (simple) element - and that element for given reference value component index.""" + """Recursively extract reference component index relative to a (simple) element. + + and that element for given reference value component index. + """ if isinstance(i, int): i = (i,) self._check_reference_component(i) return (i, self) def num_sub_elements(self): - "Return number of sub-elements." + """Return number of sub-elements.""" return 0 def sub_elements(self): - "Return list of sub-elements." + """Return list of sub-elements.""" return [] def __add__(self, other): - "Add two elements, creating an enriched element" + """Add two elements, creating an enriched element.""" if not isinstance(other, FiniteElementBase): raise ValueError(f"Can't add element and {other.__class__}.") from ufl.finiteelement import EnrichedElement return EnrichedElement(self, other) def __mul__(self, other): - "Multiply two elements, creating a mixed element" + """Multiply two elements, creating a mixed element.""" if not isinstance(other, FiniteElementBase): raise ValueError("Can't multiply element and {other.__class__}.") from ufl.finiteelement import MixedElement return MixedElement(self, other) def __getitem__(self, index): - "Restrict finite element to a subdomain, subcomponent or topology (cell)." + """Restrict finite element to a subdomain, subcomponent or topology (cell).""" if index in ("facet", "interior"): from ufl.finiteelement import RestrictedElement return RestrictedElement(self, index) @@ -224,4 +232,5 @@ def __getitem__(self, index): raise KeyError(f"Invalid index for restriction: {repr(index)}") def __iter__(self): + """Iter.""" raise TypeError(f"'{type(self).__name__}' object is not iterable") diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py index f6412ef23..fde6ff33b 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/finiteelement/hdivcurl.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +"""Doc.""" # Copyright (C) 2008-2016 Andrew T. T. McRae # # This file is part of UFL (https://www.fenicsproject.org) @@ -12,11 +12,11 @@ class HDivElement(FiniteElementBase): - """A div-conforming version of an outer product element, assuming - this makes mathematical sense.""" + """A div-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = ("_element", ) def __init__(self, element): + """Doc.""" self._element = element family = "TensorProductElement" @@ -31,9 +31,11 @@ def __init__(self, element): quad_scheme, value_shape, reference_value_shape) def __repr__(self): + """Doc.""" return f"HDivElement({repr(self._element)})" def mapping(self): + """Doc.""" return "contravariant Piola" def sobolev_space(self): @@ -41,12 +43,15 @@ def sobolev_space(self): return HDiv def reconstruct(self, **kwargs): + """Doc.""" return HDivElement(self._element.reconstruct(**kwargs)) def variant(self): + """Doc.""" return self._element.variant() def __str__(self): + """Doc.""" return f"HDivElement({repr(self._element)})" def shortstr(self): @@ -55,11 +60,11 @@ def shortstr(self): class HCurlElement(FiniteElementBase): - """A curl-conforming version of an outer product element, assuming - this makes mathematical sense.""" + """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" __slots__ = ("_element",) def __init__(self, element): + """Doc.""" self._element = element family = "TensorProductElement" @@ -75,9 +80,11 @@ def __init__(self, element): value_shape, reference_value_shape) def __repr__(self): + """Doc.""" return f"HCurlElement({repr(self._element)})" def mapping(self): + """Doc.""" return "covariant Piola" def sobolev_space(self): @@ -85,12 +92,15 @@ def sobolev_space(self): return HCurl def reconstruct(self, **kwargs): + """Doc.""" return HCurlElement(self._element.reconstruct(**kwargs)) def variant(self): + """Doc.""" return self._element.variant() def __str__(self): + """Doc.""" return f"HCurlElement({repr(self._element)})" def shortstr(self): @@ -99,18 +109,23 @@ def shortstr(self): class WithMapping(FiniteElementBase): - """Specify an alternative mapping for the wrappee. For example, + """Specify an alternative mapping for the wrappee. + + For example, to use identity mapping instead of Piola map with an element E, write remapped = WithMapping(E, "identity") """ + def __init__(self, wrapee, mapping): + """Doc.""" if mapping == "symmetries": raise ValueError("Can't change mapping to 'symmetries'") self._mapping = mapping self.wrapee = wrapee def __getattr__(self, attr): + """Doc.""" try: return getattr(self.wrapee, attr) except AttributeError: @@ -118,9 +133,11 @@ def __getattr__(self, attr): (type(self).__name__, attr)) def __repr__(self): + """Doc.""" return f"WithMapping({repr(self.wrapee)}, '{self._mapping}')" def value_shape(self): + """Doc.""" gdim = self.cell().geometric_dimension() mapping = self.mapping() if mapping in {"covariant Piola", "contravariant Piola"}: @@ -131,6 +148,7 @@ def value_shape(self): return self.wrapee.value_shape() def reference_value_shape(self): + """Doc.""" tdim = self.cell().topological_dimension() mapping = self.mapping() if mapping in {"covariant Piola", "contravariant Piola"}: @@ -141,6 +159,7 @@ def reference_value_shape(self): return self.wrapee.reference_value_shape() def mapping(self): + """Doc.""" return self._mapping def sobolev_space(self): @@ -151,15 +170,19 @@ def sobolev_space(self): return L2 def reconstruct(self, **kwargs): + """Doc.""" mapping = kwargs.pop("mapping", self._mapping) wrapee = self.wrapee.reconstruct(**kwargs) return type(self)(wrapee, mapping) def variant(self): + """Doc.""" return self.wrapee.variant() def __str__(self): + """Doc.""" return f"WithMapping({repr(self.wrapee)}, {self._mapping})" def shortstr(self): + """Doc.""" return f"WithMapping({self.wrapee.shortstr()}, {self._mapping})" diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py index f49662122..ca16a3d80 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/finiteelement/mixedelement.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." - +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -22,13 +20,11 @@ class MixedElement(FiniteElementBase): - """A finite element composed of a nested hierarchy of mixed or simple - elements.""" + """A finite element composed of a nested hierarchy of mixed or simple elements.""" __slots__ = ("_sub_elements", "_cells") def __init__(self, *elements, **kwargs): - "Create mixed finite element from given list of elements" - + """Create mixed finite element from given list of elements.""" if type(self) is MixedElement: if kwargs: raise ValueError("Not expecting keyword arguments to MixedElement constructor.") @@ -89,22 +85,26 @@ def __init__(self, *elements, **kwargs): value_shape, reference_value_shape) def __repr__(self): + """Doc.""" return "MixedElement(" + ", ".join(repr(e) for e in self._sub_elements) + ")" def _is_linear(self): + """Doc.""" return all(i._is_linear() for i in self._sub_elements) def reconstruct_from_elements(self, *elements): - "Reconstruct a mixed element from new subelements." + """Reconstruct a mixed element from new subelements.""" if all(a == b for (a, b) in zip(elements, self._sub_elements)): return self return MixedElement(*elements) def symmetry(self): - """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` + r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. + meaning that component :math:`c_0` is represented by component :math:`c_1`. - A component is a tuple of one or more ints.""" + A component is a tuple of one or more ints. + """ # Build symmetry map from symmetries of subelements sm = {} # Base index of the current subelement into mixed value @@ -125,25 +125,29 @@ def symmetry(self): return sm or {} def sobolev_space(self): + """Doc.""" return max(e.sobolev_space() for e in self._sub_elements) def mapping(self): + """Doc.""" if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" else: return "undefined" def num_sub_elements(self): - "Return number of sub elements." + """Return number of sub elements.""" return len(self._sub_elements) def sub_elements(self): - "Return list of sub elements." + """Return list of sub elements.""" return self._sub_elements def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative - component index for a given component index.""" + """Extract direct subelement index and subelement relative. + + component index for a given component index. + """ if isinstance(i, int): i = (i,) self._check_component(i) @@ -177,14 +181,18 @@ def extract_subelement_component(self, i): return (sub_element_index, component) def extract_component(self, i): - """Recursively extract component index relative to a (simple) element - and that element for given value component index.""" + """Recursively extract component index relative to a (simple) element. + + and that element for given value component index. + """ sub_element_index, component = self.extract_subelement_component(i) return self._sub_elements[sub_element_index].extract_component(component) def extract_subelement_reference_component(self, i): - """Extract direct subelement index and subelement relative - reference_component index for a given reference_component index.""" + """Extract direct subelement index and subelement relative. + + reference_component index for a given reference_component index. + """ if isinstance(i, int): i = (i,) self._check_reference_component(i) @@ -210,14 +218,15 @@ def extract_subelement_reference_component(self, i): return (sub_element_index, reference_component) def extract_reference_component(self, i): - """Recursively extract reference_component index relative to a (simple) element - and that element for given value reference_component index.""" + """Recursively extract reference_component index relative to a (simple) element. + + and that element for given value reference_component index. + """ sub_element_index, reference_component = self.extract_subelement_reference_component(i) return self._sub_elements[sub_element_index].extract_reference_component(reference_component) def is_cellwise_constant(self, component=None): - """Return whether the basis functions of this - element is spatially constant over each cell.""" + """Return whether the basis functions of this element is spatially constant over each cell.""" if component is None: return all(e.is_cellwise_constant() for e in self.sub_elements()) else: @@ -225,7 +234,7 @@ def is_cellwise_constant(self, component=None): return e.is_cellwise_constant() def degree(self, component=None): - "Return polynomial degree of finite element." + """Return polynomial degree of finite element.""" if component is None: return self._degree # from FiniteElementBase, computed as max of subelements in __init__ else: @@ -233,9 +242,11 @@ def degree(self, component=None): return e.degree() def reconstruct(self, **kwargs): + """Doc.""" return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()]) def variant(self): + """Doc.""" try: variant, = {e.variant() for e in self.sub_elements()} return variant @@ -243,44 +254,24 @@ def variant(self): return None def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" tmp = ", ".join(str(element) for element in self._sub_elements) return "" def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" tmp = ", ".join(element.shortstr() for element in self._sub_elements) return "Mixed<" + tmp + ">" class VectorElement(MixedElement): - "A special case of a mixed finite element where all elements are equal." + """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): - """ - Create vector element (repeated mixed element) - - *Arguments* - family (string) - The finite element family (or an existing FiniteElement) - cell - The geometric cell, ignored if family is a FiniteElement - degree (int) - The polynomial degree, ignored if family is a FiniteElement - dim (int) - The value dimension of the element (optional) - form_degree (int) - The form degree (FEEC notation, used when field is - viewed as k-form), ignored if family is a FiniteElement - quad_scheme - The quadrature scheme (optional), ignored if family is a FiniteElement - variant - Hint for the local basis function variant (optional) - """ - + """Create vector element (repeated mixed element).""" if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() @@ -326,9 +317,11 @@ def __init__(self, family, cell=None, degree=None, dim=None, self._repr = f"VectorElement({repr(sub_element)}, dim={dim}{var_str})" def __repr__(self): + """Doc.""" return self._repr def reconstruct(self, **kwargs): + """Doc.""" sub_element = self._sub_element.reconstruct(**kwargs) return VectorElement(sub_element, dim=len(self.sub_elements())) @@ -337,24 +330,22 @@ def variant(self): return self._sub_element.variant() def mapping(self): + """Doc.""" return self._mapping def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return ("" % (len(self._sub_elements), self._sub_element)) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "Vector<%d x %s>" % (len(self._sub_elements), self._sub_element.shortstr()) class TensorElement(MixedElement): - """A special case of a mixed finite element where all elements are - equal. - - """ + """A special case of a mixed finite element where all elements are equal.""" __slots__ = ("_sub_element", "_shape", "_symmetry", "_sub_element_mapping", "_flattened_sub_element_mapping", @@ -362,18 +353,7 @@ class TensorElement(MixedElement): def __init__(self, family, cell=None, degree=None, shape=None, symmetry=None, quad_scheme=None, variant=None): - """Create tensor element (repeated mixed element with optional symmetries). - - :arg family: The family string, or an existing FiniteElement. - :arg cell: The geometric cell (ignored if family is a FiniteElement). - :arg degree: The polynomial degree (ignored if family is a FiniteElement). - :arg shape: The shape of the element (defaults to a square - tensor given by the geometric dimension of the cell). - :arg symmetry: Optional symmetries. - :arg quad_scheme: Optional quadrature scheme (ignored if - family is a FiniteElement). - :arg variant: Hint for the local basis function variant (optional)""" - + """Create tensor element (repeated mixed element with optional symmetries).""" if isinstance(family, FiniteElementBase): sub_element = family cell = sub_element.cell() @@ -466,6 +446,7 @@ def __init__(self, family, cell=None, degree=None, shape=None, f"symmetry={symmetry}{var_str})") def __repr__(self): + """Doc.""" return self._repr def variant(self): @@ -473,14 +454,18 @@ def variant(self): return self._sub_element.variant() def mapping(self): + """Doc.""" return self._mapping def flattened_sub_element_mapping(self): + """Doc.""" return self._flattened_sub_element_mapping def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative - component index for a given component index.""" + """Extract direct subelement index and subelement relative. + + component index for a given component index. + """ if isinstance(i, int): i = (i,) self._check_component(i) @@ -495,18 +480,21 @@ def extract_subelement_component(self, i): return (k, jj) def symmetry(self): - """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` + r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. + meaning that component :math:`c_0` is represented by component :math:`c_1`. - A component is a tuple of one or more ints.""" + A component is a tuple of one or more ints. + """ return self._symmetry def reconstruct(self, **kwargs): + """Doc.""" sub_element = self._sub_element.reconstruct(**kwargs) return TensorElement(sub_element, shape=self._shape, symmetry=self._symmetry) def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" if self._symmetry: tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in self._symmetry.items()) sym = " with symmetries (%s)" % tmp @@ -516,7 +504,7 @@ def __str__(self): (self.value_shape(), self._sub_element, sym)) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" if self._symmetry: tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in self._symmetry.items()) sym = " with symmetries (%s)" % tmp diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py index c1d10b65c..7ad71b3a7 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/finiteelement/restrictedelement.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -18,8 +17,10 @@ class RestrictedElement(FiniteElementBase): - "Represents the restriction of a finite element to a type of cell entity." + """Represents the restriction of a finite element to a type of cell entity.""" + def __init__(self, element, restriction_domain): + """Doc.""" if not isinstance(element, FiniteElementBase): raise ValueError("Expecting a finite element instance.") if restriction_domain not in valid_restriction_domains: @@ -36,75 +37,74 @@ def __init__(self, element, restriction_domain): self._restriction_domain = restriction_domain def __repr__(self): + """Doc.""" return f"RestrictedElement({repr(self._element)}, {repr(self._restriction_domain)})" def sobolev_space(self): + """Doc.""" if self._restriction_domain == "interior": return L2 else: return self._element.sobolev_space() def is_cellwise_constant(self): - """Return whether the basis functions of this element is spatially - constant over each cell. - - """ + """Return whether the basis functions of this element is spatially constant over each cell.""" return self._element.is_cellwise_constant() def _is_linear(self): + """Doc.""" return self._element._is_linear() def sub_element(self): - "Return the element which is restricted." + """Return the element which is restricted.""" return self._element def mapping(self): + """Doc.""" return self._element.mapping() def restriction_domain(self): - "Return the domain onto which the element is restricted." + """Return the domain onto which the element is restricted.""" return self._restriction_domain def reconstruct(self, **kwargs): + """Doc.""" element = self._element.reconstruct(**kwargs) return RestrictedElement(element, self._restriction_domain) def __str__(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "<%s>|_{%s}" % (self._element, self._restriction_domain) def shortstr(self): - "Format as string for pretty printing." + """Format as string for pretty printing.""" return "<%s>|_{%s}" % (self._element.shortstr(), self._restriction_domain) def symmetry(self): - """Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1` + r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. + meaning that component :math:`c_0` is represented by component :math:`c_1`. A component is a tuple of one or more ints. - """ return self._element.symmetry() def num_sub_elements(self): - "Return number of sub elements." + """Return number of sub elements.""" return self._element.num_sub_elements() def sub_elements(self): - "Return list of sub elements." + """Return list of sub elements.""" return self._element.sub_elements() def num_restricted_sub_elements(self): - # FIXME: Use this where intended, for disambiguation - # w.r.t. different sub_elements meanings. - "Return number of restricted sub elements." + """Return number of restricted sub elements.""" return 1 def restricted_sub_elements(self): - # FIXME: Use this where intended, for disambiguation - # w.r.t. different sub_elements meanings. - "Return list of restricted sub elements." + """Return list of restricted sub elements.""" return (self._element,) def variant(self): + """Doc.""" return self._element.variant() diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py index 341b018f5..07aad6e8f 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/finiteelement/tensorproductelement.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"This module defines the UFL finite element classes." +"""This module defines the UFL finite element classes.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -20,7 +19,7 @@ class TensorProductElement(FiniteElementBase): - r"""The tensor product of :math:`d` element spaces: + r"""The tensor product of :math:`d` element spaces. .. math:: V = V_1 \otimes V_2 \otimes ... \otimes V_d @@ -31,7 +30,7 @@ class TensorProductElement(FiniteElementBase): __slots__ = ("_sub_elements", "_cell") def __init__(self, *elements, **kwargs): - "Create TensorProductElement from a given list of elements." + """Create TensorProductElement from a given list of elements.""" if not elements: raise ValueError("Cannot create TensorProductElement from empty list.") @@ -69,9 +68,11 @@ def __init__(self, *elements, **kwargs): self._cell = cell def __repr__(self): + """Doc.""" return "TensorProductElement(" + ", ".join(repr(e) for e in self._sub_elements) + f", cell={repr(self._cell)})" def mapping(self): + """Doc.""" if all(e.mapping() == "identity" for e in self._sub_elements): return "identity" elif all(e.mapping() == "L2 Piola" for e in self._sub_elements): @@ -80,7 +81,7 @@ def mapping(self): return "undefined" def sobolev_space(self): - "Return the underlying Sobolev space of the TensorProductElement." + """Return the underlying Sobolev space of the TensorProductElement.""" elements = self._sub_elements if all(e.sobolev_space() == elements[0].sobolev_space() for e in elements): @@ -96,18 +97,20 @@ def sobolev_space(self): return DirectionalSobolevSpace(orders) def num_sub_elements(self): - "Return number of subelements." + """Return number of subelements.""" return len(self._sub_elements) def sub_elements(self): - "Return subelements (factors)." + """Return subelements (factors).""" return self._sub_elements def reconstruct(self, **kwargs): + """Doc.""" cell = kwargs.pop("cell", self.cell()) return TensorProductElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()], cell=cell) def variant(self): + """Doc.""" try: variant, = {e.variant() for e in self.sub_elements()} return variant @@ -115,11 +118,11 @@ def variant(self): return None def __str__(self): - "Pretty-print." + """Pretty-print.""" return "TensorProductElement(%s, cell=%s)" \ % (', '.join([str(e) for e in self._sub_elements]), str(self._cell)) def shortstr(self): - "Short pretty-print." + """Short pretty-print.""" return "TensorProductElement(%s, cell=%s)" \ % (', '.join([e.shortstr() for e in self._sub_elements]), str(self._cell)) diff --git a/ufl/form.py b/ufl/form.py index b146a2d8e..ecbd4b17d 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"The Form class." - +"""The Form class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -36,9 +34,7 @@ def _sorted_integrals(integrals): - """Sort integrals by domain id, integral type, subdomain id - for a more stable signature computation.""" - + """Sort integrals by domain id, integral type, subdomain id for a more stable signature computation.""" # Group integrals in multilevel dict by keys # [domain][integral_type][subdomain_id] integrals_dict = defaultdict( @@ -46,8 +42,7 @@ def _sorted_integrals(integrals): for integral in integrals: d = integral.ufl_domain() if d is None: - raise ValueError( - "Each integral in a form must have a uniquely defined integration domain.") + raise ValueError("Each integral in a form must have a uniquely defined integration domain.") it = integral.integral_type() si = integral.subdomain_id() integrals_dict[d][it][si] += [integral] @@ -81,7 +76,7 @@ def keyfunc(item): @ufl_type() class BaseForm(object, metaclass=UFLType): - """Description of an object containing arguments""" + """Description of an object containing arguments.""" # Slots is kept empty to enable multiple inheritance with other # classes @@ -90,26 +85,28 @@ class BaseForm(object, metaclass=UFLType): _ufl_required_methods_ = ('_analyze_form_arguments', '_analyze_domains', "ufl_domains") def __init__(self): + """Initialise.""" # Internal variables for caching form argument/coefficient data self._arguments = None self._coefficients = None # --- Accessor interface --- def arguments(self): - "Return all ``Argument`` objects found in form." + """Return all ``Argument`` objects found in form.""" if self._arguments is None: self._analyze_form_arguments() return self._arguments def coefficients(self): - "Return all ``Coefficient`` objects found in form." + """Return all ``Coefficient`` objects found in form.""" if self._coefficients is None: self._analyze_form_arguments() return self._coefficients def ufl_domain(self): - """Return the single geometric integration domain occuring in the - base form. Fails if multiple domains are found. + """Return the single geometric integration domain occuring in the base form. + + Fails if multiple domains are found. """ if self._domains is None: self._analyze_domains() @@ -122,7 +119,7 @@ def ufl_domain(self): # --- Operator implementations --- def __eq__(self, other): - """Delayed evaluation of the == operator! + """Delayed evaluation of the == operator. Just 'lhs_form == rhs_form' gives an Equation, while 'bool(lhs_form == rhs_form)' delegates @@ -131,10 +128,12 @@ def __eq__(self, other): return Equation(self, other) def __radd__(self, other): + """Add.""" # Ordering of form additions make no difference return self.__add__(other) def __add__(self, other): + """Add.""" if isinstance(other, (int, float)) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self @@ -162,18 +161,19 @@ def __add__(self, other): return NotImplemented def __sub__(self, other): - "Subtract other form from this one." + """Subtract other form from this one.""" return self + (-other) def __rsub__(self, other): - "Subtract this form from other." + """Subtract this form from other.""" return other + (-self) def __neg__(self): """Negate all integrals in form. This enables the handy "-form" syntax for e.g. the - linearized system (J, -F) from a nonlinear form F.""" + linearized system (J, -F) from a nonlinear form F. + """ if isinstance(self, ZeroBaseForm): # `-` doesn't change anything for ZeroBaseForm. # This also facilitates simplifying FormSum containing ZeroBaseForm objects. @@ -181,21 +181,21 @@ def __neg__(self): return FormSum((self, -1)) def __rmul__(self, scalar): - "Multiply all integrals in form with constant scalar value." + """Multiply all integrals in form with constant scalar value.""" # This enables the handy "0*form" or "dt*form" syntax if is_scalar_constant_expression(scalar): return FormSum((self, scalar)) return NotImplemented def __mul__(self, coefficient): - "Take the action of this form on the given coefficient." + """Take the action of this form on the given coefficient.""" if isinstance(coefficient, Expr): from ufl.formoperators import action return action(self, coefficient) return NotImplemented def __ne__(self, other): - "Immediately evaluate the != operator (as opposed to the == operator)." + """Immediately evaluate the != operator (as opposed to the == operator).""" return not self.equals(other) def __call__(self, *args, **kwargs): @@ -209,7 +209,6 @@ def __call__(self, *args, **kwargs): to replace Coefficients with expressions of matching shapes. Example: - ------- V = FiniteElement("CG", triangle, 1) v = TestFunction(V) u = TrialFunction(V) @@ -219,7 +218,6 @@ def __call__(self, *args, **kwargs): M = a(f, f, coefficients={ g: 1 }) Is equivalent to M == grad(f)**2*dx. - """ repdict = {} @@ -247,24 +245,22 @@ def __call__(self, *args, **kwargs): return self def _ufl_compute_hash_(self): - "Compute the hash" + """Compute the hash.""" # Ensure compatibility with MultiFunction # `hash(self)` will call the `__hash__` method of the subclass. return hash(self) def _ufl_expr_reconstruct_(self, *operands): - "Return a new object of the same type with new operands." + """Return a new object of the same type with new operands.""" return type(self)(*operands) - # "a @ f" notation in python 3.5 __matmul__ = __mul__ - # --- String conversion functions, for UI purposes only --- - @ufl_type() class Form(BaseForm): """Description of a weak form consisting of a sum of integrals over subdomains.""" + __slots__ = ( # --- List of Integral objects (a Form is a sum of these Integrals, everything else is derived) "_integrals", @@ -288,6 +284,7 @@ class Form(BaseForm): ) def __init__(self, integrals): + """Initialise.""" BaseForm.__init__(self) # Basic input checking (further compatibilty analysis happens # later) @@ -328,20 +325,20 @@ def __init__(self, integrals): # --- Accessor interface --- def integrals(self): - "Return a sequence of all integrals in form." + """Return a sequence of all integrals in form.""" return self._integrals def integrals_by_type(self, integral_type): - "Return a sequence of all integrals with a particular domain type." + """Return a sequence of all integrals with a particular domain type.""" return tuple(integral for integral in self.integrals() if integral.integral_type() == integral_type) def integrals_by_domain(self, domain): - "Return a sequence of all integrals with a particular integration domain." + """Return a sequence of all integrals with a particular integration domain.""" return tuple(integral for integral in self.integrals() if integral.ufl_domain() == domain) def empty(self): - "Returns whether the form has no integrals." + """Returns whether the form has no integrals.""" return self.integrals() == () def ufl_domains(self): @@ -356,22 +353,20 @@ def ufl_domains(self): return self._integration_domains def ufl_cell(self): - """Return the single cell this form is defined on, fails if multiple - cells are found. + """Return the single cell this form is defined on. + Fails if multiple cells are found. """ return self.ufl_domain().ufl_cell() def ufl_domain(self): - """Return the single geometric integration domain occuring in the - form. + """Return the single geometric integration domain occuring in the form. Fails if multiple domains are found. NB! This does not include domains of coefficients defined on other meshes, look at form data for that additional information. - """ # Collect all domains domains = self.ufl_domains() @@ -393,41 +388,37 @@ def geometric_dimension(self): return gdims[0] def domain_numbering(self): - """Return a contiguous numbering of domains in a mapping - ``{domain:number}``.""" + """Return a contiguous numbering of domains in a mapping ``{domain:number}``.""" if self._domain_numbering is None: self._analyze_domains() return self._domain_numbering def subdomain_data(self): - """Returns a mapping on the form ``{domain:{integral_type: - subdomain_data}}``.""" + """Returns a mapping on the form ``{domain:{integral_type: subdomain_data}}``.""" if self._subdomain_data is None: self._analyze_subdomain_data() return self._subdomain_data def max_subdomain_ids(self): - """Returns a mapping on the form - ``{domain:{integral_type:max_subdomain_id}}``.""" + """Returns a mapping on the form ``{domain:{integral_type:max_subdomain_id}}``.""" if self._max_subdomain_ids is None: self._analyze_subdomain_data() return self._max_subdomain_ids def coefficients(self): - "Return all ``Coefficient`` objects found in form." + """Return all ``Coefficient`` objects found in form.""" if self._coefficients is None: self._analyze_form_arguments() return self._coefficients def base_form_operators(self): - "Return all ``BaseFormOperator`` objects found in form." + """Return all ``BaseFormOperator`` objects found in form.""" if self._base_form_operators is None: self._analyze_base_form_operators() return self._base_form_operators def coefficient_numbering(self): - """Return a contiguous numbering of coefficients in a mapping - ``{coefficient:number}``.""" + """Return a contiguous numbering of coefficients in a mapping ``{coefficient:number}``.""" # cyclic import from ufl.coefficient import Coefficient @@ -440,11 +431,11 @@ def coefficient_numbering(self): return self._coefficient_numbering def constants(self): + """Get constants.""" return self._constants def constant_numbering(self): - """Return a contiguous numbering of constants in a mapping - ``{constant:number}``.""" + """Return a contiguous numbering of constants in a mapping ``{constant:number}``.""" if self._constant_numbering is None: self._constant_numbering = { expr: num @@ -460,7 +451,6 @@ def terminal_numbering(self): The numbering is computed per type so :class:`Coefficient`s, :class:`Constant`s, etc will each be numbered from zero. - """ # cyclic import from ufl.algorithms.analysis import extract_type @@ -478,7 +468,7 @@ def terminal_numbering(self): return self._terminal_numbering def signature(self): - "Signature for use with jit cache (independent of incidental numbering of indices etc.)" + """Signature for use with jit cache (independent of incidental numbering of indices etc).""" if self._signature is None: self._compute_signature() return self._signature @@ -486,17 +476,17 @@ def signature(self): # --- Operator implementations --- def __hash__(self): - "Hash code for use in dicts (includes incidental numbering of indices etc.)" + """Hash.""" if self._hash is None: self._hash = hash(tuple(hash(itg) for itg in self.integrals())) return self._hash def __ne__(self, other): - "Immediate evaluation of the != operator (as opposed to the == operator)." + """Immediate evaluation of the != operator (as opposed to the == operator).""" return not self.equals(other) def equals(self, other): - "Evaluate ``bool(lhs_form == rhs_form)``." + """Evaluate ``bool(lhs_form == rhs_form)``.""" if type(other) is not Form: return False if len(self._integrals) != len(other._integrals): @@ -506,10 +496,12 @@ def equals(self, other): return all(a == b for a, b in zip(self._integrals, other._integrals)) def __radd__(self, other): + """Add.""" # Ordering of form additions make no difference return self.__add__(other) def __add__(self, other): + """Add.""" if isinstance(other, Form): # Add integrals from both forms return Form(list(chain(self.integrals(), other.integrals()))) @@ -537,37 +529,37 @@ def __add__(self, other): return NotImplemented def __sub__(self, other): - "Subtract other form from this one." + """Subtract other form from this one.""" return self + (-other) def __rsub__(self, other): - "Subtract this form from other." + """Subtract this form from other.""" return other + (-self) def __neg__(self): """Negate all integrals in form. This enables the handy "-form" syntax for e.g. the - linearized system (J, -F) from a nonlinear form F.""" + linearized system (J, -F) from a nonlinear form F. + """ return Form([-itg for itg in self.integrals()]) def __rmul__(self, scalar): - "Multiply all integrals in form with constant scalar value." + """Multiply all integrals in form with constant scalar value.""" # This enables the handy "0*form" or "dt*form" syntax if is_scalar_constant_expression(scalar): return Form([scalar * itg for itg in self.integrals()]) return NotImplemented def __mul__(self, coefficient): - "UFL form operator: Take the action of this form on the given coefficient." + """UFL form operator: Take the action of this form on the given coefficient.""" if isinstance(coefficient, Expr): from ufl.formoperators import action return action(self, coefficient) return NotImplemented def __call__(self, *args, **kwargs): - """UFL form operator: Evaluate form by replacing arguments and - coefficients. + """UFL form operator: Evaluate form by replacing arguments and coefficients. Replaces form.arguments() with given positional arguments in same number and ordering. Number of positional arguments must @@ -577,7 +569,6 @@ def __call__(self, *args, **kwargs): to replace Coefficients with expressions of matching shapes. Example: - ------- V = FiniteElement("CG", triangle, 1) v = TestFunction(V) u = TrialFunction(V) @@ -587,7 +578,6 @@ def __call__(self, *args, **kwargs): M = a(f, f, coefficients={ g: 1 }) Is equivalent to M == grad(f)**2*dx. - """ repdict = {} @@ -614,13 +604,12 @@ def __call__(self, *args, **kwargs): else: return self - # "a @ f" notation in python 3.5 __matmul__ = __mul__ # --- String conversion functions, for UI purposes only --- def __str__(self): - "Compute shorter string representation of form. This can be huge for complicated forms." + """Compute shorter string representation of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge @@ -628,7 +617,7 @@ def __str__(self): return s or "" def __repr__(self): - "Compute repr string of form. This can be huge for complicated forms." + """Compute repr string of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge @@ -639,6 +628,7 @@ def __repr__(self): # --- Analysis functions, precomputation and caching of various quantities def _analyze_domains(self): + """Analyze domains.""" from ufl.domain import join_domains, sort_domains # Collect unique integration domains @@ -652,6 +642,7 @@ def _analyze_domains(self): self._domain_numbering = dict((d, i) for i, d in enumerate(self._integration_domains)) def _analyze_subdomain_data(self): + """Analyze subdomain data.""" integration_domains = self.ufl_domains() integrals = self.integrals() @@ -674,7 +665,7 @@ def _analyze_subdomain_data(self): self._subdomain_data = subdomain_data def _analyze_form_arguments(self): - "Analyze which Argument and Coefficient objects can be found in the form." + """Analyze which Argument and Coefficient objects can be found in the form.""" from ufl.algorithms.analysis import extract_arguments_and_coefficients arguments, coefficients = extract_arguments_and_coefficients(self) @@ -685,12 +676,13 @@ def _analyze_form_arguments(self): sorted(set(coefficients), key=lambda x: x.count())) def _analyze_base_form_operators(self): - "Analyze which BaseFormOperator objects can be found in the form." + """Analyze which BaseFormOperator objects can be found in the form.""" from ufl.algorithms.analysis import extract_base_form_operators base_form_ops = extract_base_form_operators(self) self._base_form_operators = tuple(sorted(base_form_ops, key=lambda x: x.count())) def _compute_renumbering(self): + """Compute renumbering.""" # Include integration domains and coefficients in renumbering dn = self.domain_numbering() tn = self.terminal_numbering() @@ -726,12 +718,13 @@ def _compute_renumbering(self): return renumbering def _compute_signature(self): + """Compute signature.""" from ufl.algorithms.signature import compute_form_signature self._signature = compute_form_signature(self, self._compute_renumbering()) def as_form(form): - "Convert to form if not a form, otherwise return form." + """Convert to form if not a form, otherwise return form.""" if not isinstance(form, BaseForm) and form != 0: raise ValueError(f"Unable to convert object to a UFL form: {ufl_err_str(form)}") return form @@ -739,9 +732,12 @@ def as_form(form): @ufl_type() class FormSum(BaseForm): - """Description of a weighted sum of variational forms and form-like objects + """Form sum. + + Description of a weighted sum of variational forms and form-like objects components is the list of Forms to be summed - arg_weights is a list of tuples of component index and weight""" + arg_weights is a list of tuples of component index and weight + """ __slots__ = ("_arguments", "_coefficients", @@ -754,6 +750,7 @@ class FormSum(BaseForm): _ufl_required_methods_ = ('_analyze_form_arguments') def __new__(cls, *args, **kwargs): + """Create a new FormSum.""" # All the components are `ZeroBaseForm` if all(component == 0 for component, _ in args): # Assume that the arguments of all the components have consistent with each other and select @@ -767,6 +764,7 @@ def __new__(cls, *args, **kwargs): return super(FormSum, cls).__new__(cls) def __init__(self, *components): + """Initialise.""" BaseForm.__init__(self) # Remove `ZeroBaseForm` components @@ -793,12 +791,15 @@ def __init__(self, *components): self.ufl_operands = self._components def components(self): + """Get components.""" return self._components def weights(self): + """Get weights.""" return self._weights def _sum_variational_components(self): + """Sum variational components.""" var_forms = None other_components = [] new_weights = [] @@ -818,7 +819,7 @@ def _sum_variational_components(self): self._weights = new_weights def _analyze_form_arguments(self): - "Return all ``Argument`` objects found in form." + """Return all ``Argument`` objects found in form.""" arguments = [] coefficients = [] for component in self._components: @@ -834,13 +835,13 @@ def _analyze_domains(self): self._domains = join_domains([component.ufl_domain() for component in self._components]) def __hash__(self): - "Hash code for use in dicts (includes incidental numbering of indices etc.)" + """Hash.""" if self._hash is None: self._hash = hash(tuple(hash(component) for component in self.components())) return self._hash def equals(self, other): - "Evaluate ``bool(lhs_form == rhs_form)``." + """Evaluate ``bool(lhs_form == rhs_form)``.""" if type(other) is not FormSum: return False if self is other: @@ -849,7 +850,7 @@ def equals(self, other): all(a == b for a, b in zip(self.components(), other.components()))) def __str__(self): - "Compute shorter string representation of form. This can be huge for complicated forms." + """Compute shorter string representation of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge @@ -857,7 +858,7 @@ def __str__(self): return s or "" def __repr__(self): - "Compute repr string of form. This can be huge for complicated forms." + """Compute repr string of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") # Not caching this because it can be huge @@ -872,7 +873,6 @@ class ZeroBaseForm(BaseForm): ZeroBaseForm is idempotent with respect to assembly and is mostly used for sake of simplifying base-form expressions. - """ __slots__ = ("_arguments", @@ -883,6 +883,7 @@ class ZeroBaseForm(BaseForm): "form") def __init__(self, arguments): + """Initialise.""" BaseForm.__init__(self) self._arguments = arguments self.ufl_operands = arguments @@ -890,14 +891,16 @@ def __init__(self, arguments): self.form = None def _analyze_form_arguments(self): + """Analyze form arguments.""" # `self._arguments` is already set in `BaseForm.__init__` self._coefficients = () def __ne__(self, other): - # Overwrite BaseForm.__neq__ which relies on `equals` + """Overwrite BaseForm.__neq__ which relies on `equals`.""" return not self == other def __eq__(self, other): + """Check equality.""" if type(other) is ZeroBaseForm: if self is other: return True @@ -908,13 +911,15 @@ def __eq__(self, other): return False def __str__(self): + """Format as a string.""" return "ZeroBaseForm(%s)" % (", ".join(str(arg) for arg in self._arguments)) def __repr__(self): + """Representation.""" return "ZeroBaseForm(%s)" % (", ".join(repr(arg) for arg in self._arguments)) def __hash__(self): - """Hash code for use in dicts.""" + """Hash.""" if self._hash is None: self._hash = hash(("ZeroBaseForm", hash(self._arguments))) return self._hash diff --git a/ufl/formatting/__init__.py b/ufl/formatting/__init__.py index e69de29bb..b4a49422f 100644 --- a/ufl/formatting/__init__.py +++ b/ufl/formatting/__init__.py @@ -0,0 +1 @@ +"""Formatting.""" diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 3a9df00dc..842e78e3f 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -1,4 +1,4 @@ -# coding: utf-8 +"""UFL to unicode.""" import numbers @@ -9,25 +9,35 @@ from ufl.form import Form from ufl.algorithms import compute_form_data +try: + import colorama + has_colorama = True +except ImportError: + has_colorama = False + class PrecedenceRules(MultiFunction): - "An enum-like class for C operator precedence levels." + """An enum-like class for C operator precedence levels.""" def __init__(self): + """Initialise.""" MultiFunction.__init__(self) def highest(self, o): + """Return the highest precendence.""" return 0 terminal = highest list_tensor = highest component_tensor = highest def restricted(self, o): + """Return precedence of a restriced.""" return 5 cell_avg = restricted facet_avg = restricted def call(self, o): + """Return precedence of a call.""" return 10 indexed = call min_value = call @@ -36,9 +46,11 @@ def call(self, o): bessel_function = call def power(self, o): + """Return precedence of a power.""" return 12 def mathop(self, o): + """Return precedence of a mathop.""" return 15 derivative = mathop trace = mathop @@ -48,9 +60,11 @@ def mathop(self, o): sym = mathop def not_condition(self, o): + """Return precedence of a not_condition.""" return 20 def product(self, o): + """Return precedence of a product.""" return 30 division = product # mod = product @@ -60,30 +74,37 @@ def product(self, o): cross = product def add(self, o): + """Return precedence of an add.""" return 40 # sub = add index_sum = add def lt(self, o): + """Return precedence of a lt.""" return 50 le = lt gt = lt ge = lt def eq(self, o): + """Return precedence of an eq.""" return 60 ne = eq def and_condition(self, o): + """Return precedence of an and_condition.""" return 70 def or_condition(self, o): + """Return precedence of an or_condition.""" return 71 def conditional(self, o): + """Return precedence of a conditional.""" return 72 def lowest(self, o): + """Return precedence of a lowest.""" return 80 operator = lowest @@ -92,18 +113,12 @@ def lowest(self, o): def precedence(expr): + """Get the precedence of an expr.""" return _precrules(expr) -try: - import colorama - has_colorama = True -except ImportError: - has_colorama = False - - class UC: - "An enum-like class for unicode characters." + """An enum-like class for unicode characters.""" # Letters in this alphabet have contiguous code point numbers bold_math_a = u"𝐚" @@ -111,71 +126,72 @@ class UC: thin_space = u"\u2009" - superscript_plus = u'⁺' - superscript_minus = u'⁻' - superscript_equals = u'⁼' - superscript_left_paren = u'⁽' - superscript_right_paren = u'⁾' + superscript_plus = u"⁺" + superscript_minus = u"⁻" + superscript_equals = u"⁼" + superscript_left_paren = u"⁽" + superscript_right_paren = u"⁾" superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"] - subscript_plus = u'₊' - subscript_minus = u'₋' - subscript_equals = u'₌' - subscript_left_paren = u'₍' - subscript_right_paren = u'₎' + subscript_plus = u"₊" + subscript_minus = u"₋" + subscript_equals = u"₌" + subscript_left_paren = u"₍" + subscript_right_paren = u"₎" subscript_digits = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"] - sqrt = u'√' - transpose = u'ᵀ' - - integral = u'∫' - integral_double = u'∬' - integral_triple = u'∭' - integral_contour = u'∮' - integral_surface = u'∯' - integral_volume = u'∰' - - sum = u'∑' - division_slash = '∕' - partial = u'∂' - epsilon = u'ε' - omega = u'ω' - Omega = u'Ω' - gamma = u'γ' - Gamma = u'Γ' - nabla = u'∇' - for_all = u'∀' - - dot = u'⋅' - cross_product = u'⨯' - circled_times = u'⊗' - nary_product = u'∏' - - ne = u'≠' - lt = u'<' - le = u'≤' - gt = u'>' - ge = u'≥' - - logical_and = u'∧' - logical_or = u'∨' - logical_not = u'¬' - - element_of = u'∈' - not_element_of = u'∉' - - left_white_square_bracket = u'⟦' - right_white_squared_bracket = u'⟧' - left_angled_bracket = u'⟨' - right_angled_bracket = u'⟩' - left_double_angled_bracket = u'⟪' - right_double_angled_bracket = u'⟫' - - combining_right_arrow_above = '\u20D7' - combining_overline = '\u0305' + sqrt = u"√" + transpose = u"ᵀ" + + integral = u"∫" + integral_double = u"∬" + integral_triple = u"∭" + integral_contour = u"∮" + integral_surface = u"∯" + integral_volume = u"∰" + + sum = u"∑" + division_slash = "∕" + partial = u"∂" + epsilon = u"ε" + omega = u"ω" + Omega = u"Ω" + gamma = u"γ" + Gamma = u"Γ" + nabla = u"∇" + for_all = u"∀" + + dot = u"⋅" + cross_product = u"⨯" + circled_times = u"⊗" + nary_product = u"∏" + + ne = u"≠" + lt = u"<" + le = u"≤" + gt = u">" + ge = u"≥" + + logical_and = u"∧" + logical_or = u"∨" + logical_not = u"¬" + + element_of = u"∈" + not_element_of = u"∉" + + left_white_square_bracket = u"⟦" + right_white_squared_bracket = u"⟧" + left_angled_bracket = u"⟨" + right_angled_bracket = u"⟩" + left_double_angled_bracket = u"⟪" + right_double_angled_bracket = u"⟫" + + combining_right_arrow_above = "\u20D7" + combining_overline = "\u0305" def bolden_letter(c): + """Bolden a letter.""" if ord("A") <= ord(c) <= ord("Z"): c = chr(ord(c) - ord(u"A") + ord(UC.bold_math_A)) elif ord("a") <= ord(c) <= ord("z"): @@ -184,40 +200,48 @@ def bolden_letter(c): def superscript_digit(digit): + """Make a digit superscript.""" return UC.superscript_digits[ord(digit) - ord("0")] def subscript_digit(digit): + """Make a digit subscript.""" return UC.subscript_digits[ord(digit) - ord("0")] def bolden_string(s): + """Bolden a string.""" return u"".join(bolden_letter(c) for c in s) def overline_string(f): - return u"".join("%s%s" % (c, UC.combining_overline) for c in f) + """Overline a string.""" + return u"".join(f"{c}{UC.combining_overline}" for c in f) def subscript_number(number): + """Make a number subscript.""" assert isinstance(number, int) - prefix = UC.subscript_minus if number < 0 else '' + prefix = UC.subscript_minus if number < 0 else "" number = str(number) - return prefix + ''.join(subscript_digit(c) for c in str(number)) + return prefix + "".join(subscript_digit(c) for c in str(number)) def superscript_number(number): + """Make a number superscript.""" assert isinstance(number, int) - prefix = UC.superscript_minus if number < 0 else '' + prefix = UC.superscript_minus if number < 0 else "" number = str(number) - return prefix + ''.join(superscript_digit(c) for c in str(number)) + return prefix + "".join(superscript_digit(c) for c in str(number)) def opfont(opname): + """Use the font for operators.""" return bolden_string(opname) def measure_font(dx): + """Use the font for measures.""" return bolden_string(dx) @@ -266,6 +290,7 @@ def measure_font(dx): def get_integral_symbol(integral_type, domain, subdomain_id): + """Get the symbol for an integral.""" tdim = domain.topological_dimension() codim = integral_type_to_codim[integral_type] itgdim = tdim - codim @@ -291,15 +316,12 @@ def get_integral_symbol(integral_type, domain, subdomain_id): def par(s): - return "(%s)" % s - - -def prec(expr): - return 0 # FIXME - # return precedence[expr._ufl_class_] + """Wrap in parentheses.""" + return f"({s})" def is_int(s): + """Check if a value is an integer.""" try: int(s) return True @@ -308,17 +330,18 @@ def is_int(s): def format_index(ii): + """Format an index.""" if isinstance(ii, FixedIndex): - s = "%d" % ii._value + s = f"{ii._value}" elif isinstance(ii, Index): - s = "i%s" % subscript_number(ii._count) + s = "i{subscript_number(ii._count)}" else: raise ValueError(f"Invalid index type {type(ii)}.") return s def ufl2unicode(expression): - "Generate Unicode string for a UFL expression or form." + """Generate Unicode string for a UFL expression or form.""" if isinstance(expression, Form): form_data = compute_form_data(expression) preprocessed_form = form_data.preprocessed_form @@ -328,12 +351,13 @@ def ufl2unicode(expression): def expression2unicode(expression, argument_names=None, coefficient_names=None): + """Generate Unicode string for a UFL expression.""" rules = Expression2UnicodeHandler(argument_names, coefficient_names) return map_expr_dag(rules, expression) def form2unicode(form, formdata): - # formname = formdata.name + """Generate Unicode string for a UFL form.""" argument_names = None coefficient_names = None @@ -346,13 +370,14 @@ def form2unicode(form, formdata): istr, dxstr = get_integral_symbol(itg.integral_type(), itg.ufl_domain(), itg.subdomain_id()) - line = "%s %s %s" % (istr, integrand_string, dxstr) + line = f"{istr} {integrand_string} {dxstr}" lines.append(line) - return '\n + '.join(lines) + return "\n + ".join(lines) def binop(expr, a, b, op, sep=" "): + """Format a binary operation.""" eprec = precedence(expr) op0, op1 = expr.ufl_operands aprec = precedence(op0) @@ -366,6 +391,7 @@ def binop(expr, a, b, op, sep=" "): def mathop(expr, arg, opname): + """Format a math operation.""" eprec = precedence(expr) aprec = precedence(expr.ufl_operands[0]) op = opfont(opname) @@ -374,11 +400,14 @@ def mathop(expr, arg, opname): sep = "" else: sep = UC.thin_space - return "%s%s%s" % (op, sep, arg) + return f"{op}{sep}{arg}" class Expression2UnicodeHandler(MultiFunction): + """Convert expressions to unicode.""" + def __init__(self, argument_names=None, coefficient_names=None, colorama_bold=False): + """Initialise.""" MultiFunction.__init__(self) self.argument_names = argument_names self.coefficient_names = coefficient_names @@ -387,34 +416,41 @@ def __init__(self, argument_names=None, coefficient_names=None, colorama_bold=Fa # --- Terminal objects --- def scalar_value(self, o): + """Format a scalar_value.""" if o.ufl_shape and self.colorama_bold: - return "%s%s%s" % (colorama.Style.BRIGHT, o._value, colorama.Style.RESET_ALL) - return "%s" % o._value + return f"{colorama.Style.BRIGHT}{o._value}{colorama.Style.RESET_ALL}" + return f"{o._value}" def zero(self, o): + """Format a zero.""" if o.ufl_shape and self.colorama_bold: if len(o.ufl_shape) == 1: - return "0%s" % UC.combining_right_arrow_above - return "%s0%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) + return f"0{UC.combining_right_arrow_above}" + return f"{colorama.Style.BRIGHT}0{colorama.Style.RESET_ALL}" return "0" def identity(self, o): + """Format a identity.""" if self.colorama_bold: - return "%sI%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL) + return f"{colorama.Style.BRIGHT}I{colorama.Style.RESET_ALL}" return "I" def permutation_symbol(self, o): + """Format a permutation_symbol.""" if self.colorama_bold: - return "%s%s%s" % (colorama.Style.BRIGHT, UC.epsilon, colorama.Style.RESET_ALL) + return f"{colorama.Style.BRIGHT}{UC.epsilon}{colorama.Style.RESET_ALL}" return UC.epsilon def facet_normal(self, o): - return "n%s" % UC.combining_right_arrow_above + """Format a facet_normal.""" + return f"n{UC.combining_right_arrow_above}" def spatial_coordinate(self, o): - return "x%s" % UC.combining_right_arrow_above + """Format a spatial_coordinate.""" + return f"x{UC.combining_right_arrow_above}" def argument(self, o): + """Format an argument.""" # Using ^ for argument numbering and _ for indexing since # indexing is more common than exponentiation if self.argument_names is None: @@ -423,14 +459,15 @@ def argument(self, o): if not o.ufl_shape: return bfn elif len(o.ufl_shape) == 1: - return "%s%s" % (bfn, UC.combining_right_arrow_above) + return f"{bfn}{UC.combining_right_arrow_above}" elif self.colorama_bold: - return "%s%s%s" % (colorama.Style.BRIGHT, bfn, colorama.Style.RESET_ALL) + return f"{colorama.Style.BRIGHT}{bfn}{colorama.Style.RESET_ALL}" else: return bfn return self.argument_names[(o.number(), o.part())] def coefficient(self, o): + """Format a coefficient.""" # Using ^ for coefficient numbering and _ for indexing since # indexing is more common than exponentiation if self.coefficient_names is None: @@ -439,45 +476,54 @@ def coefficient(self, o): if len(o.ufl_shape) == 1: var += UC.combining_right_arrow_above elif len(o.ufl_shape) > 1 and self.colorama_bold: - var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL) - return "%s%s" % (var, subscript_number(i)) + var = f"{colorama.Style.BRIGHT}{var}{colorama.Style.RESET_ALL}" + return f"{var}{subscript_number(i)}" return self.coefficient_names[o.count()] def base_form_operator(self, o): + """Format a base_form_operator.""" return "BaseFormOperator" def constant(self, o): + """Format a constant.""" i = o.count() var = "c" if len(o.ufl_shape) == 1: var += UC.combining_right_arrow_above elif len(o.ufl_shape) > 1 and self.colorama_bold: - var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL) - return "%s%s" % (var, superscript_number(i)) + var = f"{colorama.Style.BRIGHT}{var}{colorama.Style.RESET_ALL}" + return f"{var}{superscript_number(i)}" def multi_index(self, o): + """Format a multi_index.""" return ",".join(format_index(i) for i in o) def label(self, o): - return "l%s" % (subscript_number(o.count()),) + """Format a label.""" + return f"l{subscript_number(o.count())}" # --- Non-terminal objects --- def variable(self, o, f, a): - return "var(%s,%s)" % (f, a) + """Format a variable.""" + return f"var({f},{a})" def index_sum(self, o, f, i): + """Format a index_sum.""" if 1: # prec(o.ufl_operands[0]) >? prec(o): f = par(f) - return "%s[%s]%s" % (UC.sum, i, f) + return f"{UC.sum}[{i}]{f}" def sum(self, o, a, b): + """Format a sum.""" return binop(o, a, b, "+") def product(self, o, a, b): + """Format a product.""" return binop(o, a, b, " ", sep="") def division(self, o, a, b): + """Format a division.""" if is_int(b): b = subscript_number(int(b)) if is_int(a): @@ -487,236 +533,277 @@ def division(self, o, a, b): a = superscript_number(int(a)) else: a = par(a) - return "%s %s %s" % (a, UC.division_slash, b) + return f"{a} {UC.division_slash} {b}" return binop(o, a, b, UC.division_slash) def abs(self, o, a): - return "|%s|" % (a,) + """Format an ans.""" + return f"|{a}|" def transposed(self, o, a): + """Format a transposed.""" a = par(a) - return "%s%s" % (a, UC.transpose) + return f"{a}{UC.transpose}" def indexed(self, o, A, ii): + """Format an indexed.""" op0, op1 = o.ufl_operands Aprec = precedence(op0) oprec = precedence(o) if Aprec > oprec: A = par(A) - return "%s[%s]" % (A, ii) + return f"{A}[{ii}]" def variable_derivative(self, o, f, v): + """Format a variable_derivative.""" f = par(f) v = par(v) - nom = r"%s%s" % (UC.partial, f) - denom = r"%s%s" % (UC.partial, v) - return par(r"%s%s%s" % (nom, UC.division_slash, denom)) + nom = f"{UC.partial}{f}" + denom = f"{UC.partial}{v}" + return par(f"{nom}{UC.division_slash}{denom}") def coefficient_derivative(self, o, f, w, v, cd): + """Format a coefficient_derivative.""" f = par(f) w = par(w) - nom = r"%s%s" % (UC.partial, f) - denom = r"%s%s" % (UC.partial, w) - return par(r"%s%s%s[%s]" % (nom, UC.division_slash, denom, v)) # TODO: Fix this syntax... + nom = f"{UC.partial}{f}" + denom = f"{UC.partial}{w}" + return par(f"{nom}{UC.division_slash}{denom}[{v}]") def grad(self, o, f): + """Format a grad.""" return mathop(o, f, "grad") def div(self, o, f): + """Format a div.""" return mathop(o, f, "div") def nabla_grad(self, o, f): + """Format a nabla_grad.""" oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) - return "%s%s%s" % (UC.nabla, UC.thin_space, f) + return f"{UC.nabla}{UC.thin_space}{f}" def nabla_div(self, o, f): + """Format a nabla_div.""" oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) - return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.dot, UC.thin_space, f) + return f"{UC.nabla}{UC.thin_space}{UC.dot}{UC.thin_space}{f}" def curl(self, o, f): + """Format a curl.""" oprec = precedence(o) fprec = precedence(o.ufl_operands[0]) if fprec > oprec: f = par(f) - return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.cross_product, UC.thin_space, f) + return f"{UC.nabla}{UC.thin_space}{UC.cross_product}{UC.thin_space}{f}" def math_function(self, o, f): + """Format a math_function.""" op = opfont(o._name) - f = par(f) - return "%s%s" % (op, f) + return f"{op}{par(f)}" def sqrt(self, o, f): - f = par(f) - return "%s%s" % (UC.sqrt, f) + """Format a sqrt.""" + return f"{UC.sqrt}{par(f)}" def exp(self, o, f): + """Format a exp.""" op = opfont("exp") - f = par(f) - return "%s%s" % (op, f) + return f"{op}{par(f)}" def atan2(self, o, f1, f2): + """Format a atan2.""" f1 = par(f1) f2 = par(f2) op = opfont("arctan2") - return "%s(%s, %s)" % (op, f1, f2) + return f"{op}({f1}, {f2})" def bessel_j(self, o, nu, f): + """Format a bessel_j.""" op = opfont("J") - f = par(f) nu = subscript_number(int(nu)) - return "%s%s%s" % (op, nu, f) + return f"{op}{nu}{par(f)}" def bessel_y(self, o, nu, f): + """Format a bessel_y.""" op = opfont("Y") - f = par(f) nu = subscript_number(int(nu)) - return "%s%s%s" % (op, nu, f) + return f"{op}{nu}{par(f)}" def bessel_i(self, o, nu, f): + """Format a bessel_i.""" op = opfont("I") - f = par(f) nu = subscript_number(int(nu)) - return "%s%s%s" % (op, nu, f) + return f"{op}{nu}{par(f)}" def bessel_K(self, o, nu, f): + """Format a bessel_K.""" op = opfont("K") - f = par(f) nu = subscript_number(int(nu)) - return "%s%s%s" % (op, nu, f) + return f"{op}{nu}{par(f)}" def power(self, o, a, b): + """Format a power.""" if is_int(b): b = superscript_number(int(b)) return binop(o, a, b, "", sep="") return binop(o, a, b, "^", sep="") def outer(self, o, a, b): + """Format an outer.""" return binop(o, a, b, UC.circled_times) def inner(self, o, a, b): - return "%s%s, %s%s" % (UC.left_angled_bracket, a, b, UC.right_angled_bracket) + """Format an inner.""" + return f"{UC.left_angled_bracket}{a}, {b}{UC.right_angled_bracket}" def dot(self, o, a, b): + """Format a dot.""" return binop(o, a, b, UC.dot) def cross(self, o, a, b): + """Format a cross.""" return binop(o, a, b, UC.cross_product) def determinant(self, o, A): - return "|%s|" % (A,) + """Format a determinant.""" + return f"|{A}|" def inverse(self, o, A): + """Format an inverse.""" A = par(A) - return "%s%s" % (A, superscript_number(-1)) + return f"{A}{superscript_number(-1)}" def trace(self, o, A): + """Format a trace.""" return mathop(o, A, "tr") def deviatoric(self, o, A): + """Format a deviatoric.""" return mathop(o, A, "dev") def cofactor(self, o, A): + """Format a cofactor.""" return mathop(o, A, "cofac") def skew(self, o, A): + """Format a skew.""" return mathop(o, A, "skew") def sym(self, o, A): + """Format a sym.""" return mathop(o, A, "sym") def conj(self, o, a): + """Format a conj.""" # Overbar is already taken for average, and there is no superscript asterix in unicode. return mathop(o, a, "conj") def real(self, o, a): + """Format a real.""" return mathop(o, a, "Re") def imag(self, o, a): + """Format a imag.""" return mathop(o, a, "Im") def list_tensor(self, o, *ops): - l = ", ".join(ops) # noqa: E741 - return "%s%s%s" % ("[", l, "]") + """Format a list_tensor.""" + return f"[{', '.join(ops)}]" def component_tensor(self, o, A, ii): - return "[%s %s %s]" % (A, UC.for_all, ii) + """Format a component_tensor.""" + return f"[{A} {UC.for_all} {ii}]" def positive_restricted(self, o, f): - f = par(f) - return "%s%s" % (f, UC.superscript_plus) + """Format a positive_restriced.""" + return f"{par(f)}{UC.superscript_plus}" def negative_restricted(self, o, f): - f = par(f) - return "%s%s" % (f, UC.superscript_minus) + """Format a negative_restriced.""" + return f"{par(f)}{UC.superscript_minus}" def cell_avg(self, o, f): + """Format a cell_avg.""" f = overline_string(f) return f def facet_avg(self, o, f): + """Format a facet_avg.""" f = overline_string(f) return f def eq(self, o, a, b): + """Format an eq.""" return binop(o, a, b, "=") def ne(self, o, a, b): + """Format a ne.""" return binop(o, a, b, UC.ne) def le(self, o, a, b): + """Format a le.""" return binop(o, a, b, UC.le) def ge(self, o, a, b): + """Format a ge.""" return binop(o, a, b, UC.ge) def lt(self, o, a, b): + """Format a lt.""" return binop(o, a, b, UC.lt) def gt(self, o, a, b): + """Format a gt.""" return binop(o, a, b, UC.gt) def and_condition(self, o, a, b): + """Format an and_condition.""" return binop(o, a, b, UC.logical_and) def or_condition(self, o, a, b): + """Format an or_condition.""" return binop(o, a, b, UC.logical_or) def not_condition(self, o, a): + """Format a not_condition.""" a = par(a) - return "%s%s" % (UC.logical_not, a) + return f"{UC.logical_not}{a}" def conditional(self, o, c, t, f): + """Format a conditional.""" c = par(c) t = par(t) f = par(t) If = opfont("if") Else = opfont("else") - l = " ".join((t, If, c, Else, f)) # noqa: E741 - return l + return " ".join((t, If, c, Else, f)) def min_value(self, o, a, b): + """Format an min_value.""" op = opfont("min") - return "%s(%s, %s)" % (op, a, b) + return f"{op}({a}, {b})" def max_value(self, o, a, b): + """Format an max_value.""" op = opfont("max") - return "%s(%s, %s)" % (op, a, b) + return f"{op}({a}, {b})" def expr_list(self, o, *ops): + """Format an expr_list.""" items = ", ".join(ops) - return "%s %s %s" % (UC.left_white_square_bracket, items, - UC.right_white_squared_bracket) + return f"{UC.left_white_square_bracket} {items} {UC.right_white_squared_bracket}" def expr_mapping(self, o, *ops): + """Format an expr_mapping.""" items = ", ".join(ops) - return "%s %s %s" % (UC.left_double_angled_bracket, items, - UC.left_double_angled_bracket) + return f"{UC.left_double_angled_bracket} {items} {UC.left_double_angled_bracket}" def expr(self, o): - raise ValueError("Missing handler for type %s" % str(type(o))) + """Format an expr.""" + raise ValueError(f"Missing handler for type {type(o)}") diff --git a/ufl/formoperators.py b/ufl/formoperators.py index 7aa9848aa..c22c2dedf 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Various high level ways to transform a complete Form into a new Form." - +"""Various high level ways to transform a complete Form into a new Form.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -45,30 +43,28 @@ def extract_blocks(form, i=None, j=None): - """UFL form operator: + """Extract blocks. + Given a linear or bilinear form on a mixed space, extract the block corresponding to the indices ix, iy. Example: - ------- a = inner(grad(u), grad(v))*dx + div(u)*q*dx + div(v)*p*dx extract_blocks(a, 0, 0) -> inner(grad(u), grad(v))*dx extract_blocks(a) -> [inner(grad(u), grad(v))*dx, div(v)*p*dx, div(u)*q*dx, 0] - """ return formsplitter.extract_blocks(form, i, j) def lhs(form): - """UFL form operator: + """Get the left hand side. + Given a combined bilinear and linear form, extract the left hand side (bilinear form part). Example: - ------- a = u*v*dx + f*v*dx a = lhs(a) -> u*v*dx - """ form = as_form(form) form = expand_derivatives(form) @@ -76,15 +72,14 @@ def lhs(form): def rhs(form): - """UFL form operator: + """Get the right hand side. + Given a combined bilinear and linear form, extract the right hand side (negated linear form part). Example: - ------- a = u*v*dx + f*v*dx L = rhs(a) -> -f*v*dx - """ form = as_form(form) form = expand_derivatives(form) @@ -92,20 +87,23 @@ def rhs(form): def system(form): - """UFL form operator: Split a form into the left hand side and right hand - side, see ``lhs`` and ``rhs``.""" + """Split a form into the left hand side and right hand side. + + See ``lhs`` and ``rhs``. + """ return lhs(form), rhs(form) def functional(form): # TODO: Does this make sense for anything other than testing? - "UFL form operator: Extract the functional part of form." + """Extract the functional part of form.""" form = as_form(form) form = expand_derivatives(form) return compute_form_functional(form) def action(form, coefficient=None, derivatives_expanded=None): - """UFL form operator: + """Get the action. + Given a bilinear form, return a linear form with an additional coefficient, representing the action of the form on the coefficient. This can be @@ -130,16 +128,19 @@ def action(form, coefficient=None, derivatives_expanded=None): def energy_norm(form, coefficient=None): - """UFL form operator: + """Get the energy norm. + Given a bilinear form *a* and a coefficient *f*, - return the functional :math:`a(f,f)`.""" + return the functional :math:`a(f,f)`. + """ form = as_form(form) form = expand_derivatives(form) return compute_energy_norm(form, coefficient) def adjoint(form, reordered_arguments=None, derivatives_expanded=None): - """UFL form operator: + """Get the adjoint. + Given a combined bilinear form, compute the adjoint form by changing the ordering (count) of the test and trial functions, and taking the complex conjugate of the result. @@ -169,6 +170,7 @@ def adjoint(form, reordered_arguments=None, derivatives_expanded=None): def zero_lists(shape): + """Createa list of zeros of the given shape.""" if len(shape) == 0: raise ValueError("Invalid shape.") elif len(shape) == 1: @@ -178,6 +180,7 @@ def zero_lists(shape): def set_list_item(li, i, v): + """Set an item in a nested list.""" # Get to the innermost list if len(i) > 1: for j in i[:-1]: @@ -187,6 +190,7 @@ def set_list_item(li, i, v): def _handle_derivative_arguments(form, coefficient, argument): + """Handle derivative arguments.""" # Wrap single coefficient in tuple for uniform treatment below if isinstance(coefficient, (list, tuple, ListTensor)): coefficients = tuple(coefficient) @@ -279,9 +283,7 @@ def _handle_derivative_arguments(form, coefficient, argument): def derivative(form, coefficient, argument=None, coefficient_derivatives=None): - """UFL form operator: - Compute the Gateaux derivative of *form* w.r.t. *coefficient* in direction - of *argument*. + """Compute the Gateaux derivative of *form* w.r.t. *coefficient* in direction of *argument*. If the argument is omitted, a new ``Argument`` is created in the same space as the coefficient, with argument number @@ -301,7 +303,6 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): If provided, *coefficient_derivatives* should be a mapping from ``Coefficient`` instances to their derivatives w.r.t. *coefficient*. """ - if isinstance(form, FormSum): # Distribute derivative over FormSum components return FormSum(*[(derivative(component, coefficient, argument, coefficient_derivatives), 1) @@ -377,8 +378,7 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): def sensitivity_rhs(a, u, L, v): - """UFL form operator: - Compute the right hand side for a sensitivity calculation system. + r"""Compute the right hand side for a sensitivity calculation system. The derivation behind this computation is as follows. Assume *a*, *L* to be bilinear and linear forms diff --git a/ufl/functionspace.py b/ufl/functionspace.py index d0dc82bac..7c3cfda43 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Types for representing function spaces." +"""Types for representing function spaces.""" # Copyright (C) 2015-2016 Martin Sandve Alnæs # @@ -25,7 +24,10 @@ class AbstractFunctionSpace(object): + """Abstract function space.""" + def ufl_sub_spaces(self): + """Return ufl sub spaces.""" raise NotImplementedError( "Missing implementation of IFunctionSpace.ufl_sub_spaces in %s." % self.__class__.__name__ @@ -34,7 +36,10 @@ def ufl_sub_spaces(self): @attach_operators_from_hash_data class BaseFunctionSpace(AbstractFunctionSpace): + """Base function space.""" + def __init__(self, domain, element): + """Initialise.""" if domain is None: # DOLFIN hack # TODO: Is anything expected from element.cell() in this case? @@ -53,19 +58,19 @@ def __init__(self, domain, element): self._ufl_element = element def ufl_sub_spaces(self): - "Return ufl sub spaces." + """Return ufl sub spaces.""" return () def ufl_domain(self): - "Return ufl domain." + """Return ufl domain.""" return self._ufl_domain def ufl_element(self): - "Return ufl element." + """Return ufl element.""" return self._ufl_element def ufl_domains(self): - "Return ufl domains." + """Return ufl domains.""" domain = self.ufl_domain() if domain is None: return () @@ -73,6 +78,7 @@ def ufl_domains(self): return (domain,) def _ufl_hash_data_(self, name=None): + """UFL hash data.""" name = name or "BaseFunctionSpace" domain = self.ufl_domain() element = self.ufl_element() @@ -87,6 +93,7 @@ def _ufl_hash_data_(self, name=None): return (name, ddata, edata) def _ufl_signature_data_(self, renumbering, name=None): + """UFL signature data.""" name = name or "BaseFunctionSpace" domain = self.ufl_domain() element = self.ufl_element() @@ -101,6 +108,7 @@ def _ufl_signature_data_(self, renumbering, name=None): return (name, ddata, edata) def __repr__(self): + """Representation.""" r = "BaseFunctionSpace(%s, %s)" % (repr(self._ufl_domain), repr(self._ufl_element)) return r @@ -109,19 +117,24 @@ def __repr__(self): @attach_operators_from_hash_data class FunctionSpace(BaseFunctionSpace): """Representation of a Function space.""" + _primal = True _dual = False def dual(self): + """Get the dual of the space.""" return DualSpace(self._ufl_domain, self._ufl_element) def _ufl_hash_data_(self): + """UFL hash data.""" return BaseFunctionSpace._ufl_hash_data_(self, "FunctionSpace") def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "FunctionSpace") def __repr__(self): + """Representation.""" r = "FunctionSpace(%s, %s)" % (repr(self._ufl_domain), repr(self._ufl_element)) return r @@ -130,22 +143,28 @@ def __repr__(self): @attach_operators_from_hash_data class DualSpace(BaseFunctionSpace): """Representation of a Dual space.""" + _primal = False _dual = True def __init__(self, domain, element): + """Initialise.""" BaseFunctionSpace.__init__(self, domain, element) def dual(self): + """Get the dual of the space.""" return FunctionSpace(self._ufl_domain, self._ufl_element) def _ufl_hash_data_(self): + """UFL hash data.""" return BaseFunctionSpace._ufl_hash_data_(self, "DualSpace") def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return BaseFunctionSpace._ufl_signature_data_(self, renumbering, "DualSpace") def __repr__(self): + """Representation.""" r = "DualSpace(%s, %s)" % (repr(self._ufl_domain), repr(self._ufl_element)) return r @@ -153,31 +172,40 @@ def __repr__(self): @attach_operators_from_hash_data class TensorProductFunctionSpace(AbstractFunctionSpace): + """Tensor product function space.""" + def __init__(self, *function_spaces): + """Initialise.""" AbstractFunctionSpace.__init__(self) self._ufl_function_spaces = function_spaces def ufl_sub_spaces(self): + """Return ufl sub spaces.""" return self._ufl_function_spaces def _ufl_hash_data_(self): + """UFL hash data.""" return ("TensorProductFunctionSpace",) \ + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return ("TensorProductFunctionSpace",) \ + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) def __repr__(self): + """Representation.""" r = "TensorProductFunctionSpace(*%s)" % repr(self._ufl_function_spaces) return r @attach_operators_from_hash_data class MixedFunctionSpace(AbstractFunctionSpace): + """Mixed function space.""" def __init__(self, *args): + """Initialise.""" AbstractFunctionSpace.__init__(self) self._ufl_function_spaces = args self._ufl_elements = list() @@ -194,11 +222,11 @@ def __init__(self, *args): for subspace in self._ufl_function_spaces]) def ufl_sub_spaces(self): - "Return ufl sub spaces." + """Return ufl sub spaces.""" return self._ufl_function_spaces def ufl_sub_space(self, i): - "Return i-th ufl sub space." + """Return i-th ufl sub space.""" return self._ufl_function_spaces[i] def dual(self, *args): @@ -210,7 +238,8 @@ def dual(self, *args): If additional arguments are passed, these must be integers. In this case, the MixedFunctionSpace which is returned will have dual components in the positions corresponding to the arguments passed, and - the original components in the other positions.""" + the original components in the other positions. + """ if args: spaces = [space.dual() if i in args else space for i, space in enumerate(self._ufl_function_spaces)] @@ -221,10 +250,11 @@ def dual(self, *args): ) def ufl_elements(self): - "Return ufl elements." + """Return ufl elements.""" return self._ufl_elements def ufl_element(self): + """Return ufl element.""" if len(self._ufl_elements) == 1: return self._ufl_elements[0] else: @@ -234,14 +264,14 @@ def ufl_element(self): "in case of homogeneous dimension.") def ufl_domains(self): - "Return ufl domains." + """Return ufl domains.""" domainlist = [] for s in self._ufl_function_spaces: domainlist.extend(s.ufl_domains()) return join_domains(domainlist) def ufl_domain(self): - "Return ufl domain." + """Return ufl domain.""" domains = self.ufl_domains() if len(domains) == 1: return domains[0] @@ -251,16 +281,20 @@ def ufl_domain(self): return None def num_sub_spaces(self): + """Return number of subspaces.""" return len(self._ufl_function_spaces) def _ufl_hash_data_(self): + """UFL hash data.""" return ("MixedFunctionSpace",) \ + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" return ("MixedFunctionSpace",) \ + tuple(V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces()) def __repr__(self): + """Representation.""" return f"MixedFunctionSpace(*{self._ufl_function_spaces})" diff --git a/ufl/geometry.py b/ufl/geometry.py index 96820e989..e06ec7793 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -11,7 +11,6 @@ from ufl.domain import as_domain, extract_unique_domain """ - Possible coordinate bootstrapping: Xf = Xf[q] @@ -69,7 +68,6 @@ Xf = CFK * (X - X0f) FacetCoordinate = CellFacetJacobianInverse * (CellCoordinate - CellFacetOrigin) - """ @@ -77,17 +75,21 @@ @ufl_type(is_abstract=True) class GeometricQuantity(Terminal): + """Geometric quantity.""" + __slots__ = ("_domain",) def __init__(self, domain): + """Initialise.""" Terminal.__init__(self) self._domain = as_domain(domain) def ufl_domains(self): + """Get the UFL domains.""" return (self._domain,) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell (or over each facet for facet quantities)." + """Return whether this expression is spatially constant over each cell.""" # NB! Geometric quantities are piecewise constant by # default. Override if needed. return True @@ -97,30 +99,38 @@ def is_cellwise_constant(self): ufl_shape = () def _ufl_signature_data_(self, renumbering): - "Signature data of geometric quantities depend on the domain numbering." + """Signature data of geometric quantities depend on the domain numbering.""" return (self._ufl_class_.__name__,) + self._domain._ufl_signature_data_(renumbering) def __str__(self): + """Format as a string.""" return self._ufl_class_.name def __repr__(self): + """Representation.""" r = "%s(%s)" % (self._ufl_class_.__name__, repr(self._domain)) return r def _ufl_compute_hash_(self): + """UFL compute hash.""" return hash((type(self).__name__,) + self._domain._ufl_hash_data_()) def __eq__(self, other): + """Check equality.""" return isinstance(other, self._ufl_class_) and other._domain == self._domain @ufl_type(is_abstract=True) class GeometricCellQuantity(GeometricQuantity): + """Geometric cell quantity.""" + __slots__ = () @ufl_type(is_abstract=True) class GeometricFacetQuantity(GeometricQuantity): + """Geometric facet quantity.""" + __slots__ = () @@ -128,7 +138,7 @@ class GeometricFacetQuantity(GeometricQuantity): @ufl_type() class SpatialCoordinate(GeometricCellQuantity): - """UFL geometry representation: The coordinate in a domain. + """The coordinate in a domain. In the context of expression integration, represents the domain coordinate of each quadrature point. @@ -136,24 +146,24 @@ class SpatialCoordinate(GeometricCellQuantity): In the context of expression evaluation in a point, represents the value of that point. """ + __slots__ = () name = "x" @property def ufl_shape(self): - """Return the number of coordinates defined (i.e. the geometric dimension - of the domain).""" + """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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 def evaluate(self, x, mapping, component, index_values): - "Return the value of the coordinate." + """Return the value of the coordinate.""" if component == (): if isinstance(x, (tuple, list)): return float(x[0]) @@ -163,6 +173,7 @@ def evaluate(self, x, mapping, component, index_values): return float(x[component[0]]) def count(self): + """Count.""" # FIXME: Hack to make SpatialCoordinate behave like a coefficient. # When calling `derivative`, the count is used to sort over. return -1 @@ -170,7 +181,7 @@ def count(self): @ufl_type() class CellCoordinate(GeometricCellQuantity): - """UFL geometry representation: The coordinate in a reference cell. + """The coordinate in a reference cell. In the context of expression integration, represents the reference cell coordinate of each quadrature point. @@ -178,16 +189,18 @@ class CellCoordinate(GeometricCellQuantity): In the context of expression evaluation in a point in a cell, represents that point in the reference coordinate system of the cell. """ + __slots__ = () name = "X" @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t,) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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 @@ -195,7 +208,7 @@ def is_cellwise_constant(self): @ufl_type() class FacetCoordinate(GeometricFacetQuantity): - """UFL geometry representation: The coordinate in a reference cell of a facet. + """The coordinate in a reference cell of a facet. In the context of expression integration over a facet, represents the reference facet coordinate of each quadrature point. @@ -203,10 +216,12 @@ class FacetCoordinate(GeometricFacetQuantity): In the context of expression evaluation in a point on a facet, represents that point in the reference coordinate system of the facet. """ + __slots__ = () name = "Xf" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -214,11 +229,12 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t - 1,) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -229,39 +245,46 @@ def is_cellwise_constant(self): @ufl_type() class CellOrigin(GeometricCellQuantity): - """UFL geometry representation: The spatial coordinate corresponding to origin of a reference cell.""" + """The spatial coordinate corresponding to origin of a reference cell.""" + __slots__ = () name = "x0" @property def ufl_shape(self): + """Get the UFL shape.""" g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): + """Return whether this expression is spatially constant over each cell.""" return True @ufl_type() class FacetOrigin(GeometricFacetQuantity): - """UFL geometry representation: The spatial coordinate corresponding to origin of a reference facet.""" + """The spatial coordinate corresponding to origin of a reference facet.""" + __slots__ = () name = "x0f" @property def ufl_shape(self): + """Get the UFL shape.""" g = self._domain.geometric_dimension() return (g,) @ufl_type() class CellFacetOrigin(GeometricFacetQuantity): - """UFL geometry representation: The reference cell coordinate corresponding to origin of a reference facet.""" + """The reference cell coordinate corresponding to origin of a reference facet.""" + __slots__ = () name = "X0f" @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t,) @@ -270,42 +293,43 @@ def ufl_shape(self): @ufl_type() class Jacobian(GeometricCellQuantity): - """UFL geometry representation: The Jacobian of the mapping from reference cell to spatial coordinates. + r"""The Jacobian of the mapping from reference cell to spatial coordinates. .. math:: J_{ij} = \\frac{dx_i}{dX_j} """ + __slots__ = () name = "J" @property def ufl_shape(self): - """Return the number of coordinates defined (i.e. the geometric dimension - of the domain).""" + """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (g, t) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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 FacetJacobian(GeometricFacetQuantity): - """UFL geometry representation: The Jacobian of the mapping from reference facet to spatial coordinates. + """The Jacobian of the mapping from reference facet to spatial coordinates. FJ_ij = dx_i/dXf_j The FacetJacobian is the product of the Jacobian and CellFacetJacobian: FJ = dx/dXf = dx/dX dX/dXf = J * CFJ - """ + __slots__ = () name = "FJ" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -313,12 +337,13 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (g, t - 1) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -326,14 +351,16 @@ def is_cellwise_constant(self): @ufl_type() class CellFacetJacobian(GeometricFacetQuantity): # dX/dXf - """UFL geometry representation: The Jacobian of the mapping from reference facet to reference cell coordinates. + """The Jacobian of the mapping from reference facet to reference cell coordinates. CFJ_ij = dX_i/dXf_j """ + __slots__ = () name = "CFJ" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -341,11 +368,12 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t, t - 1) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always a constant mapping between two reference # coordinate systems. return True @@ -353,11 +381,13 @@ def is_cellwise_constant(self): @ufl_type() class ReferenceCellEdgeVectors(GeometricCellQuantity): - """UFL geometry representation: The vectors between reference cell vertices for each edge in cell.""" + """The vectors between reference cell vertices for each edge in cell.""" + __slots__ = () name = "RCEV" def __init__(self, domain): + """Initialise.""" GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -365,24 +395,27 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" cell = extract_unique_domain(self).ufl_cell() ne = cell.num_edges() t = cell.topological_dimension() return (ne, t) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always constant for a given cell type return True @ufl_type() class ReferenceFacetEdgeVectors(GeometricFacetQuantity): - """UFL geometry representation: The vectors between reference cell vertices for each edge in current facet.""" + """The vectors between reference cell vertices for each edge in current facet.""" + __slots__ = () name = "RFEV" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: @@ -390,6 +423,7 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" cell = extract_unique_domain(self).ufl_cell() facet_types = cell.facet_types() @@ -402,40 +436,45 @@ def ufl_shape(self): return (nfe, t) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always constant for a given cell type return True @ufl_type() class CellVertices(GeometricCellQuantity): - """UFL geometry representation: Physical cell vertices.""" + """Physical cell vertices.""" + __slots__ = () name = "CV" def __init__(self, domain): + """Initialise.""" GeometricCellQuantity.__init__(self, domain) @property def ufl_shape(self): + """Get the UFL shape.""" cell = extract_unique_domain(self).ufl_cell() nv = cell.num_vertices() g = cell.geometric_dimension() return (nv, g) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always constant for a given cell type return True @ufl_type() class CellEdgeVectors(GeometricCellQuantity): - """UFL geometry representation: The vectors between physical cell vertices for each edge in cell.""" + """The vectors between physical cell vertices for each edge in cell.""" + __slots__ = () name = "CEV" def __init__(self, domain): + """Initialise.""" GeometricCellQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -443,24 +482,27 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" cell = extract_unique_domain(self).ufl_cell() ne = cell.num_edges() g = cell.geometric_dimension() return (ne, g) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always constant for a given cell type return True @ufl_type() class FacetEdgeVectors(GeometricFacetQuantity): - """UFL geometry representation: The vectors between physical cell vertices for each edge in current facet.""" + """The vectors between physical cell vertices for each edge in current facet.""" + __slots__ = () name = "FEV" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 3: @@ -468,6 +510,7 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" cell = extract_unique_domain(self).ufl_cell() facet_types = cell.facet_types() @@ -480,7 +523,7 @@ def ufl_shape(self): return (nfe, g) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # This is always constant for a given cell type return True @@ -489,15 +532,16 @@ def is_cellwise_constant(self): @ufl_type() class JacobianDeterminant(GeometricCellQuantity): - """UFL geometry representation: The determinant of the Jacobian. + """The determinant of the Jacobian. Represents the signed determinant of a square Jacobian or the pseudo-determinant of a non-square Jacobian. """ + __slots__ = () name = "detJ" def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -505,24 +549,26 @@ def is_cellwise_constant(self): @ufl_type() class FacetJacobianDeterminant(GeometricFacetQuantity): - """UFL geometry representation: The pseudo-determinant of the FacetJacobian.""" + """The pseudo-determinant of the FacetJacobian.""" + __slots__ = () name = "detFJ" def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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 CellFacetJacobianDeterminant(GeometricFacetQuantity): - """UFL geometry representation: The pseudo-determinant of the CellFacetJacobian.""" + """The pseudo-determinant of the CellFacetJacobian.""" + __slots__ = () name = "detCFJ" def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -532,23 +578,23 @@ def is_cellwise_constant(self): @ufl_type() class JacobianInverse(GeometricCellQuantity): - """UFL geometry representation: The inverse of the Jacobian. + """The inverse of the Jacobian. Represents the inverse of a square Jacobian or the pseudo-inverse of a non-square Jacobian. """ + __slots__ = () name = "K" @property def ufl_shape(self): - """Return the number of coordinates defined (i.e. the geometric dimension - of the domain).""" + """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (t, g) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -556,11 +602,13 @@ def is_cellwise_constant(self): @ufl_type() class FacetJacobianInverse(GeometricFacetQuantity): - """UFL geometry representation: The pseudo-inverse of the FacetJacobian.""" + """The pseudo-inverse of the FacetJacobian.""" + __slots__ = () name = "FK" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -568,12 +616,13 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" g = self._domain.geometric_dimension() t = self._domain.topological_dimension() return (t - 1, g) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -581,11 +630,13 @@ def is_cellwise_constant(self): @ufl_type() class CellFacetJacobianInverse(GeometricFacetQuantity): - """UFL geometry representation: The pseudo-inverse of the CellFacetJacobian.""" + """The pseudo-inverse of the CellFacetJacobian.""" + __slots__ = () name = "CFK" def __init__(self, domain): + """Initialise.""" GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: @@ -593,11 +644,12 @@ def __init__(self, domain): @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t - 1, t) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """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() @@ -606,19 +658,19 @@ def is_cellwise_constant(self): @ufl_type() class FacetNormal(GeometricFacetQuantity): - """UFL geometry representation: The outwards pointing normal vector of the current facet.""" + """The outwards pointing normal vector of the current facet.""" + __slots__ = () name = "n" @property def ufl_shape(self): - """Return the number of coordinates defined (i.e. the geometric dimension - of the domain).""" + """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() return (g,) def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # For product cells, this is only true for some but not all # facets. Seems like too much work to fix right now. Only # true for a piecewise linear coordinate field with simplex @@ -629,103 +681,38 @@ def is_cellwise_constant(self): @ufl_type() class CellNormal(GeometricCellQuantity): - """UFL geometry representation: The upwards pointing normal vector of the current manifold cell.""" + """The upwards pointing normal vector of the current manifold cell.""" + __slots__ = () name = "cell_normal" @property def ufl_shape(self): - """Return the number of coordinates defined (i.e. the geometric dimension - of the domain).""" + """Return the number of coordinates defined (i.e. the geometric dimension of the domain).""" g = self._domain.geometric_dimension() # t = self._domain.topological_dimension() # 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." + """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): - """UFL geometry representation: The outwards pointing normal vector of the current facet on the reference cell""" + """The outwards pointing normal vector of the current facet on the reference cell.""" + __slots__ = () name = "reference_normal" @property def ufl_shape(self): + """Get the UFL shape.""" t = self._domain.topological_dimension() return (t,) -# TODO: Implement in apply_geometry_lowering and enable -# @ufl_type() -# class FacetTangents(GeometricFacetQuantity): -# """UFL geometry representation: The tangent vectors of the current facet.""" -# __slots__ = () -# name = "t" -# -# def __init__(self, domain): -# GeometricFacetQuantity.__init__(self, domain) -# t = self._domain.topological_dimension() -# if t < 2: -# raise ValueError("FacetTangents is only defined for topological dimensions >= 2.") -# -# @property -# def ufl_shape(self): -# g = self._domain.geometric_dimension() -# t = self._domain.topological_dimension() -# return (t-1,g) -# -# def is_cellwise_constant(self): # NB! Copied from FacetNormal -# "Return whether this expression is spatially constant over each cell." -# # For product cells, this is only true for some but not all facets. Seems like too much work to fix right now. -# # Only true for a piecewise linear coordinate field with simplex _facets_. -# is_piecewise_linear = self._domain.ufl_coordinate_element().degree() == 1 -# return is_piecewise_linear and self._domain.ufl_cell().has_simplex_facets() - -# TODO: Implement in apply_geometry_lowering and enable -# @ufl_type() -# class CellTangents(GeometricCellQuantity): -# """UFL geometry representation: The tangent vectors of the current manifold cell.""" -# __slots__ = () -# name = "cell_tangents" -# -# @property -# def ufl_shape(self): -# g = self._domain.geometric_dimension() -# t = self._domain.topological_dimension() -# return (t,g) - - -# --- Types representing midpoint coordinates - -# TODO: Implement in the rest of fenics -# @ufl_type() -# class CellMidpoint(GeometricCellQuantity): -# """UFL geometry representation: The midpoint coordinate of the current cell.""" -# __slots__ = () -# name = "cell_midpoint" -# -# @property -# def ufl_shape(self): -# g = self._domain.geometric_dimension() -# return (g,) - -# TODO: Implement in the rest of fenics -# @ufl_type() -# class FacetMidpoint(GeometricFacetQuantity): -# """UFL geometry representation: The midpoint coordinate of the current facet.""" -# __slots__ = () -# name = "facet_midpoint" -# -# @property -# def ufl_shape(self): -# g = self._domain.geometric_dimension() -# return (g,) - - # --- Types representing measures of the cell and entities of the cell, typically used for stabilisation terms # TODO: Clean up this set of types? Document! @@ -733,78 +720,80 @@ def ufl_shape(self): @ufl_type() class ReferenceCellVolume(GeometricCellQuantity): - """UFL geometry representation: The volume of the reference cell.""" + """The volume of the reference cell.""" + __slots__ = () name = "reference_cell_volume" @ufl_type() class ReferenceFacetVolume(GeometricFacetQuantity): - """UFL geometry representation: The volume of the reference cell of the current facet.""" + """The volume of the reference cell of the current facet.""" + __slots__ = () name = "reference_facet_volume" @ufl_type() class CellVolume(GeometricCellQuantity): - """UFL geometry representation: The volume of the cell.""" + """The volume of the cell.""" + __slots__ = () name = "volume" @ufl_type() class Circumradius(GeometricCellQuantity): - """UFL geometry representation: The circumradius of the cell.""" + """The circumradius of the cell.""" + __slots__ = () name = "circumradius" @ufl_type() class CellDiameter(GeometricCellQuantity): - """UFL geometry representation: The diameter of the cell, i.e., - maximal distance of two points in the cell.""" + """The diameter of the cell, i.e., maximal distance of two points in the cell.""" + __slots__ = () name = "diameter" -# @ufl_type() -# class CellSurfaceArea(GeometricCellQuantity): -# """UFL geometry representation: The total surface area of the cell.""" -# __slots__ = () -# name = "surfacearea" - - @ufl_type() class FacetArea(GeometricFacetQuantity): # FIXME: Should this be allowed for interval domain? - """UFL geometry representation: The area of the facet.""" + """The area of the facet.""" + __slots__ = () name = "facetarea" @ufl_type() class MinCellEdgeLength(GeometricCellQuantity): - """UFL geometry representation: The minimum edge length of the cell.""" + """The minimum edge length of the cell.""" + __slots__ = () name = "mincelledgelength" @ufl_type() class MaxCellEdgeLength(GeometricCellQuantity): - """UFL geometry representation: The maximum edge length of the cell.""" + """The maximum edge length of the cell.""" + __slots__ = () name = "maxcelledgelength" @ufl_type() class MinFacetEdgeLength(GeometricFacetQuantity): - """UFL geometry representation: The minimum edge length of the facet.""" + """The minimum edge length of the facet.""" + __slots__ = () name = "minfacetedgelength" @ufl_type() class MaxFacetEdgeLength(GeometricFacetQuantity): - """UFL geometry representation: The maximum edge length of the facet.""" + """The maximum edge length of the facet.""" + __slots__ = () name = "maxfacetedgelength" @@ -813,7 +802,7 @@ class MaxFacetEdgeLength(GeometricFacetQuantity): @ufl_type() class CellOrientation(GeometricCellQuantity): - """UFL geometry representation: The orientation (+1/-1) of the current cell. + """The orientation (+1/-1) of the current cell. For non-manifold cells (tdim == gdim), this equals the sign of the Jacobian determinant, i.e. +1 if the physical cell is @@ -822,13 +811,15 @@ class CellOrientation(GeometricCellQuantity): For manifold cells of tdim==gdim-1 this is input data belonging to the mesh, used to distinguish between the sides of the manifold. """ + __slots__ = () name = "cell_orientation" @ufl_type() class FacetOrientation(GeometricFacetQuantity): - """UFL geometry representation: The orientation (+1/-1) of the current facet relative to the reference cell.""" + """The orientation (+1/-1) of the current facet relative to the reference cell.""" + __slots__ = () name = "facet_orientation" @@ -837,14 +828,15 @@ class FacetOrientation(GeometricFacetQuantity): # terminal types instead? @ufl_type() class QuadratureWeight(GeometricQuantity): - """UFL geometry representation: The current quadrature weight. + """The current quadrature weight. Only used inside a quadrature context. """ + __slots__ = () name = "weight" def is_cellwise_constant(self): - "Return whether this expression is spatially constant over each cell." + """Return whether this expression is spatially constant over each cell.""" # The weight usually varies with the quadrature points return False diff --git a/ufl/index_combination_utils.py b/ufl/index_combination_utils.py index 52e3c906a..6d820244e 100644 --- a/ufl/index_combination_utils.py +++ b/ufl/index_combination_utils.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -"Utilities for analysing and manipulating free index tuples" - +"""Utilities for analysing and manipulating free index tuples.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later - from ufl.core.multiindex import FixedIndex, Index, indices @@ -15,7 +12,9 @@ # be optimized def unique_sorted_indices(indices): - """Given a list of (id, dim) tuples already sorted by id, + """Get unique sorted indices. + + Given a list of (id, dim) tuples already sorted by id, return a unique list with duplicates removed. Also checks that the dimensions of duplicates are matching. """ @@ -39,7 +38,6 @@ def merge_unique_indices(afi, afid, bfi, bfid): return a unique list with duplicates removed. Also checks that the dimensions of duplicates are matching. """ - na = len(afi) nb = len(bfi) @@ -131,6 +129,7 @@ def remove_indices(fi, fid, rfi): def create_slice_indices(component, shape, fi): + """Create slice indices.""" all_indices = [] slice_indices = [] repeated_indices = [] @@ -175,12 +174,9 @@ def merge_nonoverlapping_indices(a, b): """Merge non-overlapping free indices into one representation. Example: - ------- C[i,j,r,s] = outer(A[i,s], B[j,r]) A, B -> (i,j,r,s), (idim,jdim,rdim,sdim) - """ - # Extract input properties ai = a.ufl_free_indices bi = b.ufl_free_indices @@ -204,12 +200,9 @@ def merge_overlapping_indices(afi, afid, bfi, bfid): """Merge overlapping free indices into one free and one repeated representation. Example: - ------- C[j,r] := A[i,j,k] * B[i,r,k] A, B -> (j,r), (jdim,rdim), (i,k), (idim,kdim) - """ - # Extract input properties an = len(afi) bn = len(bfi) diff --git a/ufl/indexed.py b/ufl/indexed.py index 65c28b8ae..43bae7f10 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module defines the Indexed class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -20,12 +19,15 @@ @ufl_type(is_shaping=True, num_ops=2, is_terminal_modifier=True) class Indexed(Operator): + """Indexed.""" + __slots__ = ( "ufl_free_indices", "ufl_index_dimensions", ) def __new__(cls, expression, multiindex): + """Create a new Indexed.""" if isinstance(expression, Zero): # Zero-simplify indexed Zero objects shape = expression.ufl_shape @@ -47,6 +49,7 @@ def __new__(cls, expression, multiindex): return Operator.__new__(cls) def __init__(self, expression, multiindex): + """Initialise.""" # Store operands Operator.__init__(self, (expression, multiindex)) @@ -88,6 +91,7 @@ def __init__(self, expression, multiindex): ufl_shape = () def evaluate(self, x, mapping, component, index_values, derivatives=()): + """Evaluate.""" A, ii = self.ufl_operands component = ii.evaluate(x, mapping, None, index_values) if derivatives: @@ -96,10 +100,12 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): return A.evaluate(x, mapping, component, index_values) def __str__(self): + """Format as a string.""" return "%s[%s]" % (parstr(self.ufl_operands[0], self), self.ufl_operands[1]) def __getitem__(self, key): + """Get an item.""" if key == (): # So that one doesn't have to special case indexing of # expressions without shape. diff --git a/ufl/indexsum.py b/ufl/indexsum.py index 3c6a500aa..3df273255 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module defines the IndexSum class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -20,11 +19,12 @@ @ufl_type(num_ops=2) class IndexSum(Operator): - __slots__ = ("_dimension", - "ufl_free_indices", - "ufl_index_dimensions",) + """Index sum.""" + + __slots__ = ("_dimension", "ufl_free_indices", "ufl_index_dimensions") def __new__(cls, summand, index): + """Create a new IndexSum.""" # Error checks if not isinstance(summand, Expr): raise ValueError(f"Expecting Expr instance, got {ufl_err_str(summand)}") @@ -47,6 +47,7 @@ def __new__(cls, summand, index): return Operator.__new__(cls) def __init__(self, summand, index): + """Initialise.""" j, = index fi = summand.ufl_free_indices fid = summand.ufl_index_dimensions @@ -57,16 +58,20 @@ def __init__(self, summand, index): Operator.__init__(self, (summand, index)) def index(self): + """Get index.""" return self.ufl_operands[1][0] def dimension(self): + """Get dimension.""" return self._dimension @property def ufl_shape(self): + """Get UFL shape.""" return self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" i, = self.ufl_operands[1] tmp = 0 for k in range(self._dimension): @@ -77,5 +82,6 @@ def evaluate(self, x, mapping, component, index_values): return tmp def __str__(self): + """Format as a string.""" return "sum_{%s} %s " % (str(self.ufl_operands[1]), parstr(self.ufl_operands[0], self)) diff --git a/ufl/integral.py b/ufl/integral.py index 6f41cda21..f92aefbc5 100644 --- a/ufl/integral.py +++ b/ufl/integral.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """The Integral class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -21,16 +20,14 @@ class Integral(object): - "An integral over a single domain." - __slots__ = ("_integrand", - "_integral_type", - "_ufl_domain", - "_subdomain_id", - "_metadata", - "_subdomain_data",) - - def __init__(self, integrand, integral_type, domain, subdomain_id, - metadata, subdomain_data): + """An integral over a single domain.""" + + __slots__ = ("_integrand", "_integral_type", "_ufl_domain", "_subdomain_id", "_metadata", "_subdomain_data") + + def __init__( + self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data + ): + """Initialise.""" if not isinstance(integrand, Expr): raise ValueError("Expecting integrand to be an Expr instance.") self._integrand = integrand @@ -40,18 +37,17 @@ def __init__(self, integrand, integral_type, domain, subdomain_id, self._metadata = metadata self._subdomain_data = subdomain_data - def reconstruct(self, integrand=None, - integral_type=None, domain=None, subdomain_id=None, - metadata=None, subdomain_data=None): - """Construct a new Integral object with some properties replaced with - new values. + def reconstruct( + self, integrand=None, + integral_type=None, domain=None, subdomain_id=None, + metadata=None, subdomain_data=None + ): + """Construct a new Integral object with some properties replaced with new values. Example: - ------- b = a.reconstruct(expand_compounds(a.integrand())) c = a.reconstruct(metadata={'quadrature_degree':2}) - """ if integrand is None: integrand = self.integrand() @@ -68,60 +64,67 @@ def reconstruct(self, integrand=None, return Integral(integrand, integral_type, domain, subdomain_id, metadata, subdomain_data) def integrand(self): - "Return the integrand expression, which is an ``Expr`` instance." + """Return the integrand expression, which is an ``Expr`` instance.""" return self._integrand def integral_type(self): - "Return the domain type of this integral." + """Return the domain type of this integral.""" return self._integral_type def ufl_domain(self): - "Return the integration domain of this integral." + """Return the integration domain of this integral.""" return self._ufl_domain def subdomain_id(self): - "Return the subdomain id of this integral." + """Return the subdomain id of this integral.""" return self._subdomain_id def metadata(self): - "Return the compiler metadata this integral has been annotated with." + """Return the compiler metadata this integral has been annotated with.""" return self._metadata def subdomain_data(self): - "Return the domain data of this integral." + """Return the domain data of this integral.""" return self._subdomain_data def __neg__(self): + """Negate.""" return self.reconstruct(-self._integrand) def __mul__(self, scalar): + """Multiply.""" if not is_python_scalar(scalar): raise ValueError("Cannot multiply an integral with non-constant values.") return self.reconstruct(scalar * self._integrand) def __rmul__(self, scalar): + """Multiply.""" if not is_scalar_constant_expression(scalar): raise ValueError("An integral can only be multiplied by a " "globally constant scalar expression.") return self.reconstruct(scalar * self._integrand) def __str__(self): + """Format as a string.""" fmt = "{ %s } * %s(%s[%s], %s)" mname = ufl.measure.integral_type_to_measure_name[self._integral_type] s = fmt % (self._integrand, mname, self._ufl_domain, self._subdomain_id, self._metadata) return s def __repr__(self): + """Representation.""" return (f"Integral({self._integrand!r}, {self._integral_type!r}, {self._ufl_domain!r}, " f"{self._subdomain_id!r}, {self._metadata!r}, {self._subdomain_data!r})") def __eq__(self, other): + """Check equality.""" return (isinstance(other, Integral) and self._integral_type == other._integral_type and # noqa: W504 self._ufl_domain == other._ufl_domain and self._subdomain_id == other._subdomain_id and # noqa: W504 self._integrand == other._integrand and self._metadata == other._metadata and # noqa: W504 id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data)) def __hash__(self): + """Hash.""" # Assuming few collisions by ignoring hash(self._metadata) (a # dict is not hashable but we assume it is immutable in # practice) diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index fc0619cd6..24ab0354d 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """This module provides basic mathematical functions.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -46,17 +44,20 @@ @ufl_type(is_abstract=True, is_scalar=True, num_ops=1) class MathFunction(Operator): - "Base class for all unary scalar math functions." + """Base class for all unary scalar math functions.""" + # Freeze member variables for objects in this class __slots__ = ("_name",) def __init__(self, name, argument): + """Initialise.""" Operator.__init__(self, (argument,)) if not is_true_ufl_scalar(argument): raise ValueError("Expecting scalar argument.") self._name = name def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: if isinstance(a, numbers.Real): @@ -69,14 +70,18 @@ def evaluate(self, x, mapping, component, index_values): return res def __str__(self): + """Format as a string.""" return "%s(%s)" % (self._name, self.ufl_operands[0]) @ufl_type() class Sqrt(MathFunction): + """Square root.""" + __slots__ = () def __new__(cls, argument): + """Create a new Sqrt.""" if isinstance(argument, (RealValue, Zero, numbers.Real)): if float(argument) < 0: return ComplexValue(cmath.sqrt(complex(argument))) @@ -87,14 +92,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "sqrt", argument) @ufl_type() class Exp(MathFunction): + """Exponentiation..""" + __slots__ = () def __new__(cls, argument): + """Create a new Exp.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.exp(float(argument))) if isinstance(argument, (ComplexValue)): @@ -102,14 +111,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "exp", argument) @ufl_type() class Ln(MathFunction): + """Natural logarithm.""" + __slots__ = () def __new__(cls, argument): + """Create a new Ln.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.log(float(argument))) if isinstance(argument, (ComplexValue)): @@ -117,9 +130,11 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "ln", argument) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: return math.log(a) @@ -129,9 +144,12 @@ def evaluate(self, x, mapping, component, index_values): @ufl_type() class Cos(MathFunction): + """Cosine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Cos.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.cos(float(argument))) if isinstance(argument, (ComplexValue)): @@ -139,14 +157,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "cos", argument) @ufl_type() class Sin(MathFunction): + """Sine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Sin.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.sin(float(argument))) if isinstance(argument, (ComplexValue)): @@ -154,14 +176,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "sin", argument) @ufl_type() class Tan(MathFunction): + """Tangent.""" + __slots__ = () def __new__(cls, argument): + """Create a new Tan.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.tan(float(argument))) if isinstance(argument, (ComplexValue)): @@ -169,14 +195,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "tan", argument) @ufl_type() class Cosh(MathFunction): + """Hyperbolic cosine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Cosh.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.cosh(float(argument))) if isinstance(argument, (ComplexValue)): @@ -184,14 +214,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "cosh", argument) @ufl_type() class Sinh(MathFunction): + """Hyperbolic sine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Sinh.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.sinh(float(argument))) if isinstance(argument, (ComplexValue)): @@ -199,14 +233,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "sinh", argument) @ufl_type() class Tanh(MathFunction): + """Hyperbolic tangent.""" + __slots__ = () def __new__(cls, argument): + """Create a new Tanh.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.tanh(float(argument))) if isinstance(argument, (ComplexValue)): @@ -214,14 +252,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "tanh", argument) @ufl_type() class Acos(MathFunction): + """Inverse cosine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Acos.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.acos(float(argument))) if isinstance(argument, (ComplexValue)): @@ -229,14 +271,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "acos", argument) @ufl_type() class Asin(MathFunction): + """Inverse sine.""" + __slots__ = () def __new__(cls, argument): + """Create a new Asin.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.asin(float(argument))) if isinstance(argument, (ComplexValue)): @@ -244,14 +290,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "asin", argument) @ufl_type() class Atan(MathFunction): + """Inverse tangent.""" + __slots__ = () def __new__(cls, argument): + """Create a new Atan.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.atan(float(argument))) if isinstance(argument, (ComplexValue)): @@ -259,14 +309,18 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "atan", argument) @ufl_type(is_scalar=True, num_ops=2) class Atan2(Operator): + """Inverse tangent with two inputs.""" + __slots__ = () def __new__(cls, arg1, arg2): + """Create a new Atan2.""" if isinstance(arg1, (RealValue, Zero)) and isinstance(arg2, (RealValue, Zero)): return FloatValue(math.atan2(float(arg1), float(arg2))) if isinstance(arg1, (ComplexValue)) or isinstance(arg2, (ComplexValue)): @@ -274,6 +328,7 @@ def __new__(cls, arg1, arg2): return Operator.__new__(cls) def __init__(self, arg1, arg2): + """Initialise.""" Operator.__init__(self, (arg1, arg2)) if isinstance(arg1, (ComplexValue, complex)) or isinstance(arg2, (ComplexValue, complex)): raise TypeError("Atan2 does not support complex numbers.") @@ -283,6 +338,7 @@ def __init__(self, arg1, arg2): raise ValueError("Expecting scalar argument 2.") def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: @@ -295,14 +351,18 @@ def evaluate(self, x, mapping, component, index_values): return res def __str__(self): + """Format as a string.""" return "atan2(%s,%s)" % (self.ufl_operands[0], self.ufl_operands[1]) @ufl_type() class Erf(MathFunction): + """Erf function.""" + __slots__ = () def __new__(cls, argument): + """Create a new Erf.""" if isinstance(argument, (RealValue, Zero)): return FloatValue(math.erf(float(argument))) if isinstance(argument, (ConstantValue)): @@ -310,19 +370,23 @@ def __new__(cls, argument): return MathFunction.__new__(cls) def __init__(self, argument): + """Initialise.""" MathFunction.__init__(self, "erf", argument) def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return math.erf(a) @ufl_type(is_abstract=True, is_scalar=True, num_ops=2) class BesselFunction(Operator): - "Base class for all bessel functions" + """Base class for all bessel functions.""" + __slots__ = ("_name") def __init__(self, name, nu, argument): + """Initialise.""" if not is_true_ufl_scalar(nu): raise ValueError("Expecting scalar nu.") if not is_true_ufl_scalar(argument): @@ -341,6 +405,7 @@ def __init__(self, name, nu, argument): self._name = name def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: import scipy.special @@ -358,37 +423,50 @@ def evaluate(self, x, mapping, component, index_values): return func(nu, a) def __str__(self): + """Format as a string.""" return "%s(%s, %s)" % (self._name, self.ufl_operands[0], self.ufl_operands[1]) @ufl_type() class BesselJ(BesselFunction): + """Bessel J function.""" + __slots__ = () def __init__(self, nu, argument): + """Initialise.""" BesselFunction.__init__(self, "cyl_bessel_j", nu, argument) @ufl_type() class BesselY(BesselFunction): + """Bessel Y function.""" + __slots__ = () def __init__(self, nu, argument): + """Initialise.""" BesselFunction.__init__(self, "cyl_bessel_y", nu, argument) @ufl_type() class BesselI(BesselFunction): + """Bessel I function.""" + __slots__ = () def __init__(self, nu, argument): + """Initialise.""" BesselFunction.__init__(self, "cyl_bessel_i", nu, argument) @ufl_type() class BesselK(BesselFunction): + """Bessel K function.""" + __slots__ = () def __init__(self, nu, argument): + """Initialise.""" BesselFunction.__init__(self, "cyl_bessel_k", nu, argument) diff --git a/ufl/matrix.py b/ufl/matrix.py index 23cefcc6f..7cf8e55bd 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """This module defines the Matrix class.""" - # Copyright (C) 2021 India Marsden # # This file is part of UFL (https://www.fenicsproject.org) @@ -35,10 +33,12 @@ class Matrix(BaseForm, Counted): "_domains") def __getnewargs__(self): + """Get new args.""" return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], self._count) def __init__(self, row_space, column_space, count=None): + """Initialise.""" BaseForm.__init__(self) Counted.__init__(self, count, Matrix) @@ -56,11 +56,11 @@ def __init__(self, row_space, column_space, count=None): self._repr = f"Matrix({self._ufl_function_spaces[0]!r}, {self._ufl_function_spaces[1]!r}, {self._count!r})" def ufl_function_spaces(self): - "Get the tuple of function spaces of this coefficient." + """Get the tuple of function spaces of this coefficient.""" return self._ufl_function_spaces def _analyze_form_arguments(self): - "Define arguments of a matrix when considered as a form." + """Define arguments of a matrix when considered as a form.""" self._arguments = (Argument(self._ufl_function_spaces[0], 0), Argument(self._ufl_function_spaces[1], 1)) self._coefficients = () @@ -72,6 +72,7 @@ def _analyze_domains(self): self._domains = join_domains([fs.ufl_domain() for fs in self._ufl_function_spaces]) def __str__(self): + """Format as a string.""" count = str(self._count) if len(count) == 1: return f"A_{count}" @@ -79,15 +80,17 @@ def __str__(self): return f"A_{{{count}}}" def __repr__(self): + """Representation.""" return self._repr def __hash__(self): - "Hash code for use in dicts " + """Hash.""" if self._hash is None: self._hash = hash(self._repr) return self._hash def equals(self, other): + """Check equality.""" if type(other) is not Matrix: return False if self is other: diff --git a/ufl/measure.py b/ufl/measure.py index 222cac149..e457842ab 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """The Measure class.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -63,6 +62,7 @@ def register_integral_type(integral_type, measure_name): + """Register an integral type.""" global integral_type_to_measure_name, measure_name_to_integral_type if measure_name != integral_type_to_measure_name.get(integral_type, measure_name): raise ValueError("Integral type already added with different measure name!") @@ -73,7 +73,7 @@ def register_integral_type(integral_type, measure_name): def as_integral_type(integral_type): - "Map short name to long name and require a valid one." + """Map short name to long name and require a valid one.""" integral_type = integral_type.replace(" ", "_") integral_type = measure_name_to_integral_type.get(integral_type, integral_type) @@ -83,55 +83,41 @@ def as_integral_type(integral_type): def integral_types(): - "Return a tuple of all domain type strings." + """Return a tuple of all domain type strings.""" return tuple(sorted(integral_type_to_measure_name.keys())) def measure_names(): - "Return a tuple of all measure name strings." + """Return a tuple of all measure name strings.""" return tuple(sorted(measure_name_to_integral_type.keys())) class Measure(object): - __slots__ = ("_integral_type", - "_domain", - "_subdomain_id", - "_metadata", - "_subdomain_data") """Representation of an integration measure. The Measure object holds information about integration properties to be transferred to a Form on multiplication with a scalar expression. - """ + __slots__ = ("_integral_type", "_domain", "_subdomain_id", "_metadata", "_subdomain_data") + def __init__(self, integral_type, # "dx" etc domain=None, subdomain_id="everywhere", metadata=None, subdomain_data=None): - """ - integral_type: - str, one of "cell", etc., - or short form "dx", etc. - - domain: - an AbstractDomain object (most often a Mesh) - - subdomain_id: - either string "everywhere", - a single subdomain id int, - or tuple of ints - - metadata: - dict, with additional compiler-specific parameters - affecting how code is generated, including parameters - for optimization or debugging of generated code. - - subdomain_data: - object representing data to interpret subdomain_id with. + """Initialise. + + Args: + integral_type: one of "cell", etc, or short form "dx", etc + domain: an AbstractDomain object (most often a Mesh) + subdomain_id: either string "everywhere", a single subdomain id int, or tuple of ints + metadata: dict, with additional compiler-specific parameters + affecting how code is generated, including parameters + for optimization or debugging of generated code + subdomain_data: object representing data to interpret subdomain_id with """ # Map short name to long name and require a valid one self._integral_type = as_integral_type(integral_type) @@ -179,14 +165,15 @@ def ufl_domain(self): return self._domain def subdomain_id(self): - "Return the domain id of this measure (integer)." + """Return the domain id of this measure (integer).""" return self._subdomain_id def metadata(self): - """Return the integral metadata. This data is not interpreted by UFL. + """Return the integral metadata. + + This data is not interpreted by UFL. It is passed to the form compiler which can ignore it or use it to compile each integral of a form in a different way. - """ return self._metadata @@ -196,11 +183,9 @@ def reconstruct(self, domain=None, metadata=None, subdomain_data=None): - """Construct a new Measure object with some properties replaced with - new values. + """Construct a new Measure object with some properties replaced with new values. Example: - ------- b = dm.reconstruct(subdomain_id=2) c = dm.reconstruct(metadata={ "quadrature_degree": 3 }) @@ -208,7 +193,6 @@ def reconstruct(self, Used by the call operator, so this is equivalent: b = dm(2) c = dm(0, { "quadrature_degree": 3 }) - """ if subdomain_id is None: subdomain_id = self.subdomain_id() @@ -223,10 +207,11 @@ def reconstruct(self, metadata=metadata, subdomain_data=subdomain_data) def subdomain_data(self): - """Return the integral subdomain_data. This data is not interpreted by + """Return the integral subdomain_data. + + This data is not interpreted by UFL. Its intension is to give a context in which the domain id is interpreted. - """ return self._subdomain_data @@ -236,7 +221,6 @@ def subdomain_data(self): def __call__(self, subdomain_id=None, metadata=None, domain=None, subdomain_data=None, degree=None, scheme=None): """Reconfigure measure with new domain specification or metadata.""" - # Let syntax dx() mean integral over everywhere all_args = (subdomain_id, metadata, domain, subdomain_data, degree, scheme) @@ -271,6 +255,7 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, subdomain_data=subdomain_data) def __str__(self): + """Format as a string.""" name = integral_type_to_measure_name[self._integral_type] args = [] @@ -286,7 +271,7 @@ def __str__(self): return "%s(%s)" % (name, ', '.join(args)) def __repr__(self): - "Return a repr string for this Measure." + """Return a repr string for this Measure.""" args = [] args.append(repr(self._integral_type)) @@ -303,7 +288,7 @@ def __repr__(self): return r def __hash__(self): - "Return a hash value for this Measure." + """Return a hash value for this Measure.""" metadata_hashdata = tuple(sorted((k, id(v)) for k, v in list(self._metadata.items()))) hashdata = (self._integral_type, self._subdomain_id, @@ -313,7 +298,7 @@ def __hash__(self): return hash(hashdata) def __eq__(self, other): - "Checks if two Measures are equal." + """Checks if two Measures are equal.""" sorted_metadata = sorted((k, id(v)) for k, v in list(self._metadata.items())) sorted_other_metadata = sorted((k, id(v)) for k, v in list(other._metadata.items())) @@ -326,7 +311,6 @@ def __add__(self, other): """Add two measures (self+other). Creates an intermediate object used for the notation - expr * (dx(1) + dx(2)) := expr * dx(1) + expr * dx(2) """ if isinstance(other, Measure): @@ -340,7 +324,6 @@ def __mul__(self, other): """Multiply two measures (self*other). Creates an intermediate object used for the notation - expr * (dm1 * dm2) := expr * dm1 * dm2 This is work in progress and not functional. @@ -353,15 +336,12 @@ def __mul__(self, other): return NotImplemented def __rmul__(self, integrand): - """Multiply a scalar expression with measure to construct a form with - a single integral. + """Multiply a scalar expression with measure to construct a form with a single integral. This is to implement the notation - form = integrand * self Integration properties are taken from this Measure object. - """ # Avoid circular imports from ufl.integral import Integral @@ -421,23 +401,24 @@ class MeasureSum(object): """Represents a sum of measures. This is a notational intermediate object to translate the notation - f*(ds(1)+ds(3)) - into - f*ds(1) + f*ds(3) """ + __slots__ = ("_measures",) def __init__(self, *measures): + """Initialise.""" self._measures = measures def __rmul__(self, other): + """Multiply.""" integrals = [other * m for m in self._measures] return sum(integrals) def __add__(self, other): + """Add.""" if isinstance(other, Measure): return MeasureSum(*(self._measures + (other,))) elif isinstance(other, MeasureSum): @@ -445,6 +426,7 @@ def __add__(self, other): return NotImplemented def __str__(self): + """Format as a string.""" return "{\n " + "\n + ".join(map(str, self._measures)) + "\n}" @@ -457,12 +439,12 @@ class MeasureProduct(object): This is work in progress and not functional. It needs support in other parts of ufl and the rest of the code generation chain. - """ + __slots__ = ("_measures",) def __init__(self, *measures): - "Create MeasureProduct from given list of measures." + """Create MeasureProduct from given list of measures.""" self._measures = measures if len(self._measures) < 2: raise ValueError("Expecting at least two measures.") @@ -472,7 +454,6 @@ def __mul__(self, other): This is to ensure that (dm1*dm2)*dm3 is stored as a simple list (dm1,dm2,dm3) in a single MeasureProduct. - """ if isinstance(other, Measure): measures = self.sub_measures() + [other] @@ -481,9 +462,10 @@ def __mul__(self, other): return NotImplemented def __rmul__(self, integrand): + """Multiply.""" # TODO: Implement MeasureProduct.__rmul__ to construct integral and form somehow. raise NotImplementedError() def sub_measures(self): - "Return submeasures." + """Return submeasures.""" return self._measures diff --git a/ufl/objects.py b/ufl/objects.py index 84f3f360c..7e740cf72 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Utility objects for pretty syntax in user code." - +"""Utility objects for pretty syntax in user code.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) diff --git a/ufl/operators.py b/ufl/operators.py index dfdc01956..8f99d501b 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -1,9 +1,10 @@ -# -*- coding: utf-8 -*- -"""This module extends the form language with free function operators, +"""Operators. + +This module extends the form language with free function operators, which are either already available as member functions on UFL objects or defined as compound operators involving basic operations on the UFL -objects.""" - +objects. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -42,13 +43,13 @@ def rank(f): - "UFL operator: The rank of *f*." + """The rank of f.""" f = as_ufl(f) return len(f.ufl_shape) def shape(f): - "UFL operator: The shape of *f*." + """The shape of f.""" f = as_ufl(f) return f.ufl_shape @@ -56,7 +57,7 @@ def shape(f): # --- Complex operators --- def conj(f): - "UFL operator: The complex conjugate of *f*" + """The complex conjugate of f.""" f = as_ufl(f) return Conj(f) @@ -66,13 +67,13 @@ def conj(f): def real(f): - "UFL operator: The real part of *f*" + """The real part of f.""" f = as_ufl(f) return Real(f) def imag(f): - "UFL operator: The imaginary part of *f*" + """The imaginary part of f.""" f = as_ufl(f) return Imag(f) @@ -80,6 +81,7 @@ def imag(f): # --- Elementwise tensor operators --- def elem_op_items(op_ind, indices, *args): + """Elem op items.""" sh = args[0].ufl_shape indices = tuple(indices) n = sh[len(indices)] @@ -94,7 +96,7 @@ def extind(ii): def elem_op(op, *args): - "UFL operator: Take the elementwise application of operator op on scalar values from one or more tensor arguments." + """Take the elementwise application of operator op on scalar values from one or more tensor arguments.""" args = [as_ufl(arg) for arg in args] sh = args[0].ufl_shape if not all(sh == x.ufl_shape for x in args): @@ -109,24 +111,24 @@ def op_ind(ind, *args): def elem_mult(A, B): - "UFL operator: Take the elementwise multiplication of tensors *A* and *B* with the same shape." + """Take the elementwise multiplication of tensors A and B with the same shape.""" return elem_op(operator.mul, A, B) def elem_div(A, B): - "UFL operator: Take the elementwise division of tensors *A* and *B* with the same shape." + """Take the elementwise division of tensors A and B with the same shape.""" return elem_op(operator.truediv, A, B) def elem_pow(A, B): - "UFL operator: Take the elementwise power of tensors *A* and *B* with the same shape." + """Take the elementwise power of tensors A and B with the same shape.""" return elem_op(operator.pow, A, B) # --- Tensor operators --- def transpose(A): - "UFL operator: Take the transposed of tensor A." + """Take the transposed of tensor A.""" A = as_ufl(A) if A.ufl_shape == (): return A @@ -134,7 +136,8 @@ def transpose(A): def outer(*operands): - """UFL operator: Take the outer product of two or more operands. + """Take the outer product of two or more operands. + The complex conjugate of the first argument is taken. """ n = len(operands) @@ -153,7 +156,10 @@ def outer(*operands): def inner(a, b): - "UFL operator: Take the inner product of *a* and *b*. The complex conjugate of the second argument is taken." + """Take the inner product of a and b. + + The complex conjugate of the second argument is taken. + """ a = as_ufl(a) b = as_ufl(b) if a.ufl_shape == () and b.ufl_shape == (): @@ -162,7 +168,10 @@ def inner(a, b): def dot(a, b): - "UFL operator: Take the dot product of *a* and *b*. This won't take the complex conjugate of the second argument." + """Take the dot product of a and b. + + This won't take the complex conjugate of the second argument. + """ a = as_ufl(a) b = as_ufl(b) if a.ufl_shape == () and b.ufl_shape == (): @@ -171,7 +180,10 @@ def dot(a, b): def perp(v): - "UFL operator: Take the perp of *v*, i.e. :math:`(-v_1, +v_0)`." + """Take the perp of v. + + I.e. :math:`(-v_1, +v_0)`. + """ v = as_ufl(v) if v.ufl_shape != (2,): raise ValueError("Expecting a 2D vector expression.") @@ -179,14 +191,14 @@ def perp(v): def cross(a, b): - "UFL operator: Take the cross product of *a* and *b*." + """Take the cross product of a and b.""" a = as_ufl(a) b = as_ufl(b) return Cross(a, b) def det(A): - "UFL operator: Take the determinant of *A*." + """Take the determinant of A.""" A = as_ufl(A) if A.ufl_shape == (): return A @@ -194,7 +206,7 @@ def det(A): def inv(A): - "UFL operator: Take the inverse of *A*." + """Take the inverse of A.""" A = as_ufl(A) if A.ufl_shape == (): return 1 / A @@ -202,23 +214,22 @@ def inv(A): def cofac(A): - "UFL operator: Take the cofactor of *A*." + """Take the cofactor of A.""" A = as_ufl(A) return Cofactor(A) def tr(A): - "UFL operator: Take the trace of *A*." + """Take the trace of A.""" A = as_ufl(A) return Trace(A) def diag(A): - """UFL operator: Take the diagonal part of rank 2 tensor *A* **or** - make a diagonal rank 2 tensor from a rank 1 tensor. - - Always returns a rank 2 tensor. See also ``diag_vector``.""" + """Take the diagonal part of rank 2 tensor A or make a diagonal rank 2 tensor from a rank 1 tensor. + Always returns a rank 2 tensor. See also diag_vector. + """ # TODO: Make a compound type or two for this operator # Get and check dimensions @@ -242,10 +253,10 @@ def diag(A): def diag_vector(A): - """UFL operator: Take the diagonal part of rank 2 tensor *A* and return as a vector. - - See also ``diag``.""" + """Take the diagonal part of rank 2 tensor A and return as a vector. + See also diag. + """ # TODO: Make a compound type for this operator # Get and check dimensions @@ -260,19 +271,19 @@ def diag_vector(A): def dev(A): - "UFL operator: Take the deviatoric part of *A*." + """Take the deviatoric part of A.""" A = as_ufl(A) return Deviatoric(A) def skew(A): - "UFL operator: Take the skew symmetric part of *A*." + """Take the skew symmetric part of A.""" A = as_ufl(A) return Skew(A) def sym(A): - "UFL operator: Take the symmetric part of *A*." + """Take the symmetric part of A.""" A = as_ufl(A) return Sym(A) @@ -280,15 +291,19 @@ def sym(A): # --- Differential operators def Dx(f, *i): - """UFL operator: Take the partial derivative of *f* with respect - to spatial variable number *i*. Equivalent to ``f.dx(*i)``.""" + """Take the partial derivative of f with respect to spatial variable number i. + + Equivalent to f.dx(*i). + """ f = as_ufl(f) return f.dx(*i) def Dn(f): - """UFL operator: Take the directional derivative of *f* in the - facet normal direction, Dn(f) := dot(grad(f), n).""" + """Take the directional derivative of f in the facet normal direction. + + The facet normal is 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) @@ -296,9 +311,9 @@ def Dn(f): def diff(f, v): - """UFL operator: Take the derivative of *f* with respect to the variable *v*. + """Take the derivative of f with respect to the variable v. - If *f* is a form, ``diff`` is applied to each integrand. + If f is a form, diff is applied to each integrand. """ # Apply to integrands if isinstance(f, Form): @@ -316,16 +331,12 @@ def diff(f, v): def grad(f): - """UFL operator: Take the gradient of *f*. + """Take the gradient of f. This operator follows the grad convention where - grad(s)[i] = s.dx(i) - grad(v)[i,j] = v[i].dx(j) - grad(T)[:,i] = T[:].dx(i) - for scalar expressions s, vector expressions v, and arbitrary rank tensor expressions T. @@ -336,14 +347,11 @@ def grad(f): def div(f): - """UFL operator: Take the divergence of *f*. + """Take the divergence of f. This operator follows the div convention where - div(v) = v[i].dx(i) - div(T)[:] = T[:,i].dx(i) - for vector expressions v, and arbitrary rank tensor expressions T. @@ -354,16 +362,12 @@ def div(f): def nabla_grad(f): - """UFL operator: Take the gradient of *f*. + """Take the gradient of f. This operator follows the grad convention where - nabla_grad(s)[i] = s.dx(i) - nabla_grad(v)[i,j] = v[j].dx(i) - nabla_grad(T)[i,:] = T[:].dx(i) - for scalar expressions s, vector expressions v, and arbitrary rank tensor expressions T. @@ -374,14 +378,11 @@ def nabla_grad(f): def nabla_div(f): - """UFL operator: Take the divergence of *f*. + """Take the divergence of f. This operator follows the div convention where - nabla_div(v) = v[i].dx(i) - nabla_div(T)[:] = T[i,:].dx(i) - for vector expressions v, and arbitrary rank tensor expressions T. @@ -392,7 +393,7 @@ def nabla_div(f): def curl(f): - "UFL operator: Take the curl of *f*." + """Take the curl of f.""" f = as_ufl(f) return Curl(f) @@ -403,7 +404,7 @@ def curl(f): # --- DG operators --- def jump(v, n=None): - "UFL operator: Take the jump of *v* across a facet." + """Take the jump of v across a facet.""" v = as_ufl(v) is_constant = len(extract_domains(v)) > 0 if is_constant: @@ -427,26 +428,28 @@ def jump(v, n=None): def avg(v): - "UFL operator: Take the average of *v* across a facet." + """Take the average of v across a facet.""" v = as_ufl(v) return 0.5 * (v('+') + v('-')) def cell_avg(f): - "UFL operator: Take the average of *v* over a cell." + """Take the average of v over a cell.""" return CellAvg(f) def facet_avg(f): - "UFL operator: Take the average of *v* over a facet." + """Take the average of v over a facet.""" return FacetAvg(f) # --- Other operators --- def variable(e): - """UFL operator: Define a variable representing the given expression, see also - ``diff()``.""" + """Define a variable representing the given expression. + + See also diff(). + """ e = as_ufl(e) return Variable(e) @@ -454,80 +457,77 @@ def variable(e): # --- Conditional expressions --- def conditional(condition, true_value, false_value): - """UFL operator: A conditional expression, taking the value of *true_value* - when *condition* evaluates to ``true`` and *false_value* otherwise.""" + """A conditional expression. + + This takes the value of true_value + when condition evaluates to true and false_value otherwise. + """ return Conditional(condition, true_value, false_value) def eq(left, right): - """UFL operator: A boolean expression (left == right) for use with - ``conditional``.""" + """A boolean expression (left == right) for use with conditional.""" return EQ(left, right) def ne(left, right): - """UFL operator: A boolean expression (left != right) for use with - ``conditional``.""" + """A boolean expression (left != right) for use with conditional.""" return NE(left, right) def le(left, right): - """UFL operator: A boolean expression (left <= right) for use with - ``conditional``.""" + """A boolean expression (left <= right) for use with conditional.""" return as_ufl(left) <= as_ufl(right) def ge(left, right): - """UFL operator: A boolean expression (left >= right) for use with - ``conditional``.""" + """A boolean expression (left >= right) for use with conditional.""" return as_ufl(left) >= as_ufl(right) def lt(left, right): - """UFL operator: A boolean expression (left < right) for use with - ``conditional``.""" + """A boolean expression (left < right) for use with conditional.""" return as_ufl(left) < as_ufl(right) def gt(left, right): - """UFL operator: A boolean expression (left > right) for use with - ``conditional``.""" + """A boolean expression (left > right) for use with conditional.""" return as_ufl(left) > as_ufl(right) def And(left, right): - """UFL operator: A boolean expression (left and right) for use with - ``conditional``.""" + """A boolean expression (left and right) for use with conditional.""" return AndCondition(left, right) def Or(left, right): - """UFL operator: A boolean expression (left or right) for use with - ``conditional``.""" + """A boolean expression (left or right) for use with conditional.""" return OrCondition(left, right) def Not(condition): - """UFL operator: A boolean expression (not condition) for use with - ``conditional``.""" + """A boolean expression (not condition) for use with conditional.""" return NotCondition(condition) def sign(x): - "UFL operator: Take the sign (+1 or -1) of *x*." + """Return the sign of x. + + This returns +1 if x is positive, -1 if x is negative, and 0 if x is 0. + """ # TODO: Add a Sign type for this? return conditional(eq(x, 0), 0, conditional(lt(x, 0), -1, +1)) def max_value(x, y): - "UFL operator: Take the maximum of *x* and *y*." + """Take the maximum of x and y.""" x = as_ufl(x) y = as_ufl(y) return MaxValue(x, y) def min_value(x, y): - "UFL operator: Take the minimum of *x* and *y*." + """Take the minimum of x and y.""" x = as_ufl(x) y = as_ufl(y) return MinValue(x, y) @@ -536,6 +536,7 @@ def min_value(x, y): # --- Math functions --- def _mathfunction(f, cls): + """A mat function.""" f = as_ufl(f) r = cls(f) if isinstance(r, (RealValue, Zero, int, float)): @@ -546,67 +547,67 @@ def _mathfunction(f, cls): def sqrt(f): - "UFL operator: Take the square root of *f*." + """Take the square root of f.""" return _mathfunction(f, Sqrt) def exp(f): - "UFL operator: Take the exponential of *f*." + """Take the exponential of f.""" return _mathfunction(f, Exp) def ln(f): - "UFL operator: Take the natural logarithm of *f*." + """Take the natural logarithm of f.""" return _mathfunction(f, Ln) def cos(f): - "UFL operator: Take the cosine of *f*." + """Take the cosine of f.""" return _mathfunction(f, Cos) def sin(f): - "UFL operator: Take the sine of *f*." + """Take the sine of f.""" return _mathfunction(f, Sin) def tan(f): - "UFL operator: Take the tangent of *f*." + """Take the tangent of f.""" return _mathfunction(f, Tan) def cosh(f): - "UFL operator: Take the hyperbolic cosine of *f*." + """Take the hyperbolic cosine of f.""" return _mathfunction(f, Cosh) def sinh(f): - "UFL operator: Take the hyperbolic sine of *f*." + """Take the hyperbolic sine of f.""" return _mathfunction(f, Sinh) def tanh(f): - "UFL operator: Take the hyperbolic tangent of *f*." + """Take the hyperbolic tangent of f.""" return _mathfunction(f, Tanh) def acos(f): - "UFL operator: Take the inverse cosine of *f*." + """Take the inverse cosine of f.""" return _mathfunction(f, Acos) def asin(f): - "UFL operator: Take the inverse sine of *f*." + """Take the inverse sine of f.""" return _mathfunction(f, Asin) def atan(f): - "UFL operator: Take the inverse tangent of *f*." + """Take the inverse tangent of f.""" return _mathfunction(f, Atan) def atan2(f1, f2): - "UFL operator: Take the inverse tangent with two the arguments *f1* and *f2*." + """Take the inverse tangent with two the arguments f1 and f2.""" f1 = as_ufl(f1) f2 = as_ufl(f2) if isinstance(f1, (ComplexValue, complex)) or isinstance(f2, (ComplexValue, complex)): @@ -620,33 +621,33 @@ def atan2(f1, f2): def erf(f): - "UFL operator: Take the error function of *f*." + """Take the error function of f.""" return _mathfunction(f, Erf) def bessel_J(nu, f): - """UFL operator: cylindrical Bessel function of the first kind.""" + """Cylindrical Bessel function of the first kind.""" nu = as_ufl(nu) f = as_ufl(f) return BesselJ(nu, f) def bessel_Y(nu, f): - """UFL operator: cylindrical Bessel function of the second kind.""" + """Cylindrical Bessel function of the second kind.""" nu = as_ufl(nu) f = as_ufl(f) return BesselY(nu, f) def bessel_I(nu, f): - """UFL operator: regular modified cylindrical Bessel function.""" + """Regular modified cylindrical Bessel function.""" nu = as_ufl(nu) f = as_ufl(f) return BesselI(nu, f) def bessel_K(nu, f): - """UFL operator: irregular modified cylindrical Bessel function.""" + """Irregular modified cylindrical Bessel function.""" nu = as_ufl(nu) f = as_ufl(f) return BesselK(nu, f) @@ -655,15 +656,14 @@ def bessel_K(nu, f): # --- Special function for exterior_derivative def exterior_derivative(f): - """UFL operator: Take the exterior derivative of *f*. + """Take the exterior derivative of f. The exterior derivative uses the element Sobolev space to - determine whether ``id``, ``grad``, ``curl`` or ``div`` should be used. + determine whether id, grad, curl or div should be used. - Note that this uses the ``grad`` and ``div`` operators, - as opposed to ``nabla_grad`` and ``nabla_div``. + Note that this uses the grad and div operators, + as opposed to nabla_grad and nabla_div. """ - # Extract the element from the input f if isinstance(f, Indexed): expression, indices = f.ufl_operands diff --git a/ufl/permutation.py b/ufl/permutation.py index 7675e3065..8a71a0593 100644 --- a/ufl/permutation.py +++ b/ufl/permutation.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -"""This module provides utility functions for computing permutations -and generating index lists.""" - +"""This module provides utility functions for computing permutations and generating index lists.""" # Copyright (C) 2008-2016 Anders Logg and Kent-Andre Mardal # # This file is part of UFL (https://www.fenicsproject.org) @@ -12,7 +9,7 @@ def compute_indices(shape): - "Compute all index combinations for given shape" + """Compute all index combinations for given shape.""" if len(shape) == 0: return ((),) sub_indices = compute_indices(shape[1:]) @@ -24,13 +21,13 @@ def compute_indices(shape): def build_component_numbering(shape, symmetry): - """Build a numbering of components within the given value shape, - taking into consideration a symmetry mapping which leaves the + """Build a numbering of components within the given value shape. + + This takes into consideration a symmetry mapping which leaves the mapping noncontiguous. Returns a dict { component -> numbering } and an ordered list of components [ numbering -> component ]. The dict contains all components while the list only contains the ones not mapped by the symmetry mapping. - """ vi2si, si2vi = {}, [] indices = compute_indices(shape) diff --git a/ufl/precedence.py b/ufl/precedence.py index 0813f0045..2c3b705a1 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Precedence handling." +"""Precedence handling.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -13,6 +12,7 @@ # FIXME: This code is crap... def parstr(child, parent, pre="(", post=")", format=str): + """Parstr.""" # Execute when needed instead of on import, which leads to all # kinds of circular trouble. Fixing this could be an optimization # of str(expr) though. @@ -40,6 +40,7 @@ def parstr(child, parent, pre="(", post=")", format=str): def build_precedence_list(): + """Build precedence list.""" from ufl.classes import (Operator, Terminal, Sum, IndexSum, Product, Division, Power, MathFunction, BesselFunction, Abs, Indexed) @@ -70,6 +71,7 @@ def build_precedence_list(): def build_precedence_mapping(precedence_list): """Given a precedence list, build a dict with class->int mappings. + Utility function used by some external code. """ from ufl.classes import Expr, all_ufl_classes, abstract_classes @@ -96,7 +98,7 @@ def build_precedence_mapping(precedence_list): def assign_precedences(precedence_list): - "Given a precedence list, assign ints to class._precedence." + """Given a precedence list, assign ints to class._precedence.""" pm, missing = build_precedence_mapping(precedence_list) for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p diff --git a/ufl/protocols.py b/ufl/protocols.py index 5cc6106d9..df8041473 100644 --- a/ufl/protocols.py +++ b/ufl/protocols.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +"""Protocols.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 0c2f70d0a..c1d13bcf1 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Representation of the reference value of a function." - +"""Representation of the reference value of a function.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -17,21 +15,25 @@ is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceValue(Operator): - "Representation of the reference cell value of a form argument." + """Representation of the reference cell value of a form argument.""" + __slots__ = () def __init__(self, f): + """Initialise.""" if not isinstance(f, FormArgument): raise ValueError("Can only take reference value of form arguments.") Operator.__init__(self, (f,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_element().reference_value_shape() def evaluate(self, x, mapping, component, index_values, derivatives=()): - "Get child from mapping and return the component asked for." + """Get child from mapping and return the component asked for.""" raise NotImplementedError() def __str__(self): + """Format as a string.""" return f"reference_value({self.ufl_operands[0]})" diff --git a/ufl/restriction.py b/ufl/restriction.py index 06ab116cf..77b03c71a 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Restriction operations.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -20,31 +18,41 @@ inherit_indices_from_operand=0, is_restriction=True) class Restricted(Operator): + """Restriction.""" + __slots__ = () # TODO: Add __new__ operator here, e.g. restricted(literal) == literal def __init__(self, f): + """Initialise.""" Operator.__init__(self, (f,)) def side(self): + """Get the side.""" return self._side def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): + """Format as a string.""" return f"{parstr(self.ufl_operands[0], self)}({self._side})" @ufl_type(is_terminal_modifier=True) class PositiveRestricted(Restricted): + """Positive restriction.""" + __slots__ = () _side = "+" @ufl_type(is_terminal_modifier=True) class NegativeRestricted(Restricted): + """Negative restriction.""" + __slots__ = () _side = "-" diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index f6a624d14..b697a0685 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- -"""This module defines a symbolic heirarchy of Sobolev spaces to enable -symbolic reasoning about the spaces in which finite elements lie.""" +"""Sobolev spaces. +This module defines a symbolic heirarchy of Sobolev spaces to enable +symbolic reasoning about the spaces in which finite elements lie. +""" # Copyright (C) 2014 Imperial College London and others # # This file is part of UFL (https://www.fenicsproject.org) @@ -20,18 +21,20 @@ @total_ordering class SobolevSpace(object): - """Symbolic representation of a Sobolev space. This implements a - subset of the methods of a Python set so that finite elements and + """Symbolic representation of a Sobolev space. + + This implements a subset of the methods of a Python set so that finite elements and other Sobolev spaces can be tested for inclusion. """ def __init__(self, name, parents=None): """Instantiate a SobolevSpace object. - :param name: The name of this space, - :param parents: A set of Sobolev spaces of which this - space is a subspace.""" - + Args: + name: The name of this space, + parents: A set of Sobolev spaces of which this + space is a subspace. + """ self.name = name p = frozenset(parents or []) # Ensure that the inclusion operations are transitive. @@ -51,18 +54,23 @@ def __init__(self, name, parents=None): }[self.name] def __str__(self): + """Format as a string.""" return self.name def __repr__(self): + """Representation.""" return f"SobolevSpace({self.name!r}, {list(self.parents)!r})" def __eq__(self, other): + """Check equality.""" return isinstance(other, SobolevSpace) and self.name == other.name def __ne__(self, other): + """Not equal.""" return not self == other def __hash__(self): + """Hash.""" return hash(("SobolevSpace", self.name)) def __getitem__(self, spatial_index): @@ -70,9 +78,7 @@ def __getitem__(self, spatial_index): return self def __contains__(self, other): - """Implement `fe in s` where `fe` is a - :class:`~finiteelement.FiniteElement` and `s` is a - :class:`SobolevSpace`""" + """Implement `fe in s` where `fe` is a FiniteElement and `s` is a SobolevSpace.""" if isinstance(other, SobolevSpace): raise TypeError("Unable to test for inclusion of a " "SobolevSpace in another SobolevSpace. " @@ -80,8 +86,7 @@ def __contains__(self, other): return other.sobolev_space() == self or self in other.sobolev_space().parents def __lt__(self, other): - """In common with intrinsic Python sets, < indicates "is a proper - subset of".""" + """In common with intrinsic Python sets, < indicates "is a proper subset of".""" return other in self.parents def __call__(self, element): @@ -99,17 +104,19 @@ def __call__(self, element): @total_ordering class DirectionalSobolevSpace(SobolevSpace): - """Symbolic representation of a Sobolev space with varying smoothness - in differerent spatial directions. + """Directional Sobolev space. + Symbolic representation of a Sobolev space with varying smoothness + in differerent spatial directions. """ def __init__(self, orders): """Instantiate a DirectionalSobolevSpace object. - :arg orders: an iterable of orders of weak derivatives, where - the position denotes in what spatial variable the - smoothness requirement is enforced. + Args: + orders: an iterable of orders of weak derivatives, where + the position denotes in what spatial variable the + smoothness requirement is enforced. """ assert all( isinstance(x, int) or isinf(x) @@ -121,18 +128,14 @@ def __init__(self, orders): self._spatial_indices = range(len(self._orders)) def __getitem__(self, spatial_index): - """Returns the Sobolev space associated with a particular - spatial coordinate. - """ + """Returns the Sobolev space associated with a particular spatial coordinate.""" if spatial_index not in range(len(self._orders)): raise IndexError("Spatial index out of range.") spaces = {0: L2, 1: H1, 2: H2, inf: HInf} return spaces[self._orders[spatial_index]] def __contains__(self, other): - """Implement `fe in s` where `fe` is a - :class:`~finiteelement.FiniteElement` and `s` is a - :class:`DirectionalSobolevSpace`""" + """Implement `fe in s` where `fe` is a FiniteElement and `s` is a DirectionalSobolevSpace.""" if isinstance(other, SobolevSpace): raise TypeError("Unable to test for inclusion of a " "SobolevSpace in another SobolevSpace. " @@ -142,13 +145,13 @@ def __contains__(self, other): for i in self._spatial_indices)) def __eq__(self, other): + """Check equality.""" if isinstance(other, DirectionalSobolevSpace): return self._orders == other._orders return all(self[i] == other for i in self._spatial_indices) def __lt__(self, other): - """In common with intrinsic Python sets, < indicates "is a proper - subset of.""" + """In common with intrinsic Python sets, < indicates "is a proper subset of.""" if isinstance(other, DirectionalSobolevSpace): if self._spatial_indices != other._spatial_indices: return False @@ -165,6 +168,7 @@ def __lt__(self, other): self._orders[i] > other._order for i in self._spatial_indices) def __str__(self): + """Format as a string.""" return f"{self.name}({', '.join(map(str, self._orders))})" diff --git a/ufl/sorting.py b/ufl/sorting.py index 2b74d560e..445fa0739 100644 --- a/ufl/sorting.py +++ b/ufl/sorting.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- -"""This module contains a sorting rule for expr objects that -is more robust w.r.t. argument numbering than using repr.""" +"""Sorting. +This module contains a sorting rule for expr objects that +is more robust w.r.t. argument numbering than using repr. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -11,7 +12,6 @@ # Modified by Anders Logg, 2009-2010. # Modified by Johan Hake, 2010. - from functools import cmp_to_key from ufl.core.expr import Expr @@ -22,6 +22,7 @@ def _cmp_multi_index(a, b): + """Cmp multi index.""" # Careful not to depend on Index.count() here! # This is placed first because it is most frequent. # Make decision based on the first index pair possible @@ -54,6 +55,7 @@ def _cmp_multi_index(a, b): def _cmp_label(a, b): + """Cmp label.""" # Don't compare counts! Causes circular problems when renumbering to get a canonical form. # Therefore, even though a and b are not equal in general (__eq__ won't be True), # but for this sorting they are considered equal and we return 0. @@ -61,6 +63,7 @@ def _cmp_label(a, b): def _cmp_coefficient(a, b): + """Cmp coefficient.""" # It's ok to compare relative counts for Coefficients, # since their ordering is a property of the form x, y = a._count, b._count @@ -73,6 +76,7 @@ def _cmp_coefficient(a, b): def _cmp_argument(a, b): + """Cmp argument.""" # It's ok to compare relative number and part for Arguments, # since their ordering is a property of the form x = (a._number, a._part) @@ -86,6 +90,7 @@ def _cmp_argument(a, b): def _cmp_terminal_by_repr(a, b): + """Cmp terminal by repr.""" # The cost of repr on a terminal is fairly small, and bounded x = repr(a) y = repr(b) @@ -101,8 +106,7 @@ def _cmp_terminal_by_repr(a, b): def cmp_expr(a, b): - "Replacement for cmp(a, b), removed in Python 3, for Expr objects." - + """Replacement for cmp(a, b), removed in Python 3, for Expr objects.""" # Modelled after pre_traversal to avoid recursion: left = [(a, b)] while left: @@ -156,11 +160,12 @@ def cmp_expr(a, b): def sorted_expr(sequence): - "Return a canonically sorted list of Expr objects in sequence." + """Return a canonically sorted list of Expr objects in sequence.""" return sorted(sequence, key=cmp_to_key(cmp_expr)) def sorted_expr_sum(seq): + """Sorted expr sum.""" seq2 = sorted(seq, key=cmp_to_key(cmp_expr)) s = seq2[0] for e in seq2[1:]: diff --git a/ufl/split_functions.py b/ufl/split_functions.py index b0579985a..f285f8ea5 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -"Algorithm for splitting a Coefficient or Argument into subfunctions." - +"""Algorithm for splitting a Coefficient or Argument into subfunctions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -18,9 +16,11 @@ def split(v): - """UFL operator: If v is a Coefficient or Argument in a mixed space, returns - a tuple with the function components corresponding to the subelements.""" + """Split a coefficient or argument. + If v is a Coefficient or Argument in a mixed space, returns + a tuple with the function components corresponding to the subelements. + """ # Default range is all of v begin = 0 end = None diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index e1f40d8e5..a0291d060 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Compound tensor algebra operations.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -45,9 +43,12 @@ @ufl_type(is_abstract=True) class CompoundTensorOperator(Operator): + """Compount tensor operator.""" + __slots__ = () def __init__(self, operands): + """Initialise.""" Operator.__init__(self, operands) # TODO: Use this and make Sum handle scalars only? @@ -85,33 +86,42 @@ def __init__(self, operands): @ufl_type(is_shaping=True, num_ops=1, inherit_indices_from_operand=0) class Transposed(CompoundTensorOperator): + """Transposed tensor.""" + __slots__ = () def __new__(cls, A): + """Create new Transposed.""" if isinstance(A, Zero): a, b = A.ufl_shape return Zero((b, a), A.ufl_free_indices, A.ufl_index_dimensions) return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) if len(A.ufl_shape) != 2: raise ValueError("Transposed is only defined for rank 2 tensors.") @property def ufl_shape(self): + """Get the UFL shape.""" s = self.ufl_operands[0].ufl_shape return (s[1], s[0]) def __str__(self): + """Format as a string.""" return "%s^T" % parstr(self.ufl_operands[0], self) @ufl_type(num_ops=2) class Outer(CompoundTensorOperator): + """Outer.""" + __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): + """Create new Outer.""" ash, bsh = a.ufl_shape, b.ufl_shape if isinstance(a, Zero) or isinstance(b, Zero): fi, fid = merge_nonoverlapping_indices(a, b) @@ -121,6 +131,7 @@ def __new__(cls, a, b): return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): + """Initialise.""" CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi @@ -128,18 +139,23 @@ def __init__(self, a, b): @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape + self.ufl_operands[1].ufl_shape def __str__(self): + """Format as a string.""" return "%s (X) %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(num_ops=2) class Inner(CompoundTensorOperator): + """Inner.""" + __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): + """Create new Inner.""" # Checks ash, bsh = a.ufl_shape, b.ufl_shape if ash != bsh: @@ -160,7 +176,7 @@ def __new__(cls, a, b): return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): - + """Initialise.""" CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) @@ -170,15 +186,19 @@ def __init__(self, a, b): ufl_shape = () def __str__(self): + """Format as a string.""" return "%s : %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(num_ops=2) class Dot(CompoundTensorOperator): + """Dot.""" + __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): + """Create new Dot.""" ash = a.ufl_shape bsh = b.ufl_shape ar, br = len(ash), len(bsh) @@ -203,6 +223,7 @@ def __new__(cls, a, b): return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): + """Initialise.""" CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi @@ -210,18 +231,23 @@ def __init__(self, a, b): @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape[:-1] + self.ufl_operands[1].ufl_shape[1:] def __str__(self): + """Format as a string.""" return "%s . %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(is_index_free=True, num_ops=1) class Perp(CompoundTensorOperator): + """Perp.""" + __slots__ = () def __new__(cls, A): + """Create new Perp.""" sh = A.ufl_shape # Checks @@ -237,19 +263,24 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) ufl_shape = (2,) def __str__(self): + """Format as a string.""" return "perp(%s)" % self.ufl_operands[0] @ufl_type(num_ops=2) class Cross(CompoundTensorOperator): + """Cross.""" + __slots__ = ("ufl_free_indices", "ufl_index_dimensions") def __new__(cls, a, b): + """Create new Cross.""" ash = a.ufl_shape bsh = b.ufl_shape @@ -267,6 +298,7 @@ def __new__(cls, a, b): return CompoundTensorOperator.__new__(cls) def __init__(self, a, b): + """Initialise.""" CompoundTensorOperator.__init__(self, (a, b)) fi, fid = merge_nonoverlapping_indices(a, b) self.ufl_free_indices = fi @@ -275,15 +307,19 @@ def __init__(self, a, b): ufl_shape = (3,) def __str__(self): + """Format as a string.""" return "%s x %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(num_ops=1, inherit_indices_from_operand=0) class Trace(CompoundTensorOperator): + """Trace.""" + __slots__ = () def __new__(cls, A): + """Create new Trace.""" # Checks if len(A.ufl_shape) != 2: raise ValueError("Trace of tensor with rank != 2 is undefined.") @@ -295,19 +331,24 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) ufl_shape = () def __str__(self): + """Format as a string.""" return "tr(%s)" % self.ufl_operands[0] @ufl_type(is_scalar=True, num_ops=1) class Determinant(CompoundTensorOperator): + """Determinant.""" + __slots__ = () def __new__(cls, A): + """Create new Determinant.""" sh = A.ufl_shape r = len(sh) Afi = A.ufl_free_indices @@ -329,9 +370,11 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) def __str__(self): + """Format as a string.""" return "det(%s)" % self.ufl_operands[0] @@ -339,9 +382,12 @@ def __str__(self): # Cofactor? @ufl_type(is_index_free=True, num_ops=1) class Inverse(CompoundTensorOperator): + """Inverse.""" + __slots__ = () def __new__(cls, A): + """Create new Inverse.""" sh = A.ufl_shape r = len(sh) @@ -364,21 +410,27 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape def __str__(self): + """Format as a string.""" return "%s^-1" % parstr(self.ufl_operands[0], self) @ufl_type(is_index_free=True, num_ops=1) class Cofactor(CompoundTensorOperator): + """Cofactor.""" + __slots__ = () def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) # Checks @@ -394,17 +446,22 @@ def __init__(self, A): @property def ufl_shape(self): + """Get the UFL shape.""" return self.ufl_operands[0].ufl_shape def __str__(self): + """Format as a string.""" return "cofactor(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Deviatoric(CompoundTensorOperator): + """Deviatoric.""" + __slots__ = () def __new__(cls, A): + """Create new Deviatoric.""" sh = A.ufl_shape # Checks @@ -422,17 +479,22 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) def __str__(self): + """Format as a string.""" return "dev(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Skew(CompoundTensorOperator): + """Skew.""" + __slots__ = () def __new__(cls, A): + """Create new Skew.""" sh = A.ufl_shape Afi = A.ufl_free_indices @@ -451,17 +513,22 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) def __str__(self): + """Format as a string.""" return "skew(%s)" % self.ufl_operands[0] @ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Sym(CompoundTensorOperator): + """Sym.""" + __slots__ = () def __new__(cls, A): + """Create new Sym.""" sh = A.ufl_shape Afi = A.ufl_free_indices @@ -480,7 +547,9 @@ def __new__(cls, A): return CompoundTensorOperator.__new__(cls) def __init__(self, A): + """Initialise.""" CompoundTensorOperator.__init__(self, (A,)) def __str__(self): + """Format as a string.""" return f"sym({self.ufl_operands[0]})" diff --git a/ufl/tensors.py b/ufl/tensors.py index 3f3ac9249..d89045ce9 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Classes used to group scalar expressions into expressions with rank > 0.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -22,10 +20,12 @@ @ufl_type(is_shaping=True, num_ops="varying", inherit_indices_from_operand=0) class ListTensor(Operator): - """UFL operator type: Wraps a list of expressions into a tensor valued expression of one higher rank.""" + """Wraps a list of expressions into a tensor valued expression of one higher rank.""" + __slots__ = () def __new__(cls, *expressions): + """Create a new ListTensor.""" # All lists and tuples should already be unwrapped in # as_tensor if any(not isinstance(e, Expr) for e in expressions): @@ -53,6 +53,7 @@ def __new__(cls, *expressions): return Operator.__new__(cls) def __init__(self, *expressions): + """Initialise.""" Operator.__init__(self, expressions) # Checks @@ -62,9 +63,11 @@ def __init__(self, *expressions): @property def ufl_shape(self): + """Get the UFL shape.""" return (len(self.ufl_operands),) + self.ufl_operands[0].ufl_shape def evaluate(self, x, mapping, component, index_values, derivatives=()): + """Evaluate.""" if len(component) != len(self.ufl_shape): raise ValueError( "Can only evaluate scalars, expecting a component " @@ -77,6 +80,7 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): return a.evaluate(x, mapping, component, index_values) def __getitem__(self, key): + """Get an item.""" origkey = key if isinstance(key, MultiIndex): @@ -91,6 +95,7 @@ def __getitem__(self, key): return Expr.__getitem__(self, origkey) def __str__(self): + """Format as a string.""" def substring(expressions, indent): ind = " " * indent if any(isinstance(e, ListTensor) for e in expressions): @@ -110,11 +115,12 @@ def substring(expressions, indent): @ufl_type(is_shaping=True, num_ops="varying") class ComponentTensor(Operator): - """UFL operator type: Maps the free indices of a scalar valued expression to tensor axes.""" + """Maps the free indices of a scalar valued expression to tensor axes.""" + __slots__ = ("ufl_shape", "ufl_free_indices", "ufl_index_dimensions") def __new__(cls, expression, indices): - + """Create a new ComponentTensor.""" # Simplify if isinstance(expression, Zero): fi, fid, sh = remove_indices(expression.ufl_free_indices, @@ -126,6 +132,7 @@ def __new__(cls, expression, indices): return Operator.__new__(cls) def __init__(self, expression, indices): + """Initialise.""" if not isinstance(expression, Expr): raise ValueError("Expecting ufl expression.") if expression.ufl_shape != (): @@ -145,6 +152,7 @@ def __init__(self, expression, indices): self.ufl_shape = sh def _ufl_expr_reconstruct_(self, expressions, indices): + """Reconstruct.""" # Special case for simplification as_tensor(A[ii], ii) -> A if isinstance(expressions, Indexed): A, ii = expressions.ufl_operands @@ -153,9 +161,11 @@ def _ufl_expr_reconstruct_(self, expressions, indices): return Operator._ufl_expr_reconstruct_(self, expressions, indices) def indices(self): + """Get indices.""" return self.ufl_operands[1] def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" indices = self.ufl_operands[1] a = self.ufl_operands[0] @@ -174,12 +184,14 @@ def evaluate(self, x, mapping, component, index_values): return a def __str__(self): + """Format as a string.""" return "{ A | A_{%s} = %s }" % (self.ufl_operands[1], self.ufl_operands[0]) # --- User-level functions to wrap expressions in the correct way --- def numpy2nestedlists(arr): + """Convert Numpy array to a nested list.""" from numpy import ndarray if not isinstance(arr, ndarray): return arr @@ -187,6 +199,7 @@ def numpy2nestedlists(arr): def _as_list_tensor(expressions): + """Convert to a list tensor.""" if isinstance(expressions, (list, tuple)): expressions = [_as_list_tensor(e) for e in expressions] return ListTensor(*expressions) @@ -195,6 +208,7 @@ def _as_list_tensor(expressions): def from_numpy_to_lists(expressions): + """Convert Numpy array to lists.""" try: import numpy if isinstance(expressions, numpy.ndarray): @@ -209,7 +223,7 @@ def from_numpy_to_lists(expressions): def as_tensor(expressions, indices=None): - """UFL operator: Make a tensor valued expression. + """Make a tensor valued expression. This works in two different ways, by using indices or lists. @@ -265,7 +279,7 @@ def as_tensor(expressions, indices=None): def as_matrix(expressions, indices=None): - "UFL operator: As *as_tensor()*, but limited to rank 2 tensors." + """As *as_tensor()*, but limited to rank 2 tensors.""" if indices is None: # Allow as_matrix(as_matrix(A)) in user code if isinstance(expressions, Expr): @@ -290,7 +304,7 @@ def as_matrix(expressions, indices=None): def as_vector(expressions, index=None): - "UFL operator: As ``as_tensor()``, but limited to rank 1 tensors." + """As ``as_tensor()``, but limited to rank 1 tensors.""" if index is None: # Allow as_vector(as_vector(v)) in user code if isinstance(expressions, Expr): @@ -314,12 +328,13 @@ def as_vector(expressions, index=None): def as_scalar(expression): - """Given a scalar or tensor valued expression A, returns either of the tuples:: + """As scalar. + Given a scalar or tensor valued expression A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = (A[indices], indices) - - such that a is always a scalar valued expression.""" + such that a is always a scalar valued expression. + """ ii = indices(len(expression.ufl_shape)) if ii: expression = expression[ii] @@ -327,12 +342,13 @@ def as_scalar(expression): def as_scalars(*expressions): - """Given multiple scalar or tensor valued expressions A, returns either of the tuples:: + """As scalars. + Given multiple scalar or tensor valued expressions A, returns either of the tuples:: (a,b) = (A, ()) (a,b) = ([A[0][indices], ..., A[-1][indices]], indices) - - such that a is always a list of scalar valued expressions.""" + such that a is always a list of scalar valued expressions. + """ ii = indices(len(expressions[0].ufl_shape)) if ii: expressions = [expression[ii] for expression in expressions] @@ -340,36 +356,37 @@ def as_scalars(*expressions): def unit_list(i, n): + """Create a list of zeros where the ith entry is 1.""" return [(1 if i == j else 0) for j in range(n)] def unit_list2(i, j, n): + """Creage a two dimensional list of zeros where the (i,j)th entry is 1.""" return [[(1 if (i == i0 and j == j0) else 0) for j0 in range(n)] for i0 in range(n)] def unit_vector(i, d): - "UFL value: A constant unit vector in direction *i* with dimension *d*." + """A constant unit vector in direction *i* with dimension *d*.""" return as_vector(unit_list(i, d)) def unit_vectors(d): - """UFL value: A tuple of constant unit vectors in all directions with - dimension *d*.""" + """A tuple of constant unit vectors in all directions with dimension *d*.""" return tuple(unit_vector(i, d) for i in range(d)) def unit_matrix(i, j, d): - "UFL value: A constant unit matrix in direction *i*,*j* with dimension *d*." + """A constant unit matrix in direction *i*,*j* with dimension *d*.""" return as_matrix(unit_list2(i, j, d)) def unit_matrices(d): - """UFL value: A tuple of constant unit matrices in all directions with - dimension *d*.""" + """A tuple of constant unit matrices in all directions with dimension *d*.""" return tuple(unit_matrix(i, j, d) for i in range(d) for j in range(d)) def unit_indexed_tensor(shape, component): + """Unit indexed tensor.""" from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here r = len(shape) @@ -390,6 +407,7 @@ def unit_indexed_tensor(shape, component): def unwrap_list_tensor(lt): + """Unwrap a list tensor.""" components = [] sh = lt.ufl_shape subs = lt.ufl_operands diff --git a/ufl/utils/__init__.py b/ufl/utils/__init__.py index e69de29bb..dc51750ef 100644 --- a/ufl/utils/__init__.py +++ b/ufl/utils/__init__.py @@ -0,0 +1 @@ +"""Utilities.""" diff --git a/ufl/utils/counted.py b/ufl/utils/counted.py index f04bc2aca..9aed33b7f 100644 --- a/ufl/utils/counted.py +++ b/ufl/utils/counted.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Mixin class for types with a global unique counter attached to each object." +"""Mixin class for types with a global unique counter attached to each object.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -22,10 +21,11 @@ class Counted: def __init__(self, count=None, counted_class=None): """Initialize the Counted instance. - :arg count: The object count, if ``None`` defaults to the next value - according to the global counter (per type). - :arg counted_class: Class to attach the global counter too. If ``None`` - then ``type(self)`` will be used. + Args: + count: The object count, if ``None`` defaults to the next value + according to the global counter (per type). + counted_class: Class to attach the global counter too. If ``None`` + then ``type(self)`` will be used. """ # create a new counter for each subclass @@ -37,4 +37,5 @@ def __init__(self, count=None, counted_class=None): self._counted_class = counted_class def count(self): + """Get count.""" return self._count diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index 24dd5ce10..d35164bea 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- """Various string formatting utilities.""" - # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -77,10 +75,12 @@ def estr(elements): def _indent_string(n): + """Return indentation string.""" return " " * n def _tree_format_expression(expression, indentation, parentheses): + """Tree format expression.""" ind = _indent_string(indentation) if expression._ufl_is_terminal_: s = "%s%s" % (ind, repr(expression)) @@ -96,6 +96,7 @@ def _tree_format_expression(expression, indentation, parentheses): def tree_format(expression, indentation=0, parentheses=True): + """Tree format.""" from ufl.core.expr import Expr from ufl.form import Form from ufl.integral import Integral diff --git a/ufl/utils/indexflattening.py b/ufl/utils/indexflattening.py index a71758b72..0e5aea412 100644 --- a/ufl/utils/indexflattening.py +++ b/ufl/utils/indexflattening.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """This module contains a collection of utilities for mapping between multiindices and a flattened index space.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs diff --git a/ufl/utils/sequences.py b/ufl/utils/sequences.py index 2f0afc339..20ebe1dc9 100644 --- a/ufl/utils/sequences.py +++ b/ufl/utils/sequences.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Various sequence manipulation utilities.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index fdd2c104d..cb177737d 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Utilites for sorting." +"""Utilites for sorting.""" # Copyright (C) 2008-2016 Johan Hake # @@ -11,16 +10,12 @@ def topological_sorting(nodes, edges): - """ - Return a topologically sorted list of the nodes - - Implemented algorithm from Wikipedia :P + """Return a topologically sorted list of the nodes. - + Implemented algorithm from Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting). No error for cyclic edges... """ - L = [] S = nodes[:] for node in nodes: @@ -47,12 +42,12 @@ def topological_sorting(nodes, edges): def sorted_by_count(seq): - "Sort a sequence by the item.count()." + """Sort a sequence by the item.count().""" return sorted(seq, key=lambda x: x.count()) def sorted_by_key(mapping): - "Sort dict items by key, allowing different key types." + """Sort dict items by key, allowing different key types.""" # Python3 doesn't allow comparing builtins of different type, # therefore the typename trick here def _key(x): diff --git a/ufl/utils/stacks.py b/ufl/utils/stacks.py index 736035856..a19a713de 100644 --- a/ufl/utils/stacks.py +++ b/ufl/utils/stacks.py @@ -1,5 +1,4 @@ -# -*- coding: utf-8 -*- -"Various utility data structures." +"""Various utility data structures.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -12,12 +11,15 @@ class Stack(list): """A stack datastructure.""" def __init__(self, *args): + """Initialise.""" list.__init__(self, *args) def push(self, v): + """Push.""" list.append(self, v) def peek(self): + """Peek.""" return self[-1] @@ -25,6 +27,7 @@ class StackDict(dict): """A dict that can be changed incrementally with 'd.push(k,v)' and have changes rolled back with 'k,v = d.pop()'.""" def __init__(self, *args, **kwargs): + """Initialise.""" dict.__init__(self, *args, **kwargs) self._l = [] diff --git a/ufl/variable.py b/ufl/variable.py index 935c2f8c8..ee8587119 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -"""Defines the Variable and Label classes, used to label -expressions as variables for differentiation.""" +"""Define the Variable and Label classes. +These are used to label expressions as variables for differentiation. +""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # # This file is part of UFL (https://www.fenicsproject.org) @@ -18,39 +18,49 @@ @ufl_type() class Label(Terminal, Counted): + """Label.""" + __slots__ = ("_count", "_counted_class") def __init__(self, count=None): + """Initialise.""" Terminal.__init__(self) Counted.__init__(self, count, Label) def __str__(self): + """Format as a string.""" return "Label(%d)" % self._count def __repr__(self): + """Representation.""" r = "Label(%d)" % self._count return r @property def ufl_shape(self): + """Get the UFL shape.""" raise ValueError("Label has no shape (it is not a tensor expression).") @property def ufl_free_indices(self): + """Get the UFL free indices.""" raise ValueError("Label has no free indices (it is not a tensor expression).") @property def ufl_index_dimensions(self): + """Get the UFL index dimensions.""" raise ValueError("Label has no free indices (it is not a tensor expression).") def is_cellwise_constant(self): + """Return true if the object is constant on each cell.""" return True def ufl_domains(self): - "Return tuple of domains related to this terminal object." + """Return tuple of domains related to this terminal object.""" return () def _ufl_signature_data_(self, renumbering): + """UFL signature data.""" if self not in renumbering: return ("Label", self._count) return ("Label", renumbering[self]) @@ -69,9 +79,11 @@ class Variable(Operator): f = exp(e**2) df = diff(f, e) """ + __slots__ = () def __init__(self, expression, label=None): + """Initalise.""" # Conversion expression = as_ufl(expression) if label is None: @@ -88,22 +100,28 @@ def __init__(self, expression, label=None): Operator.__init__(self, (expression, label)) def ufl_domains(self): + """Get the UFL domains.""" return self.ufl_operands[0].ufl_domains() def evaluate(self, x, mapping, component, index_values): + """Evaluate.""" a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) return a def expression(self): + """Get expression.""" return self.ufl_operands[0] def label(self): + """Get label.""" return self.ufl_operands[1] def __eq__(self, other): + """Check equality.""" return (isinstance(other, Variable) and self.ufl_operands[1] == other.ufl_operands[1] and # noqa: W504 self.ufl_operands[0] == other.ufl_operands[0]) def __str__(self): + """Format as a string.""" return "var%d(%s)" % (self.ufl_operands[1].count(), self.ufl_operands[0]) From 9fa8f5a7a1462d802166be61a4c270ae67375ebd Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Tue, 19 Sep 2023 15:14:07 +0100 Subject: [PATCH 056/136] Make tests pass flake8 checks (#209) * Flake8 on tests/test_a* (and a few others) * more flake * remove "from ufl import *" from tests * make all tests pass flake checks * Remove brackets around assert --- setup.cfg | 5 +- test/conftest.py | 6 +- test/mockobjects.py | 7 +- test/test_algorithms.py | 18 +- test/test_analyse_demos.py | 7 +- test/test_apply_algebra_lowering.py | 17 +- test/test_apply_function_pullbacks.py | 38 ++-- test/test_apply_restrictions.py | 8 +- test/test_arithmetic.py | 10 +- test/test_automatic_differentiation.py | 65 +++--- test/test_book_snippets.py | 149 +++++-------- test/test_cell.py | 4 +- test/test_change_to_local.py | 18 +- test/test_change_to_reference_frame.py | 176 +-------------- test/test_check_arities.py | 16 +- test/test_classcoverage.py | 59 ++--- test/test_complex.py | 30 +-- test/test_conditionals.py | 28 +-- test/test_degree_estimation.py | 11 +- test/test_derivative.py | 85 +++---- test/test_diff.py | 9 +- test/test_domains.py | 294 ++----------------------- test/test_duals.py | 25 +-- test/test_elements.py | 13 +- test/test_equals.py | 14 +- test/test_evaluate.py | 36 ++- test/test_expand_indices.py | 46 +--- test/test_external_operator.py | 41 ++-- test/test_ffcforms.py | 128 +++++------ test/test_form.py | 37 ++-- test/test_grad.py | 37 ++-- test/test_illegal.py | 6 +- test/test_indexing.py | 30 +-- test/test_indices.py | 125 +++++------ test/test_interpolate.py | 21 +- test/test_lhs_rhs.py | 33 ++- test/test_literals.py | 25 +-- test/test_measures.py | 24 +- test/test_mixed_function_space.py | 68 +++--- test/test_new_ad.py | 61 ++--- test/test_pickle.py | 87 ++++---- test/test_piecewise_checks.py | 15 +- test/test_reference_shapes.py | 7 +- test/test_scratch.py | 14 +- test/test_signature.py | 28 +-- test/test_simplify.py | 41 ++-- test/test_sobolevspace.py | 13 +- test/test_split.py | 10 +- test/test_str.py | 7 +- test/test_strip_forms.py | 8 +- test/test_tensoralgebra.py | 18 +- test/test_utilities.py | 15 +- 52 files changed, 657 insertions(+), 1436 deletions(-) diff --git a/setup.cfg b/setup.cfg index 55704a7bb..7bcb914d4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,7 +59,10 @@ ci = [flake8] max-line-length = 120 -exclude = doc/sphinx/source/conf.py,test +exclude = doc/sphinx/source/conf.py [pydocstyle] convention = google + +[isort] +line_length = 120 diff --git a/test/conftest.py b/test/conftest.py index 2b2a2fb8e..e2c610a6e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- +import os import pytest -import os - import ufl -from ufl import as_ufl, inner, dx +from ufl import as_ufl, dx, inner from ufl.algorithms import compute_form_data diff --git a/test/mockobjects.py b/test/mockobjects.py index 6af48f43b..4c68e0e21 100644 --- a/test/mockobjects.py +++ b/test/mockobjects.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -from ufl import * +from ufl import Measure, Mesh, triangle class MockMesh: @@ -15,7 +13,8 @@ def ufl_domain(self): return Mesh(triangle, ufl_id=self.ufl_id(), cargo=self) def ufl_measure(self, integral_type="dx", subdomain_id="everywhere", metadata=None, subdomain_data=None): - return Measure(integral_type, subdomain_id=subdomain_id, metadata=metadata, domain=self, subdomain_data=subdomain_data) + return Measure(integral_type, subdomain_id=subdomain_id, metadata=metadata, domain=self, + subdomain_data=subdomain_data) class MockMeshFunction: diff --git a/test/test_algorithms.py b/test/test_algorithms.py index c68bb15c7..c752cfed0 100755 --- a/test/test_algorithms.py +++ b/test/test_algorithms.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2008-03-12 -- 2009-01-28" @@ -7,16 +5,12 @@ # Modified by Garth N. Wells, 2009 import pytest -from pprint import * -from ufl import (FiniteElement, TestFunction, TrialFunction, Matrix, triangle, - div, grad, Argument, dx, adjoint, Coefficient, - FacetNormal, inner, dot, ds) -from ufl.algorithms import (extract_arguments, expand_derivatives, - expand_indices, extract_elements, - extract_unique_elements, extract_coefficients) -from ufl.corealg.traversal import (pre_traversal, post_traversal, - unique_pre_traversal, unique_post_traversal) +from ufl import (Argument, Coefficient, FacetNormal, FiniteElement, TestFunction, TrialFunction, adjoint, div, dot, ds, + dx, grad, inner, triangle) +from ufl.algorithms import (expand_derivatives, expand_indices, extract_arguments, extract_coefficients, + extract_elements, extract_unique_elements) +from ufl.corealg.traversal import post_traversal, pre_traversal, unique_post_traversal, unique_pre_traversal # TODO: add more tests, covering all utility algorithms @@ -64,7 +58,7 @@ def test_extract_coefficients_vs_fixture(coefficients, forms): def test_extract_elements_and_extract_unique_elements(forms): b = forms[2] integrals = b.integrals_by_type("cell") - integrand = integrals[0].integrand() + integrals[0].integrand() element1 = FiniteElement("CG", triangle, 1) element2 = FiniteElement("CG", triangle, 1) diff --git a/test/test_analyse_demos.py b/test/test_analyse_demos.py index 444aa926c..e9f41451d 100755 --- a/test/test_analyse_demos.py +++ b/test/test_analyse_demos.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2008-09-28 -- 2008-09-28" import os -import pytest -from ufl.algorithms import load_ufl_file, compute_form_data, validate_form from glob import glob +import pytest + +from ufl.algorithms import load_ufl_file, validate_form demodir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "demo")) diff --git a/test/test_apply_algebra_lowering.py b/test/test_apply_algebra_lowering.py index a66332313..97ad44b1c 100755 --- a/test/test_apply_algebra_lowering.py +++ b/test/test_apply_algebra_lowering.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- - import pytest -from ufl import * -from ufl.compound_expressions import * + +from ufl import Coefficient, FiniteElement, Index, TensorElement, as_tensor, interval, sqrt, tetrahedron, triangle from ufl.algorithms.renumbering import renumber_indices +from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr @pytest.fixture @@ -86,25 +85,25 @@ def test_inverse1(A1): def xtest_inverse2(A2): - expected = todo + expected = "TODO" assert inverse_expr(A2) == renumber_indices(expected) def xtest_inverse3(A3): - expected = todo + expected = "TODO" assert inverse_expr(A3) == renumber_indices(expected) def xtest_pseudo_inverse21(A21): - expected = todo + expected = "TODO" assert renumber_indices(inverse_expr(A21)) == renumber_indices(expected) def xtest_pseudo_inverse31(A31): - expected = todo + expected = "TODO" assert renumber_indices(inverse_expr(A31)) == renumber_indices(expected) def xtest_pseudo_inverse32(A32): - expected = todo + expected = "TODO" assert renumber_indices(inverse_expr(A32)) == renumber_indices(expected) diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index f0a00dd35..87bef3a4f 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -1,11 +1,10 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - import numpy -from ufl import * + +from ufl import (Cell, Coefficient, FiniteElement, TensorElement, VectorElement, as_domain, as_tensor, as_vector, dx, + indices, triangle) from ufl.algorithms.apply_function_pullbacks import apply_single_function_pullbacks from ufl.algorithms.renumbering import renumber_indices -from ufl.classes import Jacobian, JacobianInverse, JacobianDeterminant, ReferenceValue, CellOrientation +from ufl.classes import Jacobian, JacobianDeterminant, JacobianInverse, ReferenceValue def check_single_function_pullback(g, mappings): @@ -115,7 +114,7 @@ def test_apply_single_function_pullbacks_triangle3d(): detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) # o = CellOrientation(domain) - i, j, k, l = indices(4) + i, j, k, l = indices(4) # noqa: E741 # Contravariant H(div) Piola mapping: M_hdiv = ((1.0/detJ) * J) # Not applying cell orientation here @@ -147,16 +146,14 @@ def test_apply_single_function_pullbacks_triangle3d(): # Vd *(as_tensor(M_hdiv[i, j]*as_vector([rvdm[3], rvdm[4]])[j], (i,))[n] for n in range(3)) - ]), - vcm: as_vector([ + ]), vcm: as_vector([ # Vd *(as_tensor(M_hdiv[i, j]*as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] for n in range(3)), # Vc *(as_tensor(Jinv[i, j]*as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] for n in range(3)) - ]), - tm: as_vector([ + ]), tm: as_vector([ # Vc *(as_tensor(Jinv[i, j]*as_vector([rtm[0], rtm[1]])[i], (j,))[n] for n in range(3)), @@ -164,8 +161,7 @@ def test_apply_single_function_pullbacks_triangle3d(): rtm[2], rtm[3], rtm[4], rtm[5], rtm[6], rtm[7], rtm[8], rtm[9], rtm[10], - ]), - sm: as_vector([ + ]), sm: as_vector([ # T rsm[0], rsm[1], rsm[2], rsm[3], rsm[4], rsm[5], @@ -174,14 +170,14 @@ def test_apply_single_function_pullbacks_triangle3d(): rsm[9], rsm[10], rsm[11], rsm[10], rsm[12], rsm[13], rsm[11], rsm[13], rsm[14], - ]), + ]), # Case from failing ffc demo: vd0m: as_vector([ M_hdiv[0, j]*as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[1, j]*as_vector([rvd0m[0], rvd0m[1]])[j], M_hdiv[2, j]*as_vector([rvd0m[0], rvd0m[1]])[j], rvd0m[2] - ]), + ]), # This combines it all: w: as_vector([ # S @@ -204,7 +200,7 @@ def test_apply_single_function_pullbacks_triangle3d(): rw[21], # U rw[22], - ]), + ]), } # Check functions of various elements outside a mixed context @@ -298,7 +294,7 @@ def test_apply_single_function_pullbacks_triangle(): J = Jacobian(domain) detJ = JacobianDeterminant(domain) Jinv = JacobianInverse(domain) - i, j, k, l = indices(4) + i, j, k, l = indices(4) # noqa: E741 # Contravariant H(div) Piola mapping: M_hdiv = (1.0/detJ) * J @@ -324,7 +320,7 @@ def test_apply_single_function_pullbacks_triangle(): # Vd *(as_tensor(M_hdiv[i, j]*as_vector([rvdm[2], rvdm[3]])[j], (i,))[n] for n in range(2)), - ]), + ]), vcm: as_vector([ # Vd *(as_tensor(M_hdiv[i, j]*as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] @@ -332,7 +328,7 @@ def test_apply_single_function_pullbacks_triangle(): # Vc *(as_tensor(Jinv[i, j]*as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] for n in range(2)), - ]), + ]), tm: as_vector([ # Vc *(as_tensor(Jinv[i, j]*as_vector([rtm[0], rtm[1]])[i], (j,))[n] @@ -340,7 +336,7 @@ def test_apply_single_function_pullbacks_triangle(): # T rtm[2], rtm[3], rtm[4], rtm[5], - ]), + ]), sm: as_vector([ # T rsm[0], rsm[1], @@ -348,7 +344,7 @@ def test_apply_single_function_pullbacks_triangle(): # S rsm[4], rsm[5], rsm[5], rsm[6], - ]), + ]), # This combines it all: w: as_vector([ # S @@ -368,7 +364,7 @@ def test_apply_single_function_pullbacks_triangle(): rw[12], # U rw[13], - ]), + ]), } # Check functions of various elements outside a mixed context diff --git a/test/test_apply_restrictions.py b/test/test_apply_restrictions.py index 6e79c5f79..854a2458f 100755 --- a/test/test_apply_restrictions.py +++ b/test/test_apply_restrictions.py @@ -1,9 +1,7 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - from pytest import raises -from ufl import * -from ufl.algorithms.apply_restrictions import apply_restrictions, apply_default_restrictions + +from ufl import Coefficient, FacetNormal, FiniteElement, SpatialCoordinate, as_tensor, grad, i, triangle +from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions from ufl.algorithms.renumbering import renumber_indices diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index cf26ff85c..ddb1883ad 100755 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -1,10 +1,6 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -import pytest - -from ufl import * -from ufl.classes import Division, FloatValue, IntValue, ComplexValue +from ufl import (Identity, SpatialCoordinate, as_matrix, as_ufl, as_vector, elem_div, elem_mult, elem_op, sin, + tetrahedron, triangle) +from ufl.classes import ComplexValue, Division, FloatValue, IntValue def test_scalar_casting(self): diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index c91655655..60c3f4799 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -1,24 +1,22 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- +"""Automatic differentiation tests. -""" These tests should cover the behaviour of the automatic differentiation algorithm at a technical level, and are thus implementation specific. Other tests check for mathematical correctness of diff and derivative. """ import pytest -from itertools import chain - -import ufl -# This imports everything external code will see from ufl -from ufl import * - -import ufl.algorithms -from ufl.corealg.traversal import unique_post_traversal -from ufl.conditional import Conditional +from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, + FiniteElement, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, + MaxFacetEdgeLength, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, PermutationSymbol, + SpatialCoordinate, TensorElement, VectorElement, acos, as_matrix, as_tensor, as_ufl, as_vector, asin, + atan, bessel_I, bessel_J, bessel_K, bessel_Y, cofac, conditional, cos, cross, derivative, det, dev, + diff, dot, eq, erf, exp, ge, grad, gt, indices, inner, interval, inv, le, ln, lt, ne, outer, replace, + sin, skew, sqrt, sym, tan, tetrahedron, tr, triangle, variable) from ufl.algorithms import expand_derivatives +from ufl.conditional import Conditional +from ufl.corealg.traversal import unique_post_traversal class ExpressionCollection(object): @@ -43,7 +41,7 @@ def __init__(self, cell): invJ = JacobianInverse(cell) # FIXME: Add all new geometry types here! - I = Identity(d) + ident = Identity(d) eps = PermutationSymbol(d) U = FiniteElement("U", cell, None) @@ -63,7 +61,7 @@ class ObjectCollection(object): for key, value in list(locals().items()): setattr(self.shared_objects, key, value) - self.literals = list(map(as_ufl, [0, 1, 3.14, I, eps])) + self.literals = list(map(as_ufl, [0, 1, 3.14, ident, eps])) self.geometry = [x, n, c, R, h, f, mince, maxce, minfe, maxfe, J, detJ, invJ] self.functions = [u, du, v, dv, w, dw] @@ -99,10 +97,10 @@ class ObjectCollection(object): (u**6, u**7, u**8))) # Indexed, ListTensor, ComponentTensor, IndexSum - i, j, k, l = indices(4) + i, j, k, l = indices(4) # noqa: E741 self.indexing = ([ v[0], w[d-1, 0], v[i], w[i, j], - v[:], w[0,:], w[:, 0], + v[:], w[0, :], w[:, 0], v[...], w[0, ...], w[..., 0], v[i]*v[j], w[i, 0]*v[j], w[d-1, j]*v[i], v[i]*v[i], w[i, 0]*w[0, i], v[i]*w[0, i], @@ -178,7 +176,7 @@ class ObjectCollection(object): self.crossproducts = ([ cross(v, v), cross(v, 2*v), - cross(v, w[0,:]), + cross(v, w[0, :]), cross(v, w[:, 1]), cross(w[:, 0], v), ]) @@ -210,27 +208,32 @@ def ad_algorithm(expr): if alt == 0: return expand_derivatives(expr) elif alt == 1: - return expand_derivatives(expr, + return expand_derivatives( + expr, apply_expand_compounds_before=True, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=True) elif alt == 2: - return expand_derivatives(expr, + return expand_derivatives( + expr, apply_expand_compounds_before=False, apply_expand_compounds_after=True, use_alternative_wrapper_algorithm=False) elif alt == 3: - return expand_derivatives(expr, + return expand_derivatives( + expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=False) elif alt == 4: - return expand_derivatives(expr, + return expand_derivatives( + expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=True) elif alt == 5: - return expand_derivatives(expr, + return expand_derivatives( + expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, use_alternative_wrapper_algorithm=False) @@ -253,7 +256,7 @@ def _test_no_derivatives_but_still_changed(self, collection): # print '\n', str(before), '\n', str(after), '\n' self.assertEqualTotalShape(before, after) # assert before == after # Without expand_compounds - self.assertNotEqual(before, after) # With expand_compounds + self.assertNotEqual(before, after) # With expand_compounds def test_only_terminals_no_change(self, d_expr): @@ -266,7 +269,7 @@ def test_no_derivatives_no_change(self, d_expr): _test_no_derivatives_no_change(self, ex.noncompounds) -def xtest_compounds_no_derivatives_no_change(self, d_expr): # This test fails with expand_compounds enabled +def xtest_compounds_no_derivatives_no_change(self, d_expr): # This test fails with expand_compounds enabled d, ex = d_expr _test_no_derivatives_no_change(self, ex.compounds) @@ -285,13 +288,13 @@ def _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, for t in collection.terminals: for var in (u, v, w): - before = derivative(t, var) # This will often get preliminary simplified to zero + before = derivative(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) expected = 0*t # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected - before = derivative(c*t, var) # This will usually not get simplified to zero + before = derivative(c*t, var) # This will usually not get simplified to zero after = ad_algorithm(before) expected = 0*t # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' @@ -315,13 +318,13 @@ def _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, colle vw = variable(w) for t in collection.terminals: for var in (vu, vv, vw): - before = diff(t, var) # This will often get preliminary simplified to zero + before = diff(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) expected = 0*outer(t, var) # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected - before = diff(c*t, var) # This will usually not get simplified to zero + before = diff(c*t, var) # This will usually not get simplified to zero after = ad_algorithm(before) expected = 0*outer(t, var) # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' @@ -454,7 +457,7 @@ def _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, # for t in chain(collection.noncompounds, collection.compounds): for t in collection.noncompounds: - t = replace(t, {u:vu, v:vv, w:vw}) + t = replace(t, {u: vu, v: vv, w: vw}) for var in (vu, vv, vw): # Include d/dx [z ? y: x] but not d/dx [x ? f: z] if isinstance(t, Conditional) and (var in unique_post_traversal(t.ufl_operands[0])): @@ -468,7 +471,7 @@ def _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, after = ad_algorithm(before) if debug: print(('\n', 'after: ', str(after), '\n')) - expected_shape = 0*outer(t, var) # expected shape, not necessarily value + expected_shape = 0*outer(t, var) # expected shape, not necessarily value if debug: print(('\n', 'expected_shape: ', str(expected_shape), '\n')) # print '\n', str(expected_shape), '\n', str(after), '\n', str(before), '\n' @@ -495,7 +498,7 @@ def test_grad_coeff(self, d_expr): print(('\n', str(before), '\n', str(after), '\n')) self.assertEqualTotalShape(before, after) - if f is u: # Differing by being wrapped in indexing types + if f is u: # Differing by being wrapped in indexing types assert before == after before = grad(grad(f)) diff --git a/test/test_book_snippets.py b/test/test_book_snippets.py index a740014dc..ed1088405 100755 --- a/test/test_book_snippets.py +++ b/test/test_book_snippets.py @@ -1,6 +1,5 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -""" +"""Test book snippets. + This file contains snippets from the FEniCS book, and allows us to test that these can still run with future versions of UFL. Please don't change @@ -8,10 +7,14 @@ snippets as long as possible. """ -import pytest - -from ufl import * -from ufl.algorithms import * +from ufl import (Argument, Cell, Coefficient, Coefficients, Constant, Dx, FiniteElement, Identity, Index, MixedElement, + SpatialCoordinate, TensorConstant, TensorElement, TestFunction, TestFunctions, TrialFunction, + TrialFunctions, VectorConstant, VectorElement, action, adjoint, as_matrix, as_tensor, as_ufl, + conditional, cos, derivative, diff, dot, ds, dx, exp, grad, i, indices, inner, j, k, lt, outer, pi, + replace, sensitivity_rhs, sin, split, system, tetrahedron, tr, triangle, variable) +from ufl.algorithms import (Transformer, compute_form_data, expand_compounds, expand_derivatives, expand_indices, + post_traversal, tree_format) +from ufl.corealg.multifunction import MultiFunction def test_uflcode_269(self): @@ -27,8 +30,8 @@ def test_uflcode_269(self): c2 = Constant(cell) # Deformation gradient Fij = dXi/dxj - I = Identity(cell.geometric_dimension()) - F = I + grad(u) + ident = Identity(cell.geometric_dimension()) + F = ident + grad(u) # Right Cauchy-Green strain tensor C with invariants C = variable(F.T*F) @@ -43,16 +46,16 @@ def test_uflcode_269(self): # Weak forms L = inner(F*S, grad(phi0))*dx - a = derivative(L, u, phi1) + derivative(L, u, phi1) def test_uflcode_316(self): shapestring = 'triangle' - cell = Cell(shapestring) + cell = Cell(shapestring) # noqa: F841 def test_uflcode_323(self): - cell = tetrahedron + cell = tetrahedron # noqa: F841 def test_uflcode_356(self): @@ -62,8 +65,8 @@ def test_uflcode_356(self): V = VectorElement("Lagrange", cell, 2) T = TensorElement("DG", cell, 0, symmetry=True) - TH = V*P - ME = MixedElement(T, V, P) + TH = V*P # noqa: F841 + ME = MixedElement(T, V, P) # noqa: F841 def test_uflcode_400(self): @@ -75,8 +78,8 @@ def test_uflcode_400(self): v = TestFunction(V) u = TrialFunction(V) # ... - a = w*dot(grad(u), grad(v))*dx - L = f*v*dx + g**2*v*ds(0) + h*v*ds(1) + a = w*dot(grad(u), grad(v))*dx # noqa: F841 + L = f*v*dx + g**2*v*ds(0) + h*v*ds(1) # noqa: F841 def test_uflcode_469(self): @@ -89,25 +92,25 @@ def test_uflcode_469(self): dx02 = dx(0, {"integration_order": 2}) dx14 = dx(1, {"integration_order": 4}) dx12 = dx(1, {"integration_order": 2}) - L = f*v*dx02 + g*v*dx14 + h*v*dx12 + L = f*v*dx02 + g*v*dx14 + h*v*dx12 # noqa: F841 def test_uflcode_552(self): element = FiniteElement("CG", triangle, 1) # ... - phi = Argument(element, 2) - v = TestFunction(element) - u = TrialFunction(element) + phi = Argument(element, 2) # noqa: F841 + v = TestFunction(element) # noqa: F841 + u = TrialFunction(element) # noqa: F841 def test_uflcode_563(self): cell = triangle element = FiniteElement("CG", cell, 1) # ... - w = Coefficient(element) - c = Constant(cell) - v = VectorConstant(cell) - M = TensorConstant(cell) + w = Coefficient(element) # noqa: F841 + c = Constant(cell) # noqa: F841 + v = VectorConstant(cell) # noqa: F841 + M = TensorConstant(cell) # noqa: F841 def test_uflcode_574(self): @@ -138,7 +141,7 @@ def test_uflcode_644(self): v = Coefficient(V) # ... A = outer(u, v) - Aij = A[i, j] + Aij = A[i, j] # noqa: F841 def test_uflcode_651(self): @@ -147,12 +150,12 @@ def test_uflcode_651(self): v = Coefficient(V) # ... Aij = v[j]*u[i] - A = as_tensor(Aij, (i, j)) + A = as_tensor(Aij, (i, j)) # noqa: F841 def test_uflcode_671(self): - i = Index() - j, k, l = indices(3) + i = Index() # noqa: F841 + j, k, l = indices(3) # noqa: F841, E741 def test_uflcode_684(self): @@ -162,7 +165,7 @@ def test_uflcode_684(self): th = pi/2 A = as_matrix([[cos(th), -sin(th)], [sin(th), cos(th)]]) - u = A*v + u = A*v # noqa: F841 def test_uflcode_824(self): @@ -170,7 +173,7 @@ def test_uflcode_824(self): f = Coefficient(V) # ... df = Dx(f, i) - df = f.dx(i) + df = f.dx(i) # noqa: F841 def test_uflcode_886(self): @@ -180,33 +183,18 @@ def test_uflcode_886(self): g = sin(x[0]) v = variable(g) f = exp(v**2) - h = diff(f, v) + h = diff(f, v) # noqa: F841 # ... # print v # print h -def test_python_894(self): - # We don't have to keep the string output compatible, so no test here. - pass - #>>> print v - # var0(sin((x)[0])) - #>>> print h - # d/d[var0(sin((x)[0]))] (exp((var0(sin((x)[0]))) ** 2)) - - def test_uflcode_930(self): condition = lt(1, 0) true_value = 1 false_value = 0 # ... - f = conditional(condition, true_value, false_value) - - -def test_uflcode_1003(self): - # Not testable, but this is tested below anyway - "a = derivative(L, w, u)" - pass + f = conditional(condition, true_value, false_value) # noqa: F841 def test_uflcode_1026(self): @@ -217,7 +205,7 @@ def test_uflcode_1026(self): w = Coefficient(element) f = 0.5*w**2*dx F = derivative(f, w, v) - J = derivative(F, w, u) + J = derivative(F, w, u) # noqa: F841 def test_uflcode_1050(self): @@ -227,7 +215,7 @@ def test_uflcode_1050(self): x, y = split(u) f = inner(grad(x), grad(x))*dx + y*dot(x, x)*dx F = derivative(f, u) - J = derivative(F, u) + J = derivative(F, u) # noqa: F841 def test_uflcode_1085(self): @@ -239,7 +227,7 @@ def test_uflcode_1085(self): v = TestFunction(V) M = Coefficient(T) a = M[i, j]*u[k].dx(j)*v[k].dx(i)*dx - astar = adjoint(a) + astar = adjoint(a) # noqa: F841 def test_uflcode_1120(self): @@ -250,8 +238,8 @@ def test_uflcode_1120(self): f = Coefficient(V) g = Coefficient(V) L = f**2 / (2*g)*v*dx - L2 = replace(L, {f: g, g: 3}) - L3 = g**2 / 6*v*dx + L2 = replace(L, {f: g, g: 3}) # noqa: F841 + L3 = g**2 / 6*v*dx # noqa: F841 def test_uflcode_1157(self): @@ -277,7 +265,7 @@ def test_uflcode_1190(self): a, L = system(pde) # ... u = Coefficient(element) - sL = diff(L, c) - action(diff(a, c), u) + sL = diff(L, c) - action(diff(a, c), u) # noqa: F841 def test_uflcode_1195(self): @@ -292,22 +280,14 @@ def test_uflcode_1195(self): a, L = system(pde) u = Coefficient(element) # ... - sL = sensitivity_rhs(a, u, L, c) + sL = sensitivity_rhs(a, u, L, c) # noqa: F841 def test_uflcode_1365(self): e = 0 v = variable(e) f = sin(v) - g = diff(f, v) - - -def test_python_1426(self): - # Covered by the below test - pass - # from ufl.algorithms import Graph - # G = Graph(expression) - # V, E = G + g = diff(f, v) # noqa: F841 def test_python_1446(self): @@ -317,7 +297,7 @@ def test_python_1446(self): v = TestFunction(V) c = Constant(cell) f = Coefficient(V) - e = c*f**2*u*v + e = c*f**2*u*v # noqa: F841 # The linearized Graph functionality has been removed from UFL: # from ufl.algorithms import Graph, partition @@ -336,7 +316,7 @@ def test_python_1512(self): v = TestFunction(V) c = Constant(cell) f = Coefficient(V) - e = c*f**2*u*v + e = c*f**2*u*v # noqa: F841 # The linearized Graph functionality has been removed from UFL: # from ufl.algorithms import Graph, partition @@ -354,7 +334,7 @@ def test_python_1557(self): v = TestFunction(V) c = Constant(cell) f = Coefficient(V) - e = c*f**2*u*v + e = c*f**2*u*v # noqa: F841 # The linearized Graph functionality has been removed from UFL: # from ufl.algorithms import Graph, partition @@ -371,23 +351,12 @@ def test_python_1557(self): # v = V[i] -def test_python_1843(self): - def apply_ad(e, ad_routine): - if e._ufl_is_terminal_: - return e - ops = [apply_ad(o, ad_routine) for o in e.ufl_operands] - e = e._ufl_expr_reconstruct_(*ops) - if isinstance(e, Derivative): - e = ad_routine(e) - return e - - def test_uflcode_1901(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) # ... - v = Argument(element, 2) - w = Coefficient(element) + v = Argument(element, 2) # noqa: F841 + w = Coefficient(element) # noqa: F841 def test_python_1942(self): @@ -422,18 +391,12 @@ def post_action(e): def test_python_1990(self): - from ufl.classes import IntValue, Sum expression = as_ufl(3) def int_operation(x): return 7 - # ... - if isinstance(expression, IntValue): - result = int_operation(expression) - elif isinstance(expression, Sum): - result = sum_operation(expression) - # etc. - # ... + + result = int_operation(expression) self.assertTrue(result == 7) @@ -488,7 +451,7 @@ def terminal(self, e): f = Constant(triangle) r = Replacer({f: f**2}) - g = r.visit(2*f) + g = r.visit(2*f) # noqa: F841 def test_python_2189(self): @@ -504,10 +467,10 @@ def test_python_2189(self): ad = expand_derivatives(ac) ai = expand_indices(ad) - af = tree_format(a) - acf = tree_format(ac) - adf = "\n", tree_format(ad) - aif = tree_format(ai) + af = tree_format(a) # noqa: F841 + acf = tree_format(ac) # noqa: F841 + adf = "\n", tree_format(ad) # noqa: F841 + aif = tree_format(ai) # noqa: F841 if 0: print(("\na: ", str(a), "\n", tree_format(a))) @@ -581,4 +544,4 @@ def test_python_2462(self): # ... # print repr(preprocess(myform).preprocessed_form) # ... - r = repr(compute_form_data(myform).preprocessed_form) + r = repr(compute_form_data(myform).preprocessed_form) # noqa: F841 diff --git a/test/test_cell.py b/test/test_cell.py index 8ec754622..5d9472a1d 100644 --- a/test/test_cell.py +++ b/test/test_cell.py @@ -1,6 +1,7 @@ -import ufl import pytest +import ufl + def test_interval(): cell = ufl.interval @@ -51,7 +52,6 @@ def test_tesseract(): assert cell.num_faces() == 24 - @pytest.mark.parametrize("cell", [ufl.interval]) def test_cells_1d(cell): assert cell.num_facets() == cell.num_vertices() diff --git a/test/test_change_to_local.py b/test/test_change_to_local.py index b123c054f..80bdb2101 100755 --- a/test/test_change_to_local.py +++ b/test/test_change_to_local.py @@ -1,15 +1,9 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -""" -Tests of the change to local representaiton algorithms. -""" +"""Tests of the change to local representaiton algorithms.""" -import pytest - -from ufl import * -from ufl.classes import ReferenceGrad, JacobianInverse -from ufl.algorithms import tree_format, change_to_reference_grad +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, as_tensor, grad, indices, triangle +from ufl.algorithms import change_to_reference_grad from ufl.algorithms.renumbering import renumber_indices +from ufl.classes import JacobianInverse, ReferenceGrad def test_change_to_reference_grad(): @@ -58,8 +52,8 @@ def test_change_to_reference_grad(): expr = grad(grad(grad(v))) actual = change_to_reference_grad(expr) - expected = as_tensor( - Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), (t, i, j, k)) + expected = as_tensor(Jinv[s, k] * (Jinv[r, j] * ( + Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), (t, i, j, k)) assert renumber_indices(actual) == renumber_indices(expected) # print tree_format(expected) diff --git a/test/test_change_to_reference_frame.py b/test/test_change_to_reference_frame.py index 264d12463..c70076856 100755 --- a/test/test_change_to_reference_frame.py +++ b/test/test_change_to_reference_frame.py @@ -1,76 +1,12 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- """Tests of the change to reference frame algorithm.""" -import pytest - -from ufl import * - -from ufl.classes import Form, Integral, Expr, ReferenceGrad, ReferenceValue - -''' -from ufl.classes import ReferenceGrad, JacobianInverse -from ufl.algorithms import tree_format, change_to_reference_grad - -from ufl.log import error, warning - -from ufl.core.multiindex import Index, indices -from ufl.corealg.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dag - -from ufl.classes import (Expr, FormArgument, GeometricQuantity, - Terminal, ReferenceGrad, Grad, Restricted, ReferenceValue, - Jacobian, JacobianInverse, JacobianDeterminant, - FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant, - CellFacetJacobian, - ReferenceCellEdgeVectors, ReferenceFacetEdgeVectors, - FacetNormal, CellNormal, ReferenceNormal, - CellVolume, FacetArea, - CellOrientation, FacetOrientation, QuadratureWeight, - SpatialCoordinate, Indexed, MultiIndex, FixedIndex) - -from ufl.constantvalue import as_ufl, Identity -from ufl.tensoralgebra import Transposed -from ufl.tensors import as_tensor, as_vector, as_scalar, ComponentTensor -from ufl.operators import sqrt, max_value, min_value, sign -from ufl.permutation import compute_indices - -from ufl.algorithms.transformer import ReuseTransformer, apply_transformer -from ufl.compound_expressions import determinant_expr, cross_expr, inverse_expr -from ufl.finiteelement import FiniteElement, EnrichedElement, VectorElement, MixedElement, TensorProductElement, TensorElement, FacetElement, InteriorElement, BrokenElement. -''' - - -def change_integral_to_reference_frame(form, context): - if False: # TODO: integral.is_in_reference_frame(): - # TODO: Assume reference frame integral is written purely in - # reference frame or tramsform integrand here as well? - return integrand - else: - # Change integrand expression to reference frame - integrand = change_to_reference_frame(integral.integrand()) - - # Compute and apply integration scaling factor - scale, degree = compute_integrand_scaling_factor(integral.ufl_domain(), - integral.integral_type()) - - return integral.reconstruct(integrand * scale) # TODO: , reference=True) - - -def change_expr_to_reference_frame(expr): - expr = ReferenceValue(expr) - return expr +from ufl import Coefficient, FiniteElement, TensorElement, VectorElement, triangle +from ufl.classes import Expr, ReferenceValue def change_to_reference_frame(expr): - if isinstance(expr, Form): - return change_form_to_reference_frame(expr) - elif isinstance(expr, Integral): - return change_integral_to_reference_frame(expr) - elif isinstance(expr, Expr): - return change_expr_to_reference_frame(expr) - else: - error("Invalid type.") + assert isinstance(expr, Expr) + return ReferenceValue(expr) def test_change_unmapped_form_arguments_to_reference_frame(): @@ -174,100 +110,6 @@ def test_change_hcurl_form_arguments_to_reference_frame(): ''' -def new_analyse_modified_terminal(expr): - assert expr._ufl_is_terminal_ or expr._ufl_is_terminal_modifier_type_ - m = expr - - # The outermost expression may index to get a specific scalar value - if isinstance(m, Indexed): - unindexed, multi_index = m.ufl_operands - indices = tuple(int(i) for i in multi_index) - else: - unindexed = m - indices = () - - # The evaluation mode is one of current point, - # a cell entity midpoint, or averaging over a cell entity - if unindexed._ufl_is_evaluation_type_: # averages and point evaluations - v, = v.ufl_operand - evaluation = unindexed.ufl_handler_name - else: - v = unindexed - evaluation = "current_point" - - # We're either in reference frame or global, checks below ensure we don't mix the two - frame = "reference" if v._ufl_is_reference_type_ else "global" - - # Peel off derivatives (grad^n,div,curl,div(grad),grad(div) etc.) - t = v - derivatives = [] - while t._ufl_is_derivative_type_: - # ensure frame consistency - assert t._ufl_is_reference_type_ == v._ufl_is_reference_type_ - derivatives.append(t._ufl_class_) - t, = t.ufl_operands - core = t - derivatives = tuple(derivatives) - - # This can be an intermediate step to use derivatives instead of ngrads: - num_derivatives = len(derivatives) - - # If we had a reference type before unwrapping terminal, - # there should be a ReferenceValue inside all the derivatives - if v._ufl_is_reference_type_: - assert isinstance(t, ReferenceValue) - t, = t.ufl_operands - - # At the core we may have a restriction - if t._ufl_is_restriction_type_: - restriction = t.side() - t, = t.ufl_operands - else: - restriction = "" - - # And then finally the terminal - assert t._ufl_is_terminal_ - terminal = t - - # This will only be correct for derivatives = grad^n - gdim = terminal.ufl_domain().geometric_dimension() - derivatives_shape = (gdim,)*num_derivatives - - # Get shapes - expr_shape = expr.ufl_shape - unindexed_shape = unindexed.ufl_shape - core_shape = core.ufl_shape - - # Split indices - core_indices = indices[:len(core_shape)] - derivative_indices = indices[len(core_shape):] - - # Apply paranoid dimension checking - assert len(indices) == len(unindexed_shape) - assert all(0 <= i for i in indices) - assert all(i < j for i, j in zip(indices, unindexed_shape)) - assert len(core_indices) == len(core_shape) - assert all(0 <= i for i in core_indices) - assert all(i < j for i, j in zip(core_indices, core_shape)) - assert len(derivative_indices) == len(derivatives_shape) # This will fail for e.g. div(grad(f)) - assert all(0 <= i for i in derivative_indices) - assert all(i < j for i, j in zip(derivative_indices, derivatives_shape)) - - # Return values: - mt = ModifiedTerminal( - # TODO: Use keyword args - expr, - indices, - evaluation, - frame, - num_derivatives, - derivatives, - restriction, - terminal - ) - return mt - - ''' New form preprocessing pipeline: @@ -281,9 +123,13 @@ def new_analyse_modified_terminal(expr): ii) process integrands: a) apply_coefficient_completion # replace coefficients to ensure proper elements and domains b) lower_compound_operators # expand_compounds - c) change_to_reference_frame # change f->rv(f), m->M*rv(m), grad(f)->K*rgrad(rv(f)), grad(grad(f))->K*rgrad(K*rgrad(rv(f))), grad(expr)->K*rgrad(expr) - # if grad(expr)->K*rgrad(expr) should be valid, then rgrad must be applicable to quite generic expressions - d) apply_derivatives # one possibility is to add an apply_mapped_derivatives AD algorithm which includes mappings + c) change_to_reference_frame # change f->rv(f), m->M*rv(m), + grad(f)->K*rgrad(rv(f)), + grad(grad(f))->K*rgrad(K*rgrad(rv(f))), grad(expr)->K*rgrad(expr) + # if grad(expr)->K*rgrad(expr) should be valid, + then rgrad must be applicable to quite generic expressions + d) apply_derivatives # one possibility is to add an apply_mapped_derivatives AD + algorithm which includes mappings e) apply_geometry_lowering f) apply_restrictions # requiring grad(f)('+') instead of grad(f('+')) would simplify a lot... iii) extract final metadata about elements and coefficient ordering diff --git a/test/test_check_arities.py b/test/test_check_arities.py index bf2a5b324..0d5521ac2 100755 --- a/test/test_check_arities.py +++ b/test/test_check_arities.py @@ -1,9 +1,9 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- import pytest -from ufl import * -from ufl.algorithms.compute_form_data import compute_form_data + +from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, TrialFunction, + VectorElement, adjoint, cofac, conj, derivative, ds, dx, grad, inner, tetrahedron) from ufl.algorithms.check_arities import ArityMismatch +from ufl.algorithms.compute_form_data import compute_form_data def test_check_arities(): @@ -26,11 +26,9 @@ def test_check_arities(): L = derivative(M, u, dv) a = derivative(L, u, du) - fd = compute_form_data(M) - fd = compute_form_data(L) - fd = compute_form_data(a) - - assert True + compute_form_data(M) + compute_form_data(L) + compute_form_data(a) def test_complex_arities(): diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index af96b4e6e..28bb83776 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -1,16 +1,21 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2008-09-06 -- 2009-02-10" -import pytest - import ufl -from ufl import * -from ufl.constantvalue import as_ufl -from ufl.classes import * -from ufl.algorithms import * +from ufl import * # noqa: F403, F401 +from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, + FiniteElement, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, + MinFacetEdgeLength, MixedElement, Not, Or, PermutationSymbol, SpatialCoordinate, TensorConstant, + TensorElement, VectorConstant, VectorElement, acos, action, as_matrix, as_tensor, as_ufl, as_vector, + asin, atan, cell_avg, cofac, conditional, cos, cosh, cross, curl, derivative, det, dev, diff, div, dot, + ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, i, inner, inv, j, k, l, le, ln, lt, nabla_div, + nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, tan, tanh, tetrahedron, tr, transpose, + triangle, variable) +from ufl.algorithms import * # noqa: F403, F401 +from ufl.classes import * # noqa: F403, F401 +from ufl.classes import (Acos, Asin, Atan, CellCoordinate, Cos, Cosh, Exp, Expr, FacetJacobian, + FacetJacobianDeterminant, FacetJacobianInverse, FloatValue, IntValue, Ln, Outer, Sin, Sinh, + Sqrt, Tan, Tanh, all_ufl_classes) has_repr = set() has_dict = set() @@ -29,7 +34,7 @@ def _test_object(a, shape, free_indices): assert hash(a) == hash(e) # Can't really test str more than that it exists - s = str(a) + str(a) # Check that some properties are at least available fi = a.ufl_free_indices @@ -64,7 +69,7 @@ def _test_object2(a): assert hash(a) == hash(e) # Can't really test str more than that it exists - s = str(a) + str(a) def _test_form(a): @@ -74,7 +79,7 @@ def _test_form(a): assert hash(a) == hash(e) # Can't really test str more than that it exists - s = str(a) + str(a) def testExports(self): @@ -148,12 +153,12 @@ def testAll(self): a = IntValue(123) _test_object(a, (), ()) - I = Identity(1) - _test_object(I, (1, 1), ()) - I = Identity(2) - _test_object(I, (2, 2), ()) - I = Identity(3) - _test_object(I, (3, 3), ()) + ident = Identity(1) + _test_object(ident, (1, 1), ()) + ident = Identity(2) + _test_object(ident, (2, 2), ()) + ident = Identity(3) + _test_object(ident, (3, 3), ()) e = PermutationSymbol(2) _test_object(e, (2, 2), ()) @@ -166,9 +171,9 @@ def testAll(self): _test_object(xi, (dim,), ()) # g = CellBarycenter(cell) - #_test_object(g, (dim,), ()) + # _test_object(g, (dim,), ()) # g = FacetBarycenter(cell) - #_test_object(g, (dim,), ()) + # _test_object(g, (dim,), ()) g = Jacobian(cell) _test_object(g, (dim, dim), ()) @@ -187,7 +192,7 @@ def testAll(self): g = FacetNormal(cell) _test_object(g, (dim,), ()) # g = CellNormal(cell) - #_test_object(g, (dim,), ()) + # _test_object(g, (dim,), ()) g = CellVolume(cell) _test_object(g, (), ()) @@ -196,7 +201,7 @@ def testAll(self): g = Circumradius(cell) _test_object(g, (), ()) # g = CellSurfaceArea(cell) - #_test_object(g, (), ()) + # _test_object(g, (), ()) g = FacetArea(cell) _test_object(g, (), ()) @@ -205,7 +210,7 @@ def testAll(self): g = MaxFacetEdgeLength(cell) _test_object(g, (), ()) # g = FacetDiameter(cell) - #_test_object(g, (), ()) + # _test_object(g, (), ()) a = variable(v0) _test_object(a, (), ()) @@ -242,8 +247,8 @@ def testAll(self): a = f2[l, 1] _test_object(a, (), (l,)) - I = Identity(dim) - a = inv(I) + ident = Identity(dim) + a = inv(ident) _test_object(a, (dim, dim), ()) a = inv(v2) _test_object(a, (dim, dim), ()) @@ -524,14 +529,14 @@ def testAll(self): _test_object(a, (), ()) # a = PositiveRestricted(v0) - #_test_object(a, (), ()) + # _test_object(a, (), ()) a = v0('+') _test_object(a, (), ()) a = v0('+')*f0 _test_object(a, (), ()) # a = NegativeRestricted(v0) - #_test_object(a, (), ()) + # _test_object(a, (), ()) a = v0('-') _test_object(a, (), ()) a = v0('-') + f0 diff --git a/test/test_complex.py b/test/test_complex.py index 72926d225..d908e1367 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -1,19 +1,17 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -import pytest import cmath -import ufl -from ufl.constantvalue import Zero, ComplexValue -from ufl.algebra import Conj, Real, Imag -from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering -from ufl.algorithms.remove_complex_nodes import remove_complex_nodes + +import pytest + +from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, as_tensor, as_ufl, atan, conditional, conj, + cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, max_value, min_value, outer, real, sin, + sqrt, triangle) +from ufl.algebra import Conj, Imag, Real from ufl.algorithms import estimate_total_polynomial_degree -from ufl.algorithms.comparison_checker import do_comparison_check, ComplexComparisonError +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.algorithms.comparison_checker import ComplexComparisonError, do_comparison_check from ufl.algorithms.formtransformations import compute_form_adjoint -from ufl import (TestFunction, TrialFunction, triangle, FiniteElement, - as_ufl, inner, grad, dx, dot, outer, conj, sqrt, sin, cosh, - atan, ln, exp, as_tensor, real, imag, conditional, - min_value, max_value, gt, lt, cos, ge, le, Coefficient) +from ufl.algorithms.remove_complex_nodes import remove_complex_nodes +from ufl.constantvalue import ComplexValue, Zero def test_conj(self): @@ -67,7 +65,8 @@ def test_complex_algebra(self): assert z2/z1 == ComplexValue(1-1j) assert pow(z2, z1) == ComplexValue((1+1j)**1j) assert sqrt(z2) * as_ufl(1) == ComplexValue(cmath.sqrt(1+1j)) - assert ((sin(z2) + cosh(z2) - atan(z2)) * z1) == ComplexValue((cmath.sin(1+1j) + cmath.cosh(1+1j) - cmath.atan(1+1j))*1j) + assert (sin(z2) + cosh(z2) - atan(z2)) * z1 == ComplexValue( + (cmath.sin(1+1j) + cmath.cosh(1+1j) - cmath.atan(1+1j))*1j) assert (abs(z2) - ln(z2))/exp(z1) == ComplexValue((abs(1+1j) - cmath.log(1+1j))/cmath.exp(1j)) @@ -106,7 +105,8 @@ def test_apply_algebra_lowering_complex(self): assert lowered_a == gu[lowered_a_index] * gv[lowered_a_index] assert lowered_b == gv[lowered_b_index] * conj(gu[lowered_b_index]) - assert lowered_c == as_tensor(conj(gu[lowered_c_indices[0]]) * gv[lowered_c_indices[1]], (lowered_c_indices[0],) + (lowered_c_indices[1],)) + assert lowered_c == as_tensor( + conj(gu[lowered_c_indices[0]]) * gv[lowered_c_indices[1]], (lowered_c_indices[0],) + (lowered_c_indices[1],)) def test_remove_complex_nodes(self): diff --git a/test/test_conditionals.py b/test/test_conditionals.py index 5b1b7badf..3b6dffce2 100755 --- a/test/test_conditionals.py +++ b/test/test_conditionals.py @@ -1,14 +1,10 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2008-08-20 -- 2012-11-30" import pytest -from ufl import * -# from ufl.algorithms import * -from ufl.classes import * +from ufl import Coefficient, FiniteElement, conditional, eq, ge, gt, le, lt, ne, triangle +from ufl.classes import EQ, GE, GT, LE, LT, NE @pytest.fixture @@ -30,17 +26,6 @@ def test_conditional_does_not_allow_bool_condition(f, g): conditional(True, 1, 0) -def test_eq_produces_ufl_expr(f, g): - expr1 = eq(f, f) - expr2 = eq(f, g) - expr3 = eq(f, g) - assert isinstance(expr1, EQ) - assert isinstance(expr2, EQ) - assert not bool(expr1 == expr2) - assert bool(expr1 != expr2) - assert bool(expr2 == expr3) - - def test_eq_oper_produces_bool(f, g): expr1 = f == f expr2 = f == g @@ -50,15 +35,6 @@ def test_eq_oper_produces_bool(f, g): assert not expr2 -def xtest_eq_produces_ufl_expr(f, g): - expr1 = f == g - expr2 = eq(f, g) - assert isinstance(expr1, EQ) - assert isinstance(expr2, EQ) - assert bool(expr1 == expr2) - assert not bool(expr1 != expr2) - - def test_eq_produces_ufl_expr(f, g): expr1 = eq(f, g) expr2 = eq(f, f) diff --git a/test/test_degree_estimation.py b/test/test_degree_estimation.py index 3c344a8e3..8f05713c5 100755 --- a/test/test_degree_estimation.py +++ b/test/test_degree_estimation.py @@ -1,14 +1,11 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2008-03-12 -- 2009-01-28" -import pytest -from pprint import * -from ufl import * -from ufl.algorithms import * +from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FiniteElement, SpatialCoordinate, + TensorProductElement, VectorElement, cos, div, dot, grad, i, inner, nabla_div, nabla_grad, sin, tan, + triangle) +from ufl.algorithms import estimate_total_polynomial_degree def test_total_degree_estimation(): diff --git a/test/test_derivative.py b/test/test_derivative.py index 2a023b9ae..e8ce1837d 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -3,11 +3,13 @@ from itertools import chain -import pytest - -from ufl import * -from ufl.algorithms import (compute_form_data, expand_indices, post_traversal, - strip_variables) +from ufl import (CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, FiniteElement, + FunctionSpace, Identity, Index, Jacobian, JacobianInverse, Mesh, SpatialCoordinate, TensorElement, + TestFunction, TrialFunction, VectorElement, acos, as_matrix, as_tensor, as_vector, asin, atan, + conditional, cos, derivative, diff, dot, dx, exp, i, indices, inner, interval, j, k, ln, lt, + nabla_grad, outer, quadrilateral, replace, sign, sin, split, sqrt, tan, tetrahedron, triangle, + variable, zero) +from ufl.algorithms import compute_form_data, expand_indices, strip_variables from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering @@ -280,22 +282,6 @@ def df(w, v): return sign(w)*v _test(self, f, df) -def testConditional(self): - def cond(w): return lt(1.0, 2.0) - - def f(w): return conditional(cond(w), 2*w, 3*w) - - def df(w, v): return 2*v - _test(self, f, df) - - def cond(w): return lt(2.0, 1.0) - - def f(w): return conditional(cond(w), 2*w, 3*w) - - def df(w, v): return 3*v - _test(self, f, df) - - def testConditional(self): # This will fail without bugfix in derivative def cond(w): return lt(w, 1.0) @@ -324,22 +310,22 @@ def testListTensor(self): v = variable(as_ufl(42)) f = as_tensor(( ((0, 0), (0, 0)), - ((v, 2*v), (0, 0)), - ((v**2, 1), (2, v/2)), + ((v, 2*v), (0, 0)), + ((v**2, 1), (2, v/2)), )) assert f.ufl_shape == (3, 2, 2) g = as_tensor(( ((0, 0), (0, 0)), - ((1, 2), (0, 0)), - ((84, 0), (0, 0.5)), + ((1, 2), (0, 0)), + ((84, 0), (0, 0.5)), )) assert g.ufl_shape == (3, 2, 2) dfv = diff(f, v) x = None - for i in range(3): - for j in range(2): - for k in range(2): - self.assertEqual(dfv[i, j, k](x), g[i, j, k](x)) + for a in range(3): + for b in range(2): + for c in range(2): + self.assertEqual(dfv[a, b, c](x), g[a, b, c](x)) # --- Coefficient and argument input configurations @@ -388,7 +374,7 @@ def test_multiple_coefficient_derivative(self): def test_indexed_coefficient_derivative(self): cell = triangle - I = Identity(cell.geometric_dimension()) + ident = Identity(cell.geometric_dimension()) V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) u = Coefficient(W) @@ -400,7 +386,7 @@ def test_indexed_coefficient_derivative(self): actual = derivative(a, u[0], v) - dw = v*u[k].dx(0) + u[i]*I[0, k]*v.dx(i) + dw = v*u[k].dx(0) + u[i]*ident[0, k]*v.dx(i) expected = 2 * w[k] * dw assertEqualBySampling(actual, expected) @@ -408,7 +394,6 @@ def test_indexed_coefficient_derivative(self): def test_multiple_indexed_coefficient_derivative(self): cell = tetrahedron - I = Identity(cell.geometric_dimension()) V = FiniteElement("CG", cell, 1) V2 = V*V W = VectorElement("CG", cell, 1) @@ -437,24 +422,23 @@ def test_segregated_derivative_of_convection(self): Lv = {} Lvu = {} - for i in range(cell.geometric_dimension()): - Lv[i] = derivative(L, v[i], dv) - for j in range(cell.geometric_dimension()): - Lvu[i, j] = derivative(Lv[i], u[j], du) - - for i in range(cell.geometric_dimension()): - for j in range(cell.geometric_dimension()): - form = Lvu[i, j]*dx + for a in range(cell.geometric_dimension()): + Lv[a] = derivative(L, v[a], dv) + for b in range(cell.geometric_dimension()): + Lvu[a, b] = derivative(Lv[a], u[b], du) + + for a in range(cell.geometric_dimension()): + for b in range(cell.geometric_dimension()): + form = Lvu[a, b]*dx fd = compute_form_data(form) pf = fd.preprocessed_form - a = expand_indices(pf) - # print (i,j), str(a) + expand_indices(pf) k = Index() - for i in range(cell.geometric_dimension()): - for j in range(cell.geometric_dimension()): - actual = Lvu[i, j] - expected = du*u[i].dx(j)*dv + u[k]*du.dx(k)*dv + for a in range(cell.geometric_dimension()): + for b in range(cell.geometric_dimension()): + actual = Lvu[a, b] + expected = du*u[a].dx(b)*dv + u[k]*du.dx(k)*dv assertEqualBySampling(actual, expected) # --- User provided derivatives of coefficients @@ -654,10 +638,10 @@ def test_mass_derived_from_functional(self): f = (w**2/2)*dx L = w*v*dx - a = u*v*dx + a = u*v*dx # noqa: F841 F = derivative(f, w, v) - J1 = derivative(L, w, u) - J2 = derivative(F, w, u) + J1 = derivative(L, w, u) # noqa: F841 + J2 = derivative(F, w, u) # noqa: F841 # TODO: assert something # --- Interaction with replace @@ -715,6 +699,7 @@ def test_index_simplification_reference_grad(self): assert expr.ufl_free_indices == () assert expr.ufl_shape == () + # --- Scratch space def test_foobar(self): @@ -737,5 +722,5 @@ def NS_a(u, v): return inner(epsilon(u), epsilon(v)) L = NS_a(U, v)*dx - a = derivative(L, U, du) + a = derivative(L, U, du) # noqa: F841 # TODO: assert something diff --git a/test/test_diff.py b/test/test_diff.py index d0eeeac57..47305bc4e 100755 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -1,15 +1,12 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2009-02-17 -- 2014-10-14" import pytest -import math -from ufl import * -from ufl.constantvalue import as_ufl +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, SpatialCoordinate, VectorElement, as_vector, atan, + cos, diff, exp, indices, ln, sin, tan, triangle, variable) from ufl.algorithms import expand_derivatives +from ufl.constantvalue import as_ufl def get_variables(): diff --git a/test/test_domains.py b/test/test_domains.py index 2422a8822..81671aba4 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -1,15 +1,12 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -""" -Tests of domain language and attaching domains to forms. -""" +"""Tests of domain language and attaching domains to forms.""" -from mockobjects import MockMesh, MockMeshFunction import pytest +from mockobjects import MockMesh -from ufl import * -from ufl.domain import as_domain, default_domain +from ufl import (Cell, Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, ds, dS, dx, hexahedron, + interval, quadrilateral, tetrahedron, triangle) from ufl.algorithms import compute_form_data +from ufl.domain import as_domain, default_domain all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) @@ -197,14 +194,14 @@ def test_everywhere_integrals_with_backwards_compatibility(): # Check some integral data assert ida.integral_type == "cell" - assert(len(ida.subdomain_id) == 1) + assert len(ida.subdomain_id) == 1 assert ida.subdomain_id[0] == "otherwise" assert ida.metadata == {} # Integrands are not equal because of renumbering itg1 = ida.integrals[0].integrand() itg2 = a.integrals()[0].integrand() - assert type(itg1) == type(itg2) + assert type(itg1) is type(itg2) assert itg1.ufl_element() == itg2.ufl_element() @@ -217,298 +214,33 @@ def test_merge_sort_integral_data(): c = Constant(D) a = c * dS((2, 4)) + u * dx + u * ds + 2 * u * dx(3) + 2 * c * dS + 2 * u * dx((1, 4)) form_data = compute_form_data(a, do_append_everywhere_integrals=False).integral_data - assert(len(form_data) == 5) + assert len(form_data) == 5 # Check some integral data assert form_data[0].integral_type == "cell" - assert(len(form_data[0].subdomain_id) == 1) + assert len(form_data[0].subdomain_id) == 1 assert form_data[0].subdomain_id[0] == "otherwise" assert form_data[0].metadata == {} assert form_data[1].integral_type == "cell" - assert(len(form_data[1].subdomain_id) == 3) + assert len(form_data[1].subdomain_id) == 3 assert form_data[1].subdomain_id[0] == 1 assert form_data[1].subdomain_id[1] == 3 assert form_data[1].subdomain_id[2] == 4 assert form_data[1].metadata == {} assert form_data[2].integral_type == "exterior_facet" - assert(len(form_data[2].subdomain_id) == 1) + assert len(form_data[2].subdomain_id) == 1 assert form_data[2].subdomain_id[0] == "otherwise" assert form_data[2].metadata == {} assert form_data[3].integral_type == "interior_facet" - assert(len(form_data[3].subdomain_id) == 1) + assert len(form_data[3].subdomain_id) == 1 assert form_data[3].subdomain_id[0] == "otherwise" assert form_data[3].metadata == {} assert form_data[4].integral_type == "interior_facet" - assert(len(form_data[4].subdomain_id) == 2) + assert len(form_data[4].subdomain_id) == 2 assert form_data[4].subdomain_id[0] == 2 assert form_data[4].subdomain_id[1] == 4 assert form_data[4].metadata == {} - - -def xtest_mixed_elements_on_overlapping_regions(): # Old sketch, not working - - # Create domain and both disjoint and overlapping regions - cell = tetrahedron - D = Mesh(cell, label='D') - DD = Region(D, (0, 4), "DD") - DL = Region(D, (1, 2), "DL") - DR = Region(D, (2, 3), "DR") - - # Create function spaces on D - V = FiniteElement("CG", D, 1) - VD = FiniteElement("DG", DD, 1) - VC = FiniteElement("R", DD, 0) - VL = VectorElement("DG", DL, 2) - VR = FiniteElement("CG", DR, 3) - - # Create mixed space over all these spaces - M = MixedElement(V, VD, VC, VL, VR) - - # Check that we can get the degree for each value component of the mixed - # space - assert M.degree(0) == 1 - assert M.degree(1) == 1 - assert M.degree(2) == 0 - - assert M.degree(3) == 2 # Vector element - assert M.degree(4) == 2 - assert M.degree(5) == 2 - - assert M.degree(6) == 3 - assert M.degree() == 3 - - # Check that we can get the domain for each value component of the mixed - # space - assert M.ufl_domain(0) == D - assert M.ufl_domain(1) == DD - assert M.ufl_domain(2) == DD - - assert M.ufl_domain(3) == DL # Vector element - assert M.ufl_domain(4) == DL - assert M.ufl_domain(5) == DL - - assert M.ufl_domain(6) == DR - # assert M.ufl_domain() == None # FIXME: What? - - # Create a mixed function and fetch components with names for more - # readable test code below - m = Coefficient(M) - md = m[1] - mc = m[2] - ml = as_vector((m[3], m[4], m[5])) - mr = m[6] - - # These should all work out fine with function and integration domains - # perfectly matching - a = m[0] ** 2 * dx(D) - ad = (md ** 2 + mc ** 2) * dx(DD) - al = ml ** 2 * dx(DL) - ar = mr ** 2 * dx(DR) - - # TODO: Check properties of forms, maybe by computing and inspecting form - # data. - - # These should all work out fine with because integration domains are - # contained in the function domains - ad = m[0] ** 2 * dx(DD) - al = m[0] ** 2 * dx(DL) - ar = m[0] ** 2 * dx("DR") - a0 = m[0] ** 2 * dx(0) - a12 = m[0] ** 2 * dx((1, 2)) - a3 = m[0] ** 2 * dx(3) - - # TODO: Check properties of forms, maybe by computing and inspecting form - # data. - - # These should fail because the functions are undefined on the integration domains - # self.assertRaises(BaseException, lambda: mr**2*dx(DD)) # FIXME: Make this fail - # self.assertRaises(BaseException, lambda: mr**2*dx(DD)) # FIXME: Make this - # fail - - -def xtest_form_domain_model(): # Old sketch, not working - # Create domains with different celltypes - # TODO: Figure out PyDOLFIN integration with Mesh - DA = Mesh(tetrahedron, label='DA') - DB = Mesh(hexahedron, label='DB') - - # Check python protocol behaviour - assert DA != DB - assert {DA, DA} == {DA} - assert {DB, DB} == {DB} - assert {DA, DB} == {DB, DA} - assert sorted((DA, DB, DA, DB)) == sorted((DB, DA, DA, DB)) - - # Check basic properties - assert DA.name() == 'DA' - assert DA.geometric_dimension() == 3 - assert DA.topological_dimension() == 3 - assert DA.ufl_cell() == tetrahedron - - # Check region/domain getters - assert DA.top_domain() == DA - assert DA.subdomain_ids() == None - # assert DA.region_names() == [] - # assert DA.regions() == [] - - # BDA = Boundary(DA) # TODO - # IDAB = Intersection(DA,DB) # TODO - # ODAB = Overlap(DA,DB) # TODO - - # Create overlapping regions of each domain - DAL = Region(DA, (1, 2), "DAL") - DAR = Region(DA, (2, 3), "DAR") - DBL = Region(DB, (0, 1), "DBL") - DBR = Region(DB, (1, 4), "DBR") - - # Check that regions are available through domains - # assert DA.region_names() == ['DAL', 'DAR'] - # assert DA.regions() == [DAL, DAR] - # assert DA["DAR"] == DAR - # assert DA["DAL"] == DAL - - # Create function spaces on DA - VA = FiniteElement("CG", DA, 1) - VAL = FiniteElement("CG", DAL, 1) - VAR = FiniteElement("CG", DAR, 1) - - # Create function spaces on DB - VB = FiniteElement("CG", DB, 1) - VBL = FiniteElement("CG", DBL, 1) - VBR = FiniteElement("CG", DBR, 1) - - # Check that regions are available through elements - assert VA.ufl_domain() == DA - assert VAL.ufl_domain() == DAL - assert VAR.ufl_domain() == DAR - - # Create functions in each space on DA - fa = Coefficient(VA) - fal = Coefficient(VAL) - far = Coefficient(VAR) - - # Create functions in each space on DB - fb = Coefficient(VB) - fbl = Coefficient(VBL) - fbr = Coefficient(VBR) - - # Checks of coefficient domains is covered well in the mixed element test - - # Create measure objects directly based on domain and region objects - dxa = dx(DA) - dxal = dx(DAL) - dxar = dx('DAR') # Get Region by name - - # Create measure proxy objects from strings and ints, requiring - # domains and regions to be part of their integrands - dxb = dx('DB') # Get Mesh by name - dxbl = dx(Region(DB, (1, 4), 'DBL2')) - # Provide a region with different name but same subdomain ids as - # DBL - dxbr = dx((1, 4)) - # Assume unique Mesh and provide subdomain ids explicitly - - # Not checking measure objects in detail, as long as - # they carry information to construct integrals below - # they are good to go. - - # Create integrals on each region with matching spaces and measures - Ma = fa * dxa - Mar = far * dxar - Mal = fal * dxal - Mb = fb * dxb - Mbr = fbr * dxbr - Mbl = fbl * dxbl - - # TODO: Check forms, by getting and inspecting domains somehow. - - # TODO: How do we express the distinction between "everywhere" and - # "nowhere"? subdomain_ids=None vs []? - - # Create forms from integrals over overlapping regions on same top domain - Marl = Mar + Mal - Mbrl = Mbr + Mbl - - # Create forms from integrals over top domain and regions - Mac = Ma + Marl - Mbc = Mb + Mbrl - - # Create forms from separate top domains - Mab = Ma + Mb - - # Create forms from separate top domains with overlapping regions - Mabrl = Mac + Mbc - - # self.assertFalse(True) # Getting here, but it's not bloody likely that - # everything above is actually working. Add assertions! - - -def xtest_subdomain_stuff(): # Old sketch, not working - D = Mesh(triangle) - - D1 = D[1] - D2 = D[2] - D3 = D[3] - - DL = Region(D, (D1, D2), 'DL') - DR = Region(D, (D2, D3), 'DR') - DM = Overlap(DL, DR) - - assert DM == D2 - - VL = VectorElement(DL, "CG", 1) - VR = FiniteElement(DR, "CG", 2) - V = VL * VR - - def sub_elements_on_subdomains(W): - # Get from W: (already there) - subelements = (VL, VR) - # Get from W: - subdomains = (D1, D2, D3) - # Build in W: - dom2elm = {D1: (VL,), - D2: (VL, VR), - D3: (VR,), } - # Build in W: - elm2dom = {VL: (D1, D2), - VR: (D2, D3)} - - # ElementSwitch represents joining of elements restricted to disjunct - # subdomains. - - # An element restricted to a domain union becomes a switch - # of elements restricted to each disjoint subdomain - VL_D1 = VectorElement(D1, "CG", 1) - VL_D2 = VectorElement(D2, "CG", 1) - VLalt = ElementSwitch({D1: VL_D1, - D2: VL_D2}) - # Ditto - VR_D2 = FiniteElement(D2, "CG", 2) - VR_D3 = FiniteElement(D3, "CG", 2) - VRalt = ElementSwitch({D2: VR_D2, - D3: VR_D3}) - # A mixed element of ElementSwitches is mixed only on the overlapping - # domains: - Valt1 = VLalt * VRalt - Valt2 = ElementSwitch({D1: VL_D1, - D2: VL_D2 * VR_D2, - D3: VR_D3}) - - ul, ur = TrialFunctions(V) - vl, vr = TestFunctions(V) - - # Implemented by user: - al = dot(ul, vl) * dx(DL) - ar = ur * vr * dx(DR) - - # Disjunctified by UFL: - alonly = dot(ul, vl) * dx(D1) - # integral_1 knows that only subelement VL is active - am = (dot(ul, vl) + ur * vr) * dx(D2) - # integral_2 knows that both subelements are active - aronly = ur * vr * \ - dx(D3) # integral_3 knows that only subelement VR is active diff --git a/test/test_duals.py b/test/test_duals.py index 272294391..f21054857 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -1,21 +1,16 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -from ufl import (FiniteElement, FunctionSpace, MixedFunctionSpace, - Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument, - TestFunction, TrialFunction, Adjoint, Action, - action, adjoint, derivative, inner, tetrahedron, triangle, interval, dx) -from ufl.constantvalue import Zero -from ufl.form import ZeroBaseForm - __authors__ = "India Marsden" -__date__ = "2020-12-28 -- 2020-12-28" +__date__ = "2020-12-28" import pytest -from ufl.domain import default_domain -from ufl.duals import is_primal, is_dual +from ufl import (Action, Adjoint, Argument, Coargument, Coefficient, Cofunction, FiniteElement, FormSum, FunctionSpace, + Matrix, MixedFunctionSpace, TestFunction, TrialFunction, action, adjoint, derivative, dx, inner, + interval, tetrahedron, triangle) from ufl.algorithms.ad import expand_derivatives +from ufl.constantvalue import Zero +from ufl.domain import default_domain +from ufl.duals import is_dual, is_primal +from ufl.form import ZeroBaseForm def test_mixed_functionspace(self): @@ -72,7 +67,7 @@ def test_dual_coefficients(): assert not is_primal(w) with pytest.raises(ValueError): - x = Cofunction(V) + Cofunction(V) def test_dual_arguments(): @@ -95,7 +90,7 @@ def test_dual_arguments(): assert not is_primal(w) with pytest.raises(ValueError): - x = Coargument(V, 4) + Coargument(V, 4) def test_addition(): diff --git a/test/test_elements.py b/test/test_elements.py index 49a700cd7..f096860e4 100755 --- a/test/test_elements.py +++ b/test/test_elements.py @@ -1,11 +1,5 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -# Last changed: 2014-02-24 - -import pytest - -from ufl import * +from ufl import (Coefficient, FiniteElement, MixedElement, TensorElement, VectorElement, WithMapping, dx, hexahedron, + inner, interval, quadrilateral, tetrahedron, triangle) all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) @@ -80,7 +74,7 @@ def test_tensor_symmetry(): def test_mixed_tensor_symmetries(): - from ufl.algorithms import expand_indices, expand_compounds + from ufl.algorithms import expand_compounds, expand_indices S = FiniteElement('CG', triangle, 1) V = VectorElement('CG', triangle, 1) @@ -180,6 +174,7 @@ def test_missing_cell(): element = TensorElement("DG L2", cell, 1, shape=(2, 2)) assert element == eval(repr(element)) + def test_invalid_degree(): cell = triangle for degree in (1, None): diff --git a/test/test_equals.py b/test/test_equals.py index fd9522ead..4c36ad7cd 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -1,14 +1,6 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- +"""Test of expression comparison.""" -""" -Test of expression comparison. -""" - -import pytest - -# This imports everything external code will see from ufl -from ufl import * +from ufl import Coefficient, Cofunction, FiniteElement, triangle def test_comparison_of_coefficients(): @@ -36,6 +28,7 @@ def test_comparison_of_coefficients(): assert not v1 == u1 assert not v2 == u2 + def test_comparison_of_cofunctions(): V = FiniteElement("CG", triangle, 1) U = FiniteElement("CG", triangle, 2) @@ -62,7 +55,6 @@ def test_comparison_of_cofunctions(): assert not v2 == u2 - def test_comparison_of_products(): V = FiniteElement("CG", triangle, 1) v = Coefficient(V) diff --git a/test/test_evaluate.py b/test/test_evaluate.py index 30e3a34f1..29488e207 100755 --- a/test/test_evaluate.py +++ b/test/test_evaluate.py @@ -1,13 +1,11 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2009-02-13 -- 2009-02-13" -import pytest import math -from ufl import * +from ufl import (Argument, Coefficient, FiniteElement, Identity, SpatialCoordinate, as_matrix, as_vector, cos, cross, + det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, sym, tan, tetrahedron, tr, + triangle) from ufl.constantvalue import as_ufl @@ -27,14 +25,14 @@ def testZero(): def testIdentity(): cell = triangle - I = Identity(cell.geometric_dimension()) + ident = Identity(cell.geometric_dimension()) - s = 123 * I[0, 0] + s = 123 * ident[0, 0] e = s((5, 7)) v = 123 assert e == v - s = 123 * I[1, 0] + s = 123 * ident[1, 0] e = s((5, 7)) v = 0 assert e == v @@ -107,9 +105,9 @@ def testIndexSum(): def testIndexSum2(): cell = triangle x = SpatialCoordinate(cell) - I = Identity(cell.geometric_dimension()) + ident = Identity(cell.geometric_dimension()) i, j = indices(2) - s = (x[i] * x[j]) * I[i, j] + s = (x[i] * x[j]) * ident[i, j] e = s((5, 7)) # v = sum_i sum_j x_i x_j delta_ij = x_0 x_0 + x_1 x_1 v = 5 ** 2 + 7 ** 2 @@ -277,15 +275,15 @@ def test_cross(): as_vector((0, x[2], 0))], ] for t in ts: - for i in range(3): - for j in range(3): - cij = cross(t[i], t[j]) - dij = dot(cij, cij) - eij = dij(xv) - tni = dot(t[i], t[i])(xv) - tnj = dot(t[j], t[j])(xv) - vij = tni * tnj if i != j else 0 - assert eij == vij + for a in range(3): + for b in range(3): + cab = cross(t[a], t[b]) + dab = dot(cab, cab) + eab = dab(xv) + tna = dot(t[a], t[a])(xv) + tnb = dot(t[b], t[b])(xv) + vab = tna * tnb if a != b else 0 + assert eab == vab def xtest_dev(): diff --git a/test/test_expand_indices.py b/test/test_expand_indices.py index f0ae687c6..77977552f 100755 --- a/test/test_expand_indices.py +++ b/test/test_expand_indices.py @@ -1,20 +1,17 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" __date__ = "2009-03-19 -- 2012-03-20" # Modified by Anders Logg, 2008 # Modified by Garth N. Wells, 2009 -import pytest import math -from pprint import * -from ufl import * -from ufl.algorithms import * +import pytest + +from ufl import (Coefficient, FiniteElement, Identity, TensorElement, VectorElement, as_tensor, cos, det, div, dot, dx, + exp, grad, i, inner, j, k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) +from ufl.algorithms import compute_form_data, expand_derivatives, expand_indices from ufl.algorithms.renumbering import renumber_indices -from ufl.classes import Sum, Product # TODO: Test expand_indices2 throuroughly for correctness, then efficiency: # expand_indices, expand_indices2 = expand_indices2, expand_indices @@ -191,7 +188,6 @@ def test_basic_expand_indices(self, fixt): def test_expand_indices_index_sum(self, fixt): - sf = fixt.sf vf = fixt.vf tf = fixt.tf compare = fixt.compare @@ -208,7 +204,6 @@ def test_expand_indices_index_sum(self, fixt): def test_expand_indices_derivatives(self, fixt): sf = fixt.sf vf = fixt.vf - tf = fixt.tf compare = fixt.compare # Basic derivatives @@ -219,15 +214,13 @@ def test_expand_indices_derivatives(self, fixt): def test_expand_indices_hyperelasticity(self, fixt): - sf = fixt.sf vf = fixt.vf - tf = fixt.tf compare = fixt.compare # Deformation gradient - I = Identity(2) + ident = Identity(2) u = vf - F = I + grad(u) + F = ident + grad(u) # F = (1 + vf[0].dx(0), vf[0].dx(1), vf[1].dx(0), 1 + vf[1].dx(1)) # F = (1 + 0.50, 0.51, 0.70, 1 + 0.71) F00 = 1 + 0.50 @@ -254,7 +247,7 @@ def test_expand_indices_hyperelasticity(self, fixt): compare(C[1, 0], C10) compare(C[1, 1], C11) - E = (C-I)/2 + E = (C-ident)/2 E00 = (C00-1)/2 E01 = (C01)/2 E10 = (C10)/2 @@ -323,26 +316,3 @@ def test_expand_indices_nabla_div_grad(self, fixt): a = nabla_div(nabla_grad(tf)) compare(inner(a, a), (10.00+11.00)**2 + (10.01+11.01)**2 + (10.10+11.10)**2 + (10.11+11.11)**2) - - -def xtest_expand_indices_list_tensor_problem(self, fixt): - print() - print(('='*40)) - # TODO: This is the case marked in the expand_indices2 implementation - # as not working. Fix and then try expand_indices2 on other tests! - V = VectorElement("CG", triangle, 1) - w = Coefficient(V) - v = as_vector([w[0], 0]) - a = v[i]*w[i] - # TODO: Compare - print((type(a), str(a))) - A, comp = a.ufl_operands - print((type(A), str(A))) - print((type(comp), str(comp))) - - ei1 = expand_indices(a) - ei2 = expand_indices2(a) - print((str(ei1))) - print((str(ei2))) - print(('='*40)) - print() diff --git a/test/test_external_operator.py b/test/test_external_operator.py index d586c04a6..4a40a1c75 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -1,23 +1,18 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- +"""Test ExternalOperator object.""" __authors__ = "Nacime Bouziani" __date__ = "2019-03-26" - -""" -Test ExternalOperator object -""" - import pytest # This imports everything external code will see from ufl -from ufl import * -from ufl.core.external_operator import ExternalOperator -from ufl.form import BaseForm -from ufl.algorithms.apply_derivatives import apply_derivatives +from ufl import (Action, Argument, Coefficient, Constant, FiniteElement, Form, FunctionSpace, TestFunction, + TrialFunction, action, adjoint, cos, derivative, dx, inner, sin, triangle) from ufl.algorithms import expand_derivatives +from ufl.algorithms.apply_derivatives import apply_derivatives +from ufl.core.external_operator import ExternalOperator from ufl.domain import default_domain +from ufl.form import BaseForm @pytest.fixture @@ -183,9 +178,8 @@ def test_differentiation_procedure_action(V1, V2): def test_extractions(V1): - from ufl.algorithms.analysis import (extract_coefficients, extract_arguments, - extract_arguments_and_coefficients, extract_base_form_operators, - extract_constants) + from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, + extract_base_form_operators, extract_coefficients, extract_constants) u = Coefficient(V1) c = Constant(triangle) @@ -258,9 +252,14 @@ def test_adjoint_action_jacobian(V1, V2, V3): vstar_N, = N.arguments() # Arguments for the Gateaux-derivative - u_hat = lambda number: Argument(V1, number) # V1: degree 1 # dFdu.arguments()[-1] - m_hat = lambda number: Argument(V2, number) # V2: degree 2 # dFdm.arguments()[-1] - vstar_N = lambda number: Argument(V3.dual(), number) # V3: degree 3 + def u_hat(number): + return Argument(V1, number) # V1: degree 1 # dFdu.arguments()[-1] + + def m_hat(number): + return Argument(V2, number) # V2: degree 2 # dFdm.arguments()[-1] + + def vstar_N(number): + return Argument(V3.dual(), number) # V3: degree 3 # Coefficients for the action w = Coefficient(V1) # for u @@ -410,9 +409,8 @@ def test_multiple_external_operators(V1, V2): F = (inner(N1, v) + inner(N2, v) + inner(N3, v) + inner(N4, v)) * dx dFdu = expand_derivatives(derivative(F, u)) - assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) +\ - Action(dFdN4_partial, Action(dN4dN1_partial, dN1du)) +\ - Action(dFdN4_partial, dN4du_partial) + assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) + Action( + dFdN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dFdN4_partial, dN4du_partial) dFdm = expand_derivatives(derivative(F, m)) assert dFdm == Action(dFdN1, dN1dm) + Action(dFdN4_partial, Action(dN4dN1_partial, dN1dm)) @@ -440,7 +438,8 @@ def test_multiple_external_operators(V1, V2): dFdN5_partial = (inner(w, v) + inner(u * w, v)) * dx dN5dN4_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(1, 0), argument_slots=N4.arguments() + (w,)) dN5du_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(0, 1), argument_slots=N4.arguments() + (w,)) - dN5du = Action(dN5dN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dN5dN4_partial, dN4du_partial) + dN5du_partial + dN5du = Action(dN5dN4_partial, Action(dN4dN1_partial, dN1du)) + Action( + dN5dN4_partial, dN4du_partial) + dN5du_partial dFdu = expand_derivatives(derivative(F, u)) assert dFdu == dFdu_partial + Action(dFdN1_partial, dN1du) + Action(dFdN5_partial, dN5du) diff --git a/test/test_ffcforms.py b/test/test_ffcforms.py index 16fbcc5ba..ea01e45c3 100755 --- a/test/test_ffcforms.py +++ b/test/test_ffcforms.py @@ -1,8 +1,9 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -"""Unit tests including all demo forms from FFC 0.5.0. The forms are +"""Test FFC forms. + +Unit tests including all demo forms from FFC 0.5.0. The forms are modified (with comments) to work with the UFL notation which differs -from the FFC notation in some places.""" +from the FFC notation in some places. +""" __author__ = "Anders Logg (logg@simula.no) et al." __date__ = "2008-04-09 -- 2008-09-26" @@ -12,29 +13,27 @@ # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. -import pytest -from ufl import * +from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, TensorElement, TestFunction, TestFunctions, + TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, div, dot, ds, dS, dx, grad, i, + inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) def testConstant(): - element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) u = TrialFunction(element) - f = Coefficient(element) c = Constant("triangle") d = VectorConstant("triangle") - a = c * dot(grad(v), grad(u)) * dx + a = c * dot(grad(v), grad(u)) * dx # noqa: F841 # FFC notation: L = dot(d, grad(v))*dx - L = inner(d, grad(v)) * dx + L = inner(d, grad(v)) * dx # noqa: F841 def testElasticity(): - element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) @@ -45,19 +44,17 @@ def eps(v): return grad(v) + (grad(v)).T # FFC notation: a = 0.25*dot(eps(v), eps(u))*dx - a = 0.25 * inner(eps(v), eps(u)) * dx + a = 0.25 * inner(eps(v), eps(u)) * dx # noqa: F841 def testEnergyNorm(): - element = FiniteElement("Lagrange", "tetrahedron", 1) v = Coefficient(element) - a = (v * v + dot(grad(v), grad(v))) * dx + a = (v * v + dot(grad(v), grad(v))) * dx # noqa: F841 def testEquation(): - element = FiniteElement("Lagrange", "triangle", 1) k = 0.1 @@ -68,12 +65,11 @@ def testEquation(): F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx - a = lhs(F) - L = rhs(F) + a = lhs(F) # noqa: F841 + L = rhs(F) # noqa: F841 def testFunctionOperators(): - element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) @@ -83,12 +79,10 @@ def testFunctionOperators(): # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx - a = sqrt(1 / abs(1 / f)) * sqrt(g) * \ - dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx + a = sqrt(1 / abs(1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx # noqa: F841 def testHeat(): - element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) @@ -98,29 +92,26 @@ def testHeat(): f = Coefficient(element) k = Constant("triangle") - a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx - L = v * u0 * dx + k * v * f * dx + a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx # noqa: F841 + L = v * u0 * dx + k * v * f * dx # noqa: F841 def testMass(): - element = FiniteElement("Lagrange", "tetrahedron", 3) v = TestFunction(element) u = TrialFunction(element) - a = v * u * dx + a = v * u * dx # noqa: F841 def testMixedMixedElement(): - P3 = FiniteElement("Lagrange", "triangle", 3) - element = (P3 * P3) * (P3 * P3) + element = (P3 * P3) * (P3 * P3) # noqa: F841 def testMixedPoisson(): - q = 1 BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) @@ -133,12 +124,11 @@ def testMixedPoisson(): f = Coefficient(DG) - a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx - L = w * f * dx + a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx # noqa: F841 + L = w * f * dx # noqa: F841 def testNavierStokes(): - element = VectorElement("Lagrange", "tetrahedron", 1) v = TestFunction(element) @@ -147,11 +137,10 @@ def testNavierStokes(): w = Coefficient(element) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx - a = v[i] * w[j] * Dx(u[i], j) * dx + a = v[i] * w[j] * Dx(u[i], j) * dx # noqa: F841 def testNeumannProblem(): - element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) @@ -160,36 +149,32 @@ def testNeumannProblem(): g = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx - a = inner(grad(v), grad(u)) * dx + a = inner(grad(v), grad(u)) * dx # noqa: F841 # FFC notation: L = dot(v, f)*dx + dot(v, g)*ds - L = inner(v, f) * dx + inner(v, g) * ds + L = inner(v, f) * dx + inner(v, g) * ds # noqa: F841 def testOptimization(): - element = FiniteElement("Lagrange", "triangle", 3) v = TestFunction(element) u = TrialFunction(element) f = Coefficient(element) - a = dot(grad(v), grad(u)) * dx - L = v * f * dx + a = dot(grad(v), grad(u)) * dx # noqa: F841 + L = v * f * dx # noqa: F841 def testP5tet(): - - element = FiniteElement("Lagrange", tetrahedron, 5) + element = FiniteElement("Lagrange", tetrahedron, 5) # noqa: F841 def testP5tri(): - - element = FiniteElement("Lagrange", triangle, 5) + element = FiniteElement("Lagrange", triangle, 5) # noqa: F841 def testPoissonDG(): - element = FiniteElement("Discontinuous Lagrange", triangle, 1) v = TestFunction(element) @@ -215,19 +200,18 @@ def testPoissonDG(): # - dot(mult(v,n), grad(u))*ds \ # + gamma/h*v*u*ds - a = inner(grad(v), grad(u)) * dx \ - - inner(avg(grad(v)), jump(u, n)) * dS \ - - inner(jump(v, n), avg(grad(u))) * dS \ - + alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS \ - - inner(grad(v), u * n) * ds \ - - inner(u * n, grad(u)) * ds \ - + gamma / h * v * u * ds + a = inner(grad(v), grad(u)) * dx + a -= inner(avg(grad(v)), jump(u, n)) * dS + a -= inner(jump(v, n), avg(grad(u))) * dS + a += alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS + a -= inner(grad(v), u * n) * ds + a -= inner(u * n, grad(u)) * ds + a += gamma / h * v * u * ds - L = v * f * dx + v * gN * ds + L = v * f * dx + v * gN * ds # noqa: F841 def testPoisson(): - element = FiniteElement("Lagrange", "triangle", 1) v = TestFunction(element) @@ -235,12 +219,11 @@ def testPoisson(): f = Coefficient(element) # Note: inner() also works - a = dot(grad(v), grad(u)) * dx - L = v * f * dx + a = dot(grad(v), grad(u)) * dx # noqa: F841 + L = v * f * dx # noqa: F841 def testPoissonSystem(): - element = VectorElement("Lagrange", "triangle", 1) v = TestFunction(element) @@ -248,24 +231,21 @@ def testPoissonSystem(): f = Coefficient(element) # FFC notation: a = dot(grad(v), grad(u))*dx - a = inner(grad(v), grad(u)) * dx + a = inner(grad(v), grad(u)) * dx # noqa: F841 # FFC notation: L = dot(v, f)*dx - L = inner(v, f) * dx + L = inner(v, f) * dx # noqa: F841 def testProjection(): - # Projections are not supported by UFL and have been broken # in FFC for a while. For DOLFIN, the current (global) L^2 # projection can be extended to handle also local projections. - P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) P1 = FiniteElement("Lagrange", "triangle", 1) - P2 = FiniteElement("Lagrange", "triangle", 2) - v = TestFunction(P1) - f = Coefficient(P1) + v = TestFunction(P1) # noqa: F841 + f = Coefficient(P1) # noqa: F841 # pi0 = Projection(P0) # pi1 = Projection(P1) @@ -275,7 +255,6 @@ def testProjection(): def testQuadratureElement(): - element = FiniteElement("Lagrange", "triangle", 2) # FFC notation: @@ -292,12 +271,11 @@ def testQuadratureElement(): sig0 = Coefficient(sig) f = Coefficient(element) - a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx - L = v * f * dx - dot(grad(v), sig0) * dx + a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx # noqa: F841 + L = v * f * dx - dot(grad(v), sig0) * dx # noqa: F841 def testStokes(): - # UFLException: Shape mismatch in sum. P2 = VectorElement("Lagrange", "triangle", 2) @@ -311,35 +289,30 @@ def testStokes(): # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx - a = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx + a = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx # noqa: F841 - L = dot(v, f) * dx + L = dot(v, f) * dx # noqa: F841 def testSubDomain(): - element = FiniteElement("CG", "tetrahedron", 1) - v = TestFunction(element) - u = TrialFunction(element) f = Coefficient(element) - M = f * dx(2) + f * ds(5) + M = f * dx(2) + f * ds(5) # noqa: F841 def testSubDomains(): - element = FiniteElement("CG", "tetrahedron", 1) v = TestFunction(element) u = TrialFunction(element) - a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * \ - ds(1) + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) + a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * ds(1) + a += v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) def testTensorWeightedPoisson(): - # FFC notation: # P1 = FiniteElement("Lagrange", "triangle", 1) # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) @@ -364,11 +337,10 @@ def testTensorWeightedPoisson(): u = TrialFunction(P1) C = Coefficient(P0) - a = inner(grad(v), C * grad(u)) * dx + a = inner(grad(v), C * grad(u)) * dx # noqa: F841 def testVectorLaplaceGradCurl(): - def HodgeLaplaceGradCurl(element, felement): (tau, v) = TestFunctions(element) (sigma, u) = TrialFunctions(element) diff --git a/test/test_form.py b/test/test_form.py index 71f4a2a20..057e89319 100755 --- a/test/test_form.py +++ b/test/test_form.py @@ -1,8 +1,7 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- import pytest -from ufl import * +from ufl import (Coefficient, Cofunction, FiniteElement, Form, FormSum, FunctionSpace, Mesh, SpatialCoordinate, + TestFunction, TrialFunction, VectorElement, dot, ds, dx, grad, inner, nabla_grad, triangle) from ufl.form import BaseForm @@ -127,7 +126,7 @@ def test_form_call(): f = Coefficient(V) g = Coefficient(V) a = g*inner(grad(v), grad(u))*dx - M = a(f, f, coefficients={ g: 1 }) + M = a(f, f, coefficients={g: 1}) assert M == grad(f)**2*dx import sys @@ -136,27 +135,27 @@ def test_form_call(): M = eval("(a @ f) @ g") assert M == g*f*dx + def test_formsum(mass): V = FiniteElement("CG", triangle, 1) v = Cofunction(V) - assert(v + mass) - assert(mass + v) - assert(isinstance((mass+v), FormSum)) + assert v + mass + assert mass + v + assert isinstance((mass+v), FormSum) - assert(len((mass + v + v).components()) == 3) + assert len((mass + v + v).components()) == 3 # Variational forms are summed appropriately - assert(len((mass + v + mass).components()) == 2) - - assert(v - mass) - assert(mass - v) - assert(isinstance((mass+v), FormSum)) + assert len((mass + v + mass).components()) == 2 - assert(-v) - assert(isinstance(-v, BaseForm)) - assert((-v).weights()[0] == -1) + assert v - mass + assert mass - v + assert isinstance((mass+v), FormSum) - assert(2 * v) - assert(isinstance(2 * v, BaseForm)) - assert((2 * v).weights()[0] == 2) + assert -v + assert isinstance(-v, BaseForm) + assert (-v).weights()[0] == -1 + assert 2 * v + assert isinstance(2 * v, BaseForm) + assert (2 * v).weights()[0] == 2 diff --git a/test/test_grad.py b/test/test_grad.py index a8d5eebdb..4b5d9a996 100755 --- a/test/test_grad.py +++ b/test/test_grad.py @@ -1,16 +1,7 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- +"""Test use of grad in various situations.""" -""" -Test use of grad in various situations. -""" - -import pytest - -# This imports everything external code will see from ufl -from ufl import * - -# from ufl.classes import ... +from ufl import (Coefficient, Constant, FiniteElement, TensorConstant, TensorElement, VectorConstant, VectorElement, + div, dx, grad, indices, inner, interval, tetrahedron, triangle) from ufl.algorithms import compute_form_data @@ -125,17 +116,15 @@ def eval_t(x, derivatives=()): a8 = inner(div(grad(v)), v)*dx a9 = inner(div(grad(t)), t)*dx - fd0 = compute_form_data(a0) - fd1 = compute_form_data(a1) - fd2 = compute_form_data(a2) - fd3 = compute_form_data(a3) - - fd4 = compute_form_data(a4) - fd5 = compute_form_data(a5) - fd6 = compute_form_data(a6) + compute_form_data(a0) + compute_form_data(a1) + compute_form_data(a2) + compute_form_data(a3) - fd7 = compute_form_data(a7) - fd8 = compute_form_data(a8) - fd9 = compute_form_data(a9) + compute_form_data(a4) + compute_form_data(a5) + compute_form_data(a6) - # self.assertTrue(False) # Just to show it runs + compute_form_data(a7) + compute_form_data(a8) + compute_form_data(a9) diff --git a/test/test_illegal.py b/test/test_illegal.py index ba59c1475..f5c8d06a0 100755 --- a/test/test_illegal.py +++ b/test/test_illegal.py @@ -1,10 +1,6 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - import pytest -from ufl import * -from ufl.algorithms import * +from ufl import Argument, Coefficient, FiniteElement, VectorElement # TODO: Add more illegal expressions to check! diff --git a/test/test_indexing.py b/test/test_indexing.py index 95d1029c7..bb618e8f3 100755 --- a/test/test_indexing.py +++ b/test/test_indexing.py @@ -1,8 +1,7 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- import pytest -from ufl import * -from ufl.classes import * + +from ufl import Index, SpatialCoordinate, outer, triangle +from ufl.classes import FixedIndex, Indexed, MultiIndex, Outer, Zero @pytest.fixture @@ -28,23 +27,16 @@ def test_annotated_literals(): assert z.ufl_shape == () assert z.ufl_free_indices == () assert z.ufl_index_dimensions == () - #assert z.free_indices() == () # Deprecated interface - #assert z.index_dimensions() == {} # Deprecated interface z = Zero((3,)) assert z.ufl_shape == (3,) assert z.ufl_free_indices == () assert z.ufl_index_dimensions == () - #assert z.free_indices() == () # Deprecated interface - #assert z.index_dimensions() == {} # Deprecated interface i = Index(count=2) j = Index(count=4) - # z = Zero((), (2, 4), (3, 5)) z = Zero((), (j, i), {i: 3, j: 5}) assert z.ufl_shape == () - #assert z.free_indices() == (i, j) # Deprecated interface - #assert z.index_dimensions() == {i: 3, j: 5} # Deprecated interface assert z.ufl_free_indices == (2, 4) assert z.ufl_index_dimensions == (3, 5) @@ -66,19 +58,3 @@ def test_fixed_indexing_of_expression(x1, x2, x3): mi = x000.ufl_operands[1] assert len(mi) == 3 assert mi.indices() == (FixedIndex(0),) * 3 - - -def test_indexed(): - pass - - -def test_indexsum(): - pass - - -def test_componenttensor(): - pass - - -def test_tensoralgebra(): - pass diff --git a/test/test_indices.py b/test/test_indices.py index a4b296dcb..564d356be 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -1,50 +1,29 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - import pytest -from ufl import * -# from ufl.indexutils import * -from ufl.algorithms import * +from ufl import (Argument, Coefficient, TensorElement, TestFunction, TrialFunction, VectorElement, as_matrix, as_tensor, + as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) from ufl.classes import IndexSum # TODO: add more expressions to test as many possible combinations of index notation as feasible... -def xtest_index_utils(self): - ii = indices(3) - assert ii == unique_indices(ii) - assert ii == unique_indices(ii+ii) - - assert () == repeated_indices(ii) - assert ii == repeated_indices(ii+ii) - - assert ii == shared_indices(ii, ii) - assert ii == shared_indices(ii, ii+ii) - assert ii == shared_indices(ii+ii, ii) - assert ii == shared_indices(ii+ii, ii+ii) - - assert ii == single_indices(ii) - assert () == single_indices(ii+ii) - - def test_vector_indices(self): element = VectorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) - a = u[i]*f[i]*dx - b = u[j]*f[j]*dx + u[i]*f[i]*dx + u[j]*f[j]*dx def test_tensor_indices(self): element = TensorElement("CG", "triangle", 1) u = Argument(element, 2) f = Coefficient(element) - a = u[i, j]*f[i, j]*dx - b = u[j, i]*f[i, j]*dx - c = u[j, i]*f[j, i]*dx + u[i, j]*f[i, j]*dx + u[j, i]*f[i, j]*dx + u[j, i]*f[j, i]*dx with pytest.raises(BaseException): - d = (u[i, i]+f[j, i])*dx + (u[i, i]+f[j, i])*dx def test_indexed_sum1(self): @@ -71,7 +50,7 @@ def test_indexed_sum3(self): u = Argument(element, 2) f = Coefficient(element) with pytest.raises(BaseException): - a = u[i]+f[j] + u[i]+f[j] def test_indexed_function1(self): @@ -80,7 +59,7 @@ def test_indexed_function1(self): u = Argument(element, 3) f = Coefficient(element) aarg = (u[i]+f[i])*v[i] - a = exp(aarg)*dx + exp(aarg)*dx def test_indexed_function2(self): @@ -88,34 +67,34 @@ def test_indexed_function2(self): v = Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) - bfun = cos(f[0]) - left = u[i] + f[i] + bfun = cos(f[0]) + left = u[i] + f[i] right = v[i] * bfun assert len(left.ufl_free_indices) == 1 assert left.ufl_free_indices[0] == i.count() assert len(right.ufl_free_indices) == 1 assert right.ufl_free_indices[0] == i.count() - b = left * right * dx + left * right * dx def test_indexed_function3(self): element = VectorElement("CG", "triangle", 1) - v = Argument(element, 2) + Argument(element, 2) u = Argument(element, 3) f = Coefficient(element) with pytest.raises(BaseException): - c = sin(u[i] + f[i])*dx + sin(u[i] + f[i])*dx def test_vector_from_indices(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(element) + u = TrialFunction(element) # legal vv = as_vector(u[i], i) uu = as_vector(v[j], j) - w = v + u + w = v + u ww = vv + uu assert len(vv.ufl_shape) == 1 assert len(uu.ufl_shape) == 1 @@ -125,14 +104,14 @@ def test_vector_from_indices(self): def test_matrix_from_indices(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) - - A = as_matrix(u[i]*v[j], (i, j)) - B = as_matrix(v[k]*v[k]*u[i]*v[j], (j, i)) - C = A + A - C = B + B - D = A + B + v = TestFunction(element) + u = TrialFunction(element) + + A = as_matrix(u[i]*v[j], (i, j)) + B = as_matrix(v[k]*v[k]*u[i]*v[j], (j, i)) + C = A + A + C = B + B + D = A + B assert len(A.ufl_shape) == 2 assert len(B.ufl_shape) == 2 assert len(C.ufl_shape) == 2 @@ -141,8 +120,8 @@ def test_matrix_from_indices(self): def test_vector_from_list(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(element) + u = TrialFunction(element) # create vector from list vv = as_vector([u[0], v[0]]) @@ -153,17 +132,17 @@ def test_vector_from_list(self): def test_matrix_from_list(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(element) + u = TrialFunction(element) # create matrix from list - A = as_matrix([[u[0], u[1]], [v[0], v[1]]]) + A = as_matrix([[u[0], u[1]], [v[0], v[1]]]) # create matrix from indices - B = as_matrix((v[k]*v[k]) * u[i]*v[j], (j, i)) + B = as_matrix((v[k]*v[k]) * u[i]*v[j], (j, i)) # Test addition - C = A + A - C = B + B - D = A + B + C = A + A + C = B + B + D = A + B assert len(A.ufl_shape) == 2 assert len(B.ufl_shape) == 2 assert len(C.ufl_shape) == 2 @@ -172,10 +151,10 @@ def test_matrix_from_list(self): def test_tensor(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + v = TestFunction(element) + u = TrialFunction(element) + f = Coefficient(element) + g = Coefficient(element) # define the components of a fourth order tensor Cijkl = u[i]*v[j]*f[k]*g[l] @@ -188,34 +167,32 @@ def test_tensor(self): self.assertSameIndices(C, ()) # get sub-matrix - A = C[:,:, 0, 0] + A = C[:, :, 0, 0] assert len(A.ufl_shape) == 2 self.assertSameIndices(A, ()) - A = C[:,:, i, j] + A = C[:, :, i, j] assert len(A.ufl_shape) == 2 assert set(A.ufl_free_indices) == {i.count(), j.count()} # legal? vv = as_vector([u[i], v[i]]) - ww = f[i]*vv # this is well defined: ww = sum_i + f[i]*vv # this is well defined: ww = sum_i # illegal with pytest.raises(BaseException): - vv = as_vector([u[i], v[j]]) + as_vector([u[i], v[j]]) # illegal with pytest.raises(BaseException): - A = as_matrix([[u[0], u[1]], [v[0],]]) - - # ... + as_matrix([[u[0], u[1]], [v[0]]]) def test_indexed(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - i, j, k, l = indices(4) + v = TestFunction(element) + u = TrialFunction(element) + Coefficient(element) + i, j, k, l = indices(4) # noqa: E741 a = v[i] self.assertSameIndices(a, (i,)) @@ -231,9 +208,9 @@ def test_indexed(self): def test_spatial_derivative(self): cell = triangle element = VectorElement("CG", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) - i, j, k, l = indices(4) + v = TestFunction(element) + u = TrialFunction(element) + i, j, k, l = indices(4) # noqa: E741 d = cell.geometric_dimension() a = v[i].dx(i) diff --git a/test/test_interpolate.py b/test/test_interpolate.py index d7e79dff1..245830f74 100644 --- a/test/test_interpolate.py +++ b/test/test_interpolate.py @@ -1,24 +1,17 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -import pytest -from ufl import * - +"""Test Interpolate object.""" __authors__ = "Nacime Bouziani" __date__ = "2021-11-19" -""" -Test Interpolate object -""" +import pytest -from ufl.core.interpolate import Interpolate +from ufl import (Action, Adjoint, Argument, Coefficient, FiniteElement, FunctionSpace, TestFunction, TrialFunction, + action, adjoint, derivative, dx, grad, inner, replace, triangle) from ufl.algorithms.ad import expand_derivatives -from ufl.algorithms.analysis import (extract_coefficients, extract_arguments, - extract_arguments_and_coefficients, - extract_base_form_operators) - +from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, extract_base_form_operators, + extract_coefficients) from ufl.algorithms.expand_indices import expand_indices +from ufl.core.interpolate import Interpolate from ufl.domain import default_domain diff --git a/test/test_lhs_rhs.py b/test/test_lhs_rhs.py index d73f956f6..a34851b33 100755 --- a/test/test_lhs_rhs.py +++ b/test/test_lhs_rhs.py @@ -1,13 +1,10 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Marie E. Rognes" # First added: 2011-11-09 # Last changed: 2011-11-09 -import pytest -from ufl import * +from ufl import (Argument, Coefficient, Constant, FiniteElement, TestFunction, TrialFunction, action, derivative, ds, + dS, dx, exp, interval, system) def test_lhs_rhs_simple(): @@ -19,22 +16,22 @@ def test_lhs_rhs_simple(): F0 = f * u * v * w * dx a, L = system(F0) - assert(len(a.integrals()) == 0) - assert(len(L.integrals()) == 0) + assert len(a.integrals()) == 0 + assert len(L.integrals()) == 0 F1 = derivative(F0, f) a, L = system(F1) - assert(len(a.integrals()) == 0) - assert(len(L.integrals()) == 0) + assert len(a.integrals()) == 0 + assert len(L.integrals()) == 0 F2 = action(F0, f) a, L = system(F2) - assert(len(a.integrals()) == 1) - assert(len(L.integrals()) == 0) + assert len(a.integrals()) == 1 + assert len(L.integrals()) == 0 F3 = action(F2, f) a, L = system(F3) - assert(len(L.integrals()) == 1) + assert len(L.integrals()) == 1 def test_lhs_rhs_derivatives(): @@ -45,10 +42,10 @@ def test_lhs_rhs_derivatives(): F0 = exp(f) * u * v * dx + v * dx + f * v * ds + exp(f)('+') * v * dS a, L = system(F0) - assert(len(a.integrals()) == 1) - assert(len(L.integrals()) == 3) + assert len(a.integrals()) == 1 + assert len(L.integrals()) == 3 - F1 = derivative(F0, f) + derivative(F0, f) a, L = system(F0) @@ -65,9 +62,9 @@ def test_lhs_rhs_slightly_obscure(): # F = f*u*w*dx + f*w*dx F = f * u * w * dx a, L = system(F) - assert(len(a.integrals()) == 1) - assert(len(L.integrals()) == 0) + assert len(a.integrals()) == 1 + assert len(L.integrals()) == 0 F = f * w * dx a, L = system(F) - assert(len(L.integrals()) == 1) + assert len(L.integrals()) == 1 diff --git a/test/test_literals.py b/test/test_literals.py index ef8217a4f..aed180b24 100755 --- a/test/test_literals.py +++ b/test/test_literals.py @@ -1,29 +1,14 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "Martin Sandve Alnæs" -__date__ = "2011-04-14 -- 2011-04-14" - -import pytest +__date__ = "2011-04-14" -from ufl import * +from ufl import PermutationSymbol, as_matrix, as_vector, indices, product from ufl.classes import Indexed -from ufl.constantvalue import Zero, FloatValue, IntValue, ComplexValue, as_ufl +from ufl.constantvalue import ComplexValue, FloatValue, IntValue, Zero, as_ufl def test_zero(self): z1 = Zero(()) - z2 = Zero(()) - z3 = as_ufl(0) - z4 = as_ufl(0.0) - z5 = FloatValue(0) - z6 = FloatValue(0.0) - - # self.assertTrue(z1 is z2) - # self.assertTrue(z1 is z3) - # self.assertTrue(z1 is z4) - # self.assertTrue(z1 is z5) - # self.assertTrue(z1 is z6) + assert z1 == z1 assert int(z1) == 0 assert float(z1) == 0.0 @@ -150,7 +135,7 @@ def test_permutation_symbol_n(self): def test_unit_dyads(self): - from ufl.tensors import unit_vectors, unit_matrices + from ufl.tensors import unit_matrices, unit_vectors ei, ej = unit_vectors(2) self.assertEqual(as_vector((1, 0)), ei) self.assertEqual(as_vector((0, 1)), ej) diff --git a/test/test_measures.py b/test/test_measures.py index 28852b4a4..86a8ea68c 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -1,21 +1,9 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -""" -Tests of the various ways Measure objects can be created and used. -""" - -import pytest - -# This imports everything external code will see from ufl -from ufl import * -from ufl.algorithms import compute_form_data - -# all_cells = (interval, triangle, tetrahedron, -# quadrilateral, hexahedron) +"""Tests of the various ways Measure objects can be created and used.""" from mockobjects import MockMesh, MockMeshFunction +from ufl import Cell, Coefficient, FiniteElement, FunctionSpace, Measure, Mesh, as_ufl, dC, dI, dO, triangle + def test_construct_forms_from_default_measures(): # Create defaults: @@ -64,12 +52,12 @@ def test_construct_forms_from_default_measures(): assert dS_v.integral_type() == "interior_facet_vert" # Check that defaults are set properly - assert dx.ufl_domain() == None + assert dx.ufl_domain() is None assert dx.metadata() == {} # Check that we can create a basic form with default measure one = as_ufl(1) - a = one * dx(Mesh(triangle)) + one * dx(Mesh(triangle)) def test_foo(): @@ -147,8 +135,6 @@ def test_foo(): assert dxm.ufl_domain() is None assert dxm.subdomain_id() == "everywhere" - dxi = dx(metadata={"quadrature_degree": 3}) - # Mock some dolfin data structures dx = Measure("dx") ds = Measure("ds") diff --git a/test/test_mixed_function_space.py b/test/test_mixed_function_space.py index 4cb5abb3a..28c01f35f 100644 --- a/test/test_mixed_function_space.py +++ b/test/test_mixed_function_space.py @@ -1,17 +1,10 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -from ufl import * - __authors__ = "Cecile Daversin Catty" __date__ = "2019-03-26 -- 2019-03-26" -import pytest - -from ufl import * +from ufl import (FiniteElement, FunctionSpace, Measure, MixedFunctionSpace, TestFunctions, TrialFunctions, interval, + tetrahedron, triangle) +from ufl.algorithms.formsplitter import extract_blocks from ufl.domain import default_domain -from ufl.algorithms.formsplitter import extract_blocks - def test_mixed_functionspace(self): @@ -31,20 +24,20 @@ def test_mixed_functionspace(self): # MixedFunctionSpace = V_3d x V_2d x V_1d V = MixedFunctionSpace(V_3d, V_2d, V_1d) # Check sub spaces - assert( V.num_sub_spaces() == 3 ) - assert( V.ufl_sub_space(0) == V_3d ) - assert( V.ufl_sub_space(1) == V_2d ) - assert( V.ufl_sub_space(2) == V_1d ) + assert V.num_sub_spaces() == 3 + assert V.ufl_sub_space(0) == V_3d + assert V.ufl_sub_space(1) == V_2d + assert V.ufl_sub_space(2) == V_1d # Arguments from MixedFunctionSpace (u_3d, u_2d, u_1d) = TrialFunctions(V) (v_3d, v_2d, v_1d) = TestFunctions(V) - + # Measures dx3 = Measure("dx", domain=V_3d) dx2 = Measure("dx", domain=V_2d) dx1 = Measure("dx", domain=V_1d) - + # Mixed variational form # LHS a_11 = u_1d*v_1d*dx1 @@ -65,30 +58,29 @@ def test_mixed_functionspace(self): # Check extract_block algorithm # LHS - assert ( extract_blocks(a,0,0) == a_33 ) - assert ( extract_blocks(a,0,1) == a_23 ) - assert ( extract_blocks(a,0,2) == a_13 ) - assert ( extract_blocks(a,1,0) == a_32 ) - assert ( extract_blocks(a,1,1) == a_22 ) - assert ( extract_blocks(a,1,2) == a_12 ) - assert ( extract_blocks(a,2,0) == a_31 ) - assert ( extract_blocks(a,2,1) == a_21 ) - assert ( extract_blocks(a,2,2) == a_11 ) + assert extract_blocks(a, 0, 0) == a_33 + assert extract_blocks(a, 0, 1) == a_23 + assert extract_blocks(a, 0, 2) == a_13 + assert extract_blocks(a, 1, 0) == a_32 + assert extract_blocks(a, 1, 1) == a_22 + assert extract_blocks(a, 1, 2) == a_12 + assert extract_blocks(a, 2, 0) == a_31 + assert extract_blocks(a, 2, 1) == a_21 + assert extract_blocks(a, 2, 2) == a_11 # RHS - assert ( extract_blocks(f,0) == f_3 ) - assert ( extract_blocks(f,1) == f_2 ) - assert ( extract_blocks(f,2) == f_1 ) + assert extract_blocks(f, 0) == f_3 + assert extract_blocks(f, 1) == f_2 + assert extract_blocks(f, 2) == f_1 # Test dual space method V_dual = V.dual() - assert( V_dual.num_sub_spaces() == 3 ) - assert( V_dual.ufl_sub_space(0) == V_3d.dual() ) - assert( V_dual.ufl_sub_space(1) == V_2d.dual() ) - assert( V_dual.ufl_sub_space(2) == V_1d.dual() ) - - V_dual = V.dual(*[0,2]) - assert( V_dual.num_sub_spaces() == 3 ) - assert( V_dual.ufl_sub_space(0) == V_3d.dual() ) - assert( V_dual.ufl_sub_space(1) == V_2d ) - assert( V_dual.ufl_sub_space(2) == V_1d.dual() ) + assert V_dual.num_sub_spaces() == 3 + assert V_dual.ufl_sub_space(0) == V_3d.dual() + assert V_dual.ufl_sub_space(1) == V_2d.dual() + assert V_dual.ufl_sub_space(2) == V_1d.dual() + V_dual = V.dual(0, 2) + assert V_dual.num_sub_spaces() == 3 + assert V_dual.ufl_sub_space(0) == V_3d.dual() + assert V_dual.ufl_sub_space(1) == V_2d + assert V_dual.ufl_sub_space(2) == V_1d.dual() diff --git a/test/test_new_ad.py b/test/test_new_ad.py index 65b3dd158..173a39486 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -1,17 +1,8 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -import pytest - -from ufl import * - -from ufl.tensors import as_tensor -from ufl.classes import Grad -from ufl.algorithms import tree_format +from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FiniteElement, Identity, SpatialCoordinate, + TestFunction, VectorConstant, VectorElement, as_ufl, cos, derivative, diff, exp, grad, ln, sin, tan, + triangle, variable, zero) +from ufl.algorithms.apply_derivatives import GenericDerivativeRuleset, GradRuleset, apply_derivatives from ufl.algorithms.renumbering import renumber_indices -from ufl.algorithms.apply_derivatives import ( - apply_derivatives, GenericDerivativeRuleset, - GradRuleset, VariableRuleset, GateauxDerivativeRuleset) - # Note: the old tests in test_automatic_differentiation.py are a bit messy # but still cover many things that are not in here yet. @@ -31,8 +22,8 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): z = zero((3, 2)) one = as_ufl(1) two = as_ufl(2.0) - I = Identity(d) - literals = [z, one, two, I] + ident = Identity(d) + literals = [z, one, two, ident] # Geometry x = SpatialCoordinate(cell) @@ -70,25 +61,24 @@ def test_literal_derivatives_are_zero(): # Literals one = as_ufl(1) two = as_ufl(2.0) - I = Identity(d) - E = PermutationSymbol(d) - literals = [one, two, I] + ident = Identity(d) + literals = [one, two, ident] # Generic ruleset handles literals directly: - for l in literals: + for lit in literals: for sh in [(), (d,), (d, d+1)]: - assert GenericDerivativeRuleset(sh)(l) == zero(l.ufl_shape + sh) + assert GenericDerivativeRuleset(sh)(lit) == zero(lit.ufl_shape + sh) # Variables v0 = variable(one) v1 = variable(zero((d,))) - v2 = variable(I) + v2 = variable(ident) variables = [v0, v1, v2] # Test literals via apply_derivatives and variable ruleset: - for l in literals: + for lit in literals: for v in variables: - assert apply_derivatives(diff(l, v)) == zero(l.ufl_shape + v.ufl_shape) + assert apply_derivatives(diff(lit, v)) == zero(lit.ufl_shape + v.ufl_shape) V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) @@ -99,9 +89,9 @@ def test_literal_derivatives_are_zero(): args = [(u0, v0), (u1, v1)] # Test literals via apply_derivatives and variable ruleset: - for l in literals: + for lit in literals: for u, v in args: - assert apply_derivatives(derivative(l, u, v)) == zero(l.ufl_shape + v.ufl_shape) + assert apply_derivatives(derivative(lit, u, v)) == zero(lit.ufl_shape + v.ufl_shape) # Test grad ruleset directly since grad(literal) is invalid: assert GradRuleset(d)(one) == zero((d,)) @@ -122,14 +112,12 @@ def test_grad_ruleset(): # Literals one = as_ufl(1) two = as_ufl(2.0) - I = Identity(d) - literals = [one, two, I] + ident = Identity(d) # Geometry x = SpatialCoordinate(cell) n = FacetNormal(cell) volume = CellVolume(cell) - geometry = [x, n, volume] # Arguments u0 = TestFunction(V0) @@ -145,33 +133,20 @@ def test_grad_ruleset(): vf0 = Coefficient(W0) vf1 = Coefficient(W1) vf2 = Coefficient(W2) - coefficients = [f0, f1, vf0, vf1] - - # Expressions - e0 = f0 + f1 - e1 = u0 * (f1/3 - f0**2) - e2 = exp(sin(cos(tan(ln(x[0]))))) - expressions = [e0, e1, e2] - - # Variables - v0 = variable(one) - v1 = variable(f1) - v2 = variable(f0*f1) - variables = [v0, v1, v2] rules = GradRuleset(d) # Literals assert rules(one) == zero((d,)) assert rules(two) == zero((d,)) - assert rules(I) == zero((d, d, d)) + assert rules(ident) == zero((d, d, d)) # Assumed piecewise constant geometry for g in [n, volume]: assert rules(g) == zero(g.ufl_shape + (d,)) # Non-constant geometry - assert rules(x) == I + assert rules(x) == ident # Arguments for u in arguments: diff --git a/test/test_pickle.py b/test/test_pickle.py index 145df65b8..6e4633ff7 100755 --- a/test/test_pickle.py +++ b/test/test_pickle.py @@ -1,5 +1,3 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- """Pickle all the unit test forms from FFC 0.5.0""" __author__ = "Anders Logg (logg@simula.no) et al." @@ -10,11 +8,13 @@ # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. -import pytest -from ufl import * +import pickle + +from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, Identity, TensorElement, TestFunction, + TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, div, dot, dS, + ds, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) from ufl.algorithms import compute_form_data -import pickle p = pickle.HIGHEST_PROTOCOL @@ -24,7 +24,6 @@ def testConstant(): v = TestFunction(element) u = TrialFunction(element) - f = Coefficient(element) c = Constant("triangle") d = VectorConstant("triangle") @@ -39,8 +38,8 @@ def testConstant(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testElasticity(): @@ -60,7 +59,7 @@ def eps(v): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testEnergyNorm(): @@ -73,7 +72,7 @@ def testEnergyNorm(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testEquation(): @@ -96,8 +95,8 @@ def testEquation(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testFunctionOperators(): @@ -117,7 +116,7 @@ def testFunctionOperators(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testHeat(): @@ -139,8 +138,8 @@ def testHeat(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testMass(): @@ -155,7 +154,7 @@ def testMass(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testMixedMixedElement(): @@ -167,7 +166,7 @@ def testMixedMixedElement(): element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) - assert(element == element_restore) + assert element == element_restore def testMixedPoisson(): @@ -192,8 +191,8 @@ def testMixedPoisson(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testNavierStokes(): @@ -211,7 +210,7 @@ def testNavierStokes(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testNeumannProblem(): @@ -234,8 +233,8 @@ def testNeumannProblem(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testOptimization(): @@ -254,8 +253,8 @@ def testOptimization(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testP5tet(): @@ -265,7 +264,7 @@ def testP5tet(): element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) - assert(element == element_restore) + assert element == element_restore def testP5tri(): @@ -273,7 +272,7 @@ def testP5tri(): element = FiniteElement("Lagrange", triangle, 5) element_pickle = pickle.dumps(element, p) - element_restore = pickle.loads(element_pickle) + pickle.loads(element_pickle) def testPoissonDG(): @@ -318,8 +317,8 @@ def testPoissonDG(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testPoisson(): @@ -339,8 +338,8 @@ def testPoisson(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testPoissonSystem(): @@ -362,8 +361,8 @@ def testPoissonSystem(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testQuadratureElement(): @@ -392,8 +391,8 @@ def testQuadratureElement(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testStokes(): @@ -420,16 +419,14 @@ def testStokes(): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testSubDomain(): element = FiniteElement("CG", "tetrahedron", 1) - v = TestFunction(element) - u = TrialFunction(element) f = Coefficient(element) M = f * dx(2) + f * ds(5) @@ -437,7 +434,7 @@ def testSubDomain(): M_pickle = pickle.dumps(M, p) M_restore = pickle.loads(M_pickle) - assert(M.signature() == M_restore.signature()) + assert M.signature() == M_restore.signature() def testSubDomains(): @@ -453,7 +450,7 @@ def testSubDomains(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testTensorWeightedPoisson(): @@ -487,7 +484,7 @@ def testTensorWeightedPoisson(): a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) - assert(a.signature() == a_restore.signature()) + assert a.signature() == a_restore.signature() def testVectorLaplaceGradCurl(): @@ -524,8 +521,8 @@ def HodgeLaplaceGradCurl(element, felement): L_pickle = pickle.dumps(L, p) L_restore = pickle.loads(L_pickle) - assert(a.signature() == a_restore.signature()) - assert(L.signature() == L_restore.signature()) + assert a.signature() == a_restore.signature() + assert L.signature() == L_restore.signature() def testIdentity(): @@ -533,7 +530,7 @@ def testIdentity(): i = Identity(2) i_pickle = pickle.dumps(i, p) i_restore = pickle.loads(i_pickle) - assert(i == i_restore) + assert i == i_restore def testFormData(): @@ -550,4 +547,4 @@ def testFormData(): form_data_pickle = pickle.dumps(form_data, p) form_data_restore = pickle.loads(form_data_pickle) - assert(str(form_data) == str(form_data_restore)) + assert str(form_data) == str(form_data_restore) diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index 1db8ec6c2..ba4beb28a 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -1,14 +1,13 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -""" -Test the is_cellwise_constant function on all relevant terminal types. -""" +"""Test the is_cellwise_constant function on all relevant terminal types.""" import pytest -from ufl import * -from ufl.classes import * + +from ufl import (Cell, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, + FiniteElement, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, + MinFacetEdgeLength, SpatialCoordinate, TestFunction, VectorElement, hexahedron, interval, + quadrilateral, tetrahedron, triangle) from ufl.checks import is_cellwise_constant +from ufl.classes import CellCoordinate, FacetJacobian, FacetJacobianDeterminant, FacetJacobianInverse def get_domains(): diff --git a/test/test_reference_shapes.py b/test/test_reference_shapes.py index b5813560c..db4ac3313 100755 --- a/test/test_reference_shapes.py +++ b/test/test_reference_shapes.py @@ -1,9 +1,4 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -import pytest - -from ufl import * +from ufl import Cell, FiniteElement, MixedElement, TensorElement, VectorElement def test_reference_shapes(): diff --git a/test/test_scratch.py b/test/test_scratch.py index 81056f568..bcf0bd347 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -1,22 +1,18 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -""" +"""Test scratch. + This is a template file you can copy when making a new test case. Begin by copying this file to a filename matching test_*.py. The tests in the file will then automatically be run by ./test.py. Next look at the TODO markers below for places to edit. """ -import pytest import warnings -# This imports everything external code will see from ufl -from ufl import * +from ufl import (Coefficient, FiniteElement, Identity, TensorElement, TestFunction, VectorElement, as_matrix, as_tensor, + as_vector, dx, grad, indices, inner, outer, triangle) +from ufl.classes import FixedIndex, FormArgument, Grad, Indexed, ListTensor, Zero from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor -# TODO: Import only what you need from classes and algorithms: -from ufl.classes import Grad, FormArgument, Zero, Indexed, FixedIndex, ListTensor - class MockForwardAD: diff --git a/test/test_signature.py b/test/test_signature.py index 96d6754e2..95e655262 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -1,17 +1,11 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- -""" -Test the computation of form signatures. -""" +"""Test the computation of form signatures.""" -import pytest - -from ufl import * - -from ufl.classes import MultiIndex, FixedIndex +from ufl import (Argument, CellDiameter, CellVolume, Circumradius, Coefficient, FacetArea, FacetNormal, FiniteElement, + FunctionSpace, Identity, Mesh, SpatialCoordinate, TensorElement, TestFunction, VectorElement, + as_domain, as_vector, diff, dot, ds, dx, hexahedron, indices, inner, interval, quadrilateral, + tetrahedron, triangle, variable) from ufl.algorithms.signature import compute_multiindex_hashdata, compute_terminal_hashdata - -from itertools import chain +from ufl.classes import FixedIndex, MultiIndex # TODO: Test compute_terminal_hashdata # TODO: Check that form argument counts only affect the sig by their relative ordering @@ -79,10 +73,10 @@ def forms(): for d in (2, 3): domain = as_domain({2: triangle, 3: tetrahedron}[d]) x = SpatialCoordinate(domain) - I = Identity(d) + ident = Identity(d) for fv in (1.1, 2.2): for iv in (5, 7): - expr = (I[0, j]*(fv*x[j]))**iv + expr = (ident[0, j]*(fv*x[j]))**iv reprs.add(repr(expr)) hashes.add(hash(expr)) @@ -114,13 +108,13 @@ def forms(): a = FacetArea(cell) # s = CellSurfaceArea(cell) v = CellVolume(cell) - I = Identity(d) + ident = Identity(d) ws = (x, n) qs = (h, r, a, v) # , s) for w in ws: for q in qs: - expr = (I[0, j]*(q*w[j])) + expr = (ident[0, j]*(q*w[j])) reprs.add(repr(expr)) hashes.add(hash(expr)) @@ -386,7 +380,7 @@ def hashdatas(): # each repetition but repr and hashes changing # because new indices are created each repetition. index_numbering = {} - i, j, k, l = indices(4) + i, j, k, l = indices(4) # noqa: E741 for expr in (MultiIndex((i,)), MultiIndex((i,)), # r MultiIndex((i, j)), diff --git a/test/test_simplify.py b/test/test_simplify.py index 4bc9bd444..89afa3690 100755 --- a/test/test_simplify.py +++ b/test/test_simplify.py @@ -1,10 +1,8 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -import pytest -from ufl.classes import Sum, Product import math -from ufl import * + +from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, VectorConstant, acos, as_tensor, as_ufl, asin, + atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, outer, sin, sinh, tan, tanh, triangle) +from ufl.algorithms import compute_form_data def xtest_zero_times_argument(self): @@ -23,7 +21,6 @@ def xtest_zero_times_argument(self): def test_divisions(self): element = FiniteElement("CG", triangle, 1) f = Coefficient(element) - g = Coefficient(element) # Test simplification of division by 1 a = f @@ -97,22 +94,22 @@ def test_sums(self): def test_mathfunctions(self): - for i in (0.1, 0.3, 0.9): - assert math.sin(i) == sin(i) - assert math.cos(i) == cos(i) - assert math.tan(i) == tan(i) - assert math.sinh(i) == sinh(i) - assert math.cosh(i) == cosh(i) - assert math.tanh(i) == tanh(i) - assert math.asin(i) == asin(i) - assert math.acos(i) == acos(i) - assert math.atan(i) == atan(i) - assert math.exp(i) == exp(i) - assert math.log(i) == ln(i) + for a in (0.1, 0.3, 0.9): + assert math.sin(a) == sin(a) + assert math.cos(a) == cos(a) + assert math.tan(a) == tan(a) + assert math.sinh(a) == sinh(a) + assert math.cosh(a) == cosh(a) + assert math.tanh(a) == tanh(a) + assert math.asin(a) == asin(a) + assert math.acos(a) == acos(a) + assert math.atan(a) == atan(a) + assert math.exp(a) == exp(a) + assert math.log(a) == ln(a) # TODO: Implement automatic simplification of conditionals? - assert i == float(max_value(i, i-1)) + assert a == float(max_value(a, a-1)) # TODO: Implement automatic simplification of conditionals? - assert i == float(min_value(i, i+1)) + assert a == float(min_value(a, a+1)) def test_indexing(self): @@ -125,5 +122,5 @@ def test_indexing(self): Bij = u[i]*v[j] Bij2 = as_tensor(Bij, (i, j))[i, j] - Bij3 = as_tensor(Bij, (i, j)) + as_tensor(Bij, (i, j)) assert Bij2 == Bij diff --git a/test/test_sobolevspace.py b/test/test_sobolevspace.py index fb579a2f7..ba4eee118 100755 --- a/test/test_sobolevspace.py +++ b/test/test_sobolevspace.py @@ -1,17 +1,12 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - __authors__ = "David Ham" __date__ = "2014-03-04" -import pytest -from ufl import (EnrichedElement, TensorProductElement, - FiniteElement, triangle, interval, - quadrilateral, HDiv, HCurl) -from ufl.sobolevspace import SobolevSpace, DirectionalSobolevSpace -from ufl import H2, H1, HDiv, HCurl, L2, HInf from math import inf +from ufl import (H1, H2, L2, EnrichedElement, FiniteElement, HCurl, HDiv, HInf, TensorProductElement, interval, + quadrilateral, triangle) +from ufl.sobolevspace import SobolevSpace # noqa: F401 +from ufl.sobolevspace import DirectionalSobolevSpace # Construct directional Sobolev spaces, with varying smoothness in # spatial coordinates diff --git a/test/test_split.py b/test/test_split.py index 1b7b4bd10..597c81f26 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -1,14 +1,8 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- - -from ufl import * - __authors__ = "Martin Sandve Alnæs" __date__ = "2009-03-14 -- 2009-03-14" -import pytest - -from ufl import * +from ufl import (Coefficient, FiniteElement, MixedElement, TensorElement, TestFunction, VectorElement, as_vector, + product, split, triangle) def test_split(self): diff --git a/test/test_str.py b/test/test_str.py index ffab81ea2..25d036369 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- - -from ufl import * -from ufl.classes import * +from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FiniteElement, Index, + SpatialCoordinate, TestFunction, TrialFunction, as_matrix, as_ufl, as_vector, quadrilateral, + tetrahedron, triangle) def test_str_int_value(self): diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index 83eaa7ca7..3e8a77608 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -1,14 +1,12 @@ import gc import sys -import pytest - -from ufl import * -from ufl.algorithms import strip_terminal_data, replace_terminal_data +from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, + inner, triangle) +from ufl.algorithms import replace_terminal_data, strip_terminal_data from ufl.core.ufl_id import attach_ufl_id from ufl.core.ufl_type import attach_operators_from_hash_data - MIN_REF_COUNT = 2 """The minimum value returned by sys.getrefcount.""" diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index e8831a1d8..01b69d413 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- -""" -Test tensor algebra operators. -""" +"""Test tensor algebra operators.""" import pytest -from ufl import * + +from ufl import (FacetNormal, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, diag_vector, dot, inner, + inv, outer, perp, skew, sym, tr, transpose, triangle, zero) from ufl.algorithms.remove_complex_nodes import remove_complex_nodes @@ -182,18 +181,19 @@ def test_tr(self, A): def test_det(self, A): dims = (0, 1) C = det(A) - D = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in dims) + D = sum((-A[i, 0]*A[0, i] if i != 0 else A[i-1, -1]*A[i, 0]) for i in dims) self.assertEqualValues(C, D) def test_cofac(self, A): C = cofac(A) - D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)]) + D = as_matrix([[(-A[i, j] if i != j else A[i, j]) for j in (-1, 0)] for i in (-1, 0)]) self.assertEqualValues(C, D) def xtest_inv(self, A): + # FIXME: Test fails probably due to integer division C = inv(A) - detA = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in (0,1)) - D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)]) / detA # FIXME: Test fails probably due to integer division + detA = sum((-A[i, 0]*A[0, i] if i != 0 else A[i-1, -1]*A[i, 0]) for i in (0, 1)) + D = as_matrix([[(-A[i, j] if i != j else A[i, j]) for j in (-1, 0)] for i in (-1, 0)]) / detA self.assertEqualValues(C, D) diff --git a/test/test_utilities.py b/test/test_utilities.py index 5cdcc46e4..4fdf660d6 100755 --- a/test/test_utilities.py +++ b/test/test_utilities.py @@ -1,12 +1,6 @@ -#!/usr/bin/env py.test -# -*- coding: utf-8 -*- +"""Test internal utility functions.""" -""" -Test internal utility functions. -""" - -from ufl.utils.indexflattening import (shape_to_strides, flatten_multiindex, - unflatten_index) +from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides, unflatten_index def test_shape_to_strides(): @@ -57,9 +51,8 @@ def test_component_numbering(): def test_index_flattening(): - from ufl.utils.indexflattening import (shape_to_strides, - flatten_multiindex, - unflatten_index) + from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides, unflatten_index + # Scalar shape s = () st = shape_to_strides(s) From 91d2b2fe20aa65b5baa508d203f0c8167fd2f353 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 20 Sep 2023 08:18:11 +0100 Subject: [PATCH 057/136] Stop tests using deprecated initialisations (#212) * Make demos use FunctionSpace * isort in demos * Add NotImplementedError (to see what's failing) * Make tests not use defualt domain * warning, not exception * current deprecation message * no need to deprecate as_domain, as default_domain gives warning --- demo/Constant.py | 16 +- demo/ConvectionJacobi.py | 11 +- demo/ConvectionJacobi2.py | 11 +- demo/ConvectionVector.py | 9 +- demo/Elasticity.py | 9 +- demo/EnergyNorm.py | 6 +- demo/Equation.py | 12 +- demo/ExplicitConvection.py | 11 +- demo/FEEC.py | 15 +- demo/FunctionOperators.py | 14 +- demo/H1norm.py | 6 +- demo/HarmonicMap.py | 11 +- demo/HarmonicMap2.py | 8 +- demo/Heat.py | 18 +- demo/HornSchunck.py | 15 +- demo/HyperElasticity.py | 45 ++--- demo/HyperElasticity1D.py | 11 +- demo/L2norm.py | 6 +- demo/Mass.py | 8 +- demo/MassAD.py | 6 +- demo/MixedElasticity.py | 11 +- demo/MixedPoisson.py | 13 +- demo/MixedPoisson2.py | 12 +- demo/NavierStokes.py | 11 +- demo/NeumannProblem.py | 14 +- demo/NonlinearPoisson.py | 14 +- demo/Poisson.py | 12 +- demo/PoissonDG.py | 22 +-- demo/PoissonSystem.py | 12 +- demo/PowAD.py | 10 +- demo/ProjectionSystem.py | 10 +- demo/QuadratureElement.py | 21 ++- demo/RestrictedElement.py | 9 +- demo/Stiffness.py | 9 +- demo/StiffnessAD.py | 8 +- demo/Stokes.py | 13 +- demo/StokesEquation.py | 13 +- demo/SubDomain.py | 12 +- demo/SubDomains.py | 9 +- demo/TensorWeightedPoisson.py | 13 +- demo/VectorLaplaceGradCurl.py | 18 +- demo/_TensorProductElement.py | 14 +- test/test_algorithms.py | 71 +++++--- test/test_apply_algebra_lowering.py | 24 ++- test/test_apply_function_pullbacks.py | 78 ++++----- test/test_apply_restrictions.py | 19 ++- test/test_arithmetic.py | 22 ++- test/test_automatic_differentiation.py | 85 +++++----- test/test_change_to_reference_frame.py | 23 ++- test/test_classcoverage.py | 88 +++++----- test/test_complex.py | 42 +++-- test/test_conditionals.py | 11 +- test/test_degree_estimation.py | 42 +++-- test/test_derivative.py | 175 +++++++++++++------- test/test_diff.py | 3 +- test/test_domains.py | 40 +---- test/test_duals.py | 31 ++-- test/test_elements.py | 11 +- test/test_equals.py | 58 ++++--- test/test_evaluate.py | 80 ++++++--- test/test_expand_indices.py | 16 +- test/test_external_operator.py | 23 +-- test/test_ffcforms.py | 193 ++++++++++++++-------- test/test_form.py | 64 ++++--- test/test_illegal.py | 51 ++++-- test/test_indexing.py | 19 ++- test/test_indices.py | 108 +++++++----- test/test_interpolate.py | 16 +- test/test_lhs_rhs.py | 31 ++-- test/test_mixed_function_space.py | 11 +- test/test_new_ad.py | 69 +++++--- test/test_pickle.py | 220 ++++++++++++++----------- test/test_piecewise_checks.py | 15 +- test/test_scratch.py | 40 +++-- test/test_signature.py | 109 ++++++------ test/test_simplify.py | 32 ++-- test/test_split.py | 45 +++-- test/test_str.py | 52 +++--- test/test_tensoralgebra.py | 7 +- ufl/domain.py | 3 + 80 files changed, 1518 insertions(+), 1037 deletions(-) diff --git a/demo/Constant.py b/demo/Constant.py index aa663087d..0a2205978 100644 --- a/demo/Constant.py +++ b/demo/Constant.py @@ -18,18 +18,20 @@ # Modified by Martin Sandve Alnes, 2009 # # Test form for scalar and vector constants. -from ufl import (Coefficient, Constant, FiniteElement, TestFunction, - TrialFunction, VectorConstant, dot, dx, grad, inner, triangle) +from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, + VectorElement, dot, dx, grad, inner, triangle) cell = triangle element = FiniteElement("Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) -c = Constant(cell) -d = VectorConstant(cell) +c = Constant(space) +d = VectorConstant(space) a = c * dot(grad(v), grad(u)) * dx L = inner(d, grad(v)) * dx diff --git a/demo/ConvectionJacobi.py b/demo/ConvectionJacobi.py index a741d6ae6..bf059ade6 100644 --- a/demo/ConvectionJacobi.py +++ b/demo/ConvectionJacobi.py @@ -2,13 +2,14 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, dot, - dx, grad, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle element = VectorElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) -w = Coefficient(element) +u = TrialFunction(space) +v = TestFunction(space) +w = Coefficient(space) a = dot(dot(u, grad(w)) + dot(w, grad(u)), v) * dx diff --git a/demo/ConvectionJacobi2.py b/demo/ConvectionJacobi2.py index 4c0c1d9b5..7096cd32d 100644 --- a/demo/ConvectionJacobi2.py +++ b/demo/ConvectionJacobi2.py @@ -2,13 +2,14 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, dx, - i, j, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, i, j, triangle element = VectorElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) -w = Coefficient(element) +u = TrialFunction(space) +v = TestFunction(space) +w = Coefficient(space) a = (u[j] * w[i].dx(j) + w[j] * u[i].dx(j)) * v[i] * dx diff --git a/demo/ConvectionVector.py b/demo/ConvectionVector.py index f9a881baa..d16a52ee5 100644 --- a/demo/ConvectionVector.py +++ b/demo/ConvectionVector.py @@ -2,12 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, TestFunction, VectorElement, dot, dx, grad, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, VectorElement, dot, dx, grad, triangle element = VectorElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -w = Coefficient(element) +v = TestFunction(space) +w = Coefficient(space) a = dot(dot(w, grad(w)), v) * dx diff --git a/demo/Elasticity.py b/demo/Elasticity.py index ab13ebca1..0061c7135 100644 --- a/demo/Elasticity.py +++ b/demo/Elasticity.py @@ -3,13 +3,14 @@ # Modified by: Martin Sandve Alnes # Date: 2009-01-12 # -from ufl import (TestFunction, TrialFunction, VectorElement, dx, grad, inner, - tetrahedron) +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, grad, inner, tetrahedron element = VectorElement("Lagrange", tetrahedron, 1) +domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) +v = TestFunction(space) +u = TrialFunction(space) def epsilon(v): diff --git a/demo/EnergyNorm.py b/demo/EnergyNorm.py index 5b1ee3546..64bc8d88e 100644 --- a/demo/EnergyNorm.py +++ b/demo/EnergyNorm.py @@ -17,9 +17,11 @@ # # This example demonstrates how to define a functional, here # the energy norm (squared) for a reaction-diffusion problem. -from ufl import Coefficient, FiniteElement, dot, dx, grad, tetrahedron +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dot, dx, grad, tetrahedron element = FiniteElement("Lagrange", tetrahedron, 1) +domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +space = FunctionSpace(domain, element) -v = Coefficient(element) +v = Coefficient(space) a = (v * v + dot(grad(v), grad(v))) * dx diff --git a/demo/Equation.py b/demo/Equation.py index 3d64b4d8d..94c5ef445 100644 --- a/demo/Equation.py +++ b/demo/Equation.py @@ -34,16 +34,18 @@ # the unknown u to the right-hand side, all terms may # be listed on one line and left- and right-hand sides # extracted by lhs() and rhs(). -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, dot, - dx, grad, lhs, rhs, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, + grad, lhs, rhs, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) k = 0.1 -v = TestFunction(element) -u = TrialFunction(element) -u0 = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +u0 = Coefficient(space) F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx diff --git a/demo/ExplicitConvection.py b/demo/ExplicitConvection.py index d41177bf5..f3b684d4d 100644 --- a/demo/ExplicitConvection.py +++ b/demo/ExplicitConvection.py @@ -2,13 +2,14 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, dot, - dx, grad, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle element = VectorElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) -w = Coefficient(element) +u = TrialFunction(space) +v = TestFunction(space) +w = Coefficient(space) a = dot(dot(w, grad(u)), v) * dx diff --git a/demo/FEEC.py b/demo/FEEC.py index 58801b7df..cf79198e7 100644 --- a/demo/FEEC.py +++ b/demo/FEEC.py @@ -23,8 +23,8 @@ and their aliases. """ -from ufl import (FiniteElement, TestFunction, TestFunctions, TrialFunction, - TrialFunctions, dx) +from ufl import (FiniteElement, FunctionSpace, Mesh, TestFunction, TestFunctions, TrialFunction, TrialFunctions, + VectorElement, dx) from ufl import exterior_derivative as d from ufl import inner, interval, tetrahedron, triangle @@ -38,8 +38,10 @@ # Testing exterior derivative V = FiniteElement(family, cell, r, form_degree=k) - v = TestFunction(V) - u = TrialFunction(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + v = TestFunction(space) + u = TrialFunction(space) a = inner(d(u), d(v)) * dx @@ -47,7 +49,8 @@ if k > 0 and k < tdim + 1: S = FiniteElement(family, cell, r, form_degree=k - 1) W = S * V - (sigma, u) = TrialFunctions(W) - (tau, v) = TestFunctions(W) + mixed_space = FunctionSpace(domain, W) + (sigma, u) = TrialFunctions(mixed_space) + (tau, v) = TestFunctions(mixed_space) a = (inner(sigma, tau) - inner(d(tau), u) + inner(d(sigma), v) + inner(d(u), d(v))) * dx diff --git a/demo/FunctionOperators.py b/demo/FunctionOperators.py index 4b9bb4a93..79fa43244 100644 --- a/demo/FunctionOperators.py +++ b/demo/FunctionOperators.py @@ -16,14 +16,16 @@ # along with UFL. If not, see . # # Test form for operators on Coefficients. -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, - dot, dx, grad, sqrt, triangle, max_value) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, + grad, max_value, sqrt, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) -g = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) +g = Coefficient(space) a = sqrt(1 / max_value(1 / f, -1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx diff --git a/demo/H1norm.py b/demo/H1norm.py index 0d16d8e8a..2dd54a12a 100644 --- a/demo/H1norm.py +++ b/demo/H1norm.py @@ -2,10 +2,12 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FiniteElement, dot, dx, grad, triangle +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dot, dx, grad, triangle element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -f = Coefficient(element) +f = Coefficient(space) a = (f * f + dot(grad(f), grad(f))) * dx diff --git a/demo/HarmonicMap.py b/demo/HarmonicMap.py index a9921253e..854fae28f 100644 --- a/demo/HarmonicMap.py +++ b/demo/HarmonicMap.py @@ -3,15 +3,18 @@ # Author: Martin Alnes # Date: 2009-04-09 # -from ufl import (Coefficient, FiniteElement, VectorElement, - derivative, dot, dx, grad, inner, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, inner, + triangle) cell = triangle X = VectorElement("Lagrange", cell, 1) Y = FiniteElement("Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +X_space = FunctionSpace(domain, X) +Y_space = FunctionSpace(domain, Y) -x = Coefficient(X) -y = Coefficient(Y) +x = Coefficient(X_space) +y = Coefficient(Y_space) L = inner(grad(x), grad(x)) * dx + dot(x, x) * y * dx diff --git a/demo/HarmonicMap2.py b/demo/HarmonicMap2.py index daf92e028..811d8b666 100644 --- a/demo/HarmonicMap2.py +++ b/demo/HarmonicMap2.py @@ -3,15 +3,17 @@ # Author: Martin Alnes # Date: 2009-04-09 # -from ufl import (Coefficient, FiniteElement, VectorElement, derivative, dot, - dx, grad, inner, split, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, inner, + split, triangle) cell = triangle X = VectorElement("Lagrange", cell, 1) Y = FiniteElement("Lagrange", cell, 1) M = X * Y +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, M) -u = Coefficient(M) +u = Coefficient(space) x, y = split(u) L = inner(grad(x), grad(x)) * dx + dot(x, x) * y * dx diff --git a/demo/Heat.py b/demo/Heat.py index 46ddc7851..241cc708e 100644 --- a/demo/Heat.py +++ b/demo/Heat.py @@ -20,18 +20,20 @@ # The bilinear form a(v, u1) and linear form L(v) for # one backward Euler step with the heat equation. # -from ufl import (Coefficient, Constant, FiniteElement, TestFunction, - TrialFunction, dot, dx, grad, triangle) +from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, + dot, dx, grad, triangle) cell = triangle element = FiniteElement("Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) # Test function -u1 = TrialFunction(element) # Value at t_n -u0 = Coefficient(element) # Value at t_n-1 -c = Coefficient(element) # Heat conductivity -f = Coefficient(element) # Heat source -k = Constant(cell) # Time step +v = TestFunction(space) # Test function +u1 = TrialFunction(space) # Value at t_n +u0 = Coefficient(space) # Value at t_n-1 +c = Coefficient(space) # Heat conductivity +f = Coefficient(space) # Heat source +k = Constant(domain) # Time step a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx L = v * u0 * dx + k * v * f * dx diff --git a/demo/HornSchunck.py b/demo/HornSchunck.py index 13ec5e548..139b8a0e3 100644 --- a/demo/HornSchunck.py +++ b/demo/HornSchunck.py @@ -3,25 +3,28 @@ # http://code.google.com/p/debiosee/wiki/DemosOptiocFlowHornSchunck # but not tested so this could contain errors! # -from ufl import (Coefficient, Constant, FiniteElement, VectorElement, - derivative, dot, dx, grad, inner, triangle) +from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, + inner, triangle) # Finite element spaces for scalar and vector fields cell = triangle S = FiniteElement("CG", cell, 1) V = VectorElement("CG", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +S_space = FunctionSpace(domain, S) +V_space = FunctionSpace(domain, V) # Optical flow function -u = Coefficient(V) +u = Coefficient(V_space) # Previous image brightness -I0 = Coefficient(S) +I0 = Coefficient(S_space) # Current image brightness -I1 = Coefficient(S) +I1 = Coefficient(S_space) # Regularization parameter -lamda = Constant(cell) +lamda = Constant(domain) # Coefficiental to minimize M = (dot(u, grad(I1)) + (I1 - I0))**2 * dx\ diff --git a/demo/HyperElasticity.py b/demo/HyperElasticity.py index dba19f36d..289afbb0a 100644 --- a/demo/HyperElasticity.py +++ b/demo/HyperElasticity.py @@ -4,47 +4,52 @@ # # Modified by Garth N. Wells, 2009 -from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, Identity, - SpatialCoordinate, TensorElement, TestFunction, TrialFunction, - VectorElement, derivative, det, diff, dot, ds, dx, exp, grad, - inner, inv, tetrahedron, tr, variable) +from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, SpatialCoordinate, + TensorElement, TestFunction, TrialFunction, VectorElement, derivative, det, diff, dot, ds, dx, exp, + grad, inner, inv, tetrahedron, tr, variable) # Cell and its properties cell = tetrahedron +domain = Mesh(VectorElement("Lagrange", cell, 1)) d = cell.geometric_dimension() -N = FacetNormal(cell) -x = SpatialCoordinate(cell) +N = FacetNormal(domain) +x = SpatialCoordinate(domain) # Elements u_element = VectorElement("CG", cell, 2) p_element = FiniteElement("CG", cell, 1) A_element = TensorElement("CG", cell, 1) +# Spaces +u_space = FunctionSpace(domain, u_element) +p_space = FunctionSpace(domain, p_element) +A_space = FunctionSpace(domain, A_element) + # Test and trial functions -v = TestFunction(u_element) -w = TrialFunction(u_element) +v = TestFunction(u_space) +w = TrialFunction(u_space) # Displacement at current and two previous timesteps -u = Coefficient(u_element) -up = Coefficient(u_element) -upp = Coefficient(u_element) +u = Coefficient(u_space) +up = Coefficient(u_space) +upp = Coefficient(u_space) # Time parameters -dt = Constant(cell) +dt = Constant(domain) # Fiber field -A = Coefficient(A_element) +A = Coefficient(A_space) # External forces -T = Coefficient(u_element) -p0 = Coefficient(p_element) +T = Coefficient(u_space) +p0 = Coefficient(p_space) # Material parameters FIXME -rho = Constant(cell) -K = Constant(cell) -c00 = Constant(cell) -c11 = Constant(cell) -c22 = Constant(cell) +rho = Constant(domain) +K = Constant(domain) +c00 = Constant(domain) +c11 = Constant(domain) +c22 = Constant(domain) # Deformation gradient Id = Identity(d) diff --git a/demo/HyperElasticity1D.py b/demo/HyperElasticity1D.py index 0f0e2b376..aaacc2ce9 100644 --- a/demo/HyperElasticity1D.py +++ b/demo/HyperElasticity1D.py @@ -2,14 +2,17 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, Constant, FiniteElement, derivative, dx, exp, +from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dx, exp, interval, variable) cell = interval element = FiniteElement("CG", cell, 2) -u = Coefficient(element) -b = Constant(cell) -K = Constant(cell) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) + +u = Coefficient(space) +b = Constant(domain) +K = Constant(domain) E = u.dx(0) + u.dx(0)**2 / 2 E = variable(E) diff --git a/demo/L2norm.py b/demo/L2norm.py index 3f6c2491f..ca8d78cda 100644 --- a/demo/L2norm.py +++ b/demo/L2norm.py @@ -2,10 +2,12 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FiniteElement, dx, triangle +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dx, triangle element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -f = Coefficient(element) +f = Coefficient(space) a = f**2 * dx diff --git a/demo/Mass.py b/demo/Mass.py index 1b0c24e5e..a1603c146 100644 --- a/demo/Mass.py +++ b/demo/Mass.py @@ -20,11 +20,13 @@ # Last changed: 2009-03-02 # # The bilinear form for a mass matrix. -from ufl import FiniteElement, TestFunction, TrialFunction, dx, triangle +from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, triangle element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) +u = TrialFunction(space) +v = TestFunction(space) a = v * u * dx diff --git a/demo/MassAD.py b/demo/MassAD.py index 8cf8165ba..57dabdc7a 100644 --- a/demo/MassAD.py +++ b/demo/MassAD.py @@ -2,11 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-28 # -from ufl import Coefficient, FiniteElement, derivative, dx, triangle +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dx, triangle element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = Coefficient(element) +u = Coefficient(space) # L2 norm M = u**2 / 2 * dx diff --git a/demo/MixedElasticity.py b/demo/MixedElasticity.py index 0e63e6c3d..0fac9d36b 100644 --- a/demo/MixedElasticity.py +++ b/demo/MixedElasticity.py @@ -17,8 +17,8 @@ # # First added: 2008-10-03 # Last changed: 2011-07-22 -from ufl import (MixedElement, TestFunctions, TrialFunctions, VectorElement, - as_vector, div, dot, dx, inner, skew, tetrahedron, tr) +from ufl import (FunctionSpace, Mesh, MixedElement, TestFunctions, TrialFunctions, VectorElement, as_vector, div, dot, + dx, inner, skew, tetrahedron, tr) def skw(tau): @@ -43,8 +43,11 @@ def skw(tau): W = MixedElement(S, V, Q) -(sigma, u, gamma) = TrialFunctions(W) -(tau, v, eta) = TestFunctions(W) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, W) + +(sigma, u, gamma) = TrialFunctions(space) +(tau, v, eta) = TestFunctions(space) a = ( inner(sigma, tau) - tr(sigma) * tr(tau) + dot( diff --git a/demo/MixedPoisson.py b/demo/MixedPoisson.py index 4769ab645..57a112521 100644 --- a/demo/MixedPoisson.py +++ b/demo/MixedPoisson.py @@ -23,19 +23,22 @@ # a mixed formulation of Poisson's equation with BDM # (Brezzi-Douglas-Marini) elements. # -from ufl import (Coefficient, FiniteElement, TestFunctions, TrialFunctions, - div, dot, dx, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, + dot, dx, triangle) cell = triangle BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1) DG0 = FiniteElement("Discontinuous Lagrange", cell, 0) element = BDM1 * DG0 +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) +dg0_space = FunctionSpace(domain, DG0) -(tau, w) = TestFunctions(element) -(sigma, u) = TrialFunctions(element) +(tau, w) = TestFunctions(space) +(sigma, u) = TrialFunctions(space) -f = Coefficient(DG0) +f = Coefficient(dg0_space) a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx L = w * f * dx diff --git a/demo/MixedPoisson2.py b/demo/MixedPoisson2.py index fa8024118..4711bb97e 100644 --- a/demo/MixedPoisson2.py +++ b/demo/MixedPoisson2.py @@ -3,18 +3,20 @@ # Modified by: Martin Sandve Alnes # Date: 2009-02-12 # -from ufl import (FacetNormal, FiniteElement, TestFunctions, TrialFunctions, - div, dot, ds, dx, tetrahedron) +from ufl import (FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, + dot, ds, dx, tetrahedron) cell = tetrahedron RT = FiniteElement("Raviart-Thomas", cell, 1) DG = FiniteElement("DG", cell, 0) MX = RT * DG +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, MX) -(u, p) = TrialFunctions(MX) -(v, q) = TestFunctions(MX) +(u, p) = TrialFunctions(space) +(v, q) = TestFunctions(space) -n = FacetNormal(cell) +n = FacetNormal(domain) a0 = (dot(u, v) + div(u) * q + div(v) * p) * dx a1 = (dot(u, v) + div(u) * q + div(v) * p) * dx - p * dot(v, n) * ds diff --git a/demo/NavierStokes.py b/demo/NavierStokes.py index 77338c050..0dc87bdda 100644 --- a/demo/NavierStokes.py +++ b/demo/NavierStokes.py @@ -21,15 +21,16 @@ # # The bilinear form for the nonlinear term in the # Navier-Stokes equations with fixed convective velocity. -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, dot, - dx, grad, tetrahedron) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, tetrahedron cell = tetrahedron element = VectorElement("Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -w = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +w = Coefficient(space) Du = grad(u) a = dot(dot(w, Du), v) * dx diff --git a/demo/NeumannProblem.py b/demo/NeumannProblem.py index b6b6144a6..85563e411 100644 --- a/demo/NeumannProblem.py +++ b/demo/NeumannProblem.py @@ -17,15 +17,17 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation with Neumann boundary conditions. -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, ds, - dx, grad, inner, triangle) +from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dx, grad, inner, + triangle) element = VectorElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) -g = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) +g = Coefficient(space) a = inner(grad(v), grad(u)) * dx L = inner(v, f) * dx + inner(v, g) * ds diff --git a/demo/NonlinearPoisson.py b/demo/NonlinearPoisson.py index 5c56bdac0..f71b2c41c 100644 --- a/demo/NonlinearPoisson.py +++ b/demo/NonlinearPoisson.py @@ -1,12 +1,14 @@ -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, dot, - dx, grad, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, + grad, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -u0 = Coefficient(element) -f = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +u0 = Coefficient(space) +f = Coefficient(space) a = (1 + u0**2) * dot(grad(v), grad(u)) * dx \ + 2 * u0 * u * dot(grad(v), grad(u0)) * dx diff --git a/demo/Poisson.py b/demo/Poisson.py index 341c8f31a..470b844ec 100644 --- a/demo/Poisson.py +++ b/demo/Poisson.py @@ -21,14 +21,16 @@ # Last changed: 2009-03-02 # # The bilinear form a(v, u) and linear form L(v) for Poisson's equation. -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, dx, - grad, inner, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, grad, + inner, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) -f = Coefficient(element) +u = TrialFunction(space) +v = TestFunction(space) +f = Coefficient(space) a = inner(grad(v), grad(u)) * dx(degree=1) L = v * f * dx(degree=2) diff --git a/demo/PoissonDG.py b/demo/PoissonDG.py index 1673ea2fa..90c0c3af1 100644 --- a/demo/PoissonDG.py +++ b/demo/PoissonDG.py @@ -21,20 +21,22 @@ # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in a discontinuous Galerkin (DG) # formulation. -from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, - TestFunction, TrialFunction, avg, dot, dS, ds, dx, grad, - inner, jump, triangle) +from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, + VectorElement, avg, dot, dS, ds, dx, grad, inner, jump, triangle) -element = FiniteElement("Discontinuous Lagrange", triangle, 1) +cell = triangle +element = FiniteElement("Discontinuous Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) -n = FacetNormal(triangle) -h = Constant(triangle) +n = FacetNormal(domain) +h = Constant(domain) -gN = Coefficient(element) +gN = Coefficient(space) alpha = 4.0 gamma = 8.0 diff --git a/demo/PoissonSystem.py b/demo/PoissonSystem.py index f6613e2e2..d42a96eca 100644 --- a/demo/PoissonSystem.py +++ b/demo/PoissonSystem.py @@ -21,15 +21,17 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in system form (vector-valued). -from ufl import (Coefficient, TestFunction, TrialFunction, VectorElement, dot, - dx, grad, inner, triangle) +from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, inner, + triangle) cell = triangle element = VectorElement("Lagrange", cell, 1) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) a = inner(grad(v), grad(u)) * dx L = dot(v, f) * dx diff --git a/demo/PowAD.py b/demo/PowAD.py index 25edd10e1..1c0ad4e92 100644 --- a/demo/PowAD.py +++ b/demo/PowAD.py @@ -2,14 +2,16 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, derivative, dx, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -w = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +w = Coefficient(space) L = w**5 * v * dx a = derivative(L, w) diff --git a/demo/ProjectionSystem.py b/demo/ProjectionSystem.py index e61c92957..e082b99e1 100644 --- a/demo/ProjectionSystem.py +++ b/demo/ProjectionSystem.py @@ -1,10 +1,12 @@ -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, dx, +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, triangle) element = FiniteElement("Lagrange", triangle, 1) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) a = u * v * dx L = f * v * dx diff --git a/demo/QuadratureElement.py b/demo/QuadratureElement.py index 07c104109..3ab666e29 100644 --- a/demo/QuadratureElement.py +++ b/demo/QuadratureElement.py @@ -20,20 +20,25 @@ # # The linearised bilinear form a(u,v) and linear form L(v) for # the nonlinear equation - div (1+u) grad u = f (non-linear Poisson) -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, - VectorElement, dot, dx, grad, i, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, + grad, i, triangle) element = FiniteElement("Lagrange", triangle, 2) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) QE = FiniteElement("Quadrature", triangle, 2, quad_scheme="default") sig = VectorElement("Quadrature", triangle, 1, quad_scheme="default") -v = TestFunction(element) -u = TrialFunction(element) -u0 = Coefficient(element) -C = Coefficient(QE) -sig0 = Coefficient(sig) -f = Coefficient(element) +qe_space = FunctionSpace(domain, QE) +sig_space = FunctionSpace(domain, sig) + +v = TestFunction(space) +u = TrialFunction(space) +u0 = Coefficient(space) +C = Coefficient(qe_space) +sig0 = Coefficient(sig_space) +f = Coefficient(space) a = v.dx(i) * C * u.dx(i) * dx(metadata={"quadrature_degree": 2}) + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx L = v * f * dx - dot(grad(v), sig0) * dx(metadata={"quadrature_degree": 1}) diff --git a/demo/RestrictedElement.py b/demo/RestrictedElement.py index f63906991..6a07fa274 100644 --- a/demo/RestrictedElement.py +++ b/demo/RestrictedElement.py @@ -18,11 +18,12 @@ # Restriction of a finite element. # The below syntax show how one can restrict a higher order Lagrange element # to only take into account those DOFs that live on the facets. -from ufl import (FiniteElement, TestFunction, TrialFunction, avg, dS, ds, - triangle) +from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, avg, dS, ds, triangle # Restricted element CG_R = FiniteElement("Lagrange", triangle, 4)["facet"] -u_r = TrialFunction(CG_R) -v_r = TestFunction(CG_R) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, CG_R) +u_r = TrialFunction(space) +v_r = TestFunction(space) a = avg(v_r) * avg(u_r) * dS + v_r * u_r * ds diff --git a/demo/Stiffness.py b/demo/Stiffness.py index 19a055999..43f10ea2e 100644 --- a/demo/Stiffness.py +++ b/demo/Stiffness.py @@ -2,12 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (FiniteElement, TestFunction, TrialFunction, dot, dx, grad, - triangle) +from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -u = TrialFunction(element) -v = TestFunction(element) +u = TrialFunction(space) +v = TestFunction(space) a = dot(grad(u), grad(v)) * dx diff --git a/demo/StiffnessAD.py b/demo/StiffnessAD.py index aa9270055..affadc86b 100644 --- a/demo/StiffnessAD.py +++ b/demo/StiffnessAD.py @@ -2,12 +2,14 @@ # Author: Martin Sandve Alnes # Date: 2008-10-30 # -from ufl import (Coefficient, FiniteElement, action, adjoint, derivative, dx, - grad, inner, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, action, adjoint, derivative, dx, grad, + inner, triangle) element = FiniteElement("Lagrange", triangle, 1) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +space = FunctionSpace(domain, element) -w = Coefficient(element) +w = Coefficient(space) # H1 semi-norm f = inner(grad(w), grad(w)) / 2 * dx diff --git a/demo/Stokes.py b/demo/Stokes.py index 53c7bfce3..f128f7779 100644 --- a/demo/Stokes.py +++ b/demo/Stokes.py @@ -20,18 +20,21 @@ # # The bilinear form a(v, u) and Linear form L(v) for the Stokes # equations using a mixed formulation (Taylor-Hood elements). -from ufl import (Coefficient, FiniteElement, TestFunctions, TrialFunctions, - VectorElement, div, dot, dx, grad, inner, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, + dot, dx, grad, inner, triangle) cell = triangle P2 = VectorElement("Lagrange", cell, 2) P1 = FiniteElement("Lagrange", cell, 1) TH = P2 * P1 +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, TH) +p2_space = FunctionSpace(domain, P2) -(v, q) = TestFunctions(TH) -(u, p) = TrialFunctions(TH) +(v, q) = TestFunctions(space) +(u, p) = TrialFunctions(space) -f = Coefficient(P2) +f = Coefficient(p2_space) a = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx L = dot(v, f) * dx diff --git a/demo/StokesEquation.py b/demo/StokesEquation.py index 944b55e76..ea0c62bd7 100644 --- a/demo/StokesEquation.py +++ b/demo/StokesEquation.py @@ -19,18 +19,21 @@ # equations using a mixed formulation (Taylor-Hood elements) in # combination with the lhs() and rhs() operators to extract the # bilinear and linear forms from an expression F = 0. -from ufl import (Coefficient, FiniteElement, TestFunctions, TrialFunctions, - VectorElement, div, dot, dx, grad, inner, lhs, rhs, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, + dot, dx, grad, inner, lhs, rhs, triangle) cell = triangle P2 = VectorElement("Lagrange", cell, 2) P1 = FiniteElement("Lagrange", cell, 1) TH = P2 * P1 +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, TH) +p2_space = FunctionSpace(domain, P2) -(v, q) = TestFunctions(TH) -(u, p) = TrialFunctions(TH) +(v, q) = TestFunctions(space) +(u, p) = TrialFunctions(space) -f = Coefficient(P2) +f = Coefficient(p2_space) F = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx - dot(v, f) * dx a = lhs(F) diff --git a/demo/SubDomain.py b/demo/SubDomain.py index aade63745..c55e59d75 100644 --- a/demo/SubDomain.py +++ b/demo/SubDomain.py @@ -17,13 +17,15 @@ # # This example illustrates how to define a form over a # given subdomain of a mesh, in this case a functional. -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, ds, - dx, tetrahedron) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dx, + tetrahedron) element = FiniteElement("CG", tetrahedron, 1) +domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) -f = Coefficient(element) +v = TestFunction(space) +u = TrialFunction(space) +f = Coefficient(space) M = f * dx(2) + f * ds(5) diff --git a/demo/SubDomains.py b/demo/SubDomains.py index 27fe3f36a..f8a19caa6 100644 --- a/demo/SubDomains.py +++ b/demo/SubDomains.py @@ -17,13 +17,14 @@ # # This simple example illustrates how forms can be defined on different sub domains. # It is supported for all three integral types. -from ufl import (FiniteElement, TestFunction, TrialFunction, ds, dS, dx, - tetrahedron) +from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dS, dx, tetrahedron element = FiniteElement("CG", tetrahedron, 1) +domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +space = FunctionSpace(domain, element) -v = TestFunction(element) -u = TrialFunction(element) +v = TestFunction(space) +u = TrialFunction(space) a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * ds(1)\ + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) diff --git a/demo/TensorWeightedPoisson.py b/demo/TensorWeightedPoisson.py index d7eb4386f..0c76f32bf 100644 --- a/demo/TensorWeightedPoisson.py +++ b/demo/TensorWeightedPoisson.py @@ -17,14 +17,17 @@ # # The bilinear form a(v, u) and linear form L(v) for # tensor-weighted Poisson's equation. -from ufl import (Coefficient, FiniteElement, TensorElement, TestFunction, - TrialFunction, dx, grad, inner, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, TestFunction, TrialFunction, + VectorElement, dx, grad, inner, triangle) P1 = FiniteElement("Lagrange", triangle, 1) P0 = TensorElement("Discontinuous Lagrange", triangle, 0, shape=(2, 2)) +domain = Mesh(VectorElement("Lagrange", triangle, 1)) +p1_space = FunctionSpace(domain, P1) +p0_space = FunctionSpace(domain, P0) -v = TestFunction(P1) -u = TrialFunction(P1) -C = Coefficient(P0) +v = TestFunction(p1_space) +u = TrialFunction(p1_space) +C = Coefficient(p0_space) a = inner(grad(v), C * grad(u)) * dx diff --git a/demo/VectorLaplaceGradCurl.py b/demo/VectorLaplaceGradCurl.py index 012611299..e8e281baf 100644 --- a/demo/VectorLaplaceGradCurl.py +++ b/demo/VectorLaplaceGradCurl.py @@ -18,14 +18,14 @@ # The bilinear form a(v, u) and linear form L(v) for the Hodge Laplace # problem using 0- and 1-forms. Intended to demonstrate use of Nedelec # elements. -from ufl import (Coefficient, FiniteElement, TestFunctions, TrialFunctions, - VectorElement, curl, dx, grad, inner, tetrahedron) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, curl, + dx, grad, inner, tetrahedron) -def HodgeLaplaceGradCurl(element, felement): - tau, v = TestFunctions(element) - sigma, u = TrialFunctions(element) - f = Coefficient(felement) +def HodgeLaplaceGradCurl(space, fspace): + tau, v = TestFunctions(space) + sigma, u = TrialFunctions(space) + f = Coefficient(fspace) a = (inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx L = inner(v, f) * dx @@ -41,4 +41,8 @@ def HodgeLaplaceGradCurl(element, felement): VectorLagrange = VectorElement("Lagrange", cell, order + 1) -a, L = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) +domain = Mesh(VectorElement("Lagrange", cell, 1)) +space = FunctionSpace(domain, GRAD * CURL) +fspace = FunctionSpace(domain, VectorLagrange) + +a, L = HodgeLaplaceGradCurl(space, fspace) diff --git a/demo/_TensorProductElement.py b/demo/_TensorProductElement.py index 16d16572f..373feee99 100644 --- a/demo/_TensorProductElement.py +++ b/demo/_TensorProductElement.py @@ -17,8 +17,8 @@ # # First added: 2012-08-16 # Last changed: 2012-08-16 -from ufl import (FiniteElement, TensorProductElement, TestFunction, - TrialFunction, dx, interval, tetrahedron, triangle) +from ufl import (FiniteElement, FunctionSpace, Mesh, TensorProductElement, TestFunction, TrialFunction, dx, interval, + tetrahedron, triangle) V0 = FiniteElement("CG", triangle, 1) V1 = FiniteElement("DG", interval, 0) @@ -26,8 +26,14 @@ V = TensorProductElement(V0, V1, V2) -u = TrialFunction(V) -v = TestFunction(V) +c0 = FiniteElement("CG", triangle, 1) +c1 = FiniteElement("CG", interval, 1) +c2 = FiniteElement("CG", tetrahedron, 1) +domain = Mesh(TensorProductElement(c0, c1, c2)) +space = FunctionSpace(domain, V) + +u = TrialFunction(space) +v = TestFunction(space) dxxx = dx * dx * dx a = u * v * dxxx diff --git a/test/test_algorithms.py b/test/test_algorithms.py index c752cfed0..9beba0d9c 100755 --- a/test/test_algorithms.py +++ b/test/test_algorithms.py @@ -6,8 +6,8 @@ import pytest -from ufl import (Argument, Coefficient, FacetNormal, FiniteElement, TestFunction, TrialFunction, adjoint, div, dot, ds, - dx, grad, inner, triangle) +from ufl import (Argument, Coefficient, FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, + VectorElement, adjoint, div, dot, ds, dx, grad, inner, triangle) from ufl.algorithms import (expand_derivatives, expand_indices, extract_arguments, extract_coefficients, extract_elements, extract_unique_elements) from ufl.corealg.traversal import post_traversal, pre_traversal, unique_post_traversal, unique_pre_traversal @@ -21,28 +21,37 @@ def element(): @pytest.fixture(scope='module') -def arguments(element): - v = TestFunction(element) - u = TrialFunction(element) +def domain(): + return Mesh(VectorElement("CG", triangle, 1)) + + +@pytest.fixture(scope='module') +def space(element, domain): + return FunctionSpace(domain, element) + + +@pytest.fixture(scope='module') +def arguments(space): + v = TestFunction(space) + u = TrialFunction(space) return (v, u) @pytest.fixture(scope='module') -def coefficients(element): - c = Coefficient(element) - f = Coefficient(element) +def coefficients(space): + c = Coefficient(space) + f = Coefficient(space) return (c, f) @pytest.fixture -def forms(arguments, coefficients): +def forms(arguments, coefficients, space): v, u = arguments c, f = coefficients - n = FacetNormal(triangle) + n = FacetNormal(space) a = u * v * dx L = f * v * dx - b = u * v * dx(0) + inner(c * grad(u), grad(v)) * \ - dx(1) + dot(n, grad(u)) * v * ds + f * v * dx + b = u * v * dx(0) + inner(c * grad(u), grad(v)) * dx(1) + dot(n, grad(u)) * v * ds + f * v * dx return (a, L, b) @@ -55,7 +64,7 @@ def test_extract_coefficients_vs_fixture(coefficients, forms): assert coefficients == tuple(extract_coefficients(forms[2])) -def test_extract_elements_and_extract_unique_elements(forms): +def test_extract_elements_and_extract_unique_elements(forms, domain): b = forms[2] integrals = b.integrals_by_type("cell") integrals[0].integrand() @@ -63,19 +72,23 @@ def test_extract_elements_and_extract_unique_elements(forms): element1 = FiniteElement("CG", triangle, 1) element2 = FiniteElement("CG", triangle, 1) - v = TestFunction(element1) - u = TrialFunction(element2) + space1 = FunctionSpace(domain, element1) + space2 = FunctionSpace(domain, element2) + + v = TestFunction(space1) + u = TrialFunction(space2) a = u * v * dx assert extract_elements(a) == (element1, element2) assert extract_unique_elements(a) == (element1,) -def test_pre_and_post_traversal(): +def test_pre_and_post_traversal(domain): element = FiniteElement("CG", "triangle", 1) - v = TestFunction(element) - f = Coefficient(element) - g = Coefficient(element) + space = FunctionSpace(domain, element) + v = TestFunction(space) + f = Coefficient(space) + g = Coefficient(space) p1 = f * v p2 = g * v s = p1 + p2 @@ -89,10 +102,11 @@ def test_pre_and_post_traversal(): assert list(unique_post_traversal(s)) == [v, f, p1, g, p2, s] -def test_expand_indices(): +def test_expand_indices(domain): element = FiniteElement("Lagrange", triangle, 2) - v = TestFunction(element) - u = TrialFunction(element) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) def evaluate(form): return form.cell_integral()[0].integrand()((), {v: 3, u: 5}) # TODO: How to define values of derivatives? @@ -107,18 +121,21 @@ def evaluate(form): # TODO: Test something more -def test_adjoint(): +def test_adjoint(domain): cell = triangle V1 = FiniteElement("CG", cell, 1) V2 = FiniteElement("CG", cell, 2) - u = TrialFunction(V1) - v = TestFunction(V2) + s1 = FunctionSpace(domain, V1) + s2 = FunctionSpace(domain, V2) + + u = TrialFunction(s1) + v = TestFunction(s2) assert u.number() > v.number() - u2 = Argument(V1, 2) - v2 = Argument(V2, 3) + u2 = Argument(s1, 2) + v2 = Argument(s2, 3) assert u2.number() < v2.number() a = u * v * dx diff --git a/test/test_apply_algebra_lowering.py b/test/test_apply_algebra_lowering.py index 97ad44b1c..7b5c6db23 100755 --- a/test/test_apply_algebra_lowering.py +++ b/test/test_apply_algebra_lowering.py @@ -1,43 +1,51 @@ import pytest -from ufl import Coefficient, FiniteElement, Index, TensorElement, as_tensor, interval, sqrt, tetrahedron, triangle +from ufl import (Coefficient, FiniteElement, FunctionSpace, Index, Mesh, TensorElement, VectorElement, as_tensor, + interval, sqrt, tetrahedron, triangle) from ufl.algorithms.renumbering import renumber_indices from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr @pytest.fixture def A0(request): - return Coefficient(FiniteElement("CG", interval, 1)) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", interval, 1)), FiniteElement("CG", interval, 1))) @pytest.fixture def A1(request): - return Coefficient(TensorElement("CG", interval, 1)) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", interval, 1)), TensorElement("CG", interval, 1))) @pytest.fixture def A2(request): - return Coefficient(TensorElement("CG", triangle, 1)) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1))) @pytest.fixture def A3(request): - return Coefficient(TensorElement("CG", tetrahedron, 1)) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", tetrahedron, 1)), TensorElement("CG", tetrahedron, 1))) @pytest.fixture def A21(request): - return Coefficient(TensorElement("CG", triangle, 1, shape=(2, 1))) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(2, 1)))) @pytest.fixture def A31(request): - return Coefficient(TensorElement("CG", triangle, 1, shape=(3, 1))) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(3, 1)))) @pytest.fixture def A32(request): - return Coefficient(TensorElement("CG", triangle, 1, shape=(3, 2))) + return Coefficient(FunctionSpace( + Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(3, 2)))) def test_determinant0(A0): diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index 87bef3a4f..e352d09e9 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -1,7 +1,7 @@ import numpy -from ufl import (Cell, Coefficient, FiniteElement, TensorElement, VectorElement, as_domain, as_tensor, as_vector, dx, - indices, triangle) +from ufl import (Cell, Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, VectorElement, as_tensor, + as_vector, dx, indices, triangle) from ufl.algorithms.apply_function_pullbacks import apply_single_function_pullbacks from ufl.algorithms.renumbering import renumber_indices from ufl.classes import Jacobian, JacobianDeterminant, JacobianInverse, ReferenceValue @@ -33,7 +33,7 @@ def check_single_function_pullback(g, mappings): def test_apply_single_function_pullbacks_triangle3d(): triangle3d = Cell("triangle", geometric_dimension=3) cell = triangle3d - domain = as_domain(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) UL2 = FiniteElement("DG L2", cell, 1) U0 = FiniteElement("DG", cell, 0) @@ -58,27 +58,27 @@ def test_apply_single_function_pullbacks_triangle3d(): W = S*T*Vc*Vd*V*U - ul2 = Coefficient(UL2) - u = Coefficient(U) - v = Coefficient(V) - vd = Coefficient(Vd) - vc = Coefficient(Vc) - t = Coefficient(T) - s = Coefficient(S) - cov2t = Coefficient(COV2T) - contra2t = Coefficient(CONTRA2T) + ul2 = Coefficient(FunctionSpace(domain, UL2)) + u = Coefficient(FunctionSpace(domain, U)) + v = Coefficient(FunctionSpace(domain, V)) + vd = Coefficient(FunctionSpace(domain, Vd)) + vc = Coefficient(FunctionSpace(domain, Vc)) + t = Coefficient(FunctionSpace(domain, T)) + s = Coefficient(FunctionSpace(domain, S)) + cov2t = Coefficient(FunctionSpace(domain, COV2T)) + contra2t = Coefficient(FunctionSpace(domain, CONTRA2T)) - uml2 = Coefficient(Uml2) - um = Coefficient(Um) - vm = Coefficient(Vm) - vdm = Coefficient(Vdm) - vcm = Coefficient(Vcm) - tm = Coefficient(Tm) - sm = Coefficient(Sm) + uml2 = Coefficient(FunctionSpace(domain, Uml2)) + um = Coefficient(FunctionSpace(domain, Um)) + vm = Coefficient(FunctionSpace(domain, Vm)) + vdm = Coefficient(FunctionSpace(domain, Vdm)) + vcm = Coefficient(FunctionSpace(domain, Vcm)) + tm = Coefficient(FunctionSpace(domain, Tm)) + sm = Coefficient(FunctionSpace(domain, Sm)) - vd0m = Coefficient(Vd0) # case from failing ffc demo + vd0m = Coefficient(FunctionSpace(domain, Vd0)) # case from failing ffc demo - w = Coefficient(W) + w = Coefficient(FunctionSpace(domain, W)) rul2 = ReferenceValue(ul2) ru = ReferenceValue(u) @@ -229,7 +229,7 @@ def test_apply_single_function_pullbacks_triangle3d(): def test_apply_single_function_pullbacks_triangle(): cell = triangle - domain = as_domain(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) Ul2 = FiniteElement("DG L2", cell, 1) U = FiniteElement("CG", cell, 1) @@ -249,23 +249,23 @@ def test_apply_single_function_pullbacks_triangle(): W = S*T*Vc*Vd*V*U - ul2 = Coefficient(Ul2) - u = Coefficient(U) - v = Coefficient(V) - vd = Coefficient(Vd) - vc = Coefficient(Vc) - t = Coefficient(T) - s = Coefficient(S) - - uml2 = Coefficient(Uml2) - um = Coefficient(Um) - vm = Coefficient(Vm) - vdm = Coefficient(Vdm) - vcm = Coefficient(Vcm) - tm = Coefficient(Tm) - sm = Coefficient(Sm) - - w = Coefficient(W) + ul2 = Coefficient(FunctionSpace(domain, Ul2)) + u = Coefficient(FunctionSpace(domain, U)) + v = Coefficient(FunctionSpace(domain, V)) + vd = Coefficient(FunctionSpace(domain, Vd)) + vc = Coefficient(FunctionSpace(domain, Vc)) + t = Coefficient(FunctionSpace(domain, T)) + s = Coefficient(FunctionSpace(domain, S)) + + uml2 = Coefficient(FunctionSpace(domain, Uml2)) + um = Coefficient(FunctionSpace(domain, Um)) + vm = Coefficient(FunctionSpace(domain, Vm)) + vdm = Coefficient(FunctionSpace(domain, Vdm)) + vcm = Coefficient(FunctionSpace(domain, Vcm)) + tm = Coefficient(FunctionSpace(domain, Tm)) + sm = Coefficient(FunctionSpace(domain, Sm)) + + w = Coefficient(FunctionSpace(domain, W)) rul2 = ReferenceValue(ul2) ru = ReferenceValue(u) diff --git a/test/test_apply_restrictions.py b/test/test_apply_restrictions.py index 854a2458f..860b76d48 100755 --- a/test/test_apply_restrictions.py +++ b/test/test_apply_restrictions.py @@ -1,6 +1,7 @@ from pytest import raises -from ufl import Coefficient, FacetNormal, FiniteElement, SpatialCoordinate, as_tensor, grad, i, triangle +from ufl import (Coefficient, FacetNormal, FiniteElement, FunctionSpace, Mesh, SpatialCoordinate, VectorElement, + as_tensor, grad, i, triangle) from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions from ufl.algorithms.renumbering import renumber_indices @@ -10,11 +11,17 @@ def test_apply_restrictions(): V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) V2 = FiniteElement("Lagrange", cell, 2) - f0 = Coefficient(V0) - f = Coefficient(V1) - g = Coefficient(V2) - n = FacetNormal(cell) - x = SpatialCoordinate(cell) + + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v0_space = FunctionSpace(domain, V0) + v1_space = FunctionSpace(domain, V1) + v2_space = FunctionSpace(domain, V2) + + f0 = Coefficient(v0_space) + f = Coefficient(v1_space) + g = Coefficient(v2_space) + n = FacetNormal(domain) + x = SpatialCoordinate(domain) assert raises(BaseException, lambda: apply_restrictions(f0)) assert raises(BaseException, lambda: apply_restrictions(grad(f))) diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index ddb1883ad..f32e7bcfa 100755 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -1,5 +1,5 @@ -from ufl import (Identity, SpatialCoordinate, as_matrix, as_ufl, as_vector, elem_div, elem_mult, elem_op, sin, - tetrahedron, triangle) +from ufl import (Identity, Mesh, SpatialCoordinate, VectorElement, as_matrix, as_ufl, as_vector, elem_div, elem_mult, + elem_op, sin, tetrahedron, triangle) from ufl.classes import ComplexValue, Division, FloatValue, IntValue @@ -16,12 +16,14 @@ def test_scalar_casting(self): def test_ufl_float_division(self): - d = SpatialCoordinate(triangle)[0] / 10.0 # TODO: Use mock instead of x + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + d = SpatialCoordinate(domain)[0] / 10.0 # TODO: Use mock instead of x self.assertIsInstance(d, Division) def test_float_ufl_division(self): - d = 3.14 / SpatialCoordinate(triangle)[0] # TODO: Use mock instead of x + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + d = 3.14 / SpatialCoordinate(domain)[0] # TODO: Use mock instead of x self.assertIsInstance(d, Division) @@ -63,30 +65,34 @@ def test_elem_mult(self): def test_elem_mult_on_matrices(self): + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + A = as_matrix(((1, 2), (3, 4))) B = as_matrix(((4, 5), (6, 7))) self.assertEqual(elem_mult(A, B), as_matrix(((4, 10), (18, 28)))) - x, y = SpatialCoordinate(triangle) + x, y = SpatialCoordinate(domain) A = as_matrix(((x, y), (3, 4))) B = as_matrix(((4, 5), (y, x))) self.assertEqual(elem_mult(A, B), as_matrix(((4*x, 5*y), (3*y, 4*x)))) - x, y = SpatialCoordinate(triangle) + x, y = SpatialCoordinate(domain) A = as_matrix(((x, y), (3, 4))) B = Identity(2) self.assertEqual(elem_mult(A, B), as_matrix(((x, 0), (0, 4)))) def test_elem_div(self): - x, y, z = SpatialCoordinate(tetrahedron) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) B = as_matrix(((7, 8, 9), (z, x, y))) self.assertEqual(elem_div(A, B), as_matrix(((x/7, y/8, z/9), (3/z, 4/x, 5/y)))) def test_elem_op(self): - x, y, z = SpatialCoordinate(tetrahedron) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) self.assertEqual(elem_op(sin, A), as_matrix(((sin(x), sin(y), sin(z)), (sin(3), sin(4), sin(5))))) diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index 60c3f4799..f8b2e840b 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -8,12 +8,12 @@ import pytest from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, - MaxFacetEdgeLength, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, PermutationSymbol, - SpatialCoordinate, TensorElement, VectorElement, acos, as_matrix, as_tensor, as_ufl, as_vector, asin, - atan, bessel_I, bessel_J, bessel_K, bessel_Y, cofac, conditional, cos, cross, derivative, det, dev, - diff, dot, eq, erf, exp, ge, grad, gt, indices, inner, interval, inv, le, ln, lt, ne, outer, replace, - sin, skew, sqrt, sym, tan, tetrahedron, tr, triangle, variable) + FiniteElement, FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, + MaxCellEdgeLength, MaxFacetEdgeLength, Mesh, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, + PermutationSymbol, SpatialCoordinate, TensorElement, VectorElement, acos, as_matrix, as_tensor, as_ufl, + as_vector, asin, atan, bessel_I, bessel_J, bessel_K, bessel_Y, cofac, conditional, cos, cross, + derivative, det, dev, diff, dot, eq, erf, exp, ge, grad, gt, indices, inner, interval, inv, le, ln, lt, + ne, outer, replace, sin, skew, sqrt, sym, tan, tetrahedron, tr, triangle, variable) from ufl.algorithms import expand_derivatives from ufl.conditional import Conditional from ufl.corealg.traversal import unique_post_traversal @@ -23,22 +23,23 @@ class ExpressionCollection(object): def __init__(self, cell): self.cell = cell + domain = Mesh(VectorElement("Lagrange", cell, 1)) d = cell.geometric_dimension() - x = SpatialCoordinate(cell) - n = FacetNormal(cell) - c = CellVolume(cell) - R = Circumradius(cell) - h = CellDiameter(cell) - f = FacetArea(cell) - # s = CellSurfaceArea(cell) - mince = MinCellEdgeLength(cell) - maxce = MaxCellEdgeLength(cell) - minfe = MinFacetEdgeLength(cell) - maxfe = MaxFacetEdgeLength(cell) - J = Jacobian(cell) - detJ = JacobianDeterminant(cell) - invJ = JacobianInverse(cell) + x = SpatialCoordinate(domain) + n = FacetNormal(domain) + c = CellVolume(domain) + R = Circumradius(domain) + h = CellDiameter(domain) + f = FacetArea(domain) + # s = CellSurfaceArea(domain) + mince = MinCellEdgeLength(domain) + maxce = MaxCellEdgeLength(domain) + minfe = MinFacetEdgeLength(domain) + maxfe = MaxFacetEdgeLength(domain) + J = Jacobian(domain) + detJ = JacobianDeterminant(domain) + invJ = JacobianInverse(domain) # FIXME: Add all new geometry types here! ident = Identity(d) @@ -48,12 +49,16 @@ def __init__(self, cell): V = VectorElement("U", cell, None) W = TensorElement("U", cell, None) - u = Coefficient(U) - v = Coefficient(V) - w = Coefficient(W) - du = Argument(U, 0) - dv = Argument(V, 1) - dw = Argument(W, 2) + u_space = FunctionSpace(domain, U) + v_space = FunctionSpace(domain, V) + w_space = FunctionSpace(domain, W) + + u = Coefficient(u_space) + v = Coefficient(v_space) + w = Coefficient(w_space) + du = Argument(u_space, 0) + dv = Argument(v_space, 1) + dw = Argument(w_space, 2) class ObjectCollection(object): pass @@ -280,11 +285,11 @@ def test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, def _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, collection): - c = Constant(collection.shared_objects.cell) + c = Constant(collection.shared_objects.domain) - u = Coefficient(collection.shared_objects.U) - v = Coefficient(collection.shared_objects.V) - w = Coefficient(collection.shared_objects.W) + u = Coefficient(collection.shared_objects.u_space) + v = Coefficient(collection.shared_objects.v_space) + w = Coefficient(collection.shared_objects.w_space) for t in collection.terminals: for var in (u, v, w): @@ -307,11 +312,11 @@ def test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, d_expr def _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, collection): - c = Constant(collection.shared_objects.cell) + c = Constant(collection.shared_objects.domain) - u = Coefficient(collection.shared_objects.U) - v = Coefficient(collection.shared_objects.V) - w = Coefficient(collection.shared_objects.W) + u = Coefficient(collection.shared_objects.u_space) + v = Coefficient(collection.shared_objects.v_space) + w = Coefficient(collection.shared_objects.w_space) vu = variable(u) vv = variable(v) @@ -339,9 +344,9 @@ def test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(sel def _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 - u = Coefficient(collection.shared_objects.U) - v = Coefficient(collection.shared_objects.V) - w = Coefficient(collection.shared_objects.W) + u = Coefficient(collection.shared_objects.u_space) + v = Coefficient(collection.shared_objects.v_space) + w = Coefficient(collection.shared_objects.w_space) # for t in chain(collection.noncompounds, collection.compounds): # debug = True @@ -375,9 +380,9 @@ def test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, d_e def _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, collection): debug = 0 - u = Coefficient(collection.shared_objects.U) - v = Coefficient(collection.shared_objects.V) - w = Coefficient(collection.shared_objects.W) + u = Coefficient(collection.shared_objects.u_space) + v = Coefficient(collection.shared_objects.v_space) + w = Coefficient(collection.shared_objects.w_space) vu = variable(u) vv = variable(v) diff --git a/test/test_change_to_reference_frame.py b/test/test_change_to_reference_frame.py index c70076856..a7346b195 100755 --- a/test/test_change_to_reference_frame.py +++ b/test/test_change_to_reference_frame.py @@ -1,6 +1,6 @@ """Tests of the change to reference frame algorithm.""" -from ufl import Coefficient, FiniteElement, TensorElement, VectorElement, triangle +from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, VectorElement, triangle from ufl.classes import Expr, ReferenceValue @@ -14,25 +14,36 @@ def test_change_unmapped_form_arguments_to_reference_frame(): V = VectorElement("CG", triangle, 1) T = TensorElement("CG", triangle, 1) - expr = Coefficient(U) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + u_space = FunctionSpace(domain, U) + v_space = FunctionSpace(domain, V) + t_space = FunctionSpace(domain, T) + + expr = Coefficient(u_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) - expr = Coefficient(V) + expr = Coefficient(v_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) - expr = Coefficient(T) + expr = Coefficient(t_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) def test_change_hdiv_form_arguments_to_reference_frame(): V = FiniteElement("RT", triangle, 1) - expr = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + + expr = Coefficient(v_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) def test_change_hcurl_form_arguments_to_reference_frame(): V = FiniteElement("RT", triangle, 1) - expr = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + + expr = Coefficient(v_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) ''' diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index 28bb83776..7b7d6f583 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -4,13 +4,13 @@ import ufl from ufl import * # noqa: F403, F401 from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, - MinFacetEdgeLength, MixedElement, Not, Or, PermutationSymbol, SpatialCoordinate, TensorConstant, - TensorElement, VectorConstant, VectorElement, acos, action, as_matrix, as_tensor, as_ufl, as_vector, - asin, atan, cell_avg, cofac, conditional, cos, cosh, cross, curl, derivative, det, dev, diff, div, dot, - ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, i, inner, inv, j, k, l, le, ln, lt, nabla_div, - nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, tan, tanh, tetrahedron, tr, transpose, - triangle, variable) + FiniteElement, FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, + MaxFacetEdgeLength, Mesh, MinFacetEdgeLength, MixedElement, Not, Or, PermutationSymbol, + SpatialCoordinate, TensorConstant, TensorElement, VectorConstant, VectorElement, acos, action, + as_matrix, as_tensor, as_ufl, as_vector, asin, atan, cell_avg, cofac, conditional, cos, cosh, cross, + curl, derivative, det, dev, diff, div, dot, ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, i, inner, + inv, j, k, l, le, ln, lt, nabla_div, nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, tan, tanh, + tetrahedron, tr, transpose, triangle, variable) from ufl.algorithms import * # noqa: F403, F401 from ufl.classes import * # noqa: F403, F401 from ufl.classes import (Acos, Asin, Atan, CellCoordinate, Cos, Cosh, Exp, Expr, FacetJacobian, @@ -113,38 +113,46 @@ def testAll(self): e13D = VectorElement("CG", tetrahedron, 1) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain3D = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + e0_space = FunctionSpace(domain, e0) + e1_space = FunctionSpace(domain, e1) + e2_space = FunctionSpace(domain, e2) + e3_space = FunctionSpace(domain, e3) + e13d_space = FunctionSpace(domain3D, e13D) + # --- Terminals: - v13D = Argument(e13D, 3) - f13D = Coefficient(e13D) + v13D = Argument(e13d_space, 3) + f13D = Coefficient(e13d_space) - v0 = Argument(e0, 4) - v1 = Argument(e1, 5) - v2 = Argument(e2, 6) - v3 = Argument(e3, 7) + v0 = Argument(e0_space, 4) + v1 = Argument(e1_space, 5) + v2 = Argument(e2_space, 6) + v3 = Argument(e3_space, 7) _test_object(v0, (), ()) _test_object(v1, (dim,), ()) _test_object(v2, (dim, dim), ()) _test_object(v3, (dim*dim+dim+1,), ()) - f0 = Coefficient(e0) - f1 = Coefficient(e1) - f2 = Coefficient(e2) - f3 = Coefficient(e3) + f0 = Coefficient(e0_space) + f1 = Coefficient(e1_space) + f2 = Coefficient(e2_space) + f3 = Coefficient(e3_space) _test_object(f0, (), ()) _test_object(f1, (dim,), ()) _test_object(f2, (dim, dim), ()) _test_object(f3, (dim*dim+dim+1,), ()) - c = Constant(cell) + c = Constant(domain) _test_object(c, (), ()) - c = VectorConstant(cell) + c = VectorConstant(domain) _test_object(c, (dim,), ()) - c = TensorConstant(cell) + c = TensorConstant(domain) _test_object(c, (dim, dim), ()) a = FloatValue(1.23) @@ -165,51 +173,51 @@ def testAll(self): e = PermutationSymbol(3) _test_object(e, (3, 3, 3), ()) - x = SpatialCoordinate(cell) + x = SpatialCoordinate(domain) _test_object(x, (dim,), ()) - xi = CellCoordinate(cell) + xi = CellCoordinate(domain) _test_object(xi, (dim,), ()) - # g = CellBarycenter(cell) + # g = CellBarycenter(domain) # _test_object(g, (dim,), ()) - # g = FacetBarycenter(cell) + # g = FacetBarycenter(domain) # _test_object(g, (dim,), ()) - g = Jacobian(cell) + g = Jacobian(domain) _test_object(g, (dim, dim), ()) - g = JacobianDeterminant(cell) + g = JacobianDeterminant(domain) _test_object(g, (), ()) - g = JacobianInverse(cell) + g = JacobianInverse(domain) _test_object(g, (dim, dim), ()) - g = FacetJacobian(cell) + g = FacetJacobian(domain) _test_object(g, (dim, dim-1), ()) - g = FacetJacobianDeterminant(cell) + g = FacetJacobianDeterminant(domain) _test_object(g, (), ()) - g = FacetJacobianInverse(cell) + g = FacetJacobianInverse(domain) _test_object(g, (dim-1, dim), ()) - g = FacetNormal(cell) + g = FacetNormal(domain) _test_object(g, (dim,), ()) - # g = CellNormal(cell) + # g = CellNormal(domain) # _test_object(g, (dim,), ()) - g = CellVolume(cell) + g = CellVolume(domain) _test_object(g, (), ()) - g = CellDiameter(cell) + g = CellDiameter(domain) _test_object(g, (), ()) - g = Circumradius(cell) + g = Circumradius(domain) _test_object(g, (), ()) - # g = CellSurfaceArea(cell) + # g = CellSurfaceArea(domain) # _test_object(g, (), ()) - g = FacetArea(cell) + g = FacetArea(domain) _test_object(g, (), ()) - g = MinFacetEdgeLength(cell) + g = MinFacetEdgeLength(domain) _test_object(g, (), ()) - g = MaxFacetEdgeLength(cell) + g = MaxFacetEdgeLength(domain) _test_object(g, (), ()) - # g = FacetDiameter(cell) + # g = FacetDiameter(domain) # _test_object(g, (), ()) a = variable(v0) diff --git a/test/test_complex.py b/test/test_complex.py index d908e1367..999485ef1 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -2,9 +2,9 @@ import pytest -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, as_tensor, as_ufl, atan, conditional, conj, - cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, max_value, min_value, outer, real, sin, - sqrt, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, as_tensor, + as_ufl, atan, conditional, conj, cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, + max_value, min_value, outer, real, sin, sqrt, triangle) from ufl.algebra import Conj, Imag, Real from ufl.algorithms import estimate_total_polynomial_degree from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering @@ -46,9 +46,11 @@ def test_imag(self): def test_compute_form_adjoint(self): cell = triangle element = FiniteElement('Lagrange', cell, 1) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - u = TrialFunction(element) - v = TestFunction(element) + u = TrialFunction(space) + v = TestFunction(space) a = inner(grad(u), grad(v)) * dx @@ -73,9 +75,11 @@ def test_complex_algebra(self): def test_automatic_simplification(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) assert inner(u, v) == u * conj(v) assert dot(u, v) == u * v @@ -85,9 +89,11 @@ def test_automatic_simplification(self): def test_apply_algebra_lowering_complex(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) gv = grad(v) gu = grad(u) @@ -112,10 +118,12 @@ def test_apply_algebra_lowering_complex(self): def test_remove_complex_nodes(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - u = TrialFunction(element) - v = TestFunction(element) - f = Coefficient(element) + u = TrialFunction(space) + v = TestFunction(space) + f = Coefficient(space) a = conj(v) b = real(u) @@ -133,9 +141,11 @@ def test_remove_complex_nodes(self): def test_comparison_checker(self): cell = triangle element = FiniteElement("Lagrange", cell, 1) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - u = TrialFunction(element) - v = TestFunction(element) + u = TrialFunction(space) + v = TestFunction(space) a = conditional(ge(abs(u), imag(v)), u, v) b = conditional(le(sqrt(abs(u)), imag(v)), as_ufl(1), as_ufl(1j)) @@ -159,8 +169,10 @@ def test_comparison_checker(self): def test_complex_degree_handling(self): cell = triangle element = FiniteElement("Lagrange", cell, 3) + domain = Mesh(VectorElement('Lagrange', cell, 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) + v = TestFunction(space) a = conj(v) b = imag(v) diff --git a/test/test_conditionals.py b/test/test_conditionals.py index 3b6dffce2..e16fb1fc1 100755 --- a/test/test_conditionals.py +++ b/test/test_conditionals.py @@ -3,20 +3,25 @@ import pytest -from ufl import Coefficient, FiniteElement, conditional, eq, ge, gt, le, lt, ne, triangle +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, conditional, eq, ge, gt, le, lt, ne, + triangle) from ufl.classes import EQ, GE, GT, LE, LT, NE @pytest.fixture def f(): element = FiniteElement("Lagrange", triangle, 1) - return Coefficient(element) + domain = Mesh(VectorElement('Lagrange', triangle, 1)) + space = FunctionSpace(domain, element) + return Coefficient(space) @pytest.fixture def g(): element = FiniteElement("Lagrange", triangle, 1) - return Coefficient(element) + domain = Mesh(VectorElement('Lagrange', triangle, 1)) + space = FunctionSpace(domain, element) + return Coefficient(space) def test_conditional_does_not_allow_bool_condition(f, g): diff --git a/test/test_degree_estimation.py b/test/test_degree_estimation.py index 8f05713c5..e0d6c05cd 100755 --- a/test/test_degree_estimation.py +++ b/test/test_degree_estimation.py @@ -2,9 +2,9 @@ __date__ = "2008-03-12 -- 2009-01-28" -from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FiniteElement, SpatialCoordinate, - TensorProductElement, VectorElement, cos, div, dot, grad, i, inner, nabla_div, nabla_grad, sin, tan, - triangle) +from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FiniteElement, FunctionSpace, Mesh, + SpatialCoordinate, TensorProductElement, VectorElement, cos, div, dot, grad, i, inner, nabla_div, + nabla_grad, sin, tan, triangle) from ufl.algorithms import estimate_total_polynomial_degree @@ -15,15 +15,26 @@ def test_total_degree_estimation(): VM = V1 * V2 O1 = TensorProductElement(V1, V1) O2 = TensorProductElement(V2, V1) - v1 = Argument(V1, 2) - v2 = Argument(V2, 3) - f1, f2 = Coefficients(VM) - u1 = Coefficient(O1) - u2 = Coefficient(O2) - vv = Argument(VV, 4) - vu = Argument(VV, 5) - - x, y = SpatialCoordinate(triangle) + + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + tensor_domain = Mesh(VectorElement("Lagrange", O1.cell(), 1)) + + v1_space = FunctionSpace(domain, V1) + v2_space = FunctionSpace(domain, V2) + vv_space = FunctionSpace(domain, VV) + vm_space = FunctionSpace(domain, VM) + o1_space = FunctionSpace(tensor_domain, O1) + o2_space = FunctionSpace(tensor_domain, O2) + + v1 = Argument(v1_space, 2) + v2 = Argument(v2_space, 3) + f1, f2 = Coefficients(vm_space) + u1 = Coefficient(o1_space) + u2 = Coefficient(o2_space) + vv = Argument(vv_space, 4) + vu = Argument(vv_space, 5) + + x, y = SpatialCoordinate(domain) assert estimate_total_polynomial_degree(x) == 1 assert estimate_total_polynomial_degree(x * y) == 2 assert estimate_total_polynomial_degree(x ** 3) == 3 @@ -83,7 +94,7 @@ def test_total_degree_estimation(): assert estimate_total_polynomial_degree(u2 ** 2 * u1) == (5, 3) # Math functions of constant values are constant values - nx, ny = FacetNormal(triangle) + nx, ny = FacetNormal(domain) e = nx ** 2 for f in [sin, cos, tan, abs, lambda z:z**7]: assert estimate_total_polynomial_degree(f(e)) == 0 @@ -106,9 +117,10 @@ def test_some_compound_types(): P2 = FiniteElement("CG", triangle, 2) V2 = VectorElement("CG", triangle, 2) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) - u = Coefficient(P2) - v = Coefficient(V2) + u = Coefficient(FunctionSpace(domain, P2)) + v = Coefficient(FunctionSpace(domain, V2)) assert etpd(u.dx(0)) == 2 - 1 assert etpd(grad(u)) == 2 - 1 diff --git a/test/test_derivative.py b/test/test_derivative.py index e8ce1837d..4b1972f27 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -73,9 +73,12 @@ def make_value(c): def _test(self, f, df): cell = triangle element = FiniteElement("CG", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) - w = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + + v = TestFunction(space) + u = TrialFunction(space) + w = Coefficient(space) xv = (0.3, 0.7) uv = 7.0 vv = 13.0 @@ -123,7 +126,8 @@ def df(w, v): return v def testArgument(self): - def f(w): return TestFunction(FiniteElement("CG", triangle, 1)) + def f(w): return TestFunction(FunctionSpace(Mesh(VectorElement("Lagrange", triangle, 1)), + FiniteElement("CG", triangle, 1))) def df(w, v): return zero() _test(self, f, df) @@ -132,47 +136,47 @@ def df(w, v): return zero() def testSpatialCoordinate(self): - def f(w): return SpatialCoordinate(triangle)[0] + def f(w): return SpatialCoordinate(Mesh(VectorElement("Lagrange", triangle, 1)))[0] def df(w, v): return zero() _test(self, f, df) def testFacetNormal(self): - def f(w): return FacetNormal(triangle)[0] + def f(w): return FacetNormal(Mesh(VectorElement("Lagrange", triangle, 1)))[0] def df(w, v): return zero() _test(self, f, df) # def testCellSurfaceArea(self): -# def f(w): return CellSurfaceArea(triangle) +# def f(w): return CellSurfaceArea(Mesh(VectorElement("Lagrange", triangle, 1))) # def df(w, v): return zero() # _test(self, f, df) def testFacetArea(self): - def f(w): return FacetArea(triangle) + def f(w): return FacetArea(Mesh(VectorElement("Lagrange", triangle, 1))) def df(w, v): return zero() _test(self, f, df) def testCellDiameter(self): - def f(w): return CellDiameter(triangle) + def f(w): return CellDiameter(Mesh(VectorElement("Lagrange", triangle, 1))) def df(w, v): return zero() _test(self, f, df) def testCircumradius(self): - def f(w): return Circumradius(triangle) + def f(w): return Circumradius(Mesh(VectorElement("Lagrange", triangle, 1))) def df(w, v): return zero() _test(self, f, df) def testCellVolume(self): - def f(w): return CellVolume(triangle) + def f(w): return CellVolume(Mesh(VectorElement("Lagrange", triangle, 1))) def df(w, v): return zero() _test(self, f, df) @@ -333,8 +337,10 @@ def testListTensor(self): def test_single_scalar_coefficient_derivative(self): cell = triangle V = FiniteElement("CG", cell, 1) - u = Coefficient(V) - v = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + u = Coefficient(space) + v = TestFunction(space) a = 3*u**2 b = derivative(a, u, v) self.assertEqualAfterPreprocessing(b, 3*(u*(2*v))) @@ -343,8 +349,10 @@ def test_single_scalar_coefficient_derivative(self): def test_single_vector_coefficient_derivative(self): cell = triangle V = VectorElement("CG", cell, 1) - u = Coefficient(V) - v = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + u = Coefficient(space) + v = TestFunction(space) a = 3*dot(u, u) actual = derivative(a, u, v) expected = 3*(2*(u[i]*v[i])) @@ -356,9 +364,13 @@ def test_multiple_coefficient_derivative(self): V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) M = V*W - uv = Coefficient(V) - uw = Coefficient(W) - v = TestFunction(M) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v_space = FunctionSpace(domain, V) + w_space = FunctionSpace(domain, W) + m_space = FunctionSpace(domain, M) + uv = Coefficient(v_space) + uw = Coefficient(w_space) + v = TestFunction(m_space) vv, vw = split(v) a = sin(uv)*dot(uw, uw) @@ -377,8 +389,11 @@ def test_indexed_coefficient_derivative(self): ident = Identity(cell.geometric_dimension()) V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) - u = Coefficient(W) - v = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v_space = FunctionSpace(domain, V) + w_space = FunctionSpace(domain, W) + u = Coefficient(w_space) + v = TestFunction(v_space) w = dot(u, nabla_grad(u)) # a = dot(w, w) @@ -397,9 +412,12 @@ def test_multiple_indexed_coefficient_derivative(self): V = FiniteElement("CG", cell, 1) V2 = V*V W = VectorElement("CG", cell, 1) - u = Coefficient(W) - w = Coefficient(W) - v = TestFunction(V2) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v2_space = FunctionSpace(domain, V2) + w_space = FunctionSpace(domain, W) + u = Coefficient(w_space) + w = Coefficient(w_space) + v = TestFunction(v2_space) vu, vw = split(v) actual = derivative(cos(u[i]*w[i]), (u[2], w[1]), (vu, vw)) @@ -413,10 +431,14 @@ def test_segregated_derivative_of_convection(self): V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) - u = Coefficient(W) - v = Coefficient(W) - du = TrialFunction(V) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v_space = FunctionSpace(domain, V) + w_space = FunctionSpace(domain, W) + + u = Coefficient(w_space) + v = Coefficient(w_space) + du = TrialFunction(v_space) + dv = TestFunction(v_space) L = dot(dot(u, nabla_grad(u)), v) @@ -447,13 +469,16 @@ def test_segregated_derivative_of_convection(self): def test_coefficient_derivatives(self): V = FiniteElement("Lagrange", triangle, 1) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + + dv = TestFunction(space) - f = Coefficient(V, count=0) - g = Coefficient(V, count=1) - df = Coefficient(V, count=2) - dg = Coefficient(V, count=3) - u = Coefficient(V, count=4) + f = Coefficient(space, count=0) + g = Coefficient(space, count=1) + df = Coefficient(space, count=2) + dg = Coefficient(space, count=3) + u = Coefficient(space, count=4) cd = {f: df, g: dg} integrand = inner(f, g) @@ -471,12 +496,16 @@ def test_vector_coefficient_scalar_derivatives(self): V = FiniteElement("Lagrange", triangle, 1) VV = VectorElement("Lagrange", triangle, 1) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + vv_space = FunctionSpace(domain, VV) + + dv = TestFunction(v_space) - df = Coefficient(VV, count=0) - g = Coefficient(VV, count=1) - f = Coefficient(VV, count=2) - u = Coefficient(V, count=3) + df = Coefficient(vv_space, count=0) + g = Coefficient(vv_space, count=1) + f = Coefficient(vv_space, count=2) + u = Coefficient(v_space, count=3) cd = {f: df} integrand = inner(f, g) @@ -495,12 +524,16 @@ def test_vector_coefficient_derivatives(self): V = VectorElement("Lagrange", triangle, 1) VV = TensorElement("Lagrange", triangle, 1) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + vv_space = FunctionSpace(domain, VV) - df = Coefficient(VV, count=0) - g = Coefficient(V, count=1) - f = Coefficient(V, count=2) - u = Coefficient(V, count=3) + dv = TestFunction(v_space) + + df = Coefficient(vv_space, count=0) + g = Coefficient(v_space, count=1) + f = Coefficient(v_space, count=2) + u = Coefficient(v_space, count=3) cd = {f: df} integrand = inner(f, g) @@ -520,13 +553,17 @@ def test_vector_coefficient_derivatives_of_product(self): V = VectorElement("Lagrange", triangle, 1) VV = TensorElement("Lagrange", triangle, 1) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + vv_space = FunctionSpace(domain, VV) + + dv = TestFunction(v_space) - df = Coefficient(VV, count=0) - g = Coefficient(V, count=1) - dg = Coefficient(VV, count=2) - f = Coefficient(V, count=3) - u = Coefficient(V, count=4) + df = Coefficient(vv_space, count=0) + g = Coefficient(v_space, count=1) + dg = Coefficient(vv_space, count=2) + f = Coefficient(v_space, count=3) + u = Coefficient(v_space, count=4) cd = {f: df, g: dg} integrand = f[i]*g[i] @@ -555,11 +592,13 @@ def test_vector_coefficient_derivatives_of_product(self): def testHyperElasticity(self): cell = interval element = FiniteElement("CG", cell, 2) - w = Coefficient(element) - v = TestFunction(element) - u = TrialFunction(element) - b = Constant(cell) - K = Constant(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + w = Coefficient(space) + v = TestFunction(space) + u = TrialFunction(space) + b = Constant(domain) + K = Constant(domain) dw = w.dx(0) dv = v.dx(0) @@ -632,9 +671,12 @@ def test_mass_derived_from_functional(self): cell = triangle V = FiniteElement("CG", cell, 1) - v = TestFunction(V) - u = TrialFunction(V) - w = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + + v = TestFunction(space) + u = TrialFunction(space) + w = Coefficient(space) f = (w**2/2)*dx L = w*v*dx @@ -651,10 +693,13 @@ def test_derivative_replace_works_together(self): cell = triangle V = FiniteElement("CG", cell, 1) - v = TestFunction(V) - u = TrialFunction(V) - f = Coefficient(V) - g = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) M = cos(f)*sin(g) F = derivative(M, f, v) @@ -704,11 +749,13 @@ def test_index_simplification_reference_grad(self): def test_foobar(self): element = VectorElement("Lagrange", triangle, 1) - v = TestFunction(element) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) - du = TrialFunction(element) + du = TrialFunction(space) - U = Coefficient(element) + U = Coefficient(space) def planarGrad(u): return as_matrix([[u[0].dx(0), 0, u[0].dx(1)], diff --git a/test/test_diff.py b/test/test_diff.py index 47305bc4e..fd1ef4f34 100755 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -179,7 +179,8 @@ def testCoefficient(): def testDiffX(): cell = triangle - x = SpatialCoordinate(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + x = SpatialCoordinate(domain) f = x[0] ** 2 * x[1] ** 2 i, = indices(1) df1 = diff(f, x) diff --git a/test/test_domains.py b/test/test_domains.py index 81671aba4..88ee286ab 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -6,7 +6,6 @@ from ufl import (Cell, Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, ds, dS, dx, hexahedron, interval, quadrilateral, tetrahedron, triangle) from ufl.algorithms import compute_form_data -from ufl.domain import as_domain, default_domain all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) @@ -14,36 +13,15 @@ def test_construct_domains_from_cells(): for cell in all_cells: - D0 = Mesh(cell) - D1 = default_domain(cell) - D2 = as_domain(cell) - assert D0 is not D1 - assert D0 is not D2 - assert D1 is D2 - if 0: - print() - for D in (D1, D2): - print(('id', id(D))) - print(('str', str(D))) - print(('repr', repr(D))) - print() - assert D0 != D1 - assert D0 != D2 - assert D1 == D2 - - -def test_as_domain_from_cell_is_equal(): - for cell in all_cells: - D1 = as_domain(cell) - D2 = as_domain(cell) - assert D1 == D2 + Mesh(VectorElement("Lagrange", cell, 1)) def test_construct_domains_with_names(): for cell in all_cells: - D2 = Mesh(cell, ufl_id=2) - D3 = Mesh(cell, ufl_id=3) - D3b = Mesh(cell, ufl_id=3) + e = VectorElement("Lagrange", cell, 1) + D2 = Mesh(e, ufl_id=2) + D3 = Mesh(e, ufl_id=3) + D3b = Mesh(e, ufl_id=3) assert D2 != D3 assert D3 == D3b @@ -51,9 +29,9 @@ def test_construct_domains_with_names(): def test_domains_sort_by_name(): # This ordering is rather arbitrary, but at least this shows sorting is # working - domains1 = [Mesh(cell, ufl_id=hash(cell.cellname())) + domains1 = [Mesh(VectorElement("Lagrange", cell, 1), ufl_id=hash(cell.cellname())) for cell in all_cells] - domains2 = [Mesh(cell, ufl_id=hash(cell.cellname())) + domains2 = [Mesh(VectorElement("Lagrange", cell, 1), ufl_id=hash(cell.cellname())) for cell in sorted(all_cells)] sdomains = sorted(domains1, key=lambda D: (D.geometric_dimension(), D.topological_dimension(), @@ -74,10 +52,10 @@ def test_topdomain_creation(): def test_cell_legacy_case(): # Passing cell like old code does - D = as_domain(triangle) + D = Mesh(VectorElement("Lagrange", triangle, 1)) V = FiniteElement("CG", triangle, 1) - f = Coefficient(V) + f = Coefficient(FunctionSpace(D, V)) assert f.ufl_domains() == (D, ) M = f * dx diff --git a/test/test_duals.py b/test/test_duals.py index f21054857..7b6df9f53 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -4,20 +4,19 @@ import pytest from ufl import (Action, Adjoint, Argument, Coargument, Coefficient, Cofunction, FiniteElement, FormSum, FunctionSpace, - Matrix, MixedFunctionSpace, TestFunction, TrialFunction, action, adjoint, derivative, dx, inner, - interval, tetrahedron, triangle) + Matrix, Mesh, MixedFunctionSpace, TestFunction, TrialFunction, VectorElement, action, adjoint, + derivative, dx, inner, interval, tetrahedron, triangle) from ufl.algorithms.ad import expand_derivatives from ufl.constantvalue import Zero -from ufl.domain import default_domain from ufl.duals import is_dual, is_primal from ufl.form import ZeroBaseForm def test_mixed_functionspace(self): # Domains - domain_3d = default_domain(tetrahedron) - domain_2d = default_domain(triangle) - domain_1d = default_domain(interval) + domain_3d = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) + domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) # Finite elements f_1d = FiniteElement("CG", interval, 1) f_2d = FiniteElement("CG", triangle, 1) @@ -48,7 +47,7 @@ def test_mixed_functionspace(self): def test_dual_coefficients(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -71,7 +70,7 @@ def test_dual_coefficients(): def test_dual_arguments(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -94,7 +93,7 @@ def test_dual_arguments(): def test_addition(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -132,7 +131,7 @@ def test_addition(): def test_scalar_mult(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -150,7 +149,7 @@ def test_scalar_mult(): def test_adjoint(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) a = Matrix(V, V) @@ -169,10 +168,10 @@ def test_adjoint(): def test_action(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = default_domain(interval) + domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) f_1d = FiniteElement("CG", interval, 1) U = FunctionSpace(domain_1d, f_1d) @@ -229,10 +228,10 @@ def test_action(): def test_differentiation(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = default_domain(interval) + domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) f_1d = FiniteElement("CG", interval, 1) U = FunctionSpace(domain_1d, f_1d) @@ -291,7 +290,7 @@ def test_differentiation(): def test_zero_base_form_mult(): - domain_2d = default_domain(triangle) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) f_2d = FiniteElement("CG", triangle, 1) V = FunctionSpace(domain_2d, f_2d) v = Argument(V, 0) diff --git a/test/test_elements.py b/test/test_elements.py index f096860e4..370679662 100755 --- a/test/test_elements.py +++ b/test/test_elements.py @@ -1,5 +1,5 @@ -from ufl import (Coefficient, FiniteElement, MixedElement, TensorElement, VectorElement, WithMapping, dx, hexahedron, - inner, interval, quadrilateral, tetrahedron, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, MixedElement, TensorElement, VectorElement, + WithMapping, dx, hexahedron, inner, interval, quadrilateral, tetrahedron, triangle) all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) @@ -82,7 +82,9 @@ def test_mixed_tensor_symmetries(): # M has dimension 4+1, symmetries are 2->1 M = T * S - P = Coefficient(M) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + m_space = FunctionSpace(domain, M) + P = Coefficient(m_space) M = inner(P, P) * dx M2 = expand_indices(expand_compounds(M)) @@ -91,7 +93,8 @@ def test_mixed_tensor_symmetries(): # M has dimension 2+(1+4), symmetries are 5->4 M = V * (S * T) - P = Coefficient(M) + m_space = FunctionSpace(domain, M) + P = Coefficient(m_space) M = inner(P, P) * dx M2 = expand_indices(expand_compounds(M)) diff --git a/test/test_equals.py b/test/test_equals.py index 4c36ad7cd..184d71c16 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -1,18 +1,24 @@ """Test of expression comparison.""" -from ufl import Coefficient, Cofunction, FiniteElement, triangle +from ufl import Coefficient, Cofunction, FiniteElement, FunctionSpace, Mesh, VectorElement, triangle def test_comparison_of_coefficients(): V = FiniteElement("CG", triangle, 1) U = FiniteElement("CG", triangle, 2) Ub = FiniteElement("CG", triangle, 2) - v1 = Coefficient(V, count=1) - v1b = Coefficient(V, count=1) - v2 = Coefficient(V, count=2) - u1 = Coefficient(U, count=1) - u2 = Coefficient(U, count=2) - u2b = Coefficient(Ub, count=2) + + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + u_space = FunctionSpace(domain, U) + ub_space = FunctionSpace(domain, Ub) + + v1 = Coefficient(v_space, count=1) + v1b = Coefficient(v_space, count=1) + v2 = Coefficient(v_space, count=2) + u1 = Coefficient(u_space, count=1) + u2 = Coefficient(u_space, count=2) + u2b = Coefficient(ub_space, count=2) # Identical objects assert v1 == v1 @@ -33,12 +39,18 @@ def test_comparison_of_cofunctions(): V = FiniteElement("CG", triangle, 1) U = FiniteElement("CG", triangle, 2) Ub = FiniteElement("CG", triangle, 2) - v1 = Cofunction(V, count=1) - v1b = Cofunction(V, count=1) - v2 = Cofunction(V, count=2) - u1 = Cofunction(U, count=1) - u2 = Cofunction(U, count=2) - u2b = Cofunction(Ub, count=2) + + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + u_space = FunctionSpace(domain, U) + ub_space = FunctionSpace(domain, Ub) + + v1 = Cofunction(v_space.dual(), count=1) + v1b = Cofunction(v_space.dual(), count=1) + v2 = Cofunction(v_space.dual(), count=2) + u1 = Cofunction(u_space.dual(), count=1) + u2 = Cofunction(u_space.dual(), count=2) + u2b = Cofunction(ub_space.dual(), count=2) # Identical objects assert v1 == v1 @@ -57,8 +69,10 @@ def test_comparison_of_cofunctions(): def test_comparison_of_products(): V = FiniteElement("CG", triangle, 1) - v = Coefficient(V) - u = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + v = Coefficient(v_space) + u = Coefficient(v_space) a = (v * 2) * u b = (2 * v) * u c = 2 * (v * u) @@ -69,8 +83,10 @@ def test_comparison_of_products(): def test_comparison_of_sums(): V = FiniteElement("CG", triangle, 1) - v = Coefficient(V) - u = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + v = Coefficient(v_space) + u = Coefficient(v_space) a = (v + 2) + u b = (2 + v) + u c = 2 + (v + u) @@ -81,9 +97,11 @@ def test_comparison_of_sums(): def test_comparison_of_deeply_nested_expression(): V = FiniteElement("CG", triangle, 1) - v = Coefficient(V, count=1) - u = Coefficient(V, count=1) - w = Coefficient(V, count=2) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v_space = FunctionSpace(domain, V) + v = Coefficient(v_space, count=1) + u = Coefficient(v_space, count=1) + w = Coefficient(v_space, count=2) def build_expr(a): for i in range(100): diff --git a/test/test_evaluate.py b/test/test_evaluate.py index 29488e207..61e8af9e9 100755 --- a/test/test_evaluate.py +++ b/test/test_evaluate.py @@ -3,9 +3,9 @@ import math -from ufl import (Argument, Coefficient, FiniteElement, Identity, SpatialCoordinate, as_matrix, as_vector, cos, cross, - det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, sym, tan, tetrahedron, tr, - triangle) +from ufl import (Argument, Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, SpatialCoordinate, VectorElement, + as_matrix, as_vector, cos, cross, det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, + sym, tan, tetrahedron, tr, triangle) from ufl.constantvalue import as_ufl @@ -40,7 +40,8 @@ def testIdentity(): def testCoords(): cell = triangle - x = SpatialCoordinate(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + x = SpatialCoordinate(domain) s = x[0] + x[1] e = s((5, 7)) v = 5 + 7 @@ -50,7 +51,9 @@ def testCoords(): def testFunction1(): cell = triangle element = FiniteElement("CG", cell, 1) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + f = Coefficient(space) s = 3 * f e = s((5, 7), {f: 123}) v = 3 * 123 @@ -60,7 +63,9 @@ def testFunction1(): def testFunction2(): cell = triangle element = FiniteElement("CG", cell, 1) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + f = Coefficient(space) def g(x): return x[0] @@ -73,7 +78,9 @@ def g(x): def testArgument2(): cell = triangle element = FiniteElement("CG", cell, 1) - f = Argument(element, 2) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + f = Argument(space, 2) def g(x): return x[0] @@ -85,7 +92,8 @@ def g(x): def testAlgebra(): cell = triangle - x = SpatialCoordinate(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + x = SpatialCoordinate(domain) s = 3 * (x[0] + x[1]) - 7 + x[0] ** (x[1] / 2) e = s((5, 7)) v = 3 * (5. + 7.) - 7 + 5. ** (7. / 2) @@ -94,7 +102,8 @@ def testAlgebra(): def testIndexSum(): cell = triangle - x = SpatialCoordinate(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + x = SpatialCoordinate(domain) i, = indices(1) s = x[i] * x[i] e = s((5, 7)) @@ -104,7 +113,8 @@ def testIndexSum(): def testIndexSum2(): cell = triangle - x = SpatialCoordinate(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + x = SpatialCoordinate(domain) ident = Identity(cell.geometric_dimension()) i, j = indices(2) s = (x[i] * x[j]) * ident[i, j] @@ -115,7 +125,8 @@ def testIndexSum2(): def testMathFunctions(): - x = SpatialCoordinate(triangle)[0] + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain)[0] s = sin(x) e = s((5, 7)) @@ -149,7 +160,8 @@ def testMathFunctions(): def testListTensor(): - x, y = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x, y = SpatialCoordinate(domain) m = as_matrix([[x, y], [-y, -x]]) @@ -165,7 +177,8 @@ def testListTensor(): def testComponentTensor1(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) m = as_vector(x[i], i) s = m[0] * m[1] @@ -175,7 +188,8 @@ def testComponentTensor1(): def testComponentTensor2(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xx = outer(x, x) m = as_matrix(xx[i, j], (i, j)) @@ -187,7 +201,8 @@ def testComponentTensor2(): def testComponentTensor3(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xx = outer(x, x) m = as_matrix(xx[i, j], (i, j)) @@ -200,7 +215,9 @@ def testComponentTensor3(): def testCoefficient(): V = FiniteElement("CG", triangle, 1) - f = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + f = Coefficient(space) e = f ** 2 def eval_f(x): @@ -210,7 +227,9 @@ def eval_f(x): def testCoefficientDerivative(): V = FiniteElement("CG", triangle, 1) - f = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + f = Coefficient(space) e = f.dx(0) ** 2 + f.dx(1) ** 2 def eval_f(x, derivatives): @@ -229,7 +248,8 @@ def eval_f(x, derivatives): def test_dot(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) s = dot(x, 2 * x) e = s((5, 7)) v = 2 * (5 * 5 + 7 * 7) @@ -237,7 +257,8 @@ def test_dot(): def test_inner(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xx = as_matrix(((2 * x[0], 3 * x[0]), (2 * x[1], 3 * x[1]))) s = inner(xx, 2 * xx) e = s((5, 7)) @@ -246,7 +267,8 @@ def test_inner(): def test_outer(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xx = outer(outer(x, x), as_vector((2, 3))) s = inner(xx, 2 * xx) e = s((5, 7)) @@ -255,7 +277,8 @@ def test_outer(): def test_cross(): - x = SpatialCoordinate(tetrahedron) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + x = SpatialCoordinate(domain) xv = (3, 5, 7) # Test cross product of triplets of orthogonal @@ -287,7 +310,8 @@ def test_cross(): def xtest_dev(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s1 = dev(2 * xx) @@ -298,7 +322,8 @@ def xtest_dev(): def test_skew(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s1 = skew(2 * xx) @@ -309,7 +334,8 @@ def test_skew(): def test_sym(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s1 = sym(2 * xx) @@ -320,7 +346,8 @@ def test_sym(): def test_tr(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s = tr(2 * xx) @@ -330,7 +357,8 @@ def test_tr(): def test_det2D(): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) xv = (5, 7) a, b = 6.5, -4 xx = as_matrix(((x[0], x[1]), (a, b))) diff --git a/test/test_expand_indices.py b/test/test_expand_indices.py index 77977552f..023b4ccb1 100755 --- a/test/test_expand_indices.py +++ b/test/test_expand_indices.py @@ -8,8 +8,8 @@ import pytest -from ufl import (Coefficient, FiniteElement, Identity, TensorElement, VectorElement, as_tensor, cos, det, div, dot, dx, - exp, grad, i, inner, j, k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, VectorElement, as_tensor, + cos, det, div, dot, dx, exp, grad, i, inner, j, k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) from ufl.algorithms import compute_form_data, expand_derivatives, expand_indices from ufl.algorithms.renumbering import renumber_indices @@ -24,10 +24,14 @@ def __init__(self): element = FiniteElement("Lagrange", cell, 1) velement = VectorElement("Lagrange", cell, 1) telement = TensorElement("Lagrange", cell, 1) - self.sf = Coefficient(element) - self.sf2 = Coefficient(element) - self.vf = Coefficient(velement) - self.tf = Coefficient(telement) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + vspace = FunctionSpace(domain, velement) + tspace = FunctionSpace(domain, telement) + self.sf = Coefficient(space) + self.sf2 = Coefficient(space) + self.vf = Coefficient(vspace) + self.tf = Coefficient(tspace) # Note: the derivatives of these functions make no sense, but # their unique constant values are used for validation. diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 4a40a1c75..13f4eaa65 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -6,32 +6,33 @@ import pytest # This imports everything external code will see from ufl -from ufl import (Action, Argument, Coefficient, Constant, FiniteElement, Form, FunctionSpace, TestFunction, - TrialFunction, action, adjoint, cos, derivative, dx, inner, sin, triangle) +from ufl import (Action, Argument, Coefficient, Constant, FiniteElement, Form, FunctionSpace, Mesh, TestFunction, + TrialFunction, VectorElement, action, adjoint, cos, derivative, dx, inner, sin, triangle) from ufl.algorithms import expand_derivatives from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.core.external_operator import ExternalOperator -from ufl.domain import default_domain from ufl.form import BaseForm @pytest.fixture -def V1(): - domain_2d = default_domain(triangle) +def domain_2d(): + return Mesh(VectorElement("Lagrange", triangle, 1)) + + +@pytest.fixture +def V1(domain_2d): f1 = FiniteElement("CG", triangle, 1) return FunctionSpace(domain_2d, f1) @pytest.fixture -def V2(): - domain_2d = default_domain(triangle) +def V2(domain_2d): f1 = FiniteElement("CG", triangle, 2) return FunctionSpace(domain_2d, f1) @pytest.fixture -def V3(): - domain_2d = default_domain(triangle) +def V3(domain_2d): f1 = FiniteElement("CG", triangle, 3) return FunctionSpace(domain_2d, f1) @@ -177,12 +178,12 @@ def test_differentiation_procedure_action(V1, V2): assert dN2du.argument_slots() == (vstar_dN2du, - sin(s) * s_hat) -def test_extractions(V1): +def test_extractions(domain_2d, V1): from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, extract_base_form_operators, extract_coefficients, extract_constants) u = Coefficient(V1) - c = Constant(triangle) + c = Constant(domain_2d) e = ExternalOperator(u, c, function_space=V1) vstar_e, = e.arguments() diff --git a/test/test_ffcforms.py b/test/test_ffcforms.py index ea01e45c3..a0dbf7e82 100755 --- a/test/test_ffcforms.py +++ b/test/test_ffcforms.py @@ -13,19 +13,21 @@ # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. -from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, TensorElement, TestFunction, TestFunctions, - TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, div, dot, ds, dS, dx, grad, i, - inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, FunctionSpace, Mesh, TensorElement, + TestFunction, TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, + div, dot, ds, dS, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) def testConstant(): element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) - c = Constant("triangle") - d = VectorConstant("triangle") + c = Constant(domain) + d = VectorConstant(domain) a = c * dot(grad(v), grad(u)) * dx # noqa: F841 @@ -35,9 +37,11 @@ def testConstant(): def testElasticity(): element = VectorElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) def eps(v): # FFC notation: return grad(v) + transp(grad(v)) @@ -49,19 +53,23 @@ def eps(v): def testEnergyNorm(): element = FiniteElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = Coefficient(element) + v = Coefficient(space) a = (v * v + dot(grad(v), grad(v))) * dx # noqa: F841 def testEquation(): element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) k = 0.1 - v = TestFunction(element) - u = TrialFunction(element) - u0 = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + u0 = Coefficient(space) F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx @@ -71,11 +79,13 @@ def testEquation(): def testFunctionOperators(): element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx @@ -84,13 +94,15 @@ def testFunctionOperators(): def testHeat(): element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u1 = TrialFunction(element) - u0 = Coefficient(element) - c = Coefficient(element) - f = Coefficient(element) - k = Constant("triangle") + v = TestFunction(space) + u1 = TrialFunction(space) + u0 = Coefficient(space) + c = Coefficient(space) + f = Coefficient(space) + k = Constant(domain) a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx # noqa: F841 L = v * u0 * dx + k * v * f * dx # noqa: F841 @@ -98,9 +110,11 @@ def testHeat(): def testMass(): element = FiniteElement("Lagrange", "tetrahedron", 3) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) a = v * u * dx # noqa: F841 @@ -118,11 +132,13 @@ def testMixedPoisson(): DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) mixed_element = BDM * DG + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, mixed_element) - (tau, w) = TestFunctions(mixed_element) - (sigma, u) = TrialFunctions(mixed_element) + (tau, w) = TestFunctions(space) + (sigma, u) = TrialFunctions(space) - f = Coefficient(DG) + f = Coefficient(FunctionSpace(domain, DG)) a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx # noqa: F841 L = w * f * dx # noqa: F841 @@ -130,11 +146,13 @@ def testMixedPoisson(): def testNavierStokes(): element = VectorElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) - w = Coefficient(element) + w = Coefficient(space) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx a = v[i] * w[j] * Dx(u[i], j) * dx # noqa: F841 @@ -142,11 +160,13 @@ def testNavierStokes(): def testNeumannProblem(): element = VectorElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # noqa: F841 @@ -157,10 +177,12 @@ def testNeumannProblem(): def testOptimization(): element = FiniteElement("Lagrange", "triangle", 3) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) a = dot(grad(v), grad(u)) * dx # noqa: F841 L = v * f * dx # noqa: F841 @@ -176,17 +198,19 @@ def testP5tri(): def testPoissonDG(): element = FiniteElement("Discontinuous Lagrange", triangle, 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) - n = FacetNormal(triangle) + n = FacetNormal(domain) - # FFC notation: h = MeshSize("triangle"), not supported by UFL - h = Constant(triangle) + # FFC notation: h = MeshSize(domain), not supported by UFL + h = Constant(domain) - gN = Coefficient(element) + gN = Coefficient(space) alpha = 4.0 gamma = 8.0 @@ -213,10 +237,12 @@ def testPoissonDG(): def testPoisson(): element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) # Note: inner() also works a = dot(grad(v), grad(u)) * dx # noqa: F841 @@ -225,10 +251,12 @@ def testPoisson(): def testPoissonSystem(): element = VectorElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx # noqa: F841 @@ -243,9 +271,11 @@ def testProjection(): # projection can be extended to handle also local projections. P1 = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, P1) - v = TestFunction(P1) # noqa: F841 - f = Coefficient(P1) # noqa: F841 + v = TestFunction(space) # noqa: F841 + f = Coefficient(space) # noqa: F841 # pi0 = Projection(P0) # pi1 = Projection(P1) @@ -264,12 +294,15 @@ def testQuadratureElement(): QE = FiniteElement("Quadrature", "triangle", 3) sig = VectorElement("Quadrature", "triangle", 3) - v = TestFunction(element) - u = TrialFunction(element) - u0 = Coefficient(element) - C = Coefficient(QE) - sig0 = Coefficient(sig) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + + v = TestFunction(space) + u = TrialFunction(space) + u0 = Coefficient(space) + C = Coefficient(FunctionSpace(domain, QE)) + sig0 = Coefficient(FunctionSpace(domain, sig)) + f = Coefficient(space) a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx # noqa: F841 L = v * f * dx - dot(grad(v), sig0) * dx # noqa: F841 @@ -282,10 +315,14 @@ def testStokes(): P1 = FiniteElement("Lagrange", "triangle", 1) TH = P2 * P1 - (v, q) = TestFunctions(TH) - (u, p) = TrialFunctions(TH) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + th_space = FunctionSpace(domain, TH) + p2_space = FunctionSpace(domain, P2) + + (v, q) = TestFunctions(th_space) + (u, p) = TrialFunctions(th_space) - f = Coefficient(P2) + f = Coefficient(p2_space) # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx @@ -296,17 +333,21 @@ def testStokes(): def testSubDomain(): element = FiniteElement("CG", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - f = Coefficient(element) + f = Coefficient(space) M = f * dx(2) + f * ds(5) # noqa: F841 def testSubDomains(): element = FiniteElement("CG", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * ds(1) a += v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) @@ -333,18 +374,22 @@ def testTensorWeightedPoisson(): P1 = FiniteElement("Lagrange", "triangle", 1) P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) - v = TestFunction(P1) - u = TrialFunction(P1) - C = Coefficient(P0) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + p1_space = FunctionSpace(domain, P1) + p0_space = FunctionSpace(domain, P0) + + v = TestFunction(p1_space) + u = TrialFunction(p1_space) + C = Coefficient(p0_space) a = inner(grad(v), C * grad(u)) * dx # noqa: F841 def testVectorLaplaceGradCurl(): - def HodgeLaplaceGradCurl(element, felement): - (tau, v) = TestFunctions(element) - (sigma, u) = TrialFunctions(element) - f = Coefficient(felement) + def HodgeLaplaceGradCurl(space, fspace): + (tau, v) = TestFunctions(space) + (sigma, u) = TrialFunctions(space) + f = Coefficient(fspace) # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx @@ -365,5 +410,7 @@ def HodgeLaplaceGradCurl(element, felement): CURL = FiniteElement("N1curl", shape, order) VectorLagrange = VectorElement("Lagrange", shape, order + 1) + domain = Mesh(VectorElement("Lagrange", shape, 1)) - [a, L] = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) + [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, GRAD * CURL), + FunctionSpace(domain, VectorLagrange)) diff --git a/test/test_form.py b/test/test_form.py index 057e89319..554ed85c5 100755 --- a/test/test_form.py +++ b/test/test_form.py @@ -13,48 +13,59 @@ def element(): @pytest.fixture -def mass(): +def domain(): + cell = triangle + return Mesh(VectorElement("Lagrange", cell, 1)) + + +@pytest.fixture +def mass(domain): cell = triangle element = FiniteElement("Lagrange", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) return u * v * dx @pytest.fixture -def stiffness(): +def stiffness(domain): cell = triangle element = FiniteElement("Lagrange", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) return inner(grad(u), grad(v)) * dx @pytest.fixture -def convection(): +def convection(domain): cell = triangle element = VectorElement("Lagrange", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) - w = Coefficient(element) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) + w = Coefficient(space) return dot(dot(w, nabla_grad(u)), v) * dx @pytest.fixture -def load(): +def load(domain): cell = triangle element = FiniteElement("Lagrange", cell, 1) - f = Coefficient(element) - v = TestFunction(element) + space = FunctionSpace(domain, element) + f = Coefficient(space) + v = TestFunction(space) return f * v * dx @pytest.fixture -def boundary_load(): +def boundary_load(domain): cell = triangle element = FiniteElement("Lagrange", cell, 1) - f = Coefficient(element) - v = TestFunction(element) + space = FunctionSpace(domain, element) + f = Coefficient(space) + v = TestFunction(space) return f * v * ds @@ -74,10 +85,11 @@ def test_form_arguments(mass, stiffness, convection, load): assert ((f * v) * u * dx + (u * 3) * (v / 2) * dx(2)).arguments() == (v, u) -def test_form_coefficients(element): - v = TestFunction(element) - f = Coefficient(element) - g = Coefficient(element) +def test_form_coefficients(element, domain): + space = FunctionSpace(domain, element) + v = TestFunction(space) + f = Coefficient(space) + g = Coefficient(space) assert (g * dx).coefficients() == (g,) assert (g * dx + g * ds).coefficients() == (g,) @@ -88,7 +100,7 @@ def test_form_coefficients(element): def test_form_domains(): cell = triangle - domain = Mesh(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1)) element = FiniteElement("Lagrange", cell, 1) V = FunctionSpace(domain, element) @@ -120,7 +132,9 @@ def test_form_integrals(mass, boundary_load): def test_form_call(): - V = FiniteElement("CG", triangle, 1) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1) + V = FunctionSpace(domain, element) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) @@ -137,8 +151,10 @@ def test_form_call(): def test_formsum(mass): - V = FiniteElement("CG", triangle, 1) - v = Cofunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1) + V = FunctionSpace(domain, element) + v = Cofunction(V.dual()) assert v + mass assert mass + v diff --git a/test/test_illegal.py b/test/test_illegal.py index f5c8d06a0..40409ef6d 100755 --- a/test/test_illegal.py +++ b/test/test_illegal.py @@ -1,56 +1,73 @@ import pytest -from ufl import Argument, Coefficient, FiniteElement, VectorElement +from ufl import Argument, Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement # TODO: Add more illegal expressions to check! +@pytest.fixture def selement(): return FiniteElement("Lagrange", "triangle", 1) +@pytest.fixture def velement(): return VectorElement("Lagrange", "triangle", 1) @pytest.fixture -def a(): - return Argument(selement(), 2) +def domain(): + return Mesh(VectorElement("Lagrange", "triangle", 1)) + + +@pytest.fixture +def sspace(domain, selement): + return FunctionSpace(domain, selement) + + +@pytest.fixture +def vspace(domain, velement): + return FunctionSpace(domain, velement) + + +@pytest.fixture +def a(sspace): + return Argument(sspace, 2) @pytest.fixture -def b(): - return Argument(selement(), 3) +def b(sspace): + return Argument(sspace, 3) @pytest.fixture -def v(): - return Argument(velement(), 4) +def v(vspace): + return Argument(vspace, 4) @pytest.fixture -def u(): - return Argument(velement(), 5) +def u(vspace): + return Argument(vspace, 5) @pytest.fixture -def f(): - return Coefficient(selement()) +def f(sspace): + return Coefficient(sspace) @pytest.fixture -def g(): - return Coefficient(selement()) +def g(sspace): + return Coefficient(sspace) @pytest.fixture -def vf(): - return Coefficient(velement()) +def vf(vspace): + return Coefficient(vspace) @pytest.fixture -def vg(): - return Coefficient(velement()) +def vg(vspace): + return Coefficient(vspace) def test_mul_v_u(v, u): diff --git a/test/test_indexing.py b/test/test_indexing.py index bb618e8f3..5b7450e9b 100755 --- a/test/test_indexing.py +++ b/test/test_indexing.py @@ -1,24 +1,29 @@ import pytest -from ufl import Index, SpatialCoordinate, outer, triangle +from ufl import Index, Mesh, SpatialCoordinate, VectorElement, outer, triangle from ufl.classes import FixedIndex, Indexed, MultiIndex, Outer, Zero @pytest.fixture -def x1(): - x = SpatialCoordinate(triangle) +def domain(): + return Mesh(VectorElement("Lagrange", triangle, 1)) + + +@pytest.fixture +def x1(domain): + x = SpatialCoordinate(domain) return x @pytest.fixture -def x2(): - x = SpatialCoordinate(triangle) +def x2(domain): + x = SpatialCoordinate(domain) return outer(x, x) @pytest.fixture -def x3(): - x = SpatialCoordinate(triangle) +def x3(domain): + x = SpatialCoordinate(domain) return outer(outer(x, x), x) diff --git a/test/test_indices.py b/test/test_indices.py index 564d356be..e1ab15587 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -1,7 +1,7 @@ import pytest -from ufl import (Argument, Coefficient, TensorElement, TestFunction, TrialFunction, VectorElement, as_matrix, as_tensor, - as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) +from ufl import (Argument, Coefficient, FunctionSpace, Mesh, TensorElement, TestFunction, TrialFunction, VectorElement, + as_matrix, as_tensor, as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) from ufl.classes import IndexSum # TODO: add more expressions to test as many possible combinations of index notation as feasible... @@ -9,16 +9,20 @@ def test_vector_indices(self): element = VectorElement("CG", "triangle", 1) - u = Argument(element, 2) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + u = Argument(space, 2) + f = Coefficient(space) u[i]*f[i]*dx u[j]*f[j]*dx def test_tensor_indices(self): element = TensorElement("CG", "triangle", 1) - u = Argument(element, 2) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + u = Argument(space, 2) + f = Coefficient(space) u[i, j]*f[i, j]*dx u[j, i]*f[i, j]*dx u[j, i]*f[j, i]*dx @@ -28,8 +32,10 @@ def test_tensor_indices(self): def test_indexed_sum1(self): element = VectorElement("CG", "triangle", 1) - u = Argument(element, 2) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + u = Argument(space, 2) + f = Coefficient(space) a = u[i]+f[i] with pytest.raises(BaseException): a*dx @@ -37,9 +43,11 @@ def test_indexed_sum1(self): def test_indexed_sum2(self): element = VectorElement("CG", "triangle", 1) - v = Argument(element, 2) - u = Argument(element, 3) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = Argument(space, 2) + u = Argument(space, 3) + f = Coefficient(space) a = u[j]+f[j]+v[j]+2*v[j]+exp(u[i]*u[i])/2*f[j] with pytest.raises(BaseException): a*dx @@ -47,26 +55,32 @@ def test_indexed_sum2(self): def test_indexed_sum3(self): element = VectorElement("CG", "triangle", 1) - u = Argument(element, 2) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + u = Argument(space, 2) + f = Coefficient(space) with pytest.raises(BaseException): u[i]+f[j] def test_indexed_function1(self): element = VectorElement("CG", "triangle", 1) - v = Argument(element, 2) - u = Argument(element, 3) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = Argument(space, 2) + u = Argument(space, 3) + f = Coefficient(space) aarg = (u[i]+f[i])*v[i] exp(aarg)*dx def test_indexed_function2(self): element = VectorElement("CG", "triangle", 1) - v = Argument(element, 2) - u = Argument(element, 3) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = Argument(space, 2) + u = Argument(space, 3) + f = Coefficient(space) bfun = cos(f[0]) left = u[i] + f[i] right = v[i] * bfun @@ -79,17 +93,21 @@ def test_indexed_function2(self): def test_indexed_function3(self): element = VectorElement("CG", "triangle", 1) - Argument(element, 2) - u = Argument(element, 3) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + Argument(space, 2) + u = Argument(space, 3) + f = Coefficient(space) with pytest.raises(BaseException): sin(u[i] + f[i])*dx def test_vector_from_indices(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) # legal vv = as_vector(u[i], i) @@ -104,8 +122,10 @@ def test_vector_from_indices(self): def test_matrix_from_indices(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) A = as_matrix(u[i]*v[j], (i, j)) B = as_matrix(v[k]*v[k]*u[i]*v[j], (j, i)) @@ -120,8 +140,10 @@ def test_matrix_from_indices(self): def test_vector_from_list(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) # create vector from list vv = as_vector([u[0], v[0]]) @@ -132,8 +154,10 @@ def test_vector_from_list(self): def test_matrix_from_list(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) # create matrix from list A = as_matrix([[u[0], u[1]], [v[0], v[1]]]) @@ -151,10 +175,12 @@ def test_matrix_from_list(self): def test_tensor(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) # define the components of a fourth order tensor Cijkl = u[i]*v[j]*f[k]*g[l] @@ -189,9 +215,11 @@ def test_tensor(self): def test_indexed(self): element = VectorElement("CG", "triangle", 1) - v = TestFunction(element) - u = TrialFunction(element) - Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) + Coefficient(space) i, j, k, l = indices(4) # noqa: E741 a = v[i] @@ -208,8 +236,10 @@ def test_indexed(self): def test_spatial_derivative(self): cell = triangle element = VectorElement("CG", cell, 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) i, j, k, l = indices(4) # noqa: E741 d = cell.geometric_dimension() diff --git a/test/test_interpolate.py b/test/test_interpolate.py index 245830f74..6deadc07d 100644 --- a/test/test_interpolate.py +++ b/test/test_interpolate.py @@ -5,26 +5,28 @@ import pytest -from ufl import (Action, Adjoint, Argument, Coefficient, FiniteElement, FunctionSpace, TestFunction, TrialFunction, - action, adjoint, derivative, dx, grad, inner, replace, triangle) +from ufl import (Action, Adjoint, Argument, Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, + TrialFunction, VectorElement, action, adjoint, derivative, dx, grad, inner, replace, triangle) from ufl.algorithms.ad import expand_derivatives from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, extract_base_form_operators, extract_coefficients) from ufl.algorithms.expand_indices import expand_indices from ufl.core.interpolate import Interpolate -from ufl.domain import default_domain @pytest.fixture -def V1(): - domain_2d = default_domain(triangle) +def domain_2d(): + return Mesh(VectorElement("Lagrange", triangle, 1)) + + +@pytest.fixture +def V1(domain_2d): f1 = FiniteElement("CG", triangle, 1) return FunctionSpace(domain_2d, f1) @pytest.fixture -def V2(): - domain_2d = default_domain(triangle) +def V2(domain_2d): f1 = FiniteElement("CG", triangle, 2) return FunctionSpace(domain_2d, f1) diff --git a/test/test_lhs_rhs.py b/test/test_lhs_rhs.py index a34851b33..4593a8aa1 100755 --- a/test/test_lhs_rhs.py +++ b/test/test_lhs_rhs.py @@ -3,16 +3,18 @@ # First added: 2011-11-09 # Last changed: 2011-11-09 -from ufl import (Argument, Coefficient, Constant, FiniteElement, TestFunction, TrialFunction, action, derivative, ds, - dS, dx, exp, interval, system) +from ufl import (Argument, Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, + VectorElement, action, derivative, ds, dS, dx, exp, interval, system) def test_lhs_rhs_simple(): V = FiniteElement("CG", interval, 1) - v = TestFunction(V) - u = TrialFunction(V) - w = Argument(V, 2) # This was 0, not sure why - f = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", interval, 1)) + space = FunctionSpace(domain, V) + v = TestFunction(space) + u = TrialFunction(space) + w = Argument(space, 2) # This was 0, not sure why + f = Coefficient(space) F0 = f * u * v * w * dx a, L = system(F0) @@ -36,9 +38,11 @@ def test_lhs_rhs_simple(): def test_lhs_rhs_derivatives(): V = FiniteElement("CG", interval, 1) - v = TestFunction(V) - u = TrialFunction(V) - f = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", interval, 1)) + space = FunctionSpace(domain, V) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) F0 = exp(f) * u * v * dx + v * dx + f * v * ds + exp(f)('+') * v * dS a, L = system(F0) @@ -50,11 +54,12 @@ def test_lhs_rhs_derivatives(): def test_lhs_rhs_slightly_obscure(): - V = FiniteElement("CG", interval, 1) - u = TrialFunction(V) - w = Argument(V, 2) - f = Constant(interval) + domain = Mesh(VectorElement("Lagrange", interval, 1)) + space = FunctionSpace(domain, V) + u = TrialFunction(space) + w = Argument(space, 2) + f = Constant(domain) # FIXME: # ufl.algorithsm.formtransformations.compute_form_with_arity diff --git a/test/test_mixed_function_space.py b/test/test_mixed_function_space.py index 28c01f35f..84af918e6 100644 --- a/test/test_mixed_function_space.py +++ b/test/test_mixed_function_space.py @@ -1,17 +1,16 @@ __authors__ = "Cecile Daversin Catty" __date__ = "2019-03-26 -- 2019-03-26" -from ufl import (FiniteElement, FunctionSpace, Measure, MixedFunctionSpace, TestFunctions, TrialFunctions, interval, - tetrahedron, triangle) +from ufl import (FiniteElement, FunctionSpace, Measure, Mesh, MixedFunctionSpace, TestFunctions, TrialFunctions, + VectorElement, interval, tetrahedron, triangle) from ufl.algorithms.formsplitter import extract_blocks -from ufl.domain import default_domain def test_mixed_functionspace(self): # Domains - domain_3d = default_domain(tetrahedron) - domain_2d = default_domain(triangle) - domain_1d = default_domain(interval) + domain_3d = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) + domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) # Finite elements f_1d = FiniteElement("CG", interval, 1) f_2d = FiniteElement("CG", triangle, 1) diff --git a/test/test_new_ad.py b/test/test_new_ad.py index 173a39486..8643dd886 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -1,6 +1,6 @@ -from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FiniteElement, Identity, SpatialCoordinate, - TestFunction, VectorConstant, VectorElement, as_ufl, cos, derivative, diff, exp, grad, ln, sin, tan, - triangle, variable, zero) +from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, + SpatialCoordinate, TestFunction, VectorConstant, VectorElement, as_ufl, cos, derivative, diff, exp, + grad, ln, sin, tan, triangle, variable, zero) from ufl.algorithms.apply_derivatives import GenericDerivativeRuleset, GradRuleset, apply_derivatives from ufl.algorithms.renumbering import renumber_indices @@ -18,6 +18,10 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v0_space = FunctionSpace(domain, V0) + v1_space = FunctionSpace(domain, V1) + # Literals z = zero((3, 2)) one = as_ufl(1) @@ -26,19 +30,19 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): literals = [z, one, two, ident] # Geometry - x = SpatialCoordinate(cell) - n = FacetNormal(cell) - volume = CellVolume(cell) + x = SpatialCoordinate(domain) + n = FacetNormal(domain) + volume = CellVolume(domain) geometry = [x, n, volume] # Arguments - v0 = TestFunction(V0) - v1 = TestFunction(V1) + v0 = TestFunction(v0_space) + v1 = TestFunction(v1_space) arguments = [v0, v1] # Coefficients - f0 = Coefficient(V0) - f1 = Coefficient(V1) + f0 = Coefficient(v0_space) + f1 = Coefficient(v1_space) coefficients = [f0, f1] # Expressions @@ -82,10 +86,13 @@ def test_literal_derivatives_are_zero(): V0 = FiniteElement("DG", cell, 0) V1 = FiniteElement("Lagrange", cell, 1) - u0 = Coefficient(V0) - u1 = Coefficient(V1) - v0 = TestFunction(V0) - v1 = TestFunction(V1) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v0_space = FunctionSpace(domain, V0) + v1_space = FunctionSpace(domain, V1) + u0 = Coefficient(v0_space) + u1 = Coefficient(v1_space) + v0 = TestFunction(v0_space) + v1 = TestFunction(v1_space) args = [(u0, v0), (u1, v1)] # Test literals via apply_derivatives and variable ruleset: @@ -109,30 +116,38 @@ def test_grad_ruleset(): W1 = VectorElement("Lagrange", cell, 1) W2 = VectorElement("Lagrange", cell, 2) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + v0_space = FunctionSpace(domain, V0) + v1_space = FunctionSpace(domain, V1) + v2_space = FunctionSpace(domain, V2) + w0_space = FunctionSpace(domain, W0) + w1_space = FunctionSpace(domain, W1) + w2_space = FunctionSpace(domain, W2) + # Literals one = as_ufl(1) two = as_ufl(2.0) ident = Identity(d) # Geometry - x = SpatialCoordinate(cell) - n = FacetNormal(cell) - volume = CellVolume(cell) + x = SpatialCoordinate(domain) + n = FacetNormal(domain) + volume = CellVolume(domain) # Arguments - u0 = TestFunction(V0) - u1 = TestFunction(V1) + u0 = TestFunction(v0_space) + u1 = TestFunction(v1_space) arguments = [u0, u1] # Coefficients - r = Constant(cell) - vr = VectorConstant(cell) - f0 = Coefficient(V0) - f1 = Coefficient(V1) - f2 = Coefficient(V2) - vf0 = Coefficient(W0) - vf1 = Coefficient(W1) - vf2 = Coefficient(W2) + r = Constant(domain) + vr = VectorConstant(domain) + f0 = Coefficient(v0_space) + f1 = Coefficient(v1_space) + f2 = Coefficient(v2_space) + vf0 = Coefficient(w0_space) + vf1 = Coefficient(w1_space) + vf2 = Coefficient(w2_space) rules = GradRuleset(d) diff --git a/test/test_pickle.py b/test/test_pickle.py index 6e4633ff7..3189bde2b 100755 --- a/test/test_pickle.py +++ b/test/test_pickle.py @@ -10,23 +10,24 @@ import pickle -from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, Identity, TensorElement, TestFunction, - TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, div, dot, dS, - ds, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, + TestFunction, TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, + div, dot, dS, ds, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) from ufl.algorithms import compute_form_data p = pickle.HIGHEST_PROTOCOL def testConstant(): - element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) - c = Constant("triangle") - d = VectorConstant("triangle") + c = Constant(domain) + d = VectorConstant(domain) a = c * dot(grad(v), grad(u)) * dx @@ -43,11 +44,12 @@ def testConstant(): def testElasticity(): - element = VectorElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) def eps(v): # FFC notation: return grad(v) + transp(grad(v)) @@ -63,10 +65,11 @@ def eps(v): def testEnergyNorm(): - element = FiniteElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = Coefficient(element) + v = Coefficient(space) a = (v * v + dot(grad(v), grad(v))) * dx a_pickle = pickle.dumps(a, p) @@ -76,14 +79,15 @@ def testEnergyNorm(): def testEquation(): - element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) k = 0.1 - v = TestFunction(element) - u = TrialFunction(element) - u0 = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + u0 = Coefficient(space) F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx @@ -100,13 +104,14 @@ def testEquation(): def testFunctionOperators(): - element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx @@ -120,15 +125,16 @@ def testFunctionOperators(): def testHeat(): - element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u1 = TrialFunction(element) - u0 = Coefficient(element) - c = Coefficient(element) - f = Coefficient(element) - k = Constant("triangle") + v = TestFunction(space) + u1 = TrialFunction(space) + u0 = Coefficient(space) + c = Coefficient(space) + f = Coefficient(space) + k = Constant(domain) a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx L = v * u0 * dx + k * v * f * dx @@ -143,11 +149,12 @@ def testHeat(): def testMass(): - element = FiniteElement("Lagrange", "tetrahedron", 3) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) a = v * u * dx @@ -158,7 +165,6 @@ def testMass(): def testMixedMixedElement(): - P3 = FiniteElement("Lagrange", "triangle", 3) element = (P3 * P3) * (P3 * P3) @@ -170,18 +176,20 @@ def testMixedMixedElement(): def testMixedPoisson(): - q = 1 BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) mixed_element = BDM * DG + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + mixed_space = FunctionSpace(domain, mixed_element) + dg_space = FunctionSpace(domain, DG) - (tau, w) = TestFunctions(mixed_element) - (sigma, u) = TrialFunctions(mixed_element) + (tau, w) = TestFunctions(mixed_space) + (sigma, u) = TrialFunctions(mixed_space) - f = Coefficient(DG) + f = Coefficient(dg_space) a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx L = w * f * dx @@ -196,13 +204,14 @@ def testMixedPoisson(): def testNavierStokes(): - element = VectorElement("Lagrange", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) - w = Coefficient(element) + w = Coefficient(space) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx a = v[i] * w[j] * Dx(u[i], j) * dx @@ -214,13 +223,14 @@ def testNavierStokes(): def testNeumannProblem(): - element = VectorElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) - g = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) + g = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx @@ -238,12 +248,13 @@ def testNeumannProblem(): def testOptimization(): - element = FiniteElement("Lagrange", "triangle", 3) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) a = dot(grad(v), grad(u)) * dx L = v * f * dx @@ -258,7 +269,6 @@ def testOptimization(): def testP5tet(): - element = FiniteElement("Lagrange", tetrahedron, 5) element_pickle = pickle.dumps(element, p) @@ -268,7 +278,6 @@ def testP5tet(): def testP5tri(): - element = FiniteElement("Lagrange", triangle, 5) element_pickle = pickle.dumps(element, p) @@ -276,19 +285,20 @@ def testP5tri(): def testPoissonDG(): - element = FiniteElement("Discontinuous Lagrange", triangle, 1) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) - n = FacetNormal(triangle) + n = FacetNormal(domain) - # FFC notation: h = MeshSize("triangle"), not supported by UFL - h = Constant(triangle) + # FFC notation: h = MeshSize(domain), not supported by UFL + h = Constant(domain) - gN = Coefficient(element) + gN = Coefficient(space) alpha = 4.0 gamma = 8.0 @@ -322,12 +332,13 @@ def testPoissonDG(): def testPoisson(): - element = FiniteElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) # Note: inner() also works a = dot(grad(v), grad(u)) * dx @@ -343,12 +354,13 @@ def testPoisson(): def testPoissonSystem(): - element = VectorElement("Lagrange", "triangle", 1) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) - f = Coefficient(element) + v = TestFunction(space) + u = TrialFunction(space) + f = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx a = inner(grad(v), grad(u)) * dx @@ -366,7 +378,6 @@ def testPoissonSystem(): def testQuadratureElement(): - element = FiniteElement("Lagrange", "triangle", 2) # FFC notation: @@ -376,12 +387,17 @@ def testQuadratureElement(): QE = FiniteElement("Quadrature", "triangle", 3) sig = VectorElement("Quadrature", "triangle", 3) - v = TestFunction(element) - u = TrialFunction(element) - u0 = Coefficient(element) - C = Coefficient(QE) - sig0 = Coefficient(sig) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + space = FunctionSpace(domain, element) + qe_space = FunctionSpace(domain, QE) + sig_space = FunctionSpace(domain, sig) + + v = TestFunction(space) + u = TrialFunction(space) + u0 = Coefficient(space) + C = Coefficient(qe_space) + sig0 = Coefficient(sig_space) + f = Coefficient(space) a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx L = v * f * dx - dot(grad(v), sig0) * dx @@ -396,17 +412,20 @@ def testQuadratureElement(): def testStokes(): - # UFLException: Shape mismatch in sum. P2 = VectorElement("Lagrange", "triangle", 2) P1 = FiniteElement("Lagrange", "triangle", 1) TH = P2 * P1 - (v, q) = TestFunctions(TH) - (u, r) = TrialFunctions(TH) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + th_space = FunctionSpace(domain, TH) + p2_space = FunctionSpace(domain, P2) + + (v, q) = TestFunctions(th_space) + (u, r) = TrialFunctions(th_space) - f = Coefficient(P2) + f = Coefficient(p2_space) # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx @@ -424,10 +443,11 @@ def testStokes(): def testSubDomain(): - element = FiniteElement("CG", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - f = Coefficient(element) + f = Coefficient(space) M = f * dx(2) + f * ds(5) @@ -438,11 +458,12 @@ def testSubDomain(): def testSubDomains(): - element = FiniteElement("CG", "tetrahedron", 1) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * \ ds(1) + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) @@ -454,7 +475,6 @@ def testSubDomains(): def testTensorWeightedPoisson(): - # FFC notation: # P1 = FiniteElement("Lagrange", "triangle", 1) # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) @@ -475,9 +495,13 @@ def testTensorWeightedPoisson(): P1 = FiniteElement("Lagrange", "triangle", 1) P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) - v = TestFunction(P1) - u = TrialFunction(P1) - C = Coefficient(P0) + domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + p1_space = FunctionSpace(domain, P1) + p0_space = FunctionSpace(domain, P0) + + v = TestFunction(p1_space) + u = TrialFunction(p1_space) + C = Coefficient(p0_space) a = inner(grad(v), C * grad(u)) * dx @@ -488,11 +512,10 @@ def testTensorWeightedPoisson(): def testVectorLaplaceGradCurl(): - - def HodgeLaplaceGradCurl(element, felement): - (tau, v) = TestFunctions(element) - (sigma, u) = TrialFunctions(element) - f = Coefficient(felement) + def HodgeLaplaceGradCurl(space, fspace): + (tau, v) = TestFunctions(space) + (sigma, u) = TrialFunctions(space) + f = Coefficient(fspace) # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx @@ -513,8 +536,9 @@ def HodgeLaplaceGradCurl(element, felement): CURL = FiniteElement("N1curl", shape, order) VectorLagrange = VectorElement("Lagrange", shape, order + 1) + domain = Mesh(VectorElement("Lagrange", shape, 1)) - [a, L] = HodgeLaplaceGradCurl(GRAD * CURL, VectorLagrange) + [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, GRAD * CURL), FunctionSpace(domain, VectorLagrange)) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) @@ -526,7 +550,6 @@ def HodgeLaplaceGradCurl(element, felement): def testIdentity(): - i = Identity(2) i_pickle = pickle.dumps(i, p) i_restore = pickle.loads(i_pickle) @@ -534,11 +557,12 @@ def testIdentity(): def testFormData(): - element = FiniteElement("Lagrange", "tetrahedron", 3) + domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + space = FunctionSpace(domain, element) - v = TestFunction(element) - u = TrialFunction(element) + v = TestFunction(space) + u = TrialFunction(space) a = v * u * dx diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index ba4beb28a..0536b62b1 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -3,7 +3,7 @@ import pytest from ufl import (Cell, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, + FiniteElement, FunctionSpace, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, MinFacetEdgeLength, SpatialCoordinate, TestFunction, VectorElement, hexahedron, interval, quadrilateral, tetrahedron, triangle) from ufl.checks import is_cellwise_constant @@ -217,10 +217,13 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): assert is_cellwise_constant(e) V = FiniteElement("DG", domains_not_linear.ufl_cell(), 0) - e = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", domains_not_linear.ufl_cell(), 1)) + space = FunctionSpace(domain, V) + e = Coefficient(space) assert is_cellwise_constant(e) V = FiniteElement("R", domains_not_linear.ufl_cell(), 0) - e = Coefficient(V) + space = FunctionSpace(domain, V) + e = Coefficient(space) assert is_cellwise_constant(e) # This should be true, but that has to wait for a fix of issue #13 @@ -233,7 +236,9 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1) - e = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", domains_not_linear.ufl_cell(), 1)) + space = FunctionSpace(domain, V) + e = Coefficient(space) assert not is_cellwise_constant(e) - e = TestFunction(V) + e = TestFunction(space) assert not is_cellwise_constant(e) diff --git a/test/test_scratch.py b/test/test_scratch.py index bcf0bd347..1ccceecc0 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -8,8 +8,8 @@ import warnings -from ufl import (Coefficient, FiniteElement, Identity, TensorElement, TestFunction, VectorElement, as_matrix, as_tensor, - as_vector, dx, grad, indices, inner, outer, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, TestFunction, VectorElement, + as_matrix, as_tensor, as_vector, dx, grad, indices, inner, outer, triangle) from ufl.classes import FixedIndex, FormArgument, Grad, Indexed, ListTensor, Zero from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor @@ -211,8 +211,10 @@ def test_unwrap_list_tensor(self): def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): U = FiniteElement("CG", triangle, 1) - u = Coefficient(U) - du = TestFunction(U) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, U) + u = Coefficient(space) + du = TestFunction(space) mad = MockForwardAD() mad._w = (u,) @@ -235,8 +237,10 @@ def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient(self): V = VectorElement("CG", triangle, 1) - v = Coefficient(V) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + v = Coefficient(space) + dv = TestFunction(space) mad = MockForwardAD() mad._w = (v,) @@ -259,8 +263,10 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation(self): V = VectorElement("CG", triangle, 1) - v = Coefficient(V) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + v = Coefficient(space) + dv = TestFunction(space) mad = MockForwardAD() @@ -314,8 +320,10 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation_in_list(self): V = VectorElement("CG", triangle, 1) - v = Coefficient(V) - dv = TestFunction(V) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, V) + v = Coefficient(space) + dv = TestFunction(space) mad = MockForwardAD() @@ -369,8 +377,10 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): W = TensorElement("CG", triangle, 1) - w = Coefficient(W) - dw = TestFunction(W) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, W) + w = Coefficient(space) + dw = TestFunction(space) mad = MockForwardAD() mad._w = (w,) @@ -393,8 +403,10 @@ def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): def test__forward_coefficient_ad__grad_of_tensor_coefficient__with_component_variation(self): W = TensorElement("CG", triangle, 1) - w = Coefficient(W) - dw = TestFunction(W) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, W) + w = Coefficient(space) + dw = TestFunction(space) mad = MockForwardAD() diff --git a/test/test_signature.py b/test/test_signature.py index 95e655262..b0c6cd3d9 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -2,8 +2,8 @@ from ufl import (Argument, CellDiameter, CellVolume, Circumradius, Coefficient, FacetArea, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, SpatialCoordinate, TensorElement, TestFunction, VectorElement, - as_domain, as_vector, diff, dot, ds, dx, hexahedron, indices, inner, interval, quadrilateral, - tetrahedron, triangle, variable) + as_vector, diff, dot, ds, dx, hexahedron, indices, inner, interval, quadrilateral, tetrahedron, + triangle, variable) from ufl.algorithms.signature import compute_multiindex_hashdata, compute_terminal_hashdata from ufl.classes import FixedIndex, MultiIndex @@ -19,25 +19,11 @@ def domain_numbering(*cells): renumbering = {} for i, cell in enumerate(cells): - domain = as_domain(cell) + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) renumbering[domain] = i return renumbering -def test_domain_signatures_of_cell2domains(self): - all_cells = (interval, quadrilateral, hexahedron, triangle, tetrahedron) - for cell in all_cells: - # Equality holds when constructing two domains from a cell: - assert as_domain(cell) == as_domain(cell) - # Hash value holds when constructing two domains from a cell: - assert hash(as_domain(cell)) == hash(as_domain(cell)) - # Signature data holds when constructing two domains from a cell: - D1 = as_domain(cell) - D2 = as_domain(cell) - self.assertEqual(D1._ufl_signature_data_({D1: 0}), - D2._ufl_signature_data_({D2: 0})) - - def compute_unique_terminal_hashdatas(hashdatas): count = 0 data = set() @@ -70,8 +56,8 @@ def test_terminal_hashdata_depends_on_literals(self): def forms(): i, j = indices(2) - for d in (2, 3): - domain = as_domain({2: triangle, 3: tetrahedron}[d]) + for d, cell in [(2, triangle), (3, tetrahedron)]: + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=d-2) x = SpatialCoordinate(domain) ident = Identity(d) for fv in (1.1, 2.2): @@ -98,16 +84,17 @@ def test_terminal_hashdata_depends_on_geometry(self): def forms(): i, j = indices(2) cells = (triangle, tetrahedron) - for cell in cells: + for i, cell in enumerate(cells): + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) d = cell.geometric_dimension() - x = SpatialCoordinate(cell) - n = FacetNormal(cell) - h = CellDiameter(cell) - r = Circumradius(cell) - a = FacetArea(cell) - # s = CellSurfaceArea(cell) - v = CellVolume(cell) + x = SpatialCoordinate(domain) + n = FacetNormal(domain) + h = CellDiameter(domain) + r = Circumradius(domain) + a = FacetArea(domain) + # s = CellSurfaceArea(domain) + v = CellVolume(domain) ident = Identity(d) ws = (x, n) @@ -142,8 +129,9 @@ def test_terminal_hashdata_depends_on_form_argument_properties(self): def forms(): for rep in range(nreps): - for cell in cells: + for i, cell in enumerate(cells): d = cell.geometric_dimension() + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) for degree in degrees: for family in families: V = FiniteElement(family, cell, degree) @@ -156,9 +144,10 @@ def forms(): assert len(elements) == nelm for H in elements[:nelm]: + space = FunctionSpace(domain, H) # Keep number and count fixed, we're not testing that here - a = Argument(H, number=1) - c = Coefficient(H, count=1) + a = Argument(space, number=1) + c = Coefficient(space, count=1) renumbering = domain_numbering(*cells) renumbering[c] = 0 for f in (a, c): @@ -191,11 +180,13 @@ def test_terminal_hashdata_does_not_depend_on_coefficient_count_values_only_orde def forms(): for rep in range(nreps): - for cell in cells: + for i, cell in enumerate(cells): + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) for k in counts: V = FiniteElement("CG", cell, 2) - f = Coefficient(V, count=k) - g = Coefficient(V, count=k+2) + space = FunctionSpace(domain, V) + f = Coefficient(space, count=k) + g = Coefficient(space, count=k+2) expr = inner(f, g) renumbering = domain_numbering(*cells) @@ -227,11 +218,13 @@ def test_terminal_hashdata_does_depend_on_argument_number_values(self): def forms(): for rep in range(nreps): - for cell in cells: + for i, cell in enumerate(cells): + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) for k in counts: V = FiniteElement("CG", cell, 2) - f = Argument(V, k) - g = Argument(V, k+2) + space = FunctionSpace(domain, V) + f = Argument(space, k) + g = Argument(space, k+2) expr = inner(f, g) reprs.add(repr(expr)) @@ -254,10 +247,11 @@ def test_domain_signature_data_does_not_depend_on_domain_label_value(self): s0s = set() s1s = set() s2s = set() - for cell in cells: - d0 = Mesh(cell) - d1 = Mesh(cell, ufl_id=1) - d2 = Mesh(cell, ufl_id=2) + for i, cell in enumerate(cells): + domain = VectorElement("Lagrange", cell, 1) + d0 = Mesh(domain) + d1 = Mesh(domain, ufl_id=1) + d2 = Mesh(domain, ufl_id=2) s0 = d0._ufl_signature_data_({d0: 0}) s1 = d1._ufl_signature_data_({d1: 0}) s2 = d2._ufl_signature_data_({d2: 0}) @@ -424,11 +418,13 @@ def test_signature_is_affected_by_element_properties(self): def forms(): for family in ("CG", "DG"): for cell in (triangle, tetrahedron, quadrilateral): + domain = Mesh(VectorElement("Lagrange", cell, 1)) for degree in (1, 2): V = FiniteElement(family, cell, degree) - u = Coefficient(V) - v = TestFunction(V) - x = SpatialCoordinate(cell) + space = FunctionSpace(domain, V) + u = Coefficient(space) + v = TestFunction(space) + x = SpatialCoordinate(domain) w = as_vector([v]*x.ufl_shape[0]) f = dot(w, u*x) a = f*dx @@ -439,11 +435,13 @@ def forms(): def test_signature_is_affected_by_domains(self): def forms(): for cell in (triangle, tetrahedron): + domain = Mesh(VectorElement("Lagrange", cell, 1)) for di in (1, 2): for dj in (1, 2): for dk in (1, 2): V = FiniteElement("CG", cell, 1) - u = Coefficient(V) + space = FunctionSpace(domain, V) + u = Coefficient(space) a = u*dx(di) + 2*u*dx(dj) + 3*u*ds(dk) yield a check_unique_signatures(forms()) @@ -451,17 +449,20 @@ def forms(): def test_signature_of_forms_with_diff(self): def forms(): - for cell in (triangle, tetrahedron): + for i, cell in enumerate([triangle, tetrahedron]): + domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) for k in (1, 2, 3): V = FiniteElement("CG", cell, 1) W = VectorElement("CG", cell, 1) - u = Coefficient(V) - w = Coefficient(W) + v_space = FunctionSpace(domain, V) + w_space = FunctionSpace(domain, W) + u = Coefficient(v_space) + w = Coefficient(w_space) vu = variable(u) vw = variable(w) f = vu*dot(vw, vu**k*vw) g = diff(f, vu) - h = dot(diff(f, vw), FacetNormal(cell)) + h = dot(diff(f, vw), FacetNormal(domain)) a = f*dx(1) + g*dx(2) + h*ds(0) yield a check_unique_signatures(forms()) @@ -470,8 +471,10 @@ def forms(): def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self): cell = triangle V = FiniteElement("CG", cell, 1) - f = Coefficient(V) - g = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + f = Coefficient(space) + g = Coefficient(space) M1 = f*dx(0) + g*dx(1) M2 = g*dx(0) + f*dx(1) M3 = g*dx(0) + g*dx(1) @@ -484,8 +487,10 @@ def test_signature_of_forms_change_with_operators(self): def forms(): for cell in (triangle, tetrahedron): V = FiniteElement("CG", cell, 1) - u = Coefficient(V) - v = Coefficient(V) + domain = Mesh(VectorElement("Lagrange", cell, 1)) + space = FunctionSpace(domain, V) + u = Coefficient(space) + v = Coefficient(space) fs = [(u*v)+(u/v), (u+v)+(u/v), (u+v)*(u/v), diff --git a/test/test_simplify.py b/test/test_simplify.py index 89afa3690..faa9d4e6e 100755 --- a/test/test_simplify.py +++ b/test/test_simplify.py @@ -1,15 +1,18 @@ import math -from ufl import (Coefficient, FiniteElement, TestFunction, TrialFunction, VectorConstant, acos, as_tensor, as_ufl, asin, - atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, outer, sin, sinh, tan, tanh, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, + VectorElement, acos, as_tensor, as_ufl, asin, atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, + outer, sin, sinh, tan, tanh, triangle) from ufl.algorithms import compute_form_data def xtest_zero_times_argument(self): # FIXME: Allow zero forms element = FiniteElement("CG", triangle, 1) - v = TestFunction(element) - u = TrialFunction(element) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) + v = TestFunction(space) + u = TrialFunction(space) L = 0*v*dx a = 0*(u*v)*dx b = (0*u)*v*dx @@ -20,7 +23,9 @@ def xtest_zero_times_argument(self): def test_divisions(self): element = FiniteElement("CG", triangle, 1) - f = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) + f = Coefficient(space) # Test simplification of division by 1 a = f @@ -45,8 +50,10 @@ def test_divisions(self): def test_products(self): element = FiniteElement("CG", triangle, 1) - f = Coefficient(element) - g = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) + f = Coefficient(space) + g = Coefficient(space) # Test simplification of literal multiplication assert f*0 == as_ufl(0) @@ -65,8 +72,10 @@ def test_products(self): def test_sums(self): element = FiniteElement("CG", triangle, 1) - f = Coefficient(element) - g = Coefficient(element) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + space = FunctionSpace(domain, element) + f = Coefficient(space) + g = Coefficient(space) # Test reordering of operands assert f + g == g + f @@ -113,8 +122,9 @@ def test_mathfunctions(self): def test_indexing(self): - u = VectorConstant(triangle) - v = VectorConstant(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + u = VectorConstant(domain) + v = VectorConstant(domain) A = outer(u, v) A2 = as_tensor(A[i, j], (i, j)) diff --git a/test/test_split.py b/test/test_split.py index 597c81f26..bce2dbf03 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -1,12 +1,13 @@ __authors__ = "Martin Sandve Alnæs" __date__ = "2009-03-14 -- 2009-03-14" -from ufl import (Coefficient, FiniteElement, MixedElement, TensorElement, TestFunction, VectorElement, as_vector, - product, split, triangle) +from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, MixedElement, TensorElement, TestFunction, + VectorElement, as_vector, product, split, triangle) def test_split(self): cell = triangle + domain = Mesh(VectorElement("Lagrange", cell, 1)) d = cell.geometric_dimension() f = FiniteElement("CG", cell, 1) v = VectorElement("CG", cell, 1) @@ -16,18 +17,26 @@ def test_split(self): r = TensorElement("CG", cell, 1, symmetry={(1, 0): (0, 1)}, shape=(d, d)) m = MixedElement(f, v, w, t, s, r) + f_space = FunctionSpace(domain, f) + v_space = FunctionSpace(domain, v) + w_space = FunctionSpace(domain, w) + t_space = FunctionSpace(domain, t) + s_space = FunctionSpace(domain, s) + r_space = FunctionSpace(domain, r) + m_space = FunctionSpace(domain, m) + # Check that shapes of all these functions are correct: - assert () == Coefficient(f).ufl_shape - assert (d,) == Coefficient(v).ufl_shape - assert (d+1,) == Coefficient(w).ufl_shape - assert (d, d) == Coefficient(t).ufl_shape - assert (d, d) == Coefficient(s).ufl_shape - assert (d, d) == Coefficient(r).ufl_shape + assert () == Coefficient(f_space).ufl_shape + assert (d,) == Coefficient(v_space).ufl_shape + assert (d+1,) == Coefficient(w_space).ufl_shape + assert (d, d) == Coefficient(t_space).ufl_shape + assert (d, d) == Coefficient(s_space).ufl_shape + assert (d, d) == Coefficient(r_space).ufl_shape # sum of value sizes, not accounting for symmetries: - assert (3*d*d + 2*d + 2,) == Coefficient(m).ufl_shape + assert (3*d*d + 2*d + 2,) == Coefficient(m_space).ufl_shape # Shapes of subelements are reproduced: - g = Coefficient(m) + g = Coefficient(m_space) s, = g.ufl_shape for g2 in split(g): s -= product(g2.ufl_shape) @@ -36,24 +45,26 @@ def test_split(self): # Mixed elements of non-scalar subelements are flattened v2 = MixedElement(v, v) m2 = MixedElement(t, t) + v2_space = FunctionSpace(domain, v2) + m2_space = FunctionSpace(domain, m2) # assert d == 2 - # assert (2,2) == Coefficient(v2).ufl_shape - assert (d+d,) == Coefficient(v2).ufl_shape - assert (2*d*d,) == Coefficient(m2).ufl_shape + # assert (2,2) == Coefficient(v2_space).ufl_shape + assert (d+d,) == Coefficient(v2_space).ufl_shape + assert (2*d*d,) == Coefficient(m2_space).ufl_shape # Split simple element - t = TestFunction(f) + t = TestFunction(f_space) assert split(t) == (t,) # Split twice on nested mixed elements gets # the innermost scalar subcomponents - t = TestFunction(f*v) + t = TestFunction(FunctionSpace(domain, f*v)) assert split(t) == (t[0], as_vector((t[1], t[2]))) assert split(split(t)[1]) == (t[1], t[2]) - t = TestFunction(f*(f*v)) + t = TestFunction(FunctionSpace(domain, f*(f*v))) assert split(t) == (t[0], as_vector((t[1], t[2], t[3]))) assert split(split(t)[1]) == (t[1], as_vector((t[2], t[3]))) - t = TestFunction((v*f)*(f*v)) + t = TestFunction(FunctionSpace(domain, (v*f)*(f*v))) assert split(t) == (as_vector((t[0], t[1], t[2])), as_vector((t[3], t[4], t[5]))) assert split(split(t)[0]) == (as_vector((t[0], t[1])), t[2]) diff --git a/test/test_str.py b/test/test_str.py index 25d036369..96683ae38 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -1,6 +1,6 @@ -from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FiniteElement, Index, - SpatialCoordinate, TestFunction, TrialFunction, as_matrix, as_ufl, as_vector, quadrilateral, - tetrahedron, triangle) +from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FiniteElement, FunctionSpace, Index, + Mesh, SpatialCoordinate, TestFunction, TrialFunction, VectorElement, as_matrix, as_ufl, as_vector, + quadrilateral, tetrahedron, triangle) def test_str_int_value(self): @@ -12,7 +12,8 @@ def test_str_float_value(self): def test_str_zero(self): - x = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x = SpatialCoordinate(domain) assert str(as_ufl(0)) == "0" assert str(0*x) == "0 (shape (2,))" assert str(0*x*x[Index(42)]) == "0 (shape (2,), index labels (42,))" @@ -24,38 +25,41 @@ def test_str_index(self): def test_str_coordinate(self): - assert str(SpatialCoordinate(triangle)) == "x" - assert str(SpatialCoordinate(triangle)[0]) == "x[0]" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(SpatialCoordinate(domain)) == "x" + assert str(SpatialCoordinate(domain)[0]) == "x[0]" def test_str_normal(self): - assert str(FacetNormal(triangle)) == "n" - assert str(FacetNormal(triangle)[0]) == "n[0]" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(FacetNormal(domain)) == "n" + assert str(FacetNormal(domain)[0]) == "n[0]" def test_str_circumradius(self): - assert str(Circumradius(triangle)) == "circumradius" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(Circumradius(domain)) == "circumradius" def test_str_diameter(self): - assert str(CellDiameter(triangle)) == "diameter" - - -# def test_str_cellsurfacearea(self): -# assert str(CellSurfaceArea(triangle)) == "surfacearea" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(CellDiameter(domain)) == "diameter" def test_str_facetarea(self): - assert str(FacetArea(triangle)) == "facetarea" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(FacetArea(domain)) == "facetarea" def test_str_volume(self): - assert str(CellVolume(triangle)) == "volume" + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + assert str(CellVolume(domain)) == "volume" def test_str_scalar_argument(self): - v = TestFunction(FiniteElement("CG", triangle, 1)) - u = TrialFunction(FiniteElement("CG", triangle, 1)) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + v = TestFunction(FunctionSpace(domain, FiniteElement("CG", triangle, 1))) + u = TrialFunction(FunctionSpace(domain, FiniteElement("CG", triangle, 1))) assert str(v) == "v_0" assert str(u) == "v_1" @@ -68,19 +72,22 @@ def test_str_scalar_argument(self): def test_str_list_vector(): - x, y, z = SpatialCoordinate(tetrahedron) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + x, y, z = SpatialCoordinate(domain) v = as_vector((x, y, z)) assert str(v) == ("[%s, %s, %s]" % (x, y, z)) def test_str_list_vector_with_zero(): - x, y, z = SpatialCoordinate(tetrahedron) + domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + x, y, z = SpatialCoordinate(domain) v = as_vector((x, 0, 0)) assert str(v) == ("[%s, 0, 0]" % (x,)) def test_str_list_matrix(): - x, y = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x, y = SpatialCoordinate(domain) v = as_matrix(((2*x, 3*y), (4*x, 5*y))) a = str(2*x) @@ -91,7 +98,8 @@ def test_str_list_matrix(): def test_str_list_matrix_with_zero(): - x, y = SpatialCoordinate(triangle) + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + x, y = SpatialCoordinate(domain) v = as_matrix(((2*x, 3*y), (0, 0))) a = str(2*x) diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index 01b69d413..d070a3dd4 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -2,8 +2,8 @@ import pytest -from ufl import (FacetNormal, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, diag_vector, dot, inner, - inv, outer, perp, skew, sym, tr, transpose, triangle, zero) +from ufl import (FacetNormal, Mesh, VectorElement, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, + diag_vector, dot, inner, inv, outer, perp, skew, sym, tr, transpose, triangle, zero) from ufl.algorithms.remove_complex_nodes import remove_complex_nodes @@ -63,7 +63,8 @@ def test_inner(self, A, B, u, v): def test_pow2_inner(self, A, u): - f = FacetNormal(triangle)[0] + domain = Mesh(VectorElement("Lagrange", triangle, 1)) + f = FacetNormal(domain)[0] f2 = f*f assert f2 == remove_complex_nodes(inner(f, f)) diff --git a/ufl/domain.py b/ufl/domain.py index e3b9502ee..59de4f121 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -274,6 +274,9 @@ def affine_mesh(cell, ufl_id=None): def default_domain(cell): """Create a singular default Mesh from a cell, always returning the same Mesh object for the same cell.""" global _default_domains + + warnings.warn("default_domain is deprecated.", FutureWarning) + assert isinstance(cell, AbstractCell) domain = _default_domains.get(cell) if domain is None: From e3c5fce6928ee028c1575fb84004d3904a990e66 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 21 Sep 2023 08:50:41 +0100 Subject: [PATCH 058/136] Remove IrreducibleInt (#213) * remove IrreducibleInt * cell check is needed, basix branch * improve docstring --- .github/workflows/fenicsx-tests.yml | 4 +-- ufl/algorithms/estimate_degrees.py | 38 ++++++----------------------- ufl/finiteelement/finiteelement.py | 6 ----- 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 2075498b0..e96ad9ede 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -34,7 +34,7 @@ jobs: - name: Install Basix run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git + python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/irreducibleint - name: Clone FFCx uses: actions/checkout@v3 @@ -78,7 +78,7 @@ jobs: - name: Install Basix and FFCx run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git + python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/irreducibleint python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 24b77f789..a5889fe2c 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -20,14 +20,6 @@ from ufl.integral import Integral -class IrreducibleInt(int): - """Degree type used by quadrilaterals. - - Unlike int, values of this type are not decremeneted by _reduce_degree. - """ - pass - - class SumDegreeEstimator(MultiFunction): """Sum degree estimator. @@ -98,48 +90,34 @@ def coefficient(self, v): return d def _reduce_degree(self, v, f): - """Apply to _reduce_degree. + """Reduce the estimated degree by one. - Reduces the estimated degree by one; used when derivatives - are taken. Does not reduce the degree when TensorProduct elements - or quadrilateral elements are involved. + This is used when derivatives are taken. It does not reduce the degree when + TensorProduct elements or quadrilateral elements are involved. """ - if isinstance(f, int) and not isinstance(f, IrreducibleInt): + if isinstance(f, int) and v.ufl_domain().ufl_cell().cellname() not in ["quadrilateral", "hexahedron"]: return max(f - 1, 0) else: - # if tuple, do not reduce return f def _add_degrees(self, v, *ops): """Apply to _add_degrees.""" - def add_single(ops): - if any(isinstance(o, IrreducibleInt) for o in ops): - return IrreducibleInt(sum(ops)) - else: - return sum(ops) - if any(isinstance(o, tuple) for o in ops): # we can add a slight hack here to handle things # like adding 0 to (3, 3) [by expanding # 0 to (0, 0) when making tempops] tempops = [foo if isinstance(foo, tuple) else (foo, foo) for foo in ops] - return tuple(map(add_single, zip(*tempops))) + return tuple(map(sum, zip(*tempops))) else: - return add_single(ops) + return sum(ops) def _max_degrees(self, v, *ops): """Apply to _max_degrees.""" - def max_single(ops): - if any(isinstance(o, IrreducibleInt) for o in ops): - return IrreducibleInt(max(ops)) - else: - return max(ops) - if any(isinstance(o, tuple) for o in ops): tempops = [foo if isinstance(foo, tuple) else (foo, foo) for foo in ops] - return tuple(map(max_single, zip(*tempops))) + return tuple(map(max, zip(*tempops))) else: - return max_single(ops + (0,)) + return max(ops + (0,)) def _not_handled(self, v, *args): """Apply to _not_handled.""" diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py index 790ed107e..0962e82a3 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/finiteelement/finiteelement.py @@ -151,12 +151,6 @@ def __init__(self, self._short_name = short_name or family self._variant = variant - # Finite elements on quadrilaterals and hexahedrons have an IrreducibleInt as degree - if cell is not None: - if cell.cellname() in ["quadrilateral", "hexahedron"]: - from ufl.algorithms.estimate_degrees import IrreducibleInt - degree = IrreducibleInt(degree) - # Type check variant if variant is not None and not isinstance(variant, str): raise ValueError("Illegal variant: must be string or None") From b11925d1dd6f1ef578f9631c8e946dc23fd85520 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 21 Sep 2023 09:00:05 +0100 Subject: [PATCH 059/136] reset branch names (#215) --- .github/workflows/fenicsx-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index e96ad9ede..2075498b0 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -34,7 +34,7 @@ jobs: - name: Install Basix run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/irreducibleint + python3 -m pip install git+https://github.com/FEniCS/basix.git - name: Clone FFCx uses: actions/checkout@v3 @@ -78,7 +78,7 @@ jobs: - name: Install Basix and FFCx run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/irreducibleint + python3 -m pip install git+https://github.com/FEniCS/basix.git python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx From 58c3068f42831026a482a973f2941da42c9b4899 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:53:52 +0100 Subject: [PATCH 060/136] Update action differentiation (#214) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names * Prevent eager derivatives expansion for action differentiation * Also prevent eager derivatives expansion for action * Address PR's comment --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: David A. Ham Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward Co-authored-by: Matthew Scroggs --- test/test_duals.py | 5 +++-- ufl/algorithms/map_integrands.py | 5 +++++ ufl/formoperators.py | 3 ++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/test/test_duals.py b/test/test_duals.py index 7b6df9f53..7b68d3cca 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -274,7 +274,8 @@ def test_differentiation(): # -- Action -- # Ac = Action(w, u) dAcdu = derivative(Ac, u) - assert dAcdu == action(adjoint(derivative(w, u)), u) + action(w, derivative(u, u)) + assert dAcdu == (action(adjoint(derivative(w, u), derivatives_expanded=True), u, derivatives_expanded=True) + + action(w, derivative(u, u), derivatives_expanded=True)) dAcdu = expand_derivatives(dAcdu) # Since dw/du = 0 @@ -286,7 +287,7 @@ def test_differentiation(): Fs = M + inner(u * uhat, v) * dx dFsdu = expand_derivatives(derivative(Fs, u)) # Distribute differentiation over FormSum components - assert dFsdu == FormSum([inner(what * uhat, v) * dx, 1]) + assert dFsdu == inner(what * uhat, v) * dx def test_zero_base_form_mult(): diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index e1884b525..250360f83 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -39,6 +39,11 @@ def map_integrands(function, form, only_integral_type=None): nonzero_components = [(component, w) for component, w in zip(mapped_components, form.weights()) # Catch ufl.Zero and ZeroBaseForm if component != 0] + + # Simplify case with one nonzero component and the corresponding weight is 1 + if len(nonzero_components) == 1 and nonzero_components[0][1] == 1: + return nonzero_components[0][0] + if all(not isinstance(component, BaseForm) for component, _ in nonzero_components): # Simplification of `BaseForm` objects may turn `FormSum` into a sum of `Expr` objects # that are not `BaseForm`, i.e. into a `Sum` object. diff --git a/ufl/formoperators.py b/ufl/formoperators.py index c22c2dedf..b71acd9d9 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -322,7 +322,8 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): dleft = derivative(left, coefficient, argument, coefficient_derivatives) dright = derivative(right, coefficient, argument, coefficient_derivatives) # Leibniz formula - return action(adjoint(dleft), right) + action(left, dright) + return (action(adjoint(dleft, derivatives_expanded=True), right, derivatives_expanded=True) + + action(left, dright, derivatives_expanded=True)) else: raise NotImplementedError('Action derivative not supported when the left argument is not a 1-form.') From 77ae57c4b9594851f6bff4a63b7eecb81477c486 Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Fri, 22 Sep 2023 11:45:25 +0100 Subject: [PATCH 061/136] convert Atan2 to atan2 not atan_2 (#216) --- ufl/utils/formatting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index d35164bea..4df63cd98 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -11,7 +11,7 @@ def camel2underscore(name): letters = [] lastlower = False for i in name: - thislower = i.islower() + thislower = i.islower() or i.isdigit() if not thislower: # Don't insert _ between multiple upper case letters if lastlower: From 4843c3770cc44e5c6c41ff24652719456701995c Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Mon, 9 Oct 2023 11:27:54 +0200 Subject: [PATCH 062/136] Bump version. (#217) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7bcb914d4..ccd6223e3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ # future [metadata] name = fenics-ufl -version = 2023.2.0.dev0 +version = 2023.3.0.dev0 author = FEniCS Project Contributors email = fenics-dev@googlegroups.com maintainer = FEniCS Project Steering Council From 5a5faf2a546da51d0c514e969b4bddb9ba6f9a9e Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Fri, 13 Oct 2023 17:01:07 +0100 Subject: [PATCH 063/136] Add Spack test to Actions CI (#218) * Add Spack CI test * Update comment --- .github/workflows/spack.yml | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 .github/workflows/spack.yml diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml new file mode 100644 index 000000000..5baa0d00c --- /dev/null +++ b/.github/workflows/spack.yml @@ -0,0 +1,88 @@ +name: Spack build + +on: + # Uncomment the below 'push' to trigger on push + # push: + # branches: + # - "**" + schedule: + # '*' is a special character in YAML, so string must be quoted + - cron: "0 2 * * WED" + workflow_dispatch: + inputs: + spack_repo: + description: "Spack repository to test" + default: "spack/spack" + type: string + spack_ref: + description: "Spack repository branch/tag to test" + default: "develop" + type: string + +jobs: + build: + runs-on: ubuntu-latest + container: ubuntu:latest + steps: + - name: Get Spack + if: github.event_name != 'workflow_dispatch' + uses: actions/checkout@v4 + with: + path: ./spack + repository: spack/spack + - name: Get Spack + if: github.event_name == 'workflow_dispatch' + uses: actions/checkout@v4 + with: + path: ./spack + repository: ${{ github.event.inputs.spack_repo }} + ref: ${{ github.event.inputs.spack_ref }} + + - name: Install Spack requirements + run: | + apt-get -y update + apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar xz-utils + apt-get install -y g++ gfortran # compilers + + - name: Insall UFL development version via Spack + run: | + . ./spack/share/spack/setup-env.sh + spack env create main + spack env activate main + spack add py-fenics-ufl@main + spack install + - name: Get UFL code (to access test files) + uses: actions/checkout@v4 + with: + path: ./ufl-main + - name: Run tests (development version) + run: | + . ./spack/share/spack/setup-env.sh + spack env create main-test + spack env activate main-test + spack add py-fencis-ufl@main py-pytest + cd ufl-main/test/ + pytest -n auto . + + - name: Install UFL release version via Spack + run: | + . ./spack/share/spack/setup-env.sh + spack env create release + spack env activate release + spack add py-fenics-ufl + spack install + - name: Get UFL release code (to access test files) + uses: actions/checkout@v4 + with: + ref: v2023.2.0 + # ref: v${{ github.event.inputs.spack_branch }} + path: ./ufl-release + - name: Run tests (release version) + run: | + . ./spack/share/spack/setup-env.sh + spack env create release-test + spack env activate release-test + spack add py-fenics-ufl py-pytest + spack install + cd ufl-release/test/ + pytest -n auto . From 66b78e1ab18a5bc292ba3bf48fc726ed890c2597 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 16 Oct 2023 08:49:15 +0100 Subject: [PATCH 064/136] Add Spack test to CI (#219) * Fix Spack CI * Fix typo * Add badge * Simplify * Remove verose output --- .github/workflows/spack.yml | 47 ++++++++----------------------------- README.rst | 4 ++++ 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/.github/workflows/spack.yml b/.github/workflows/spack.yml index 5baa0d00c..7af85df62 100644 --- a/.github/workflows/spack.yml +++ b/.github/workflows/spack.yml @@ -24,6 +24,12 @@ jobs: runs-on: ubuntu-latest container: ubuntu:latest steps: + - name: Install Spack requirements + run: | + apt-get -y update + apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar xz-utils + apt-get install -y g++ gfortran # compilers + - name: Get Spack if: github.event_name != 'workflow_dispatch' uses: actions/checkout@v4 @@ -38,51 +44,18 @@ jobs: repository: ${{ github.event.inputs.spack_repo }} ref: ${{ github.event.inputs.spack_ref }} - - name: Install Spack requirements - run: | - apt-get -y update - apt-get install -y bzip2 curl file git gzip make patch python3-minimal tar xz-utils - apt-get install -y g++ gfortran # compilers - - - name: Insall UFL development version via Spack + - name: Install UFL development version and run tests run: | . ./spack/share/spack/setup-env.sh spack env create main spack env activate main spack add py-fenics-ufl@main - spack install - - name: Get UFL code (to access test files) - uses: actions/checkout@v4 - with: - path: ./ufl-main - - name: Run tests (development version) - run: | - . ./spack/share/spack/setup-env.sh - spack env create main-test - spack env activate main-test - spack add py-fencis-ufl@main py-pytest - cd ufl-main/test/ - pytest -n auto . + spack install --test=root - - name: Install UFL release version via Spack + - name: Install UFL release version and run tests run: | . ./spack/share/spack/setup-env.sh spack env create release spack env activate release spack add py-fenics-ufl - spack install - - name: Get UFL release code (to access test files) - uses: actions/checkout@v4 - with: - ref: v2023.2.0 - # ref: v${{ github.event.inputs.spack_branch }} - path: ./ufl-release - - name: Run tests (release version) - run: | - . ./spack/share/spack/setup-env.sh - spack env create release-test - spack env activate release-test - spack add py-fenics-ufl py-pytest - spack install - cd ufl-release/test/ - pytest -n auto . + spack install --test=root diff --git a/README.rst b/README.rst index 0d43d1e08..452963331 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,8 @@ https://www.fenicsproject.org .. image:: https://github.com/FEniCS/ufl/workflows/UFL%20CI/badge.svg :target: https://github.com/FEniCS/ufl/workflows/UFL%20CI +.. image:: https://github.com/FEniCS/ufl/actions/workflows/spack.yml/badge.svg + :target: https://github.com/FEniCS/ufl/actions/workflows/spack.yml .. image:: https://coveralls.io/repos/github/FEniCS/ufl/badge.svg?branch=master :target: https://coveralls.io/github/FEniCS/ufl?branch=master :alt: Coverage Status @@ -20,11 +22,13 @@ https://www.fenicsproject.org :target: https://fenics.readthedocs.io/projects/ufl/en/latest/?badge=latest :alt: Documentation Status + Documentation ============= Documentation can be viewed at https://fenics-ufl.readthedocs.org/. + License ======= From ff3f69f89e6d33f3ea10970846d504997f097916 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 16 Oct 2023 13:16:13 +0100 Subject: [PATCH 065/136] AbstractFiniteElement (#197) * Remove a lots of finiteelement stuff * __all_classes__ * remove mixedelement * remove use of deleted elements * remove print * working on element interface * working on new elements * Add broken demos * make (almost) all tests pass * Move demos out of broken folder, fix a few tests * Hermite is H1 * flake8 * symmetric elements * subelements in splitting test * fix another test * MixedElasticity demo * fix final test * rename FiniteElementBase -> AbstractFiniteElement * doc * Template structure for pullback * working on pull back * fixes * skip flake8 on pull_back for now * flake and pydocstyle * update tests * basix branch * flake8 * start making ffc tests pass * fix imports * flake * isort * make test pass * ffc branch * as_domain in measure (if domain is not None) * corrections * dolfinx branch * Add some types * no product needed * sub_rsh * () * flattened... not property * documentation * remove component functions * flake, remove unused function * remove _is_linear * put function back * remove is_cellwise_constant * remove is_globally_constant * remove sorting of elements by hash * improve doc * working on new pull back classes * update pull backs in demos and tests * flake8 * add mixed pull back * symmetric pull back * flake * flake8 * a -> A * Get (physical) value shape from the pull back * tidy up element classes * flake * add embedded super- and sub-degrees * Add Legacy elements (for TSFC support) * flake * tsfc branch * don't look up components for scalar-valued elements * sub_elements() -> sub_elements * sobolev_space() -> sobolev_space * is_identity rather than checking type * abstractproperty * docs * use is_identity * doc * simplify pullback code * tweak docstrings * clarify * property and custom pullback * () * Use IdentityPullBack if mixed elements sub elements all use it * Docstring * fixes in legacy * l -> L * physical pull back * @property in legacy * fixing legacy elements * fix symmetry ordering * generalise symmetric pull back to sub-elements with a value size * update AUTHORS * fdlake * fix when element may not be fully initialised * set self._sub_elements before they're needed * update index order in legacy * Rename pull_back -> pullback and improve docs * rename pullback in tests * fix embedded degrees * flake * pullback rename in demos * replace degree() with embedded_superdegree * embedded degrees in hdivcurl * fix more embedded degrees * correct blockshape * max not sum * Revert "max not sum" This reverts commit 145fbb4b928a77dc39ad08ca80dd4f127d0951ba. * only for testing * set branches to main --- .github/workflows/tsfc-tests.yml | 2 +- AUTHORS | 1 + demo/Constant.py | 15 +- demo/ConvectionJacobi.py | 9 +- demo/ConvectionJacobi2.py | 9 +- demo/ConvectionVector.py | 9 +- demo/Elasticity.py | 9 +- demo/EnergyNorm.py | 9 +- demo/Equation.py | 10 +- demo/ExplicitConvection.py | 9 +- demo/FEEC.py | 56 -- demo/FunctionOperators.py | 10 +- demo/H1norm.py | 9 +- demo/HarmonicMap.py | 12 +- demo/HarmonicMap2.py | 14 +- demo/Heat.py | 10 +- demo/HornSchunck.py | 12 +- demo/HyperElasticity.py | 16 +- demo/HyperElasticity1D.py | 10 +- demo/L2norm.py | 9 +- demo/Mass.py | 9 +- demo/MassAD.py | 9 +- demo/MixedElasticity.py | 22 +- demo/MixedMixedElement.py | 9 +- demo/MixedPoisson.py | 14 +- demo/MixedPoisson2.py | 14 +- demo/NavierStokes.py | 9 +- demo/NeumannProblem.py | 10 +- demo/NonlinearPoisson.py | 10 +- demo/P5tet.py | 7 +- demo/P5tri.py | 7 +- demo/Poisson.py | 10 +- demo/PoissonDG.py | 11 +- demo/PoissonSystem.py | 10 +- demo/PowAD.py | 10 +- demo/ProjectionSystem.py | 10 +- demo/QuadratureElement.py | 14 +- demo/RestrictedElement.py | 29 - demo/Stiffness.py | 9 +- demo/StiffnessAD.py | 10 +- demo/Stokes.py | 14 +- demo/StokesEquation.py | 15 +- demo/SubDomain.py | 10 +- demo/SubDomains.py | 9 +- demo/TensorWeightedPoisson.py | 12 +- demo/VectorLaplaceGradCurl.py | 16 +- demo/_TensorProductElement.py | 13 +- setup.cfg | 3 + test/test_algorithms.py | 39 +- test/test_apply_algebra_lowering.py | 27 +- test/test_apply_function_pullbacks.py | 101 ++-- test/test_apply_restrictions.py | 14 +- test/test_arithmetic.py | 17 +- test/test_automatic_differentiation.py | 25 +- test/test_book_snippets.py | 547 ----------------- test/test_change_to_local.py | 11 +- test/test_change_to_reference_frame.py | 21 +- test/test_check_arities.py | 15 +- test/test_classcoverage.py | 38 +- test/test_complex.py | 33 +- test/test_conditionals.py | 14 +- test/test_degree_estimation.py | 44 +- test/test_derivative.py | 292 +++++---- test/test_diff.py | 13 +- test/test_domains.py | 134 +++-- test/test_duals.py | 61 +- test/test_equals.py | 33 +- test/test_evaluate.py | 65 ++- test/test_expand_indices.py | 15 +- test/test_external_operator.py | 16 +- test/test_ffcforms.py | 132 ++--- test/test_form.py | 33 +- test/test_grad.py | 13 +- test/test_illegal.py | 11 +- test/test_indexing.py | 7 +- test/test_indices.py | 67 ++- test/test_interpolate.py | 13 +- test/{test_elements.py => test_legacy.py} | 49 +- test/test_lhs_rhs.py | 19 +- test/test_measures.py | 12 +- test/test_mixed_function_space.py | 25 +- test/test_new_ad.py | 35 +- test/test_pickle.py | 132 +++-- test/test_piecewise_checks.py | 61 +- test/test_reference_shapes.py | 49 +- test/test_scratch.py | 31 +- test/test_signature.py | 109 ++-- test/test_simplify.py | 26 +- test/test_sobolevspace.py | 142 +---- test/test_split.py | 36 +- test/test_str.py | 40 +- test/test_strip_forms.py | 16 +- test/test_tensoralgebra.py | 9 +- ufl/__init__.py | 174 ++---- ufl/action.py | 9 +- ufl/adjoint.py | 4 +- ufl/algebra.py | 8 +- ufl/algorithms/__init__.py | 83 +-- ufl/algorithms/analysis.py | 19 +- ufl/algorithms/apply_algebra_lowering.py | 12 +- ufl/algorithms/apply_derivatives.py | 34 +- ufl/algorithms/apply_function_pullbacks.py | 158 +---- ufl/algorithms/apply_geometry_lowering.py | 22 +- ufl/algorithms/apply_integral_scaling.py | 6 +- ufl/algorithms/apply_restrictions.py | 2 +- ufl/algorithms/balancing.py | 4 +- ufl/algorithms/change_to_reference.py | 14 +- ufl/algorithms/check_arities.py | 6 +- ufl/algorithms/check_restrictions.py | 2 +- ufl/algorithms/checks.py | 13 +- ufl/algorithms/comparison_checker.py | 6 +- ufl/algorithms/compute_form_data.py | 40 +- .../coordinate_derivative_helpers.py | 6 +- ufl/algorithms/domain_analysis.py | 9 +- ufl/algorithms/estimate_degrees.py | 10 +- ufl/algorithms/expand_compounds.py | 1 + ufl/algorithms/expand_indices.py | 14 +- ufl/algorithms/formdata.py | 2 +- ufl/algorithms/formfiles.py | 19 +- ufl/algorithms/formsplitter.py | 6 +- ufl/algorithms/formtransformations.py | 13 +- ufl/algorithms/map_integrands.py | 8 +- ufl/algorithms/remove_complex_nodes.py | 4 +- ufl/algorithms/renumbering.py | 6 +- ufl/algorithms/replace.py | 8 +- ufl/algorithms/replace_derivative_nodes.py | 6 +- ufl/algorithms/signature.py | 10 +- ufl/algorithms/strip_terminal_data.py | 9 +- ufl/algorithms/traversal.py | 8 +- ufl/argument.py | 25 +- ufl/averaging.py | 2 +- ufl/cell.py | 4 +- ufl/checks.py | 41 +- ufl/classes.py | 69 +-- ufl/coefficient.py | 26 +- ufl/compound_expressions.py | 6 +- ufl/conditional.py | 8 +- ufl/constant.py | 2 +- ufl/constantvalue.py | 8 +- ufl/core/base_form_operator.py | 4 +- ufl/core/expr.py | 6 - ufl/core/interpolate.py | 10 +- ufl/core/multiindex.py | 4 +- ufl/core/operator.py | 2 - ufl/core/ufl_type.py | 7 +- ufl/corealg/map_dag.py | 2 +- ufl/differentiation.py | 6 +- ufl/domain.py | 141 +---- ufl/exprcontainers.py | 6 +- ufl/exproperators.py | 24 +- ufl/finiteelement.py | 368 ++++++++++++ ufl/finiteelement/__init__.py | 42 -- ufl/form.py | 1 + ufl/formatting/ufl2unicode.py | 6 +- ufl/formoperators.py | 50 +- ufl/functionspace.py | 4 +- ufl/geometry.py | 4 +- ufl/index_combination_utils.py | 2 +- ufl/indexed.py | 8 +- ufl/indexsum.py | 8 +- ufl/integral.py | 2 +- ufl/legacy/__init__.py | 25 + .../brokenelement.py | 9 +- ufl/{finiteelement => legacy}/elementlist.py | 6 +- .../enrichedelement.py | 37 +- .../finiteelement.py | 24 +- .../finiteelementbase.py | 68 ++- ufl/{finiteelement => legacy}/hdivcurl.py | 49 +- ufl/{finiteelement => legacy}/mixedelement.py | 106 +++- .../restrictedelement.py | 17 +- .../tensorproductelement.py | 36 +- ufl/mathfunctions.py | 6 +- ufl/matrix.py | 7 +- ufl/measure.py | 20 +- ufl/objects.py | 5 +- ufl/operators.py | 51 +- ufl/precedence.py | 8 +- ufl/pullback.py | 552 ++++++++++++++++++ ufl/referencevalue.py | 4 +- ufl/restriction.py | 4 +- ufl/sobolevspace.py | 23 +- ufl/sorting.py | 9 +- ufl/split_functions.py | 26 +- ufl/tensoralgebra.py | 6 +- ufl/tensors.py | 10 +- ufl/utils/sequences.py | 1 + ufl/variable.py | 8 +- 187 files changed, 3094 insertions(+), 2883 deletions(-) delete mode 100644 demo/FEEC.py delete mode 100644 demo/RestrictedElement.py delete mode 100755 test/test_book_snippets.py rename test/{test_elements.py => test_legacy.py} (81%) mode change 100755 => 100644 create mode 100644 ufl/finiteelement.py delete mode 100644 ufl/finiteelement/__init__.py create mode 100644 ufl/legacy/__init__.py rename ufl/{finiteelement => legacy}/brokenelement.py (86%) rename ufl/{finiteelement => legacy}/elementlist.py (99%) rename ufl/{finiteelement => legacy}/enrichedelement.py (83%) rename ufl/{finiteelement => legacy}/finiteelement.py (93%) rename ufl/{finiteelement => legacy}/finiteelementbase.py (81%) rename ufl/{finiteelement => legacy}/hdivcurl.py (81%) rename ufl/{finiteelement => legacy}/mixedelement.py (86%) rename ufl/{finiteelement => legacy}/restrictedelement.py (91%) rename ufl/{finiteelement => legacy}/tensorproductelement.py (83%) create mode 100644 ufl/pullback.py diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 06b10a13d..a861b4728 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -32,7 +32,7 @@ jobs: with: path: ./tsfc repository: firedrakeproject/tsfc - ref: master + ref: mscroggs/newfl-legacy - name: Install tsfc run: | cd tsfc diff --git a/AUTHORS b/AUTHORS index c3c6f37c6..5866e1c39 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,3 +31,4 @@ Contributors: | Nacime Bouziani | Reuben W. Hill | Nacime Bouziani + | Matthew Scroggs diff --git a/demo/Constant.py b/demo/Constant.py index 0a2205978..96acaf22c 100644 --- a/demo/Constant.py +++ b/demo/Constant.py @@ -18,20 +18,23 @@ # Modified by Martin Sandve Alnes, 2009 # # Test form for scalar and vector constants. -from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, - VectorElement, dot, dx, grad, inner, triangle) +from ufl import (Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, dot, dx, grad, + inner, triangle) +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -element = FiniteElement("Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) f = Coefficient(space) -c = Constant(space) -d = VectorConstant(space) +c = Constant(domain) +d = VectorConstant(domain) a = c * dot(grad(v), grad(u)) * dx L = inner(d, grad(v)) * dx diff --git a/demo/ConvectionJacobi.py b/demo/ConvectionJacobi.py index bf059ade6..5f3b2da10 100644 --- a/demo/ConvectionJacobi.py +++ b/demo/ConvectionJacobi.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/ConvectionJacobi2.py b/demo/ConvectionJacobi2.py index 7096cd32d..c88108a17 100644 --- a/demo/ConvectionJacobi2.py +++ b/demo/ConvectionJacobi2.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, i, j, triangle +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, i, j, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/ConvectionVector.py b/demo/ConvectionVector.py index d16a52ee5..e83e60698 100644 --- a/demo/ConvectionVector.py +++ b/demo/ConvectionVector.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, VectorElement, dot, dx, grad, triangle +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/Elasticity.py b/demo/Elasticity.py index 0061c7135..73513d5d6 100644 --- a/demo/Elasticity.py +++ b/demo/Elasticity.py @@ -3,10 +3,13 @@ # Modified by: Martin Sandve Alnes # Date: 2009-01-12 # -from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, grad, inner, tetrahedron +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", tetrahedron, 1) -domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/EnergyNorm.py b/demo/EnergyNorm.py index 64bc8d88e..30f1ade40 100644 --- a/demo/EnergyNorm.py +++ b/demo/EnergyNorm.py @@ -17,10 +17,13 @@ # # This example demonstrates how to define a functional, here # the energy norm (squared) for a reaction-diffusion problem. -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dot, dx, grad, tetrahedron +from ufl import Coefficient, FunctionSpace, Mesh, dot, dx, grad, tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", tetrahedron, 1) -domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) diff --git a/demo/Equation.py b/demo/Equation.py index 94c5ef445..33625545b 100644 --- a/demo/Equation.py +++ b/demo/Equation.py @@ -34,11 +34,13 @@ # the unknown u to the right-hand side, all terms may # be listed on one line and left- and right-hand sides # extracted by lhs() and rhs(). -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, - grad, lhs, rhs, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, lhs, rhs, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 diff --git a/demo/ExplicitConvection.py b/demo/ExplicitConvection.py index f3b684d4d..c56d27376 100644 --- a/demo/ExplicitConvection.py +++ b/demo/ExplicitConvection.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/FEEC.py b/demo/FEEC.py deleted file mode 100644 index cf79198e7..000000000 --- a/demo/FEEC.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2010 Marie Rognes -# -# This file is part of UFL. -# -# UFL is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# UFL is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with UFL. If not, see . - -""" -This demo illustrates the FEEC notation - - V = FiniteElement("P Lambda", cell, r, k) - V = FiniteElement("P- Lambda", cell, r, k) - -and their aliases. -""" -from ufl import (FiniteElement, FunctionSpace, Mesh, TestFunction, TestFunctions, TrialFunction, TrialFunctions, - VectorElement, dx) -from ufl import exterior_derivative as d -from ufl import inner, interval, tetrahedron, triangle - -cells = [interval, triangle, tetrahedron] -r = 1 - -for cell in cells: - for family in ["P Lambda", "P- Lambda"]: - tdim = cell.topological_dimension() - for k in range(0, tdim + 1): - - # Testing exterior derivative - V = FiniteElement(family, cell, r, form_degree=k) - domain = Mesh(VectorElement("Lagrange", cell, 1)) - space = FunctionSpace(domain, V) - v = TestFunction(space) - u = TrialFunction(space) - - a = inner(d(u), d(v)) * dx - - # Testing mixed formulation of Hodge Laplace - if k > 0 and k < tdim + 1: - S = FiniteElement(family, cell, r, form_degree=k - 1) - W = S * V - mixed_space = FunctionSpace(domain, W) - (sigma, u) = TrialFunctions(mixed_space) - (tau, v) = TestFunctions(mixed_space) - - a = (inner(sigma, tau) - inner(d(tau), u) + inner(d(sigma), v) + inner(d(u), d(v))) * dx diff --git a/demo/FunctionOperators.py b/demo/FunctionOperators.py index 79fa43244..075e7b8c1 100644 --- a/demo/FunctionOperators.py +++ b/demo/FunctionOperators.py @@ -16,11 +16,13 @@ # along with UFL. If not, see . # # Test form for operators on Coefficients. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, - grad, max_value, sqrt, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, max_value, sqrt, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/H1norm.py b/demo/H1norm.py index 2dd54a12a..9da6b28e4 100644 --- a/demo/H1norm.py +++ b/demo/H1norm.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dot, dx, grad, triangle +from ufl import Coefficient, FunctionSpace, Mesh, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) diff --git a/demo/HarmonicMap.py b/demo/HarmonicMap.py index 854fae28f..8aa3ee5d2 100644 --- a/demo/HarmonicMap.py +++ b/demo/HarmonicMap.py @@ -3,13 +3,15 @@ # Author: Martin Alnes # Date: 2009-04-09 # -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, inner, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, derivative, dot, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -X = VectorElement("Lagrange", cell, 1) -Y = FiniteElement("Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +X = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +Y = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) X_space = FunctionSpace(domain, X) Y_space = FunctionSpace(domain, Y) diff --git a/demo/HarmonicMap2.py b/demo/HarmonicMap2.py index 811d8b666..dfc47c3b1 100644 --- a/demo/HarmonicMap2.py +++ b/demo/HarmonicMap2.py @@ -3,14 +3,16 @@ # Author: Martin Alnes # Date: 2009-04-09 # -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, inner, - split, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, derivative, dot, dx, grad, inner, split, triangle +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -X = VectorElement("Lagrange", cell, 1) -Y = FiniteElement("Lagrange", cell, 1) -M = X * Y -domain = Mesh(VectorElement("Lagrange", cell, 1)) +X = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +Y = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +M = MixedElement([X, Y]) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, M) u = Coefficient(space) diff --git a/demo/Heat.py b/demo/Heat.py index 241cc708e..f695341b2 100644 --- a/demo/Heat.py +++ b/demo/Heat.py @@ -20,12 +20,14 @@ # The bilinear form a(v, u1) and linear form L(v) for # one backward Euler step with the heat equation. # -from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, - dot, dx, grad, triangle) +from ufl import Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -element = FiniteElement("Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) # Test function diff --git a/demo/HornSchunck.py b/demo/HornSchunck.py index 139b8a0e3..d3ec43840 100644 --- a/demo/HornSchunck.py +++ b/demo/HornSchunck.py @@ -3,14 +3,16 @@ # http://code.google.com/p/debiosee/wiki/DemosOptiocFlowHornSchunck # but not tested so this could contain errors! # -from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dot, dx, grad, - inner, triangle) +from ufl import Coefficient, Constant, FunctionSpace, Mesh, derivative, dot, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # Finite element spaces for scalar and vector fields cell = triangle -S = FiniteElement("CG", cell, 1) -V = VectorElement("CG", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +S = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) S_space = FunctionSpace(domain, S) V_space = FunctionSpace(domain, V) diff --git a/demo/HyperElasticity.py b/demo/HyperElasticity.py index 289afbb0a..abbc8314c 100644 --- a/demo/HyperElasticity.py +++ b/demo/HyperElasticity.py @@ -3,22 +3,24 @@ # Date: 2008-12-22 # +from ufl import (Coefficient, Constant, FacetNormal, FunctionSpace, Identity, Mesh, SpatialCoordinate, TestFunction, + TrialFunction, derivative, det, diff, dot, ds, dx, exp, grad, inner, inv, tetrahedron, tr, variable) +from ufl.finiteelement import FiniteElement # Modified by Garth N. Wells, 2009 -from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, SpatialCoordinate, - TensorElement, TestFunction, TrialFunction, VectorElement, derivative, det, diff, dot, ds, dx, exp, - grad, inner, inv, tetrahedron, tr, variable) +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # Cell and its properties cell = tetrahedron -domain = Mesh(VectorElement("Lagrange", cell, 1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) d = cell.geometric_dimension() N = FacetNormal(domain) x = SpatialCoordinate(domain) # Elements -u_element = VectorElement("CG", cell, 2) -p_element = FiniteElement("CG", cell, 1) -A_element = TensorElement("CG", cell, 1) +u_element = FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1) +p_element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +A_element = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) # Spaces u_space = FunctionSpace(domain, u_element) diff --git a/demo/HyperElasticity1D.py b/demo/HyperElasticity1D.py index aaacc2ce9..9dfcbed96 100644 --- a/demo/HyperElasticity1D.py +++ b/demo/HyperElasticity1D.py @@ -2,12 +2,14 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dx, exp, - interval, variable) +from ufl import Coefficient, Constant, FunctionSpace, Mesh, derivative, dx, exp, interval, variable +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = interval -element = FiniteElement("CG", cell, 2) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (1, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Coefficient(space) diff --git a/demo/L2norm.py b/demo/L2norm.py index ca8d78cda..31050a9df 100644 --- a/demo/L2norm.py +++ b/demo/L2norm.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, dx, triangle +from ufl import Coefficient, FunctionSpace, Mesh, dx, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) diff --git a/demo/Mass.py b/demo/Mass.py index a1603c146..4f083140b 100644 --- a/demo/Mass.py +++ b/demo/Mass.py @@ -20,10 +20,13 @@ # Last changed: 2009-03-02 # # The bilinear form for a mass matrix. -from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, triangle +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dx, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/MassAD.py b/demo/MassAD.py index 57dabdc7a..ed36e8c2c 100644 --- a/demo/MassAD.py +++ b/demo/MassAD.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-28 # -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, derivative, dx, triangle +from ufl import Coefficient, FunctionSpace, Mesh, derivative, dx, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Coefficient(space) diff --git a/demo/MixedElasticity.py b/demo/MixedElasticity.py index 0fac9d36b..6fe1a96a3 100644 --- a/demo/MixedElasticity.py +++ b/demo/MixedElasticity.py @@ -17,8 +17,11 @@ # # First added: 2008-10-03 # Last changed: 2011-07-22 -from ufl import (FunctionSpace, Mesh, MixedElement, TestFunctions, TrialFunctions, VectorElement, as_vector, div, dot, - dx, inner, skew, tetrahedron, tr) +from ufl import (FunctionSpace, Mesh, TestFunctions, TrialFunctions, as_vector, div, dot, dx, inner, skew, tetrahedron, + tr) +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import contravariant_piola, identity_pullback +from ufl.sobolevspace import H1, L2, HDiv def skw(tau): @@ -32,18 +35,13 @@ def skw(tau): # Finite element exterior calculus syntax r = 1 -S = VectorElement("P Lambda", cell, r, form_degree=n - 1) -V = VectorElement("P Lambda", cell, r - 1, form_degree=n) -Q = VectorElement("P Lambda", cell, r - 1, form_degree=n) +S = FiniteElement("vector BDM", cell, r, (3, 3), contravariant_piola, HDiv) +V = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3, ), identity_pullback, L2) +Q = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3, ), identity_pullback, L2) -# Alternative syntax: -# S = VectorElement("BDM", cell, r) -# V = VectorElement("Discontinuous Lagrange", cell, r-1) -# Q = VectorElement("Discontinuous Lagrange", cell, r-1) +W = MixedElement([S, V, Q]) -W = MixedElement(S, V, Q) - -domain = Mesh(VectorElement("Lagrange", cell, 1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, W) (sigma, u, gamma) = TrialFunctions(space) diff --git a/demo/MixedMixedElement.py b/demo/MixedMixedElement.py index ddffe3c97..0d1b1df20 100644 --- a/demo/MixedMixedElement.py +++ b/demo/MixedMixedElement.py @@ -16,8 +16,11 @@ # along with UFL. If not, see . # # A mixed element of mixed elements -from ufl import FiniteElement, triangle +from ufl import triangle +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -P3 = FiniteElement("Lagrange", triangle, 3) +P3 = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) -element = (P3 * P3) * (P3 * P3) +element = MixedElement([[P3, P3], [P3, P3]]) diff --git a/demo/MixedPoisson.py b/demo/MixedPoisson.py index 57a112521..1580372a3 100644 --- a/demo/MixedPoisson.py +++ b/demo/MixedPoisson.py @@ -23,15 +23,17 @@ # a mixed formulation of Poisson's equation with BDM # (Brezzi-Douglas-Marini) elements. # -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, - dot, dx, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, triangle +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import contravariant_piola, identity_pullback +from ufl.sobolevspace import H1, HDiv cell = triangle -BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1) -DG0 = FiniteElement("Discontinuous Lagrange", cell, 0) +BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1, (2, ), contravariant_piola, HDiv) +DG0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, H1) -element = BDM1 * DG0 -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = MixedElement([BDM1, DG0]) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) dg0_space = FunctionSpace(domain, DG0) diff --git a/demo/MixedPoisson2.py b/demo/MixedPoisson2.py index 4711bb97e..29268a309 100644 --- a/demo/MixedPoisson2.py +++ b/demo/MixedPoisson2.py @@ -3,14 +3,16 @@ # Modified by: Martin Sandve Alnes # Date: 2009-02-12 # -from ufl import (FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, - dot, ds, dx, tetrahedron) +from ufl import FacetNormal, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, ds, dx, tetrahedron +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import contravariant_piola, identity_pullback +from ufl.sobolevspace import H1, HDiv cell = tetrahedron -RT = FiniteElement("Raviart-Thomas", cell, 1) -DG = FiniteElement("DG", cell, 0) -MX = RT * DG -domain = Mesh(VectorElement("Lagrange", cell, 1)) +RT = FiniteElement("Raviart-Thomas", cell, 1, (3, ), contravariant_piola, HDiv) +DG = FiniteElement("DG", cell, 0, (), identity_pullback, H1) +MX = MixedElement([RT, DG]) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, MX) (u, p) = TrialFunctions(space) diff --git a/demo/NavierStokes.py b/demo/NavierStokes.py index 0dc87bdda..14dfa5f56 100644 --- a/demo/NavierStokes.py +++ b/demo/NavierStokes.py @@ -21,11 +21,14 @@ # # The bilinear form for the nonlinear term in the # Navier-Stokes equations with fixed convective velocity. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, tetrahedron +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = tetrahedron -element = VectorElement("Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/NeumannProblem.py b/demo/NeumannProblem.py index 85563e411..d384c4315 100644 --- a/demo/NeumannProblem.py +++ b/demo/NeumannProblem.py @@ -17,11 +17,13 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation with Neumann boundary conditions. -from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dx, grad, inner, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, ds, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = VectorElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/NonlinearPoisson.py b/demo/NonlinearPoisson.py index f71b2c41c..7604459b5 100644 --- a/demo/NonlinearPoisson.py +++ b/demo/NonlinearPoisson.py @@ -1,8 +1,10 @@ -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, - grad, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/P5tet.py b/demo/P5tet.py index 44dbb7fb5..5114b6724 100644 --- a/demo/P5tet.py +++ b/demo/P5tet.py @@ -16,6 +16,9 @@ # along with UFL. If not, see . # # A fifth degree Lagrange finite element on a tetrahedron -from ufl import FiniteElement, tetrahedron +from ufl import tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", tetrahedron, 5) +element = FiniteElement("Lagrange", tetrahedron, 5, (), identity_pullback, H1) diff --git a/demo/P5tri.py b/demo/P5tri.py index eb616e896..ebfd6ba6f 100644 --- a/demo/P5tri.py +++ b/demo/P5tri.py @@ -16,6 +16,9 @@ # along with UFL. If not, see . # # A fifth degree Lagrange finite element on a triangle -from ufl import FiniteElement, triangle +from ufl import triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 5) +element = FiniteElement("Lagrange", triangle, 5, (), identity_pullback, H1) diff --git a/demo/Poisson.py b/demo/Poisson.py index 470b844ec..779273391 100644 --- a/demo/Poisson.py +++ b/demo/Poisson.py @@ -21,11 +21,13 @@ # Last changed: 2009-03-02 # # The bilinear form a(v, u) and linear form L(v) for Poisson's equation. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, grad, - inner, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/PoissonDG.py b/demo/PoissonDG.py index 90c0c3af1..fd289213a 100644 --- a/demo/PoissonDG.py +++ b/demo/PoissonDG.py @@ -21,12 +21,15 @@ # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in a discontinuous Galerkin (DG) # formulation. -from ufl import (Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, - VectorElement, avg, dot, dS, ds, dx, grad, inner, jump, triangle) +from ufl import (Coefficient, Constant, FacetNormal, FunctionSpace, Mesh, TestFunction, TrialFunction, avg, dot, dS, ds, + dx, grad, inner, jump, triangle) +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 cell = triangle -element = FiniteElement("Discontinuous Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Discontinuous Lagrange", cell, 1, (), identity_pullback, L2) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/PoissonSystem.py b/demo/PoissonSystem.py index d42a96eca..29967d025 100644 --- a/demo/PoissonSystem.py +++ b/demo/PoissonSystem.py @@ -21,12 +21,14 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in system form (vector-valued). -from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, inner, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -element = VectorElement("Lagrange", cell, 1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) +element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/PowAD.py b/demo/PowAD.py index 1c0ad4e92..106f1f799 100644 --- a/demo/PowAD.py +++ b/demo/PowAD.py @@ -2,11 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, - derivative, dx, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, derivative, dx, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/ProjectionSystem.py b/demo/ProjectionSystem.py index e082b99e1..7548a0b22 100644 --- a/demo/ProjectionSystem.py +++ b/demo/ProjectionSystem.py @@ -1,8 +1,10 @@ -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dx, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) diff --git a/demo/QuadratureElement.py b/demo/QuadratureElement.py index 3ab666e29..4e01cd31b 100644 --- a/demo/QuadratureElement.py +++ b/demo/QuadratureElement.py @@ -20,15 +20,17 @@ # # The linearised bilinear form a(u,v) and linear form L(v) for # the nonlinear equation - div (1+u) grad u = f (non-linear Poisson) -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, - grad, i, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, i, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 2) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) -QE = FiniteElement("Quadrature", triangle, 2, quad_scheme="default") -sig = VectorElement("Quadrature", triangle, 1, quad_scheme="default") +QE = FiniteElement("Quadrature", triangle, 2, (), identity_pullback, H1) +sig = FiniteElement("Quadrature", triangle, 1, (2, ), identity_pullback, H1) qe_space = FunctionSpace(domain, QE) sig_space = FunctionSpace(domain, sig) diff --git a/demo/RestrictedElement.py b/demo/RestrictedElement.py deleted file mode 100644 index 6a07fa274..000000000 --- a/demo/RestrictedElement.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2009 Kristian B. Oelgaard -# -# This file is part of UFL. -# -# UFL is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# UFL is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with UFL. If not, see . -# -# Restriction of a finite element. -# The below syntax show how one can restrict a higher order Lagrange element -# to only take into account those DOFs that live on the facets. -from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, avg, dS, ds, triangle - -# Restricted element -CG_R = FiniteElement("Lagrange", triangle, 4)["facet"] -domain = Mesh(VectorElement("Lagrange", triangle, 1)) -space = FunctionSpace(domain, CG_R) -u_r = TrialFunction(space) -v_r = TestFunction(space) -a = avg(v_r) * avg(u_r) * dS + v_r * u_r * ds diff --git a/demo/Stiffness.py b/demo/Stiffness.py index 43f10ea2e..32bf00a54 100644 --- a/demo/Stiffness.py +++ b/demo/Stiffness.py @@ -2,10 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, dot, dx, grad, triangle +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/StiffnessAD.py b/demo/StiffnessAD.py index affadc86b..59a8bcba4 100644 --- a/demo/StiffnessAD.py +++ b/demo/StiffnessAD.py @@ -2,11 +2,13 @@ # Author: Martin Sandve Alnes # Date: 2008-10-30 # -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, action, adjoint, derivative, dx, grad, - inner, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, action, adjoint, derivative, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) w = Coefficient(space) diff --git a/demo/Stokes.py b/demo/Stokes.py index f128f7779..b4d240ffd 100644 --- a/demo/Stokes.py +++ b/demo/Stokes.py @@ -20,14 +20,16 @@ # # The bilinear form a(v, u) and Linear form L(v) for the Stokes # equations using a mixed formulation (Taylor-Hood elements). -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, - dot, dx, grad, inner, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -P2 = VectorElement("Lagrange", cell, 2) -P1 = FiniteElement("Lagrange", cell, 1) -TH = P2 * P1 -domain = Mesh(VectorElement("Lagrange", cell, 1)) +P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) +P1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +TH = MixedElement([P2, P1]) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) diff --git a/demo/StokesEquation.py b/demo/StokesEquation.py index ea0c62bd7..949551846 100644 --- a/demo/StokesEquation.py +++ b/demo/StokesEquation.py @@ -19,14 +19,17 @@ # equations using a mixed formulation (Taylor-Hood elements) in # combination with the lhs() and rhs() operators to extract the # bilinear and linear forms from an expression F = 0. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, div, - dot, dx, grad, inner, lhs, rhs, triangle) +from ufl import (Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, grad, inner, lhs, rhs, + triangle) +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 cell = triangle -P2 = VectorElement("Lagrange", cell, 2) -P1 = FiniteElement("Lagrange", cell, 1) -TH = P2 * P1 -domain = Mesh(VectorElement("Lagrange", cell, 1)) +P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) +P1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) +TH = MixedElement([P2, P1]) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) diff --git a/demo/SubDomain.py b/demo/SubDomain.py index c55e59d75..4205a7790 100644 --- a/demo/SubDomain.py +++ b/demo/SubDomain.py @@ -17,11 +17,13 @@ # # This example illustrates how to define a form over a # given subdomain of a mesh, in this case a functional. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dx, - tetrahedron) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, ds, dx, tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("CG", tetrahedron, 1) -domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/SubDomains.py b/demo/SubDomains.py index f8a19caa6..55e9ddbe5 100644 --- a/demo/SubDomains.py +++ b/demo/SubDomains.py @@ -17,10 +17,13 @@ # # This simple example illustrates how forms can be defined on different sub domains. # It is supported for all three integral types. -from ufl import FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, ds, dS, dx, tetrahedron +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, ds, dS, dx, tetrahedron +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -element = FiniteElement("CG", tetrahedron, 1) -domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) +element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/TensorWeightedPoisson.py b/demo/TensorWeightedPoisson.py index 0c76f32bf..6ebc8e3c1 100644 --- a/demo/TensorWeightedPoisson.py +++ b/demo/TensorWeightedPoisson.py @@ -17,12 +17,14 @@ # # The bilinear form a(v, u) and linear form L(v) for # tensor-weighted Poisson's equation. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, TestFunction, TrialFunction, - VectorElement, dx, grad, inner, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 -P1 = FiniteElement("Lagrange", triangle, 1) -P0 = TensorElement("Discontinuous Lagrange", triangle, 0, shape=(2, 2)) -domain = Mesh(VectorElement("Lagrange", triangle, 1)) +P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) diff --git a/demo/VectorLaplaceGradCurl.py b/demo/VectorLaplaceGradCurl.py index e8e281baf..f5d9b863f 100644 --- a/demo/VectorLaplaceGradCurl.py +++ b/demo/VectorLaplaceGradCurl.py @@ -18,8 +18,10 @@ # The bilinear form a(v, u) and linear form L(v) for the Hodge Laplace # problem using 0- and 1-forms. Intended to demonstrate use of Nedelec # elements. -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunctions, TrialFunctions, VectorElement, curl, - dx, grad, inner, tetrahedron) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, curl, dx, grad, inner, tetrahedron +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import covariant_piola, identity_pullback +from ufl.sobolevspace import H1, HCurl def HodgeLaplaceGradCurl(space, fspace): @@ -36,13 +38,13 @@ def HodgeLaplaceGradCurl(space, fspace): cell = tetrahedron order = 1 -GRAD = FiniteElement("Lagrange", cell, order) -CURL = FiniteElement("N1curl", cell, order) +GRAD = FiniteElement("Lagrange", cell, order, (), identity_pullback, H1) +CURL = FiniteElement("N1curl", cell, order, (3, ), covariant_piola, HCurl) -VectorLagrange = VectorElement("Lagrange", cell, order + 1) +VectorLagrange = FiniteElement("Lagrange", cell, order + 1, (3, ), identity_pullback, H1) -domain = Mesh(VectorElement("Lagrange", cell, 1)) -space = FunctionSpace(domain, GRAD * CURL) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +space = FunctionSpace(domain, MixedElement([GRAD, CURL])) fspace = FunctionSpace(domain, VectorLagrange) a, L = HodgeLaplaceGradCurl(space, fspace) diff --git a/demo/_TensorProductElement.py b/demo/_TensorProductElement.py index 373feee99..9e6fb6ef0 100644 --- a/demo/_TensorProductElement.py +++ b/demo/_TensorProductElement.py @@ -17,12 +17,15 @@ # # First added: 2012-08-16 # Last changed: 2012-08-16 -from ufl import (FiniteElement, FunctionSpace, Mesh, TensorProductElement, TestFunction, TrialFunction, dx, interval, - tetrahedron, triangle) +from ufl import (FunctionSpace, Mesh, TensorProductElement, TestFunction, TrialFunction, dx, interval, tetrahedron, + triangle) +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 -V0 = FiniteElement("CG", triangle, 1) -V1 = FiniteElement("DG", interval, 0) -V2 = FiniteElement("DG", tetrahedron, 0) +V0 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) +V1 = FiniteElement("DG", interval, 0, (), identity_pullback, L2) +V2 = FiniteElement("DG", tetrahedron, 0, (), identity_pullback, L2) V = TensorProductElement(V0, V1, V2) diff --git a/setup.cfg b/setup.cfg index ccd6223e3..4bbece177 100644 --- a/setup.cfg +++ b/setup.cfg @@ -59,7 +59,10 @@ ci = [flake8] max-line-length = 120 +builtins = ufl exclude = doc/sphinx/source/conf.py +per-file-ignores = + */__init__.py: F401 [pydocstyle] convention = google diff --git a/test/test_algorithms.py b/test/test_algorithms.py index 9beba0d9c..4b87c2851 100755 --- a/test/test_algorithms.py +++ b/test/test_algorithms.py @@ -6,23 +6,26 @@ import pytest -from ufl import (Argument, Coefficient, FacetNormal, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, - VectorElement, adjoint, div, dot, ds, dx, grad, inner, triangle) +from ufl import (Argument, Coefficient, FacetNormal, FunctionSpace, Mesh, TestFunction, TrialFunction, adjoint, div, + dot, ds, dx, grad, inner, triangle) from ufl.algorithms import (expand_derivatives, expand_indices, extract_arguments, extract_coefficients, extract_elements, extract_unique_elements) from ufl.corealg.traversal import post_traversal, pre_traversal, unique_post_traversal, unique_pre_traversal +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # TODO: add more tests, covering all utility algorithms @pytest.fixture(scope='module') def element(): - return FiniteElement("CG", triangle, 1) + return FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) @pytest.fixture(scope='module') def domain(): - return Mesh(VectorElement("CG", triangle, 1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) @pytest.fixture(scope='module') @@ -45,10 +48,10 @@ def coefficients(space): @pytest.fixture -def forms(arguments, coefficients, space): +def forms(arguments, coefficients, domain): v, u = arguments c, f = coefficients - n = FacetNormal(space) + n = FacetNormal(domain) a = u * v * dx L = f * v * dx b = u * v * dx(0) + inner(c * grad(u), grad(v)) * dx(1) + dot(n, grad(u)) * v * ds + f * v * dx @@ -64,13 +67,13 @@ def test_extract_coefficients_vs_fixture(coefficients, forms): assert coefficients == tuple(extract_coefficients(forms[2])) -def test_extract_elements_and_extract_unique_elements(forms, domain): +def test_extract_elements_and_extract_unique_elements(forms, element, domain): b = forms[2] integrals = b.integrals_by_type("cell") integrals[0].integrand() - element1 = FiniteElement("CG", triangle, 1) - element2 = FiniteElement("CG", triangle, 1) + element1 = element + element2 = element space1 = FunctionSpace(domain, element1) space2 = FunctionSpace(domain, element2) @@ -83,9 +86,7 @@ def test_extract_elements_and_extract_unique_elements(forms, domain): assert extract_unique_elements(a) == (element1,) -def test_pre_and_post_traversal(domain): - element = FiniteElement("CG", "triangle", 1) - space = FunctionSpace(domain, element) +def test_pre_and_post_traversal(space): v = TestFunction(space) f = Coefficient(space) g = Coefficient(space) @@ -103,7 +104,7 @@ def test_pre_and_post_traversal(domain): def test_expand_indices(domain): - element = FiniteElement("Lagrange", triangle, 2) + element = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -124,8 +125,8 @@ def evaluate(form): def test_adjoint(domain): cell = triangle - V1 = FiniteElement("CG", cell, 1) - V2 = FiniteElement("CG", cell, 2) + V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V2 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) s1 = FunctionSpace(domain, V1) s2 = FunctionSpace(domain, V2) @@ -139,17 +140,17 @@ def test_adjoint(domain): assert u2.number() < v2.number() a = u * v * dx - a_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(a)] + a_arg_degrees = [arg.ufl_element().embedded_superdegree for arg in extract_arguments(a)] assert a_arg_degrees == [2, 1] b = adjoint(a) - b_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(b)] + b_arg_degrees = [arg.ufl_element().embedded_superdegree for arg in extract_arguments(b)] assert b_arg_degrees == [1, 2] c = adjoint(a, (u2, v2)) - c_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(c)] + c_arg_degrees = [arg.ufl_element().embedded_superdegree for arg in extract_arguments(c)] assert c_arg_degrees == [1, 2] d = adjoint(b) - d_arg_degrees = [arg.ufl_element().degree() for arg in extract_arguments(d)] + d_arg_degrees = [arg.ufl_element().embedded_superdegree for arg in extract_arguments(d)] assert d_arg_degrees == [2, 1] diff --git a/test/test_apply_algebra_lowering.py b/test/test_apply_algebra_lowering.py index 7b5c6db23..e1f496eb9 100755 --- a/test/test_apply_algebra_lowering.py +++ b/test/test_apply_algebra_lowering.py @@ -1,51 +1,60 @@ import pytest -from ufl import (Coefficient, FiniteElement, FunctionSpace, Index, Mesh, TensorElement, VectorElement, as_tensor, - interval, sqrt, tetrahedron, triangle) +from ufl import Coefficient, FunctionSpace, Index, Mesh, as_tensor, interval, sqrt, triangle from ufl.algorithms.renumbering import renumber_indices from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def A0(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", interval, 1)), FiniteElement("CG", interval, 1))) + Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)), + FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1))) @pytest.fixture def A1(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", interval, 1)), TensorElement("CG", interval, 1))) + Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)), + FiniteElement("Lagrange", interval, 1, (1, 1), identity_pullback, H1))) @pytest.fixture def A2(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1))) + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1))) @pytest.fixture def A3(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", tetrahedron, 1)), TensorElement("CG", tetrahedron, 1))) + Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 3), identity_pullback, H1))) @pytest.fixture def A21(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(2, 1)))) + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, 1), identity_pullback, H1))) @pytest.fixture def A31(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(3, 1)))) + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 1), identity_pullback, H1))) @pytest.fixture def A32(request): return Coefficient(FunctionSpace( - Mesh(VectorElement("CG", triangle, 1)), TensorElement("CG", triangle, 1, shape=(3, 2)))) + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 2), identity_pullback, H1))) def test_determinant0(A0): diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index e352d09e9..3cb73e898 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -1,15 +1,17 @@ import numpy -from ufl import (Cell, Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, VectorElement, as_tensor, - as_vector, dx, indices, triangle) -from ufl.algorithms.apply_function_pullbacks import apply_single_function_pullbacks +from ufl import Cell, Coefficient, FunctionSpace, Mesh, as_tensor, as_vector, dx, indices, triangle from ufl.algorithms.renumbering import renumber_indices from ufl.classes import Jacobian, JacobianDeterminant, JacobianInverse, ReferenceValue +from ufl.finiteelement import FiniteElement, MixedElement, SymmetricElement +from ufl.pullback import (contravariant_piola, covariant_piola, double_contravariant_piola, double_covariant_piola, + identity_pullback, l2_piola) +from ufl.sobolevspace import H1, L2, HCurl, HDiv, HDivDiv, HEin def check_single_function_pullback(g, mappings): expected = mappings[g] - actual = apply_single_function_pullbacks(ReferenceValue(g), g.ufl_element()) + actual = g.ufl_element().pullback.apply(ReferenceValue(g)) assert expected.ufl_shape == actual.ufl_shape for idx in numpy.ndindex(actual.ufl_shape): rexp = renumber_indices(expected[idx]) @@ -33,30 +35,34 @@ def check_single_function_pullback(g, mappings): def test_apply_single_function_pullbacks_triangle3d(): triangle3d = Cell("triangle", geometric_dimension=3) cell = triangle3d - domain = Mesh(VectorElement("Lagrange", cell, 1)) - - UL2 = FiniteElement("DG L2", cell, 1) - U0 = FiniteElement("DG", cell, 0) - U = FiniteElement("CG", cell, 1) - V = VectorElement("CG", cell, 1) - Vd = FiniteElement("RT", cell, 1) - Vc = FiniteElement("N1curl", cell, 1) - T = TensorElement("CG", cell, 1) - S = TensorElement("CG", cell, 1, symmetry=True) - COV2T = FiniteElement("Regge", cell, 0) # (0, 2)-symmetric tensors - CONTRA2T = FiniteElement("HHJ", cell, 0) # (2, 0)-symmetric tensors - - Uml2 = UL2*UL2 - Um = U*U - Vm = U*V - Vdm = V*Vd - Vcm = Vd*Vc - Tm = Vc*T - Sm = T*S - - Vd0 = Vd*U0 # case from failing ffc demo - - W = S*T*Vc*Vd*V*U + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + + UL2 = FiniteElement("Discontinuous Lagrange", cell, 1, (), l2_piola, L2) + U0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) + U = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) + Vd = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) + Vc = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + T = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) + S = SymmetricElement( + {(0, 0): 0, (1, 0): 1, (2, 0): 2, (0, 1): 1, (1, 1): 3, (2, 1): 4, (0, 2): 2, (1, 2): 4, (2, 2): 5}, + [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)]) + # (0, 2)-symmetric tensors + COV2T = FiniteElement("Regge", cell, 0, (2, 2), double_covariant_piola, HEin) + # (2, 0)-symmetric tensors + CONTRA2T = FiniteElement("HHJ", cell, 0, (2, 2), double_contravariant_piola, HDivDiv) + + Uml2 = MixedElement([UL2, UL2]) + Um = MixedElement([U, U]) + Vm = MixedElement([U, V]) + Vdm = MixedElement([V, Vd]) + Vcm = MixedElement([Vd, Vc]) + Tm = MixedElement([Vc, T]) + Sm = MixedElement([T, S]) + + Vd0 = MixedElement([Vd, U0]) # case from failing ffc demo + + W = MixedElement([S, T, Vc, Vd, V, U]) ul2 = Coefficient(FunctionSpace(domain, UL2)) u = Coefficient(FunctionSpace(domain, U)) @@ -229,25 +235,26 @@ def test_apply_single_function_pullbacks_triangle3d(): def test_apply_single_function_pullbacks_triangle(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) - - Ul2 = FiniteElement("DG L2", cell, 1) - U = FiniteElement("CG", cell, 1) - V = VectorElement("CG", cell, 1) - Vd = FiniteElement("RT", cell, 1) - Vc = FiniteElement("N1curl", cell, 1) - T = TensorElement("CG", cell, 1) - S = TensorElement("CG", cell, 1, symmetry=True) - - Uml2 = Ul2*Ul2 - Um = U*U - Vm = U*V - Vdm = V*Vd - Vcm = Vd*Vc - Tm = Vc*T - Sm = T*S - - W = S*T*Vc*Vd*V*U + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + + Ul2 = FiniteElement("Discontinuous Lagrange", cell, 1, (), l2_piola, L2) + U = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + Vd = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) + Vc = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + T = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) + S = SymmetricElement({(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, [ + FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for i in range(3)]) + + Uml2 = MixedElement([Ul2, Ul2]) + Um = MixedElement([U, U]) + Vm = MixedElement([U, V]) + Vdm = MixedElement([V, Vd]) + Vcm = MixedElement([Vd, Vc]) + Tm = MixedElement([Vc, T]) + Sm = MixedElement([T, S]) + + W = MixedElement([S, T, Vc, Vd, V, U]) ul2 = Coefficient(FunctionSpace(domain, Ul2)) u = Coefficient(FunctionSpace(domain, U)) diff --git a/test/test_apply_restrictions.py b/test/test_apply_restrictions.py index 860b76d48..efb9ad514 100755 --- a/test/test_apply_restrictions.py +++ b/test/test_apply_restrictions.py @@ -1,18 +1,20 @@ from pytest import raises -from ufl import (Coefficient, FacetNormal, FiniteElement, FunctionSpace, Mesh, SpatialCoordinate, VectorElement, - as_tensor, grad, i, triangle) +from ufl import Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, as_tensor, grad, i, triangle from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions from ufl.algorithms.renumbering import renumber_indices +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 def test_apply_restrictions(): cell = triangle - V0 = FiniteElement("DG", cell, 0) - V1 = FiniteElement("Lagrange", cell, 1) - V2 = FiniteElement("Lagrange", cell, 2) + V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) + V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V2 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index f32e7bcfa..858f9ddd6 100755 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -1,6 +1,9 @@ -from ufl import (Identity, Mesh, SpatialCoordinate, VectorElement, as_matrix, as_ufl, as_vector, elem_div, elem_mult, - elem_op, sin, tetrahedron, triangle) +from ufl import (Identity, Mesh, SpatialCoordinate, as_matrix, as_ufl, as_vector, elem_div, elem_mult, elem_op, sin, + tetrahedron, triangle) from ufl.classes import ComplexValue, Division, FloatValue, IntValue +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_scalar_casting(self): @@ -16,13 +19,13 @@ def test_scalar_casting(self): def test_ufl_float_division(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) d = SpatialCoordinate(domain)[0] / 10.0 # TODO: Use mock instead of x self.assertIsInstance(d, Division) def test_float_ufl_division(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) d = 3.14 / SpatialCoordinate(domain)[0] # TODO: Use mock instead of x self.assertIsInstance(d, Division) @@ -65,7 +68,7 @@ def test_elem_mult(self): def test_elem_mult_on_matrices(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) A = as_matrix(((1, 2), (3, 4))) B = as_matrix(((4, 5), (6, 7))) @@ -83,7 +86,7 @@ def test_elem_mult_on_matrices(self): def test_elem_div(self): - domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) B = as_matrix(((7, 8, 9), (z, x, y))) @@ -91,7 +94,7 @@ def test_elem_div(self): def test_elem_op(self): - domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) self.assertEqual(elem_op(sin, A), as_matrix(((sin(x), sin(y), sin(z)), diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index f8b2e840b..37bfd02f1 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -8,24 +8,27 @@ import pytest from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, - MaxCellEdgeLength, MaxFacetEdgeLength, Mesh, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, - PermutationSymbol, SpatialCoordinate, TensorElement, VectorElement, acos, as_matrix, as_tensor, as_ufl, - as_vector, asin, atan, bessel_I, bessel_J, bessel_K, bessel_Y, cofac, conditional, cos, cross, - derivative, det, dev, diff, dot, eq, erf, exp, ge, grad, gt, indices, inner, interval, inv, le, ln, lt, - ne, outer, replace, sin, skew, sqrt, sym, tan, tetrahedron, tr, triangle, variable) + FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, + MaxFacetEdgeLength, Mesh, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, PermutationSymbol, + SpatialCoordinate, acos, as_matrix, as_tensor, as_ufl, as_vector, asin, atan, bessel_I, bessel_J, + bessel_K, bessel_Y, cofac, conditional, cos, cross, derivative, det, dev, diff, dot, eq, erf, exp, ge, + grad, gt, indices, inner, interval, inv, le, ln, lt, ne, outer, replace, sin, skew, sqrt, sym, tan, + tetrahedron, tr, triangle, variable) from ufl.algorithms import expand_derivatives from ufl.conditional import Conditional from ufl.corealg.traversal import unique_post_traversal +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 class ExpressionCollection(object): def __init__(self, cell): self.cell = cell - domain = Mesh(VectorElement("Lagrange", cell, 1)) - d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + x = SpatialCoordinate(domain) n = FacetNormal(domain) c = CellVolume(domain) @@ -45,9 +48,9 @@ def __init__(self, cell): ident = Identity(d) eps = PermutationSymbol(d) - U = FiniteElement("U", cell, None) - V = VectorElement("U", cell, None) - W = TensorElement("U", cell, None) + U = FiniteElement("Undefined", cell, None, (), identity_pullback, L2) + V = FiniteElement("Undefined", cell, None, (d, ), identity_pullback, L2) + W = FiniteElement("Undefined", cell, None, (d, d), identity_pullback, L2) u_space = FunctionSpace(domain, U) v_space = FunctionSpace(domain, V) diff --git a/test/test_book_snippets.py b/test/test_book_snippets.py deleted file mode 100755 index ed1088405..000000000 --- a/test/test_book_snippets.py +++ /dev/null @@ -1,547 +0,0 @@ -"""Test book snippets. - -This file contains snippets from the FEniCS book, -and allows us to test that these can still run -with future versions of UFL. Please don't change -these and please do keep UFL compatible with these -snippets as long as possible. -""" - -from ufl import (Argument, Cell, Coefficient, Coefficients, Constant, Dx, FiniteElement, Identity, Index, MixedElement, - SpatialCoordinate, TensorConstant, TensorElement, TestFunction, TestFunctions, TrialFunction, - TrialFunctions, VectorConstant, VectorElement, action, adjoint, as_matrix, as_tensor, as_ufl, - conditional, cos, derivative, diff, dot, ds, dx, exp, grad, i, indices, inner, j, k, lt, outer, pi, - replace, sensitivity_rhs, sin, split, system, tetrahedron, tr, triangle, variable) -from ufl.algorithms import (Transformer, compute_form_data, expand_compounds, expand_derivatives, expand_indices, - post_traversal, tree_format) -from ufl.corealg.multifunction import MultiFunction - - -def test_uflcode_269(self): - # Finite element spaces - cell = tetrahedron - element = VectorElement("Lagrange", cell, 1) - - # Form arguments - phi0 = TestFunction(element) - phi1 = TrialFunction(element) - u = Coefficient(element) - c1 = Constant(cell) - c2 = Constant(cell) - - # Deformation gradient Fij = dXi/dxj - ident = Identity(cell.geometric_dimension()) - F = ident + grad(u) - - # Right Cauchy-Green strain tensor C with invariants - C = variable(F.T*F) - I_C = tr(C) - II_C = (I_C**2 - tr(C*C))/2 - - # Mooney-Rivlin constitutive law - W = c1*(I_C-3) + c2*(II_C-3) - - # Second Piola-Kirchoff stress tensor - S = 2*diff(W, C) - - # Weak forms - L = inner(F*S, grad(phi0))*dx - derivative(L, u, phi1) - - -def test_uflcode_316(self): - shapestring = 'triangle' - cell = Cell(shapestring) # noqa: F841 - - -def test_uflcode_323(self): - cell = tetrahedron # noqa: F841 - - -def test_uflcode_356(self): - cell = tetrahedron - - P = FiniteElement("Lagrange", cell, 1) - V = VectorElement("Lagrange", cell, 2) - T = TensorElement("DG", cell, 0, symmetry=True) - - TH = V*P # noqa: F841 - ME = MixedElement(T, V, P) # noqa: F841 - - -def test_uflcode_400(self): - V = FiniteElement("CG", triangle, 1) - f = Coefficient(V) - g = Coefficient(V) - h = Coefficient(V) - w = Coefficient(V) - v = TestFunction(V) - u = TrialFunction(V) - # ... - a = w*dot(grad(u), grad(v))*dx # noqa: F841 - L = f*v*dx + g**2*v*ds(0) + h*v*ds(1) # noqa: F841 - - -def test_uflcode_469(self): - V = FiniteElement("CG", triangle, 1) - f = Coefficient(V) - g = Coefficient(V) - h = Coefficient(V) - v = TestFunction(V) - # ... - dx02 = dx(0, {"integration_order": 2}) - dx14 = dx(1, {"integration_order": 4}) - dx12 = dx(1, {"integration_order": 2}) - L = f*v*dx02 + g*v*dx14 + h*v*dx12 # noqa: F841 - - -def test_uflcode_552(self): - element = FiniteElement("CG", triangle, 1) - # ... - phi = Argument(element, 2) # noqa: F841 - v = TestFunction(element) # noqa: F841 - u = TrialFunction(element) # noqa: F841 - - -def test_uflcode_563(self): - cell = triangle - element = FiniteElement("CG", cell, 1) - # ... - w = Coefficient(element) # noqa: F841 - c = Constant(cell) # noqa: F841 - v = VectorConstant(cell) # noqa: F841 - M = TensorConstant(cell) # noqa: F841 - - -def test_uflcode_574(self): - V0 = FiniteElement("CG", triangle, 1) - V1 = V0 - # ... - V = V0*V1 - u = Coefficient(V) - u0, u1 = split(u) - - -def test_uflcode_582(self): - V0 = FiniteElement("CG", triangle, 1) - V1 = V0 - # ... - V = V0*V1 - u = Coefficient(V) - u0, u1 = split(u) - # ... - v0, v1 = TestFunctions(V) - u0, u1 = TrialFunctions(V) - f0, f1 = Coefficients(V) - - -def test_uflcode_644(self): - V = VectorElement("CG", triangle, 1) - u = Coefficient(V) - v = Coefficient(V) - # ... - A = outer(u, v) - Aij = A[i, j] # noqa: F841 - - -def test_uflcode_651(self): - V = VectorElement("CG", triangle, 1) - u = Coefficient(V) - v = Coefficient(V) - # ... - Aij = v[j]*u[i] - A = as_tensor(Aij, (i, j)) # noqa: F841 - - -def test_uflcode_671(self): - i = Index() # noqa: F841 - j, k, l = indices(3) # noqa: F841, E741 - - -def test_uflcode_684(self): - V = VectorElement("CG", triangle, 1) - v = Coefficient(V) - # ... - th = pi/2 - A = as_matrix([[cos(th), -sin(th)], - [sin(th), cos(th)]]) - u = A*v # noqa: F841 - - -def test_uflcode_824(self): - V = VectorElement("CG", triangle, 1) - f = Coefficient(V) - # ... - df = Dx(f, i) - df = f.dx(i) # noqa: F841 - - -def test_uflcode_886(self): - cell = triangle - # ... - x = SpatialCoordinate(cell) # Original: x = cell.x - g = sin(x[0]) - v = variable(g) - f = exp(v**2) - h = diff(f, v) # noqa: F841 - # ... - # print v - # print h - - -def test_uflcode_930(self): - condition = lt(1, 0) - true_value = 1 - false_value = 0 - # ... - f = conditional(condition, true_value, false_value) # noqa: F841 - - -def test_uflcode_1026(self): - element = FiniteElement("CG", triangle, 1) - # ... - v = TestFunction(element) - u = TrialFunction(element) - w = Coefficient(element) - f = 0.5*w**2*dx - F = derivative(f, w, v) - J = derivative(F, w, u) # noqa: F841 - - -def test_uflcode_1050(self): - Vx = VectorElement("Lagrange", triangle, 1) - Vy = FiniteElement("Lagrange", triangle, 1) - u = Coefficient(Vx*Vy) - x, y = split(u) - f = inner(grad(x), grad(x))*dx + y*dot(x, x)*dx - F = derivative(f, u) - J = derivative(F, u) # noqa: F841 - - -def test_uflcode_1085(self): - cell = triangle - # ... - V = VectorElement("Lagrange", cell, 1) - T = TensorElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - M = Coefficient(T) - a = M[i, j]*u[k].dx(j)*v[k].dx(i)*dx - astar = adjoint(a) # noqa: F841 - - -def test_uflcode_1120(self): - cell = triangle - # ... - V = FiniteElement("Lagrange", cell, 1) - v = TestFunction(V) - f = Coefficient(V) - g = Coefficient(V) - L = f**2 / (2*g)*v*dx - L2 = replace(L, {f: g, g: 3}) # noqa: F841 - L3 = g**2 / 6*v*dx # noqa: F841 - - -def test_uflcode_1157(self): - cell = triangle - # ... - V = FiniteElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - f = Coefficient(V) - pde = u*v*dx - f*v*dx - a, L = system(pde) - - -def test_uflcode_1190(self): - cell = triangle - element = FiniteElement("Lagrange", cell, 1) - V = element - u = TrialFunction(V) - v = TestFunction(V) - f = Coefficient(V) - c = variable(Coefficient(V)) - pde = c*u*v*dx - c*f*v*dx - a, L = system(pde) - # ... - u = Coefficient(element) - sL = diff(L, c) - action(diff(a, c), u) # noqa: F841 - - -def test_uflcode_1195(self): - cell = triangle - element = FiniteElement("Lagrange", cell, 1) - V = element - u = TrialFunction(V) - v = TestFunction(V) - f = Coefficient(V) - c = variable(Coefficient(V)) - pde = c*u*v*dx - c*f*v*dx - a, L = system(pde) - u = Coefficient(element) - # ... - sL = sensitivity_rhs(a, u, L, c) # noqa: F841 - - -def test_uflcode_1365(self): - e = 0 - v = variable(e) - f = sin(v) - g = diff(f, v) # noqa: F841 - - -def test_python_1446(self): - cell = triangle - V = FiniteElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - c = Constant(cell) - f = Coefficient(V) - e = c*f**2*u*v # noqa: F841 - - # The linearized Graph functionality has been removed from UFL: - # from ufl.algorithms import Graph, partition - # G = Graph(e) - # V, E, = G - - # print(("str(e) = %s\n" % str(e))) - # print(("\n".join("V[%d] = %s" % (i, v) for (i, v) in enumerate(V)), "\n")) - # print(("\n".join("E[%d] = %s" % (i, e) for (i, e) in enumerate(E)), "\n")) - - -def test_python_1512(self): - cell = triangle - V = FiniteElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - c = Constant(cell) - f = Coefficient(V) - e = c*f**2*u*v # noqa: F841 - - # The linearized Graph functionality has been removed from UFL: - # from ufl.algorithms import Graph, partition - # G = Graph(e) - # V, E, = G - # ... - # Vin = G.Vin() - # Vout = G.Vout() - - -def test_python_1557(self): - cell = triangle - V = FiniteElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - c = Constant(cell) - f = Coefficient(V) - e = c*f**2*u*v # noqa: F841 - - # The linearized Graph functionality has been removed from UFL: - # from ufl.algorithms import Graph, partition - # G = Graph(e) - # V, E, = G - # ... - # partitions, keys = partition(G) - # for deps in sorted(partitions.keys()): - # P = partitions[deps] - # print "The following depends on", tuple(deps) - # for i in sorted(P): - # print "V[%d] = %s" % (i, V[i]) - # ... - # v = V[i] - - -def test_uflcode_1901(self): - cell = triangle - element = FiniteElement("Lagrange", cell, 1) - # ... - v = Argument(element, 2) # noqa: F841 - w = Coefficient(element) # noqa: F841 - - -def test_python_1942(self): - def walk(expression, pre_action, post_action): - pre_action(expression) - for o in expression.ufl_operands: - walk(o) - post_action(expression) - - -def test_python_1955(self): - def post_traversal(root): - for o in root.ufl_operands: - yield post_traversal(o) - yield root - - -def test_python_1963(self): - def post_action(e): - # print str(e) - pass - cell = triangle - V = FiniteElement("Lagrange", cell, 1) - u = TrialFunction(V) - v = TestFunction(V) - c = Constant(cell) - f = Coefficient(V) - expression = c*f**2*u*v - # ... - for e in post_traversal(expression): - post_action(e) - - -def test_python_1990(self): - expression = as_ufl(3) - - def int_operation(x): - return 7 - - result = int_operation(expression) - self.assertTrue(result == 7) - - -def test_python_2024(self): - class ExampleFunction(MultiFunction): - - def __init__(self): - MultiFunction.__init__(self) - - def terminal(self, expression): - return "Got a Terminal subtype %s." % type(expression) - - def operator(self, expression): - return "Got an Operator subtype %s." % type(expression) - - def argument(self, expression): - return "Got an Argument." - - def sum(self, expression): - return "Got a Sum." - - m = ExampleFunction() - - cell = triangle - element = FiniteElement("Lagrange", cell, 1) - x = SpatialCoordinate(cell) # Original: x = cell.x - if 0: - print((m(Argument(element, 2)))) - print((m(x))) - print((m(x[0] + x[1]))) - print((m(x[0] * x[1]))) - - -def test_python_2066(self): - def apply(e, multifunction): - ops = [apply(o, multifunction) for o in e.ufl_operands] - return multifunction(e, *ops) - - -def test_python_2087(self): - class Replacer(Transformer): - - def __init__(self, mapping): - Transformer.__init__(self) - self.mapping = mapping - - def operator(self, e, *ops): - return e._ufl_expr_reconstruct_(*ops) - - def terminal(self, e): - return self.mapping.get(e, e) - - f = Constant(triangle) - r = Replacer({f: f**2}) - g = r.visit(2*f) # noqa: F841 - - -def test_python_2189(self): - V = FiniteElement("Lagrange", triangle, 1) - u = TestFunction(V) - v = TrialFunction(V) - f = Coefficient(V) - - # Note no *dx! This is an expression, not a form. - a = dot(grad(f*u), grad(v)) - - ac = expand_compounds(a) - ad = expand_derivatives(ac) - ai = expand_indices(ad) - - af = tree_format(a) # noqa: F841 - acf = tree_format(ac) # noqa: F841 - adf = "\n", tree_format(ad) # noqa: F841 - aif = tree_format(ai) # noqa: F841 - - if 0: - print(("\na: ", str(a), "\n", tree_format(a))) - print(("\nac:", str(ac), "\n", tree_format(ac))) - print(("\nad:", str(ad), "\n", tree_format(ad))) - print(("\nai:", str(ai), "\n", tree_format(ai))) - - -def test_python_2328(self): - cell = triangle - x = SpatialCoordinate(cell) # Original: x = cell.x - e = x[0] + x[1] - # print e((0.5, 0.7)) # prints 1.2 - # ... - self.assertEqual(e((0.5, 0.7)), 1.2) - - -def test_python_2338(self): - cell = triangle - x = SpatialCoordinate(cell) # Original: x = cell.x - # ... - c = Constant(cell) - e = c*(x[0] + x[1]) - # print e((0.5, 0.7), { c: 10 }) # prints 12.0 - # ... - self.assertEqual(e((0.5, 0.7), {c: 10}), 12.0) - - -def test_python_2349(self): - element = VectorElement("Lagrange", triangle, 1) - c = Constant(triangle) - f = Coefficient(element) - e = c*(f[0] + f[1]) - - def fh(x): - return (x[0], x[1]) - # print e((0.5, 0.7), { c: 10, f: fh }) # prints 12.0 - # ... - self.assertEqual(e((0.5, 0.7), {c: 10, f: fh}), 12.0) - - -def test_python_2364(self): - element = FiniteElement("Lagrange", triangle, 1) - g = Coefficient(element) - e = g**2 + g.dx(0)**2 + g.dx(1)**2 - - def gh(x, der=()): - if der == (): - return x[0]*x[1] - if der == (0,): - return x[1] - if der == (1,): - return x[0] - # print e((2, 3), { g: gh }) # prints 49 - # ... - self.assertEqual(e((2, 3), {g: gh}), 49) - - -def test_python_2462(self): - cell = triangle - element = FiniteElement("Lagrange", cell, 1) - V = element - u = TrialFunction(V) - v = TestFunction(V) - f = Coefficient(V) - c = variable(Coefficient(V)) - pde = c*u*v*dx - c*f*v*dx - a, L = system(pde) - u = Coefficient(element) - myform = a - # ... - # print repr(preprocess(myform).preprocessed_form) - # ... - r = repr(compute_form_data(myform).preprocessed_form) # noqa: F841 diff --git a/test/test_change_to_local.py b/test/test_change_to_local.py index 80bdb2101..f0789fbba 100755 --- a/test/test_change_to_local.py +++ b/test/test_change_to_local.py @@ -1,16 +1,19 @@ """Tests of the change to local representaiton algorithms.""" -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, as_tensor, grad, indices, triangle +from ufl import Coefficient, FunctionSpace, Mesh, as_tensor, grad, indices, triangle from ufl.algorithms import change_to_reference_grad from ufl.algorithms.renumbering import renumber_indices from ufl.classes import JacobianInverse, ReferenceGrad +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_change_to_reference_grad(): cell = triangle - domain = Mesh(cell) - U = FunctionSpace(domain, FiniteElement("CG", cell, 1)) - V = FunctionSpace(domain, VectorElement("CG", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + U = FunctionSpace(domain, FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1)) + V = FunctionSpace(domain, FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) u = Coefficient(U) v = Coefficient(V) Jinv = JacobianInverse(domain) diff --git a/test/test_change_to_reference_frame.py b/test/test_change_to_reference_frame.py index a7346b195..9b46b510c 100755 --- a/test/test_change_to_reference_frame.py +++ b/test/test_change_to_reference_frame.py @@ -1,7 +1,10 @@ """Tests of the change to reference frame algorithm.""" -from ufl import Coefficient, FiniteElement, FunctionSpace, Mesh, TensorElement, VectorElement, triangle +from ufl import Coefficient, FunctionSpace, Mesh, triangle from ufl.classes import Expr, ReferenceValue +from ufl.finiteelement import FiniteElement +from ufl.pullback import contravariant_piola, identity_pullback +from ufl.sobolevspace import H1, HDiv def change_to_reference_frame(expr): @@ -10,11 +13,11 @@ def change_to_reference_frame(expr): def test_change_unmapped_form_arguments_to_reference_frame(): - U = FiniteElement("CG", triangle, 1) - V = VectorElement("CG", triangle, 1) - T = TensorElement("CG", triangle, 1) + U = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + T = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) u_space = FunctionSpace(domain, U) v_space = FunctionSpace(domain, V) t_space = FunctionSpace(domain, T) @@ -28,9 +31,9 @@ def test_change_unmapped_form_arguments_to_reference_frame(): def test_change_hdiv_form_arguments_to_reference_frame(): - V = FiniteElement("RT", triangle, 1) + V = FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) expr = Coefficient(v_space) @@ -38,9 +41,9 @@ def test_change_hdiv_form_arguments_to_reference_frame(): def test_change_hcurl_form_arguments_to_reference_frame(): - V = FiniteElement("RT", triangle, 1) + V = FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) expr = Coefficient(v_space) diff --git a/test/test_check_arities.py b/test/test_check_arities.py index 0d5521ac2..37ce7a26d 100755 --- a/test/test_check_arities.py +++ b/test/test_check_arities.py @@ -1,16 +1,19 @@ import pytest -from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, TrialFunction, - VectorElement, adjoint, cofac, conj, derivative, ds, dx, grad, inner, tetrahedron) +from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, TrialFunction, adjoint, + cofac, conj, derivative, ds, dx, grad, inner, tetrahedron) from ufl.algorithms.check_arities import ArityMismatch from ufl.algorithms.compute_form_data import compute_form_data +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_check_arities(): # Code from bitbucket issue #49 cell = tetrahedron - D = Mesh(cell) - V = FunctionSpace(D, VectorElement("P", cell, 2)) + D = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1)) dv = TestFunction(V) du = TrialFunction(V) @@ -33,8 +36,8 @@ def test_check_arities(): def test_complex_arities(): cell = tetrahedron - D = Mesh(cell) - V = FunctionSpace(D, VectorElement("P", cell, 2)) + D = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1)) v = TestFunction(V) u = TrialFunction(V) diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index 7b7d6f583..9a61261e3 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -4,18 +4,20 @@ import ufl from ufl import * # noqa: F403, F401 from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, - MaxFacetEdgeLength, Mesh, MinFacetEdgeLength, MixedElement, Not, Or, PermutationSymbol, - SpatialCoordinate, TensorConstant, TensorElement, VectorConstant, VectorElement, acos, action, - as_matrix, as_tensor, as_ufl, as_vector, asin, atan, cell_avg, cofac, conditional, cos, cosh, cross, - curl, derivative, det, dev, diff, div, dot, ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, i, inner, - inv, j, k, l, le, ln, lt, nabla_div, nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, tan, tanh, - tetrahedron, tr, transpose, triangle, variable) + FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, + MinFacetEdgeLength, Not, Or, PermutationSymbol, SpatialCoordinate, TensorConstant, VectorConstant, + acos, action, as_matrix, as_tensor, as_ufl, as_vector, asin, atan, cell_avg, cofac, conditional, cos, + cosh, cross, curl, derivative, det, dev, diff, div, dot, ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, + i, inner, inv, j, k, l, le, ln, lt, nabla_div, nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, + tan, tanh, tetrahedron, tr, transpose, triangle, variable) from ufl.algorithms import * # noqa: F403, F401 from ufl.classes import * # noqa: F403, F401 from ufl.classes import (Acos, Asin, Atan, CellCoordinate, Cos, Cosh, Exp, Expr, FacetJacobian, FacetJacobianDeterminant, FacetJacobianInverse, FloatValue, IntValue, Ln, Outer, Sin, Sinh, Sqrt, Tan, Tanh, all_ufl_classes) +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 has_repr = set() has_dict = set() @@ -106,15 +108,15 @@ def testAll(self): cell = triangle dim = cell.geometric_dimension() - e0 = FiniteElement("CG", cell, 1) - e1 = VectorElement("CG", cell, 1) - e2 = TensorElement("CG", cell, 1) - e3 = MixedElement(e0, e1, e2) + e0 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + e1 = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + e2 = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) + e3 = MixedElement([e0, e1, e2]) - e13D = VectorElement("CG", tetrahedron, 1) + e13D = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) - domain3D = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (dim, ), identity_pullback, H1)) + domain3D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) e0_space = FunctionSpace(domain, e0) e1_space = FunctionSpace(domain, e1) e2_space = FunctionSpace(domain, e2) @@ -134,7 +136,7 @@ def testAll(self): _test_object(v0, (), ()) _test_object(v1, (dim,), ()) _test_object(v2, (dim, dim), ()) - _test_object(v3, (dim*dim+dim+1,), ()) + _test_object(v3, (1 + dim + dim ** 2, ), ()) f0 = Coefficient(e0_space) f1 = Coefficient(e1_space) @@ -144,7 +146,7 @@ def testAll(self): _test_object(f0, (), ()) _test_object(f1, (dim,), ()) _test_object(f2, (dim, dim), ()) - _test_object(f3, (dim*dim+dim+1,), ()) + _test_object(f3, (1 + dim + dim ** 2, ), ()) c = Constant(domain) _test_object(c, (), ()) @@ -227,7 +229,7 @@ def testAll(self): a = variable(v2) _test_object(a, (dim, dim), ()) a = variable(v3) - _test_object(a, (dim*dim+dim+1,), ()) + _test_object(a, (1 + dim + dim ** 2, ), ()) a = variable(f0) _test_object(a, (), ()) a = variable(f1) @@ -235,7 +237,7 @@ def testAll(self): a = variable(f2) _test_object(a, (dim, dim), ()) a = variable(f3) - _test_object(a, (dim*dim+dim+1,), ()) + _test_object(a, (1 + dim + dim ** 2, ), ()) # a = MultiIndex() diff --git a/test/test_complex.py b/test/test_complex.py index 999485ef1..6f3eda840 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -2,9 +2,9 @@ import pytest -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorElement, as_tensor, - as_ufl, atan, conditional, conj, cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, - max_value, min_value, outer, real, sin, sqrt, triangle) +from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, as_tensor, as_ufl, atan, conditional, + conj, cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, max_value, min_value, outer, + real, sin, sqrt, triangle) from ufl.algebra import Conj, Imag, Real from ufl.algorithms import estimate_total_polynomial_degree from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering @@ -12,6 +12,9 @@ from ufl.algorithms.formtransformations import compute_form_adjoint from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.constantvalue import ComplexValue, Zero +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_conj(self): @@ -45,8 +48,8 @@ def test_imag(self): def test_compute_form_adjoint(self): cell = triangle - element = FiniteElement('Lagrange', cell, 1) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement('Lagrange', cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -74,8 +77,8 @@ def test_complex_algebra(self): def test_automatic_simplification(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -88,8 +91,8 @@ def test_automatic_simplification(self): def test_apply_algebra_lowering_complex(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -117,8 +120,8 @@ def test_apply_algebra_lowering_complex(self): def test_remove_complex_nodes(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -140,8 +143,8 @@ def test_remove_complex_nodes(self): def test_comparison_checker(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -168,8 +171,8 @@ def test_comparison_checker(self): def test_complex_degree_handling(self): cell = triangle - element = FiniteElement("Lagrange", cell, 3) - domain = Mesh(VectorElement('Lagrange', cell, 1)) + element = FiniteElement("Lagrange", cell, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/test/test_conditionals.py b/test/test_conditionals.py index e16fb1fc1..7b60054d5 100755 --- a/test/test_conditionals.py +++ b/test/test_conditionals.py @@ -3,23 +3,25 @@ import pytest -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement, conditional, eq, ge, gt, le, lt, ne, - triangle) +from ufl import Coefficient, FunctionSpace, Mesh, conditional, eq, ge, gt, le, lt, ne, triangle from ufl.classes import EQ, GE, GT, LE, LT, NE +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def f(): - element = FiniteElement("Lagrange", triangle, 1) - domain = Mesh(VectorElement('Lagrange', triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) return Coefficient(space) @pytest.fixture def g(): - element = FiniteElement("Lagrange", triangle, 1) - domain = Mesh(VectorElement('Lagrange', triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) return Coefficient(space) diff --git a/test/test_degree_estimation.py b/test/test_degree_estimation.py index e0d6c05cd..dbadffef6 100755 --- a/test/test_degree_estimation.py +++ b/test/test_degree_estimation.py @@ -1,36 +1,30 @@ __authors__ = "Martin Sandve Alnæs" __date__ = "2008-03-12 -- 2009-01-28" - -from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FiniteElement, FunctionSpace, Mesh, - SpatialCoordinate, TensorProductElement, VectorElement, cos, div, dot, grad, i, inner, nabla_div, - nabla_grad, sin, tan, triangle) +from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, cos, div, + dot, grad, i, inner, nabla_div, nabla_grad, sin, tan, triangle) from ufl.algorithms import estimate_total_polynomial_degree +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_total_degree_estimation(): - V1 = FiniteElement("CG", triangle, 1) - V2 = FiniteElement("CG", triangle, 2) - VV = VectorElement("CG", triangle, 3) - VM = V1 * V2 - O1 = TensorProductElement(V1, V1) - O2 = TensorProductElement(V2, V1) + V1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + V2 = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) + VV = FiniteElement("Lagrange", triangle, 3, (2, ), identity_pullback, H1) + VM = MixedElement([V1, V2]) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) - tensor_domain = Mesh(VectorElement("Lagrange", O1.cell(), 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) vv_space = FunctionSpace(domain, VV) vm_space = FunctionSpace(domain, VM) - o1_space = FunctionSpace(tensor_domain, O1) - o2_space = FunctionSpace(tensor_domain, O2) v1 = Argument(v1_space, 2) v2 = Argument(v2_space, 3) f1, f2 = Coefficients(vm_space) - u1 = Coefficient(o1_space) - u2 = Coefficient(o2_space) vv = Argument(vv_space, 4) vu = Argument(vv_space, 5) @@ -81,18 +75,6 @@ def test_total_degree_estimation(): assert estimate_total_polynomial_degree(f2 ** 3 * v1) == 7 assert estimate_total_polynomial_degree(f2 ** 3 * v1 + f1 * v1) == 7 - # outer product tuple-degree tests - assert estimate_total_polynomial_degree(u1) == (1, 1) - assert estimate_total_polynomial_degree(u2) == (2, 1) - # derivatives should do nothing (don't know in which direction they act) - assert estimate_total_polynomial_degree(grad(u2)) == (2, 1) - assert estimate_total_polynomial_degree(u1 * u1) == (2, 2) - assert estimate_total_polynomial_degree(u2 * u1) == (3, 2) - assert estimate_total_polynomial_degree(u2 * u2) == (4, 2) - assert estimate_total_polynomial_degree(u1 ** 3) == (3, 3) - assert estimate_total_polynomial_degree(u1 ** 3 + u2 * u2) == (4, 3) - assert estimate_total_polynomial_degree(u2 ** 2 * u1) == (5, 3) - # Math functions of constant values are constant values nx, ny = FacetNormal(domain) e = nx ** 2 @@ -115,9 +97,9 @@ def test_some_compound_types(): etpd = estimate_total_polynomial_degree - P2 = FiniteElement("CG", triangle, 2) - V2 = VectorElement("CG", triangle, 2) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + P2 = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) + V2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) u = Coefficient(FunctionSpace(domain, P2)) v = Coefficient(FunctionSpace(domain, V2)) diff --git a/test/test_derivative.py b/test/test_derivative.py index 4b1972f27..63528d97f 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -3,12 +3,11 @@ from itertools import chain -from ufl import (CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, FiniteElement, - FunctionSpace, Identity, Index, Jacobian, JacobianInverse, Mesh, SpatialCoordinate, TensorElement, - TestFunction, TrialFunction, VectorElement, acos, as_matrix, as_tensor, as_vector, asin, atan, - conditional, cos, derivative, diff, dot, dx, exp, i, indices, inner, interval, j, k, ln, lt, - nabla_grad, outer, quadrilateral, replace, sign, sin, split, sqrt, tan, tetrahedron, triangle, - variable, zero) +from ufl import (CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, FunctionSpace, + Identity, Index, Jacobian, JacobianInverse, Mesh, SpatialCoordinate, TestFunction, TrialFunction, acos, + as_matrix, as_tensor, as_vector, asin, atan, conditional, cos, derivative, diff, dot, dx, exp, i, + indices, inner, interval, j, k, ln, lt, nabla_grad, outer, quadrilateral, replace, sign, sin, split, + sqrt, tan, tetrahedron, triangle, variable, zero) from ufl.algorithms import compute_form_data, expand_indices, strip_variables from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives @@ -16,6 +15,9 @@ from ufl.classes import Indexed, MultiIndex, ReferenceGrad from ufl.constantvalue import as_ufl from ufl.domain import extract_unique_domain +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 def assertEqualBySampling(actual, expected): @@ -72,8 +74,8 @@ def make_value(c): def _test(self, f, df): cell = triangle - element = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -103,175 +105,242 @@ def _test(self, f, df): def testScalarLiteral(self): - def f(w): return as_ufl(1) + def f(w): + return as_ufl(1) + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) def testIdentityLiteral(self): - def f(w): return Identity(2)[i, i] + def f(w): + return Identity(2)[i, i] + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) # --- Form arguments def testCoefficient(self): - def f(w): return w + def f(w): + return w + + def df(w, v): + return v - def df(w, v): return v _test(self, f, df) def testArgument(self): - def f(w): return TestFunction(FunctionSpace(Mesh(VectorElement("Lagrange", triangle, 1)), - FiniteElement("CG", triangle, 1))) + def f(w): + return TestFunction(FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) - def df(w, v): return zero() + def df(w, v): + return zero() _test(self, f, df) # --- Geometry def testSpatialCoordinate(self): - def f(w): return SpatialCoordinate(Mesh(VectorElement("Lagrange", triangle, 1)))[0] + def f(w): + return SpatialCoordinate( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)))[0] + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) def testFacetNormal(self): - def f(w): return FacetNormal(Mesh(VectorElement("Lagrange", triangle, 1)))[0] + def f(w): + return FacetNormal( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)))[0] - def df(w, v): return zero() - _test(self, f, df) + def df(w, v): + return zero() -# def testCellSurfaceArea(self): -# def f(w): return CellSurfaceArea(Mesh(VectorElement("Lagrange", triangle, 1))) -# def df(w, v): return zero() -# _test(self, f, df) + _test(self, f, df) def testFacetArea(self): - def f(w): return FacetArea(Mesh(VectorElement("Lagrange", triangle, 1))) + def f(w): + return FacetArea( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) def testCellDiameter(self): - def f(w): return CellDiameter(Mesh(VectorElement("Lagrange", triangle, 1))) + def f(w): + return CellDiameter( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) def testCircumradius(self): - def f(w): return Circumradius(Mesh(VectorElement("Lagrange", triangle, 1))) + def f(w): + return Circumradius(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) - def df(w, v): return zero() + def df(w, v): + return zero() _test(self, f, df) def testCellVolume(self): - def f(w): return CellVolume(Mesh(VectorElement("Lagrange", triangle, 1))) + def f(w): + return CellVolume(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + + def df(w, v): + return zero() - def df(w, v): return zero() _test(self, f, df) # --- Basic operators def testSum(self): - def f(w): return w + 1 + def f(w): + return w + 1 + + def df(w, v): + return v - def df(w, v): return v _test(self, f, df) def testProduct(self): - def f(w): return 3*w + def f(w): + return 3*w + + def df(w, v): + return 3*v - def df(w, v): return 3*v _test(self, f, df) def testPower(self): - def f(w): return w**3 + def f(w): + return w**3 + + def df(w, v): + return 3*w**2*v - def df(w, v): return 3*w**2*v _test(self, f, df) def testDivision(self): - def f(w): return w / 3.0 + def f(w): + return w / 3.0 + + def df(w, v): + return v / 3.0 - def df(w, v): return v / 3.0 _test(self, f, df) def testDivision2(self): - def f(w): return 3.0 / w + def f(w): + return 3.0 / w + + def df(w, v): + return -3.0 * v / w**2 - def df(w, v): return -3.0 * v / w**2 _test(self, f, df) def testExp(self): - def f(w): return exp(w) + def f(w): + return exp(w) + + def df(w, v): + return v*exp(w) - def df(w, v): return v*exp(w) _test(self, f, df) def testLn(self): - def f(w): return ln(w) + def f(w): + return ln(w) + + def df(w, v): + return v / w - def df(w, v): return v / w _test(self, f, df) def testCos(self): - def f(w): return cos(w) + def f(w): + return cos(w) + + def df(w, v): + return -v*sin(w) - def df(w, v): return -v*sin(w) _test(self, f, df) def testSin(self): - def f(w): return sin(w) + def f(w): + return sin(w) + + def df(w, v): + return v*cos(w) - def df(w, v): return v*cos(w) _test(self, f, df) def testTan(self): - def f(w): return tan(w) + def f(w): + return tan(w) + + def df(w, v): + return v*2.0/(cos(2.0*w) + 1.0) - def df(w, v): return v*2.0/(cos(2.0*w) + 1.0) _test(self, f, df) def testAcos(self): - def f(w): return acos(w/1000) + def f(w): + return acos(w/1000) + + def df(w, v): + return -(v/1000)/sqrt(1.0 - (w/1000)**2) - def df(w, v): return -(v/1000)/sqrt(1.0 - (w/1000)**2) _test(self, f, df) def testAsin(self): - def f(w): return asin(w/1000) + def f(w): + return asin(w/1000) + + def df(w, v): + return (v/1000)/sqrt(1.0 - (w/1000)**2) - def df(w, v): return (v/1000)/sqrt(1.0 - (w/1000)**2) _test(self, f, df) def testAtan(self): - def f(w): return atan(w) + def f(w): + return atan(w) + + def df(w, v): + return v/(1.0 + w**2) - def df(w, v): return v/(1.0 + w**2) _test(self, f, df) # FIXME: Add the new erf and bessel_* @@ -280,19 +349,26 @@ def df(w, v): return v/(1.0 + w**2) def testAbs(self): - def f(w): return abs(w) + def f(w): + return abs(w) + + def df(w, v): + return sign(w)*v - def df(w, v): return sign(w)*v _test(self, f, df) def testConditional(self): # This will fail without bugfix in derivative - def cond(w): return lt(w, 1.0) + def cond(w): + return lt(w, 1.0) - def f(w): return conditional(cond(w), 2*w, 3*w) + def f(w): + return conditional(cond(w), 2*w, 3*w) + + def df(w, v): + return (conditional(cond(w), 1, 0) * 2*v + + conditional(cond(w), 0, 1) * 3*v) - def df(w, v): return (conditional(cond(w), 1, 0) * 2*v + - conditional(cond(w), 0, 1) * 3*v) _test(self, f, df) # --- Tensor algebra basics @@ -306,7 +382,9 @@ def f(w): i, = indices(1) return a[i]*b[i] - def df(w, v): return 3*v + 4*2*w*v + 5*3*w**2*v + def df(w, v): + return 3*v + 4*2*w*v + 5*3*w**2*v + _test(self, f, df) @@ -336,8 +414,8 @@ def testListTensor(self): def test_single_scalar_coefficient_derivative(self): cell = triangle - V = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) @@ -348,8 +426,8 @@ def test_single_scalar_coefficient_derivative(self): def test_single_vector_coefficient_derivative(self): cell = triangle - V = VectorElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) @@ -361,10 +439,10 @@ def test_single_vector_coefficient_derivative(self): def test_multiple_coefficient_derivative(self): cell = triangle - V = FiniteElement("CG", cell, 1) - W = VectorElement("CG", cell, 1) - M = V*W - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + M = MixedElement([V, W]) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) m_space = FunctionSpace(domain, M) @@ -387,9 +465,9 @@ def test_multiple_coefficient_derivative(self): def test_indexed_coefficient_derivative(self): cell = triangle ident = Identity(cell.geometric_dimension()) - V = FiniteElement("CG", cell, 1) - W = VectorElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) u = Coefficient(w_space) @@ -409,10 +487,10 @@ def test_indexed_coefficient_derivative(self): def test_multiple_indexed_coefficient_derivative(self): cell = tetrahedron - V = FiniteElement("CG", cell, 1) - V2 = V*V - W = VectorElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V2 = MixedElement([V, V]) + W = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) v2_space = FunctionSpace(domain, V2) w_space = FunctionSpace(domain, W) u = Coefficient(w_space) @@ -428,10 +506,10 @@ def test_multiple_indexed_coefficient_derivative(self): def test_segregated_derivative_of_convection(self): cell = tetrahedron - V = FiniteElement("CG", cell, 1) - W = VectorElement("CG", cell, 1) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) @@ -467,9 +545,9 @@ def test_segregated_derivative_of_convection(self): def test_coefficient_derivatives(self): - V = FiniteElement("Lagrange", triangle, 1) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) dv = TestFunction(space) @@ -493,10 +571,10 @@ def test_coefficient_derivatives(self): def test_vector_coefficient_scalar_derivatives(self): - V = FiniteElement("Lagrange", triangle, 1) - VV = VectorElement("Lagrange", triangle, 1) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + VV = FiniteElement("vector Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -521,10 +599,10 @@ def test_vector_coefficient_scalar_derivatives(self): def test_vector_coefficient_derivatives(self): - V = VectorElement("Lagrange", triangle, 1) - VV = TensorElement("Lagrange", triangle, 1) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + VV = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -550,10 +628,10 @@ def test_vector_coefficient_derivatives(self): def test_vector_coefficient_derivatives_of_product(self): - V = VectorElement("Lagrange", triangle, 1) - VV = TensorElement("Lagrange", triangle, 1) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + VV = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -591,8 +669,8 @@ def test_vector_coefficient_derivatives_of_product(self): def testHyperElasticity(self): cell = interval - element = FiniteElement("CG", cell, 2) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (1, ), identity_pullback, H1)) space = FunctionSpace(domain, element) w = Coefficient(space) v = TestFunction(space) @@ -669,9 +747,9 @@ def Nw(x, derivatives): def test_mass_derived_from_functional(self): cell = triangle - V = FiniteElement("CG", cell, 1) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) @@ -691,9 +769,9 @@ def test_mass_derived_from_functional(self): def test_derivative_replace_works_together(self): cell = triangle - V = FiniteElement("CG", cell, 1) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) @@ -716,8 +794,8 @@ def test_derivative_replace_works_together(self): def test_index_simplification_handles_repeated_indices(self): - mesh = Mesh(VectorElement("P", quadrilateral, 1)) - V = FunctionSpace(mesh, TensorElement("DQ", quadrilateral, 0)) + mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1)) + V = FunctionSpace(mesh, FiniteElement("DQ", quadrilateral, 0, (2, 2), identity_pullback, L2)) K = JacobianInverse(mesh) G = outer(Identity(2), Identity(2)) i, j, k, l, m, n = indices(6) @@ -735,7 +813,7 @@ def test_index_simplification_handles_repeated_indices(self): def test_index_simplification_reference_grad(self): - mesh = Mesh(VectorElement("P", quadrilateral, 1)) + mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1)) i, = indices(1) A = as_tensor(Indexed(Jacobian(mesh), MultiIndex((i, i))), (i,)) expr = apply_derivatives(apply_geometry_lowering( @@ -748,8 +826,8 @@ def test_index_simplification_reference_grad(self): # --- Scratch space def test_foobar(self): - element = VectorElement("Lagrange", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/test/test_diff.py b/test/test_diff.py index fd1ef4f34..5a53e5c8d 100755 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -3,10 +3,13 @@ import pytest -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, SpatialCoordinate, VectorElement, as_vector, atan, - cos, diff, exp, indices, ln, sin, tan, triangle, variable) +from ufl import (Coefficient, FunctionSpace, Mesh, SpatialCoordinate, as_vector, atan, cos, diff, exp, indices, ln, sin, + tan, triangle, variable) from ufl.algorithms import expand_derivatives from ufl.constantvalue import as_ufl +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def get_variables(): @@ -170,16 +173,16 @@ def df(v): def testCoefficient(): - coord_elem = VectorElement("P", triangle, 1, dim=3) + coord_elem = FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1) mesh = Mesh(coord_elem) - V = FunctionSpace(mesh, FiniteElement("P", triangle, 1)) + V = FunctionSpace(mesh, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) v = Coefficient(V) assert round(expand_derivatives(diff(v, v))-1.0, 7) == 0 def testDiffX(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) f = x[0] ** 2 * x[1] ** 2 i, = indices(1) diff --git a/test/test_domains.py b/test/test_domains.py index 88ee286ab..cb9df4ca0 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -1,24 +1,31 @@ + """Tests of domain language and attaching domains to forms.""" import pytest from mockobjects import MockMesh -from ufl import (Cell, Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, VectorElement, ds, dS, dx, hexahedron, - interval, quadrilateral, tetrahedron, triangle) +import ufl # noqa: F401 +from ufl import (Cell, Coefficient, Constant, FunctionSpace, Mesh, ds, dS, dx, hexahedron, interval, quadrilateral, + tetrahedron, triangle) from ufl.algorithms import compute_form_data +from ufl.finiteelement import FiniteElement +from ufl.pullback import IdentityPullback # noqa: F401 +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 -all_cells = (interval, triangle, tetrahedron, - quadrilateral, hexahedron) +all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) def test_construct_domains_from_cells(): for cell in all_cells: - Mesh(VectorElement("Lagrange", cell, 1)) + d = cell.geometric_dimension() + Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) def test_construct_domains_with_names(): for cell in all_cells: - e = VectorElement("Lagrange", cell, 1) + d = cell.geometric_dimension() + e = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) D2 = Mesh(e, ufl_id=2) D3 = Mesh(e, ufl_id=3) D3b = Mesh(e, ufl_id=3) @@ -29,9 +36,13 @@ def test_construct_domains_with_names(): def test_domains_sort_by_name(): # This ordering is rather arbitrary, but at least this shows sorting is # working - domains1 = [Mesh(VectorElement("Lagrange", cell, 1), ufl_id=hash(cell.cellname())) + domains1 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1), + ufl_id=hash(cell.cellname())) for cell in all_cells] - domains2 = [Mesh(VectorElement("Lagrange", cell, 1), ufl_id=hash(cell.cellname())) + domains2 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1), + ufl_id=hash(cell.cellname())) for cell in sorted(all_cells)] sdomains = sorted(domains1, key=lambda D: (D.geometric_dimension(), D.topological_dimension(), @@ -42,19 +53,19 @@ def test_domains_sort_by_name(): def test_topdomain_creation(): - D = Mesh(interval) + D = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) assert D.geometric_dimension() == 1 - D = Mesh(triangle) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert D.geometric_dimension() == 2 - D = Mesh(tetrahedron) + D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) assert D.geometric_dimension() == 3 def test_cell_legacy_case(): # Passing cell like old code does - D = Mesh(VectorElement("Lagrange", triangle, 1)) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - V = FiniteElement("CG", triangle, 1) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) f = Coefficient(FunctionSpace(D, V)) assert f.ufl_domains() == (D, ) @@ -64,9 +75,9 @@ def test_cell_legacy_case(): def test_simple_domain_case(): # Creating domain from just cell with label like new dolfin will do - D = Mesh(triangle, ufl_id=3) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3) - V = FunctionSpace(D, FiniteElement("CG", D.ufl_cell(), 1)) + V = FunctionSpace(D, FiniteElement("Lagrange", D.ufl_cell(), 1, (), identity_pullback, "H1")) f = Coefficient(V) assert f.ufl_domains() == (D, ) @@ -79,11 +90,11 @@ def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new ap # Mesh with P2 representation of coordinates cell = triangle - P2 = VectorElement("CG", cell, 2) + P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) domain = Mesh(P2) # Piecewise linear function space over quadratic mesh - element = FiniteElement("CG", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = FunctionSpace(domain, element) f = Coefficient(V) @@ -105,66 +116,79 @@ def test_join_domains(): mesh7 = MockMesh(7) mesh8 = MockMesh(8) triangle3 = Cell("triangle", geometric_dimension=3) - xa = VectorElement("CG", triangle, 1) - xb = VectorElement("CG", triangle, 1) + xa = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + xb = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) # Equal domains are joined - assert 1 == len(join_domains([Mesh(triangle, ufl_id=7), - Mesh(triangle, ufl_id=7)])) - assert 1 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), - Mesh(triangle, ufl_id=7, cargo=mesh7)])) + assert 1 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7)])) + assert 1 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7)])) assert 1 == len(join_domains([Mesh(xa, ufl_id=3), Mesh(xa, ufl_id=3)])) # Different domains are not joined - assert 2 == len(join_domains([Mesh(triangle), Mesh(triangle)])) - assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), - Mesh(triangle, ufl_id=8)])) - assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), - Mesh(quadrilateral, ufl_id=8)])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=8)])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), + Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), ufl_id=8)])) assert 2 == len(join_domains([Mesh(xa, ufl_id=7), Mesh(xa, ufl_id=8)])) assert 2 == len(join_domains([Mesh(xa), Mesh(xb)])) - # Incompatible cells require labeling - # self.assertRaises(BaseException, lambda: join_domains([Mesh(triangle), Mesh(triangle3)])) # FIXME: Figure out - # self.assertRaises(BaseException, lambda: join_domains([Mesh(triangle), - # Mesh(quadrilateral)])) # FIXME: Figure out - # Incompatible coordinates require labeling - xc = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) - xd = Coefficient(FunctionSpace(Mesh(triangle), VectorElement("CG", triangle, 1))) + xc = Coefficient(FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + xd = Coefficient(FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) with pytest.raises(BaseException): join_domains([Mesh(xc), Mesh(xd)]) # Incompatible data is checked if and only if the domains are the same - assert 2 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), - Mesh(triangle, ufl_id=8, cargo=mesh8)])) - assert 2 == len(join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), - Mesh(quadrilateral, ufl_id=8, cargo=mesh8)])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), + Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), + ufl_id=8, cargo=mesh8)])) # Geometric dimensions must match with pytest.raises(BaseException): - join_domains([Mesh(triangle), - Mesh(triangle3)]) + join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), + Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1))]) with pytest.raises(BaseException): - join_domains([Mesh(triangle, ufl_id=7, cargo=mesh7), - Mesh(triangle3, ufl_id=8, cargo=mesh8)]) + join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), + Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)]) # Cargo and mesh ids must match with pytest.raises(BaseException): - Mesh(triangle, ufl_id=7, cargo=mesh8) + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh8) # Nones are removed - assert 2 == len(join_domains([None, Mesh(triangle, ufl_id=3), - None, Mesh(triangle, ufl_id=3), - None, Mesh(triangle, ufl_id=4)])) - assert 2 == len(join_domains([Mesh(triangle, ufl_id=7), None, - Mesh(quadrilateral, ufl_id=8)])) - assert None not in join_domains([Mesh(triangle3, ufl_id=7), None, - Mesh(tetrahedron, ufl_id=8)]) + assert 2 == len(join_domains([ + None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3), + None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3), + None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=4)])) + assert 2 == len(join_domains([ + Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), None, + Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), ufl_id=8)])) + assert None not in join_domains([ + Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1), ufl_id=7), None, + Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1), ufl_id=8)]) def test_everywhere_integrals_with_backwards_compatibility(): - D = Mesh(triangle) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - V = FunctionSpace(D, FiniteElement("CG", triangle, 1)) + V = FunctionSpace(D, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) f = Coefficient(V) a = f * dx @@ -184,9 +208,9 @@ def test_everywhere_integrals_with_backwards_compatibility(): def test_merge_sort_integral_data(): - D = Mesh(triangle) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - V = FunctionSpace(D, FiniteElement("CG", triangle, 1)) + V = FunctionSpace(D, FiniteElement("CG", triangle, 1, (), identity_pullback, H1)) u = Coefficient(V) c = Constant(D) diff --git a/test/test_duals.py b/test/test_duals.py index 7b68d3cca..b82736e0f 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -3,24 +3,27 @@ import pytest -from ufl import (Action, Adjoint, Argument, Coargument, Coefficient, Cofunction, FiniteElement, FormSum, FunctionSpace, - Matrix, Mesh, MixedFunctionSpace, TestFunction, TrialFunction, VectorElement, action, adjoint, - derivative, dx, inner, interval, tetrahedron, triangle) +from ufl import (Action, Adjoint, Argument, Coargument, Coefficient, Cofunction, FormSum, FunctionSpace, Matrix, Mesh, + MixedFunctionSpace, TestFunction, TrialFunction, action, adjoint, derivative, dx, inner, interval, + tetrahedron, triangle) from ufl.algorithms.ad import expand_derivatives from ufl.constantvalue import Zero from ufl.duals import is_dual, is_primal +from ufl.finiteelement import FiniteElement from ufl.form import ZeroBaseForm +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_mixed_functionspace(self): # Domains - domain_3d = Mesh(VectorElement("Lagrange", tetrahedron, 1)) - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) + domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) # Finite elements - f_1d = FiniteElement("CG", interval, 1) - f_2d = FiniteElement("CG", triangle, 1) - f_3d = FiniteElement("CG", tetrahedron, 1) + f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + f_3d = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) # Function spaces V_3d = FunctionSpace(domain_3d, f_3d) V_2d = FunctionSpace(domain_2d, f_2d) @@ -47,8 +50,8 @@ def test_mixed_functionspace(self): def test_dual_coefficients(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -70,8 +73,8 @@ def test_dual_coefficients(): def test_dual_arguments(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -93,8 +96,8 @@ def test_dual_arguments(): def test_addition(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -131,8 +134,8 @@ def test_addition(): def test_scalar_mult(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -149,8 +152,8 @@ def test_scalar_mult(): def test_adjoint(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) a = Matrix(V, V) @@ -168,11 +171,11 @@ def test_adjoint(): def test_action(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) - f_1d = FiniteElement("CG", interval, 1) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) U = FunctionSpace(domain_1d, f_1d) a = Matrix(V, U) @@ -228,11 +231,11 @@ def test_action(): def test_differentiation(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) - f_1d = FiniteElement("CG", interval, 1) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) U = FunctionSpace(domain_1d, f_1d) u = Coefficient(U) @@ -291,8 +294,8 @@ def test_differentiation(): def test_zero_base_form_mult(): - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - f_2d = FiniteElement("CG", triangle, 1) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) v = Argument(V, 0) Z = ZeroBaseForm((v, v)) diff --git a/test/test_equals.py b/test/test_equals.py index 184d71c16..ddfa5058c 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -1,14 +1,17 @@ """Test of expression comparison.""" -from ufl import Coefficient, Cofunction, FiniteElement, FunctionSpace, Mesh, VectorElement, triangle +from ufl import Coefficient, Cofunction, FunctionSpace, Mesh, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_comparison_of_coefficients(): - V = FiniteElement("CG", triangle, 1) - U = FiniteElement("CG", triangle, 2) - Ub = FiniteElement("CG", triangle, 2) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + U = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) + Ub = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) u_space = FunctionSpace(domain, U) ub_space = FunctionSpace(domain, Ub) @@ -36,11 +39,11 @@ def test_comparison_of_coefficients(): def test_comparison_of_cofunctions(): - V = FiniteElement("CG", triangle, 1) - U = FiniteElement("CG", triangle, 2) - Ub = FiniteElement("CG", triangle, 2) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + U = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) + Ub = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) u_space = FunctionSpace(domain, U) ub_space = FunctionSpace(domain, Ub) @@ -68,8 +71,8 @@ def test_comparison_of_cofunctions(): def test_comparison_of_products(): - V = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space) u = Coefficient(v_space) @@ -82,8 +85,8 @@ def test_comparison_of_products(): def test_comparison_of_sums(): - V = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space) u = Coefficient(v_space) @@ -96,8 +99,8 @@ def test_comparison_of_sums(): def test_comparison_of_deeply_nested_expression(): - V = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space, count=1) u = Coefficient(v_space, count=1) diff --git a/test/test_evaluate.py b/test/test_evaluate.py index 61e8af9e9..964740aac 100755 --- a/test/test_evaluate.py +++ b/test/test_evaluate.py @@ -3,10 +3,13 @@ import math -from ufl import (Argument, Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, SpatialCoordinate, VectorElement, - as_matrix, as_vector, cos, cross, det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, - sym, tan, tetrahedron, tr, triangle) +from ufl import (Argument, Coefficient, FunctionSpace, Identity, Mesh, SpatialCoordinate, as_matrix, as_vector, cos, + cross, det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, sym, tan, tetrahedron, tr, + triangle) from ufl.constantvalue import as_ufl +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def testScalars(): @@ -40,7 +43,7 @@ def testIdentity(): def testCoords(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) s = x[0] + x[1] e = s((5, 7)) @@ -50,8 +53,8 @@ def testCoords(): def testFunction1(): cell = triangle - element = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) s = 3 * f @@ -62,8 +65,8 @@ def testFunction1(): def testFunction2(): cell = triangle - element = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) @@ -77,8 +80,8 @@ def g(x): def testArgument2(): cell = triangle - element = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Argument(space, 2) @@ -92,7 +95,7 @@ def g(x): def testAlgebra(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) s = 3 * (x[0] + x[1]) - 7 + x[0] ** (x[1] / 2) e = s((5, 7)) @@ -102,7 +105,7 @@ def testAlgebra(): def testIndexSum(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) i, = indices(1) s = x[i] * x[i] @@ -113,7 +116,7 @@ def testIndexSum(): def testIndexSum2(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) ident = Identity(cell.geometric_dimension()) i, j = indices(2) @@ -125,7 +128,7 @@ def testIndexSum2(): def testMathFunctions(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain)[0] s = sin(x) @@ -160,7 +163,7 @@ def testMathFunctions(): def testListTensor(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x, y = SpatialCoordinate(domain) m = as_matrix([[x, y], [-y, -x]]) @@ -177,7 +180,7 @@ def testListTensor(): def testComponentTensor1(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) m = as_vector(x[i], i) @@ -188,7 +191,7 @@ def testComponentTensor1(): def testComponentTensor2(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(x, x) @@ -201,7 +204,7 @@ def testComponentTensor2(): def testComponentTensor3(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(x, x) @@ -214,8 +217,8 @@ def testComponentTensor3(): def testCoefficient(): - V = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) e = f ** 2 @@ -226,8 +229,8 @@ def eval_f(x): def testCoefficientDerivative(): - V = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) e = f.dx(0) ** 2 + f.dx(1) ** 2 @@ -248,7 +251,7 @@ def eval_f(x, derivatives): def test_dot(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) s = dot(x, 2 * x) e = s((5, 7)) @@ -257,7 +260,7 @@ def test_dot(): def test_inner(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = as_matrix(((2 * x[0], 3 * x[0]), (2 * x[1], 3 * x[1]))) s = inner(xx, 2 * xx) @@ -267,7 +270,7 @@ def test_inner(): def test_outer(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(outer(x, x), as_vector((2, 3))) s = inner(xx, 2 * xx) @@ -277,7 +280,7 @@ def test_outer(): def test_cross(): - domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (3, 5, 7) @@ -310,7 +313,7 @@ def test_cross(): def xtest_dev(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -322,7 +325,7 @@ def xtest_dev(): def test_skew(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -334,7 +337,7 @@ def test_skew(): def test_sym(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -346,7 +349,7 @@ def test_sym(): def test_tr(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -357,7 +360,7 @@ def test_tr(): def test_det2D(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) a, b = 6.5, -4 diff --git a/test/test_expand_indices.py b/test/test_expand_indices.py index 023b4ccb1..f8314df2c 100755 --- a/test/test_expand_indices.py +++ b/test/test_expand_indices.py @@ -8,10 +8,13 @@ import pytest -from ufl import (Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, VectorElement, as_tensor, - cos, det, div, dot, dx, exp, grad, i, inner, j, k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) +from ufl import (Coefficient, FunctionSpace, Identity, Mesh, as_tensor, cos, det, div, dot, dx, exp, grad, i, inner, j, + k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) from ufl.algorithms import compute_form_data, expand_derivatives, expand_indices from ufl.algorithms.renumbering import renumber_indices +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # TODO: Test expand_indices2 throuroughly for correctness, then efficiency: # expand_indices, expand_indices2 = expand_indices2, expand_indices @@ -21,10 +24,10 @@ class Fixture: def __init__(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1) - velement = VectorElement("Lagrange", cell, 1) - telement = TensorElement("Lagrange", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + velement = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + telement = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) vspace = FunctionSpace(domain, velement) tspace = FunctionSpace(domain, telement) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 13f4eaa65..0cdebb422 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -5,35 +5,37 @@ import pytest -# This imports everything external code will see from ufl -from ufl import (Action, Argument, Coefficient, Constant, FiniteElement, Form, FunctionSpace, Mesh, TestFunction, - TrialFunction, VectorElement, action, adjoint, cos, derivative, dx, inner, sin, triangle) +from ufl import (Action, Argument, Coefficient, Constant, Form, FunctionSpace, Mesh, TestFunction, TrialFunction, + action, adjoint, cos, derivative, dx, inner, sin, triangle) from ufl.algorithms import expand_derivatives from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.core.external_operator import ExternalOperator +from ufl.finiteelement import FiniteElement from ufl.form import BaseForm +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def domain_2d(): - return Mesh(VectorElement("Lagrange", triangle, 1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) @pytest.fixture def V1(domain_2d): - f1 = FiniteElement("CG", triangle, 1) + f1 = FiniteElement("CG", triangle, 1, (), identity_pullback, H1) return FunctionSpace(domain_2d, f1) @pytest.fixture def V2(domain_2d): - f1 = FiniteElement("CG", triangle, 2) + f1 = FiniteElement("CG", triangle, 2, (), identity_pullback, H1) return FunctionSpace(domain_2d, f1) @pytest.fixture def V3(domain_2d): - f1 = FiniteElement("CG", triangle, 3) + f1 = FiniteElement("CG", triangle, 3, (), identity_pullback, H1) return FunctionSpace(domain_2d, f1) diff --git a/test/test_ffcforms.py b/test/test_ffcforms.py index a0dbf7e82..5ab8778ab 100755 --- a/test/test_ffcforms.py +++ b/test/test_ffcforms.py @@ -13,14 +13,17 @@ # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. -from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, FunctionSpace, Mesh, TensorElement, - TestFunction, TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, - div, dot, ds, dS, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import (Coefficient, Constant, Dx, FacetNormal, FunctionSpace, Mesh, TestFunction, TestFunctions, + TrialFunction, TrialFunctions, VectorConstant, avg, curl, div, dot, ds, dS, dx, grad, i, inner, j, + jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback +from ufl.sobolevspace import H1, L2, HCurl, HDiv def testConstant(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -36,8 +39,8 @@ def testConstant(): def testElasticity(): - element = VectorElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -52,8 +55,8 @@ def eps(v): def testEnergyNorm(): - element = FiniteElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) @@ -61,8 +64,8 @@ def testEnergyNorm(): def testEquation(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 @@ -78,8 +81,8 @@ def testEquation(): def testFunctionOperators(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -93,8 +96,8 @@ def testFunctionOperators(): def testHeat(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -109,8 +112,8 @@ def testHeat(): def testMass(): - element = FiniteElement("Lagrange", "tetrahedron", 3) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -120,19 +123,18 @@ def testMass(): def testMixedMixedElement(): - P3 = FiniteElement("Lagrange", "triangle", 3) - - element = (P3 * P3) * (P3 * P3) # noqa: F841 + P3 = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) + MixedElement([[P3, P3], [P3, P3]]) def testMixedPoisson(): q = 1 - BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) - DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) + BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2, ), contravariant_piola, HDiv) + DG = FiniteElement("Discontinuous Lagrange", triangle, q - 1, (), identity_pullback, L2) - mixed_element = BDM * DG - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + mixed_element = MixedElement([BDM, DG]) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, mixed_element) (tau, w) = TestFunctions(space) @@ -145,8 +147,8 @@ def testMixedPoisson(): def testNavierStokes(): - element = VectorElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -159,8 +161,8 @@ def testNavierStokes(): def testNeumannProblem(): - element = VectorElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -176,8 +178,8 @@ def testNeumannProblem(): def testOptimization(): - element = FiniteElement("Lagrange", "triangle", 3) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -189,16 +191,16 @@ def testOptimization(): def testP5tet(): - element = FiniteElement("Lagrange", tetrahedron, 5) # noqa: F841 + FiniteElement("Lagrange", tetrahedron, 5, (), identity_pullback, H1) def testP5tri(): - element = FiniteElement("Lagrange", triangle, 5) # noqa: F841 + FiniteElement("Lagrange", triangle, 5, (), identity_pullback, H1) def testPoissonDG(): - element = FiniteElement("Discontinuous Lagrange", triangle, 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Discontinuous Lagrange", triangle, 1, (), identity_pullback, L2) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -236,8 +238,8 @@ def testPoissonDG(): def testPoisson(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -250,8 +252,8 @@ def testPoisson(): def testPoissonSystem(): - element = VectorElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -270,8 +272,8 @@ def testProjection(): # in FFC for a while. For DOLFIN, the current (global) L^2 # projection can be extended to handle also local projections. - P1 = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, P1) v = TestFunction(space) # noqa: F841 @@ -285,16 +287,16 @@ def testProjection(): def testQuadratureElement(): - element = FiniteElement("Lagrange", "triangle", 2) + element = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) # FFC notation: - # QE = QuadratureElement("triangle", 3) - # sig = VectorQuadratureElement("triangle", 3) + # QE = QuadratureElement(triangle, 3) + # sig = VectorQuadratureElement(triangle, 3) - QE = FiniteElement("Quadrature", "triangle", 3) - sig = VectorElement("Quadrature", "triangle", 3) + QE = FiniteElement("Quadrature", triangle, 3, (), identity_pullback, L2) + sig = FiniteElement("Quadrature", triangle, 3, (2, ), identity_pullback, L2) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -311,11 +313,11 @@ def testQuadratureElement(): def testStokes(): # UFLException: Shape mismatch in sum. - P2 = VectorElement("Lagrange", "triangle", 2) - P1 = FiniteElement("Lagrange", "triangle", 1) - TH = P2 * P1 + P2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) + P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + TH = MixedElement([P2, P1]) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) th_space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) @@ -332,8 +334,8 @@ def testStokes(): def testSubDomain(): - element = FiniteElement("CG", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) @@ -342,8 +344,8 @@ def testSubDomain(): def testSubDomains(): - element = FiniteElement("CG", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -355,8 +357,8 @@ def testSubDomains(): def testTensorWeightedPoisson(): # FFC notation: - # P1 = FiniteElement("Lagrange", "triangle", 1) - # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) + # P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + # P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (), identity_pullback, L2) # # v = TestFunction(P1) # u = TrialFunction(P1) @@ -371,10 +373,10 @@ def testTensorWeightedPoisson(): # # a = dot(grad(v), mult(C, grad(u)))*dx - P1 = FiniteElement("Lagrange", "triangle", 1) - P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) + P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) @@ -401,16 +403,16 @@ def HodgeLaplaceGradCurl(space, fspace): return [a, L] - shape = "tetrahedron" + shape = tetrahedron order = 1 - GRAD = FiniteElement("Lagrange", shape, order) + GRAD = FiniteElement("Lagrange", shape, order, (), identity_pullback, H1) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) - CURL = FiniteElement("N1curl", shape, order) + CURL = FiniteElement("N1curl", shape, order, (3, ), covariant_piola, HCurl) - VectorLagrange = VectorElement("Lagrange", shape, order + 1) - domain = Mesh(VectorElement("Lagrange", shape, 1)) + VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", shape, 1, (3, ), identity_pullback, H1)) - [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, GRAD * CURL), + [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, MixedElement([GRAD, CURL])), FunctionSpace(domain, VectorLagrange)) diff --git a/test/test_form.py b/test/test_form.py index 554ed85c5..30ca5117a 100755 --- a/test/test_form.py +++ b/test/test_form.py @@ -1,27 +1,30 @@ import pytest -from ufl import (Coefficient, Cofunction, FiniteElement, Form, FormSum, FunctionSpace, Mesh, SpatialCoordinate, - TestFunction, TrialFunction, VectorElement, dot, ds, dx, grad, inner, nabla_grad, triangle) +from ufl import (Coefficient, Cofunction, Form, FormSum, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, + TrialFunction, dot, ds, dx, grad, inner, nabla_grad, triangle) +from ufl.finiteelement import FiniteElement from ufl.form import BaseForm +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def element(): cell = triangle - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) return element @pytest.fixture def domain(): cell = triangle - return Mesh(VectorElement("Lagrange", cell, 1)) + return Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) @pytest.fixture def mass(domain): cell = triangle - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -31,7 +34,7 @@ def mass(domain): @pytest.fixture def stiffness(domain): cell = triangle - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -41,7 +44,7 @@ def stiffness(domain): @pytest.fixture def convection(domain): cell = triangle - element = VectorElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -52,7 +55,7 @@ def convection(domain): @pytest.fixture def load(domain): cell = triangle - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, element) f = Coefficient(space) v = TestFunction(space) @@ -62,7 +65,7 @@ def load(domain): @pytest.fixture def boundary_load(domain): cell = triangle - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, element) f = Coefficient(space) v = TestFunction(space) @@ -100,8 +103,8 @@ def test_form_coefficients(element, domain): def test_form_domains(): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) - element = FiniteElement("Lagrange", cell, 1) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) V = FunctionSpace(domain, element) v = TestFunction(V) @@ -132,8 +135,8 @@ def test_form_integrals(mass, boundary_load): def test_form_call(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) - element = FiniteElement("Lagrange", triangle, 1) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) V = FunctionSpace(domain, element) v = TestFunction(V) u = TrialFunction(V) @@ -151,8 +154,8 @@ def test_form_call(): def test_formsum(mass): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) - element = FiniteElement("Lagrange", triangle, 1) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) V = FunctionSpace(domain, element) v = Cofunction(V.dual()) diff --git a/test/test_grad.py b/test/test_grad.py index 4b5d9a996..21b269e2f 100755 --- a/test/test_grad.py +++ b/test/test_grad.py @@ -1,8 +1,11 @@ """Test use of grad in various situations.""" -from ufl import (Coefficient, Constant, FiniteElement, TensorConstant, TensorElement, VectorConstant, VectorElement, - div, dx, grad, indices, inner, interval, tetrahedron, triangle) +from ufl import (Coefficient, Constant, TensorConstant, VectorConstant, div, dx, grad, indices, inner, interval, + tetrahedron, triangle) from ufl.algorithms import compute_form_data +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def xtest_grad_div_curl_properties_in_1D(self): @@ -20,9 +23,9 @@ def xtest_grad_div_curl_properties_in_3D(self): def _test_grad_div_curl_properties(self, cell): d = cell.geometric_dimension() - S = FiniteElement("CG", cell, 1) - V = VectorElement("CG", cell, 1) - T = TensorElement("CG", cell, 1) + S = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) + T = FiniteElement("Lagrange", cell, 1, (d, d), identity_pullback, H1) cs = Constant(cell) cv = VectorConstant(cell) diff --git a/test/test_illegal.py b/test/test_illegal.py index 40409ef6d..9931946a4 100755 --- a/test/test_illegal.py +++ b/test/test_illegal.py @@ -1,23 +1,26 @@ import pytest -from ufl import Argument, Coefficient, FiniteElement, FunctionSpace, Mesh, VectorElement +from ufl import Argument, Coefficient, FunctionSpace, Mesh, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # TODO: Add more illegal expressions to check! @pytest.fixture def selement(): - return FiniteElement("Lagrange", "triangle", 1) + return FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) @pytest.fixture def velement(): - return VectorElement("Lagrange", "triangle", 1) + return FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) @pytest.fixture def domain(): - return Mesh(VectorElement("Lagrange", "triangle", 1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) @pytest.fixture diff --git a/test/test_indexing.py b/test/test_indexing.py index 5b7450e9b..4ffdc7eb5 100755 --- a/test/test_indexing.py +++ b/test/test_indexing.py @@ -1,12 +1,15 @@ import pytest -from ufl import Index, Mesh, SpatialCoordinate, VectorElement, outer, triangle +from ufl import Index, Mesh, SpatialCoordinate, outer, triangle from ufl.classes import FixedIndex, Indexed, MultiIndex, Outer, Zero +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def domain(): - return Mesh(VectorElement("Lagrange", triangle, 1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) @pytest.fixture diff --git a/test/test_indices.py b/test/test_indices.py index e1ab15587..49cf16a1a 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -1,15 +1,18 @@ import pytest -from ufl import (Argument, Coefficient, FunctionSpace, Mesh, TensorElement, TestFunction, TrialFunction, VectorElement, - as_matrix, as_tensor, as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) +from ufl import (Argument, Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, as_matrix, as_tensor, + as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) from ufl.classes import IndexSum +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 # TODO: add more expressions to test as many possible combinations of index notation as feasible... def test_vector_indices(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) @@ -18,8 +21,8 @@ def test_vector_indices(self): def test_tensor_indices(self): - element = TensorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) @@ -31,8 +34,8 @@ def test_tensor_indices(self): def test_indexed_sum1(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) @@ -42,8 +45,8 @@ def test_indexed_sum1(self): def test_indexed_sum2(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) @@ -54,8 +57,8 @@ def test_indexed_sum2(self): def test_indexed_sum3(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) @@ -64,8 +67,8 @@ def test_indexed_sum3(self): def test_indexed_function1(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) @@ -75,8 +78,8 @@ def test_indexed_function1(self): def test_indexed_function2(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) @@ -92,8 +95,8 @@ def test_indexed_function2(self): def test_indexed_function3(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) Argument(space, 2) u = Argument(space, 3) @@ -103,8 +106,8 @@ def test_indexed_function3(self): def test_vector_from_indices(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -121,8 +124,8 @@ def test_vector_from_indices(self): def test_matrix_from_indices(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -139,8 +142,8 @@ def test_matrix_from_indices(self): def test_vector_from_list(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -153,8 +156,8 @@ def test_vector_from_list(self): def test_matrix_from_list(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -174,8 +177,8 @@ def test_matrix_from_list(self): def test_tensor(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -214,8 +217,8 @@ def test_tensor(self): def test_indexed(self): - element = VectorElement("CG", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -235,8 +238,8 @@ def test_indexed(self): def test_spatial_derivative(self): cell = triangle - element = VectorElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) diff --git a/test/test_interpolate.py b/test/test_interpolate.py index 6deadc07d..71f3cd145 100644 --- a/test/test_interpolate.py +++ b/test/test_interpolate.py @@ -5,29 +5,32 @@ import pytest -from ufl import (Action, Adjoint, Argument, Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, - TrialFunction, VectorElement, action, adjoint, derivative, dx, grad, inner, replace, triangle) +from ufl import (Action, Adjoint, Argument, Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, action, + adjoint, derivative, dx, grad, inner, replace, triangle) from ufl.algorithms.ad import expand_derivatives from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, extract_base_form_operators, extract_coefficients) from ufl.algorithms.expand_indices import expand_indices from ufl.core.interpolate import Interpolate +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture def domain_2d(): - return Mesh(VectorElement("Lagrange", triangle, 1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) @pytest.fixture def V1(domain_2d): - f1 = FiniteElement("CG", triangle, 1) + f1 = FiniteElement("CG", triangle, 1, (), identity_pullback, H1) return FunctionSpace(domain_2d, f1) @pytest.fixture def V2(domain_2d): - f1 = FiniteElement("CG", triangle, 2) + f1 = FiniteElement("CG", triangle, 2, (), identity_pullback, H1) return FunctionSpace(domain_2d, f1) diff --git a/test/test_elements.py b/test/test_legacy.py old mode 100755 new mode 100644 similarity index 81% rename from test/test_elements.py rename to test/test_legacy.py index 370679662..fd3de052f --- a/test/test_elements.py +++ b/test/test_legacy.py @@ -1,9 +1,26 @@ -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, MixedElement, TensorElement, VectorElement, - WithMapping, dx, hexahedron, inner, interval, quadrilateral, tetrahedron, triangle) +from ufl import (H1, Coefficient, FunctionSpace, Mesh, dx, hexahedron, identity_pullback, inner, interval, + quadrilateral, tetrahedron, triangle) +from ufl.legacy import FiniteElement, MixedElement, TensorElement, VectorElement, WithMapping all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) -# TODO: cover all valid element definitions + +def test_legacy_vs_new(): + from ufl.finiteelement import FiniteElement as NewFiniteElement + e = FiniteElement("Lagrange", triangle, 1) + new_e = NewFiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + assert e.sobolev_space == new_e.sobolev_space + assert e.pullback == new_e.pullback + assert e.embedded_superdegree == new_e.embedded_superdegree + assert e.embedded_subdegree == new_e.embedded_subdegree + assert e.cell == new_e.cell + assert e.reference_value_shape == new_e.reference_value_shape + assert e.value_shape == new_e.value_shape + assert e.reference_value_size == new_e.reference_value_size + assert e.value_size == new_e.value_size + assert e.num_sub_elements == new_e.num_sub_elements + assert e.sub_elements == new_e.sub_elements + assert e.is_cellwise_constant() == new_e.is_cellwise_constant() def test_scalar_galerkin(): @@ -11,12 +28,12 @@ def test_scalar_galerkin(): for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): element = FiniteElement(family, cell, p) - assert element.value_shape() == () + assert element.value_shape == () assert element == eval(repr(element)) for p in range(1, 10): for family in ("TDG", "Discontinuous Taylor"): element = FiniteElement(family, interval, p) - assert element.value_shape() == () + assert element.value_shape == () def test_vector_galerkin(): @@ -27,7 +44,7 @@ def test_vector_galerkin(): for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): element = VectorElement(family, cell, p) - assert element.value_shape() == shape + assert element.value_shape == shape assert element == eval(repr(element)) for i in range(dim): c = element.extract_component(i) @@ -42,7 +59,7 @@ def test_tensor_galerkin(): for p in range(1, 10): for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): element = TensorElement(family, cell, p) - assert element.value_shape() == shape + assert element.value_shape == shape assert element == eval(repr(element)) for i in range(dim): for j in range(dim): @@ -65,7 +82,7 @@ def test_tensor_symmetry(): family, cell, p, shape=(dim, dim), symmetry=s) else: element = TensorElement(family, cell, p, symmetry=s) - assert element.value_shape(), (dim == dim) + assert element.value_shape, (dim == dim) assert element == eval(repr(element)) for i in range(dim): for j in range(dim): @@ -80,6 +97,8 @@ def test_mixed_tensor_symmetries(): V = VectorElement('CG', triangle, 1) T = TensorElement('CG', triangle, 1, symmetry=True) + print(T.pullback) + # M has dimension 4+1, symmetries are 2->1 M = T * S domain = Mesh(VectorElement("Lagrange", triangle, 1)) @@ -106,7 +125,7 @@ def test_bdm(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() element = FiniteElement("BDM", cell, 1) - assert element.value_shape() == (dim,) + assert element.value_shape == (dim,) assert element == eval(repr(element)) @@ -114,14 +133,14 @@ def test_vector_bdm(): for cell in (triangle, tetrahedron): dim = cell.geometric_dimension() element = VectorElement("BDM", cell, 1) - assert element.value_shape(), (dim == dim) + assert element.value_shape, (dim == dim) assert element == eval(repr(element)) def test_mtw(): cell = triangle element = FiniteElement("MTW", cell, 3) - assert element.value_shape() == (cell.geometric_dimension(), ) + assert element.value_shape == (cell.geometric_dimension(), ) assert element == eval(repr(element)) assert element.mapping() == "contravariant Piola" @@ -133,8 +152,8 @@ def test_mixed(): pelement = FiniteElement("CG", cell, 1) TH1 = MixedElement(velement, pelement) TH2 = velement * pelement - assert TH1.value_shape() == (dim + 1,) - assert TH2.value_shape() == (dim + 1,) + assert TH1.value_shape == (dim + 1,) + assert TH2.value_shape == (dim + 1,) assert repr(TH1) == repr(TH2) assert TH1 == eval(repr(TH2)) assert TH2 == eval(repr(TH1)) @@ -147,8 +166,8 @@ def test_nested_mixed(): pelement = FiniteElement("CG", cell, 1) TH1 = MixedElement((velement, pelement), pelement) TH2 = velement * pelement * pelement - assert TH1.value_shape() == (dim + 2,) - assert TH2.value_shape() == (dim + 2,) + assert TH1.value_shape == (dim + 2,) + assert TH2.value_shape == (dim + 2,) assert repr(TH1) == repr(TH2) assert TH1 == eval(repr(TH2)) assert TH2 == eval(repr(TH1)) diff --git a/test/test_lhs_rhs.py b/test/test_lhs_rhs.py index 4593a8aa1..2be0e584c 100755 --- a/test/test_lhs_rhs.py +++ b/test/test_lhs_rhs.py @@ -3,13 +3,16 @@ # First added: 2011-11-09 # Last changed: 2011-11-09 -from ufl import (Argument, Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, - VectorElement, action, derivative, ds, dS, dx, exp, interval, system) +from ufl import (Argument, Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, action, derivative, + ds, dS, dx, exp, interval, system) +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_lhs_rhs_simple(): - V = FiniteElement("CG", interval, 1) - domain = Mesh(VectorElement("Lagrange", interval, 1)) + V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) u = TrialFunction(space) @@ -37,8 +40,8 @@ def test_lhs_rhs_simple(): def test_lhs_rhs_derivatives(): - V = FiniteElement("CG", interval, 1) - domain = Mesh(VectorElement("Lagrange", interval, 1)) + V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) u = TrialFunction(space) @@ -54,8 +57,8 @@ def test_lhs_rhs_derivatives(): def test_lhs_rhs_slightly_obscure(): - V = FiniteElement("CG", interval, 1) - domain = Mesh(VectorElement("Lagrange", interval, 1)) + V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) space = FunctionSpace(domain, V) u = TrialFunction(space) w = Argument(space, 2) diff --git a/test/test_measures.py b/test/test_measures.py index 86a8ea68c..2a31d51e1 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -2,7 +2,10 @@ from mockobjects import MockMesh, MockMeshFunction -from ufl import Cell, Coefficient, FiniteElement, FunctionSpace, Measure, Mesh, as_ufl, dC, dI, dO, triangle +from ufl import Cell, Coefficient, FunctionSpace, Measure, Mesh, as_ufl, dC, dI, dO, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_construct_forms_from_default_measures(): @@ -57,7 +60,7 @@ def test_construct_forms_from_default_measures(): # Check that we can create a basic form with default measure one = as_ufl(1) - one * dx(Mesh(triangle)) + one * dx(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) def test_foo(): @@ -67,7 +70,8 @@ def test_foo(): tdim = 2 cell = Cell("triangle", gdim) mymesh = MockMesh(9) - mydomain = Mesh(cell, ufl_id=9, cargo=mymesh) + mydomain = Mesh(FiniteElement("Lagrange", cell, 1, (gdim, ), identity_pullback, H1), + ufl_id=9, cargo=mymesh) assert cell.topological_dimension() == tdim assert cell.geometric_dimension() == gdim @@ -79,7 +83,7 @@ def test_foo(): assert mydomain.ufl_cargo() == mymesh # Define a coefficient for use in tests below - V = FunctionSpace(mydomain, FiniteElement("CG", cell, 1)) + V = FunctionSpace(mydomain, FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1)) f = Coefficient(V) # Test definition of a custom measure with explicit parameters diff --git a/test/test_mixed_function_space.py b/test/test_mixed_function_space.py index 84af918e6..6e3e68356 100644 --- a/test/test_mixed_function_space.py +++ b/test/test_mixed_function_space.py @@ -1,20 +1,23 @@ __authors__ = "Cecile Daversin Catty" __date__ = "2019-03-26 -- 2019-03-26" -from ufl import (FiniteElement, FunctionSpace, Measure, Mesh, MixedFunctionSpace, TestFunctions, TrialFunctions, - VectorElement, interval, tetrahedron, triangle) +from ufl import (FunctionSpace, Measure, Mesh, MixedFunctionSpace, TestFunctions, TrialFunctions, interval, tetrahedron, + triangle) from ufl.algorithms.formsplitter import extract_blocks +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_mixed_functionspace(self): # Domains - domain_3d = Mesh(VectorElement("Lagrange", tetrahedron, 1)) - domain_2d = Mesh(VectorElement("Lagrange", triangle, 1)) - domain_1d = Mesh(VectorElement("Lagrange", interval, 1)) + domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) # Finite elements - f_1d = FiniteElement("CG", interval, 1) - f_2d = FiniteElement("CG", triangle, 1) - f_3d = FiniteElement("CG", tetrahedron, 1) + f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + f_3d = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) # Function spaces V_3d = FunctionSpace(domain_3d, f_3d) V_2d = FunctionSpace(domain_2d, f_2d) @@ -33,9 +36,9 @@ def test_mixed_functionspace(self): (v_3d, v_2d, v_1d) = TestFunctions(V) # Measures - dx3 = Measure("dx", domain=V_3d) - dx2 = Measure("dx", domain=V_2d) - dx1 = Measure("dx", domain=V_1d) + dx3 = Measure("dx", domain=domain_3d) + dx2 = Measure("dx", domain=domain_2d) + dx1 = Measure("dx", domain=domain_1d) # Mixed variational form # LHS diff --git a/test/test_new_ad.py b/test/test_new_ad.py index 8643dd886..be6b6d403 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -1,8 +1,11 @@ -from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, - SpatialCoordinate, TestFunction, VectorConstant, VectorElement, as_ufl, cos, derivative, diff, exp, - grad, ln, sin, tan, triangle, variable, zero) +from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FunctionSpace, Identity, Mesh, SpatialCoordinate, + TestFunction, VectorConstant, as_ufl, cos, derivative, diff, exp, grad, ln, sin, tan, triangle, + variable, zero) from ufl.algorithms.apply_derivatives import GenericDerivativeRuleset, GradRuleset, apply_derivatives from ufl.algorithms.renumbering import renumber_indices +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 # Note: the old tests in test_automatic_differentiation.py are a bit messy # but still cover many things that are not in here yet. @@ -15,10 +18,10 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): cell = triangle d = cell.geometric_dimension() - V0 = FiniteElement("DG", cell, 0) - V1 = FiniteElement("Lagrange", cell, 1) + V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) + V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) @@ -84,9 +87,9 @@ def test_literal_derivatives_are_zero(): for v in variables: assert apply_derivatives(diff(lit, v)) == zero(lit.ufl_shape + v.ufl_shape) - V0 = FiniteElement("DG", cell, 0) - V1 = FiniteElement("Lagrange", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) + V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) u0 = Coefficient(v0_space) @@ -109,14 +112,14 @@ def test_grad_ruleset(): cell = triangle d = cell.geometric_dimension() - V0 = FiniteElement("DG", cell, 0) - V1 = FiniteElement("Lagrange", cell, 1) - V2 = FiniteElement("Lagrange", cell, 2) - W0 = VectorElement("DG", cell, 0) - W1 = VectorElement("Lagrange", cell, 1) - W2 = VectorElement("Lagrange", cell, 2) + V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) + V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + V2 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) + W0 = FiniteElement("Discontinuous Lagrange", cell, 0, (2, ), identity_pullback, L2) + W1 = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) + W2 = FiniteElement("Lagrange", cell, 2, (d, ), identity_pullback, H1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) diff --git a/test/test_pickle.py b/test/test_pickle.py index 3189bde2b..5ab4026f7 100755 --- a/test/test_pickle.py +++ b/test/test_pickle.py @@ -10,17 +10,20 @@ import pickle -from ufl import (Coefficient, Constant, Dx, FacetNormal, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, - TestFunction, TestFunctions, TrialFunction, TrialFunctions, VectorConstant, VectorElement, avg, curl, - div, dot, dS, ds, dx, grad, i, inner, j, jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import (Coefficient, Constant, Dx, FacetNormal, FunctionSpace, Identity, Mesh, TestFunction, TestFunctions, + TrialFunction, TrialFunctions, VectorConstant, avg, curl, div, dot, dS, ds, dx, grad, i, inner, j, + jump, lhs, rhs, sqrt, tetrahedron, triangle) from ufl.algorithms import compute_form_data +from ufl.finiteelement import FiniteElement, MixedElement +from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback +from ufl.sobolevspace import H1, L2, HCurl, HDiv p = pickle.HIGHEST_PROTOCOL def testConstant(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -44,8 +47,8 @@ def testConstant(): def testElasticity(): - element = VectorElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -65,8 +68,8 @@ def eps(v): def testEnergyNorm(): - element = FiniteElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) @@ -79,8 +82,8 @@ def testEnergyNorm(): def testEquation(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 @@ -104,8 +107,8 @@ def testEquation(): def testFunctionOperators(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -125,8 +128,8 @@ def testFunctionOperators(): def testHeat(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -149,8 +152,8 @@ def testHeat(): def testMass(): - element = FiniteElement("Lagrange", "tetrahedron", 3) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -165,9 +168,9 @@ def testMass(): def testMixedMixedElement(): - P3 = FiniteElement("Lagrange", "triangle", 3) + P3 = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) - element = (P3 * P3) * (P3 * P3) + element = MixedElement([[P3, P3], [P3, P3]]) element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) @@ -178,11 +181,11 @@ def testMixedMixedElement(): def testMixedPoisson(): q = 1 - BDM = FiniteElement("Brezzi-Douglas-Marini", "triangle", q) - DG = FiniteElement("Discontinuous Lagrange", "triangle", q - 1) + BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2, ), contravariant_piola, HDiv) + DG = FiniteElement("Discontinuous Lagrange", triangle, q - 1, (), identity_pullback, L2) - mixed_element = BDM * DG - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + mixed_element = MixedElement([BDM, DG]) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) mixed_space = FunctionSpace(domain, mixed_element) dg_space = FunctionSpace(domain, DG) @@ -204,8 +207,8 @@ def testMixedPoisson(): def testNavierStokes(): - element = VectorElement("Lagrange", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -223,8 +226,8 @@ def testNavierStokes(): def testNeumannProblem(): - element = VectorElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -248,8 +251,8 @@ def testNeumannProblem(): def testOptimization(): - element = FiniteElement("Lagrange", "triangle", 3) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -269,7 +272,7 @@ def testOptimization(): def testP5tet(): - element = FiniteElement("Lagrange", tetrahedron, 5) + element = FiniteElement("Lagrange", tetrahedron, 5, (), identity_pullback, H1) element_pickle = pickle.dumps(element, p) element_restore = pickle.loads(element_pickle) @@ -278,15 +281,15 @@ def testP5tet(): def testP5tri(): - element = FiniteElement("Lagrange", triangle, 5) + element = FiniteElement("Lagrange", triangle, 5, (), identity_pullback, H1) element_pickle = pickle.dumps(element, p) pickle.loads(element_pickle) def testPoissonDG(): - element = FiniteElement("Discontinuous Lagrange", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Discontinuous Lagrange", triangle, 1, (), identity_pullback, L2) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -332,8 +335,8 @@ def testPoissonDG(): def testPoisson(): - element = FiniteElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -354,8 +357,8 @@ def testPoisson(): def testPoissonSystem(): - element = VectorElement("Lagrange", "triangle", 1) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -378,16 +381,16 @@ def testPoissonSystem(): def testQuadratureElement(): - element = FiniteElement("Lagrange", "triangle", 2) + element = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) # FFC notation: - # QE = QuadratureElement("triangle", 3) - # sig = VectorQuadratureElement("triangle", 3) + # QE = QuadratureElement(triangle, 3) + # sig = VectorQuadratureElement(triangle, 3) - QE = FiniteElement("Quadrature", "triangle", 3) - sig = VectorElement("Quadrature", "triangle", 3) + QE = FiniteElement("Quadrature", triangle, 3, (), identity_pullback, L2) + sig = FiniteElement("Quadrature", triangle, 3, (2, ), identity_pullback, L2) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) qe_space = FunctionSpace(domain, QE) sig_space = FunctionSpace(domain, sig) @@ -414,11 +417,11 @@ def testQuadratureElement(): def testStokes(): # UFLException: Shape mismatch in sum. - P2 = VectorElement("Lagrange", "triangle", 2) - P1 = FiniteElement("Lagrange", "triangle", 1) - TH = P2 * P1 + P2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) + P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + TH = MixedElement([P2, P1]) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) th_space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) @@ -443,8 +446,8 @@ def testStokes(): def testSubDomain(): - element = FiniteElement("CG", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) @@ -458,8 +461,8 @@ def testSubDomain(): def testSubDomains(): - element = FiniteElement("CG", "tetrahedron", 1) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -476,8 +479,8 @@ def testSubDomains(): def testTensorWeightedPoisson(): # FFC notation: - # P1 = FiniteElement("Lagrange", "triangle", 1) - # P0 = FiniteElement("Discontinuous Lagrange", "triangle", 0) + # P1 = FiniteElement("Lagrange", triangle, 1) + # P0 = FiniteElement("Discontinuous Lagrange", triangle, 0) # # v = TestFunction(P1) # u = TrialFunction(P1) @@ -492,10 +495,10 @@ def testTensorWeightedPoisson(): # # a = dot(grad(v), mult(C, grad(u)))*dx - P1 = FiniteElement("Lagrange", "triangle", 1) - P0 = TensorElement("Discontinuous Lagrange", "triangle", 0, shape=(2, 2)) + P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) - domain = Mesh(VectorElement("Lagrange", "triangle", 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) @@ -527,18 +530,19 @@ def HodgeLaplaceGradCurl(space, fspace): return [a, L] - shape = "tetrahedron" + shape = tetrahedron order = 1 - GRAD = FiniteElement("Lagrange", shape, order) + GRAD = FiniteElement("Lagrange", shape, order, (), identity_pullback, H1) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) - CURL = FiniteElement("N1curl", shape, order) + CURL = FiniteElement("N1curl", shape, order, (3, ), covariant_piola, HCurl) - VectorLagrange = VectorElement("Lagrange", shape, order + 1) - domain = Mesh(VectorElement("Lagrange", shape, 1)) + VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", shape, 1, (3, ), identity_pullback, H1)) - [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, GRAD * CURL), FunctionSpace(domain, VectorLagrange)) + [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, MixedElement([GRAD, CURL])), + FunctionSpace(domain, VectorLagrange)) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) @@ -557,8 +561,8 @@ def testIdentity(): def testFormData(): - element = FiniteElement("Lagrange", "tetrahedron", 3) - domain = Mesh(VectorElement("Lagrange", "tetrahedron", 1)) + element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index 0536b62b1..d5d419d37 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -3,11 +3,14 @@ import pytest from ufl import (Cell, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FiniteElement, FunctionSpace, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, - MinFacetEdgeLength, SpatialCoordinate, TestFunction, VectorElement, hexahedron, interval, - quadrilateral, tetrahedron, triangle) + FunctionSpace, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, + MinFacetEdgeLength, SpatialCoordinate, TestFunction, hexahedron, interval, quadrilateral, tetrahedron, + triangle) from ufl.checks import is_cellwise_constant from ufl.classes import CellCoordinate, FacetJacobian, FacetJacobianDeterminant, FacetJacobianInverse +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2, HInf def get_domains(): @@ -19,13 +22,15 @@ def get_domains(): tetrahedron, hexahedron, ] - return [Mesh(cell) for cell in all_cells] + return [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1)) for cell in all_cells] def get_nonlinear(): domains_with_quadratic_coordinates = [] for D in get_domains(): - V = VectorElement("CG", D.ufl_cell(), 2) + V = FiniteElement("Lagrange", D.ufl_cell(), 2, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) domains_with_quadratic_coordinates.append(E) @@ -48,7 +53,8 @@ def domains(request): domains = get_domains() domains_with_linear_coordinates = [] for D in domains: - V = VectorElement("CG", D.ufl_cell(), 1) + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) domains_with_linear_coordinates.append(E) @@ -63,11 +69,14 @@ def affine_domains(request): triangle, tetrahedron, ] - affine_domains = [Mesh(cell) for cell in affine_cells] + affine_domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1)) + for cell in affine_cells] affine_domains_with_linear_coordinates = [] for D in affine_domains: - V = VectorElement("CG", D.ufl_cell(), 1) + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) affine_domains_with_linear_coordinates.append(E) @@ -84,10 +93,13 @@ def affine_facet_domains(request): quadrilateral, tetrahedron, ] - affine_facet_domains = [Mesh(cell) for cell in affine_facet_cells] + affine_facet_domains = [Mesh(FiniteElement( + "Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1)) for cell in affine_facet_cells] affine_facet_domains_with_linear_coordinates = [] for D in affine_facet_domains: - V = VectorElement("CG", D.ufl_cell(), 1) + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) affine_facet_domains_with_linear_coordinates.append(E) @@ -103,10 +115,13 @@ def nonaffine_domains(request): quadrilateral, hexahedron, ] - nonaffine_domains = [Mesh(cell) for cell in nonaffine_cells] + nonaffine_domains = [Mesh(FiniteElement( + "Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1)) for cell in nonaffine_cells] nonaffine_domains_with_linear_coordinates = [] for D in nonaffine_domains: - V = VectorElement("CG", D.ufl_cell(), 1) + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) nonaffine_domains_with_linear_coordinates.append(E) @@ -121,10 +136,13 @@ def nonaffine_facet_domains(request): nonaffine_facet_cells = [ hexahedron, ] - nonaffine_facet_domains = [Mesh(cell) for cell in nonaffine_facet_cells] + nonaffine_facet_domains = [Mesh(FiniteElement( + "Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1)) for cell in nonaffine_facet_cells] nonaffine_facet_domains_with_linear_coordinates = [] for D in nonaffine_facet_domains: - V = VectorElement("CG", D.ufl_cell(), 1) + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + identity_pullback, H1) E = Mesh(V) nonaffine_facet_domains_with_linear_coordinates.append(E) @@ -159,7 +177,7 @@ def test_coordinates_never_cellwise_constant(domains): def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: - domains = Mesh(Cell("vertex", 3)) + domains = Mesh(FiniteElement("Lagrange", Cell("vertex", 3), 1, (3, ), identity_pullback, H1)) assert domains.ufl_cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert is_cellwise_constant(e) @@ -216,12 +234,14 @@ 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) - domain = Mesh(VectorElement("Lagrange", domains_not_linear.ufl_cell(), 1)) + V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 0, (), identity_pullback, L2) + d = domains_not_linear.ufl_cell().geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) e = Coefficient(space) assert is_cellwise_constant(e) - V = FiniteElement("R", domains_not_linear.ufl_cell(), 0) + + V = FiniteElement("Real", domains_not_linear.ufl_cell(), 0, (), identity_pullback, HInf) space = FunctionSpace(domain, V) e = Coefficient(space) assert is_cellwise_constant(e) @@ -235,8 +255,9 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): - V = FiniteElement("DG", domains_not_linear.ufl_cell(), 1) - domain = Mesh(VectorElement("Lagrange", domains_not_linear.ufl_cell(), 1)) + V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 1, (), identity_pullback, L2) + d = domains_not_linear.ufl_cell().geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) e = Coefficient(space) assert not is_cellwise_constant(e) diff --git a/test/test_reference_shapes.py b/test/test_reference_shapes.py index db4ac3313..12c5028be 100755 --- a/test/test_reference_shapes.py +++ b/test/test_reference_shapes.py @@ -1,4 +1,7 @@ -from ufl import Cell, FiniteElement, MixedElement, TensorElement, VectorElement +from ufl import Cell +from ufl.finiteelement import FiniteElement, MixedElement, SymmetricElement +from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback +from ufl.sobolevspace import H1, HCurl, HDiv def test_reference_shapes(): @@ -6,30 +9,32 @@ def test_reference_shapes(): cell = Cell("triangle", 3) - V = FiniteElement("N1curl", cell, 1) - assert V.value_shape() == (3,) - assert V.reference_value_shape() == (2,) + V = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + assert V.value_shape == (3,) + assert V.reference_value_shape == (2,) - U = FiniteElement("RT", cell, 1) - assert U.value_shape() == (3,) - assert U.reference_value_shape() == (2,) + U = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) + assert U.value_shape == (3,) + assert U.reference_value_shape == (2,) - W = FiniteElement("CG", cell, 1) - assert W.value_shape() == () - assert W.reference_value_shape() == () + W = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + assert W.value_shape == () + assert W.reference_value_shape == () - Q = VectorElement("CG", cell, 1) - assert Q.value_shape() == (3,) - assert Q.reference_value_shape() == (3,) + Q = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) + assert Q.value_shape == (3,) + assert Q.reference_value_shape == (3,) - T = TensorElement("CG", cell, 1) - assert T.value_shape() == (3, 3) - assert T.reference_value_shape() == (3, 3) + T = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) + assert T.value_shape == (3, 3) + assert T.reference_value_shape == (3, 3) - S = TensorElement("CG", cell, 1, symmetry=True) - assert S.value_shape() == (3, 3) - assert S.reference_value_shape() == (6,) + S = SymmetricElement( + {(0, 0): 0, (1, 0): 1, (2, 0): 2, (0, 1): 1, (1, 1): 3, (2, 1): 4, (0, 2): 2, (1, 2): 4, (2, 2): 5}, + [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)]) + assert S.value_shape == (3, 3) + assert S.reference_value_shape == (6,) - M = MixedElement(V, U, W) - assert M.value_shape() == (7,) - assert M.reference_value_shape() == (5,) + M = MixedElement([V, U, W]) + assert M.value_shape == (7,) + assert M.reference_value_shape == (5,) diff --git a/test/test_scratch.py b/test/test_scratch.py index 1ccceecc0..32ced3346 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -8,9 +8,12 @@ import warnings -from ufl import (Coefficient, FiniteElement, FunctionSpace, Identity, Mesh, TensorElement, TestFunction, VectorElement, - as_matrix, as_tensor, as_vector, dx, grad, indices, inner, outer, triangle) +from ufl import (Coefficient, FunctionSpace, Identity, Mesh, TestFunction, as_matrix, as_tensor, as_vector, dx, grad, + indices, inner, outer, triangle) from ufl.classes import FixedIndex, FormArgument, Grad, Indexed, ListTensor, Zero +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 from ufl.tensors import as_scalar, unit_indexed_tensor, unwrap_list_tensor @@ -210,8 +213,8 @@ def test_unwrap_list_tensor(self): def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): - U = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + U = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, U) u = Coefficient(space) du = TestFunction(space) @@ -236,8 +239,8 @@ def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient(self): - V = VectorElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -262,8 +265,8 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation(self): - V = VectorElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -319,8 +322,8 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation_in_list(self): - V = VectorElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -376,8 +379,8 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): - W = TensorElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + W = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, W) w = Coefficient(space) dw = TestFunction(space) @@ -402,8 +405,8 @@ def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): def test__forward_coefficient_ad__grad_of_tensor_coefficient__with_component_variation(self): - W = TensorElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + W = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, W) w = Coefficient(space) dw = TestFunction(space) diff --git a/test/test_signature.py b/test/test_signature.py index b0c6cd3d9..4e270d500 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -1,11 +1,13 @@ """Test the computation of form signatures.""" -from ufl import (Argument, CellDiameter, CellVolume, Circumradius, Coefficient, FacetArea, FacetNormal, FiniteElement, - FunctionSpace, Identity, Mesh, SpatialCoordinate, TensorElement, TestFunction, VectorElement, - as_vector, diff, dot, ds, dx, hexahedron, indices, inner, interval, quadrilateral, tetrahedron, - triangle, variable) +from ufl import (Argument, CellDiameter, CellVolume, Circumradius, Coefficient, FacetArea, FacetNormal, FunctionSpace, + Identity, Mesh, SpatialCoordinate, TestFunction, as_vector, diff, dot, ds, dx, hexahedron, indices, + inner, interval, quadrilateral, tetrahedron, triangle, variable) from ufl.algorithms.signature import compute_multiindex_hashdata, compute_terminal_hashdata from ufl.classes import FixedIndex, MultiIndex +from ufl.finiteelement import FiniteElement, SymmetricElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1, L2 # TODO: Test compute_terminal_hashdata # TODO: Check that form argument counts only affect the sig by their relative ordering @@ -19,7 +21,8 @@ def domain_numbering(*cells): renumbering = {} for i, cell in enumerate(cells): - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) renumbering[domain] = i return renumbering @@ -57,7 +60,7 @@ def test_terminal_hashdata_depends_on_literals(self): def forms(): i, j = indices(2) for d, cell in [(2, triangle), (3, tetrahedron)]: - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=d-2) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=d-2) x = SpatialCoordinate(domain) ident = Identity(d) for fv in (1.1, 2.2): @@ -85,9 +88,9 @@ def forms(): i, j = indices(2) cells = (triangle, tetrahedron) for i, cell in enumerate(cells): - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) - d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + x = SpatialCoordinate(domain) n = FacetNormal(domain) h = CellDiameter(domain) @@ -119,28 +122,38 @@ def forms(): def test_terminal_hashdata_depends_on_form_argument_properties(self): reprs = set() hashes = set() - nelm = 6 + nelm = 5 nreps = 2 # Data cells = (triangle, tetrahedron) degrees = (1, 2) - families = ("CG", "Lagrange", "DG") + families = (("Lagrange", H1), ("Lagrange", H1), ("Discontinuous Lagrange", L2)) def forms(): for rep in range(nreps): for i, cell in enumerate(cells): d = cell.geometric_dimension() - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for degree in degrees: - for family in families: - V = FiniteElement(family, cell, degree) - W = VectorElement(family, cell, degree) - W2 = VectorElement(family, cell, degree, dim=d+1) - T = TensorElement(family, cell, degree) - S = TensorElement(family, cell, degree, symmetry=True) - S2 = TensorElement(family, cell, degree, shape=(d, d), symmetry={(0, 0): (1, 1)}) - elements = [V, W, W2, T, S, S2] + for family, sobolev in families: + V = FiniteElement(family, cell, degree, (), identity_pullback, sobolev) + W = FiniteElement(family, cell, degree, (d, ), identity_pullback, sobolev) + W2 = FiniteElement(family, cell, degree, (d+1, ), identity_pullback, sobolev) + T = FiniteElement(family, cell, degree, (d, d), identity_pullback, sobolev) + if d == 2: + S = SymmetricElement( + {(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, + [FiniteElement(family, cell, degree, (), identity_pullback, sobolev) + for _ in range(3)]) + else: + assert d == 3 + S = SymmetricElement( + {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, (1, 1): 3, + (1, 2): 4, (2, 0): 2, (2, 1): 4, (2, 2): 5}, + [FiniteElement(family, cell, degree, (), identity_pullback, sobolev) + for _ in range(6)]) + elements = [V, W, W2, T, S] assert len(elements) == nelm for H in elements[:nelm]: @@ -158,11 +171,10 @@ def forms(): yield compute_terminal_hashdata(expr, renumbering) c, d, r, h = compute_unique_terminal_hashdatas(forms()) - c1 = nreps * len(cells) * len(degrees) * len(families) * nelm * 2 # Number of cases with repetitions + c1 = nreps * len(cells) * len(degrees) * len(families) * nelm * 2 assert c == c1 - c0 = len(cells) * len(degrees) * (len(families)-1) * nelm * 2 # Number of unique cases, "CG" == "Lagrange" - # c0 = len(cells) * len(degrees) * (len(families)) * nelm * 2 # Number of unique cases, "CG" != "Lagrange" + c0 = len(cells) * len(degrees) * (len(families)-1) * nelm * 2 assert d == c0 assert r == c0 assert h == c0 @@ -181,9 +193,10 @@ def test_terminal_hashdata_does_not_depend_on_coefficient_count_values_only_orde def forms(): for rep in range(nreps): for i, cell in enumerate(cells): - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in counts: - V = FiniteElement("CG", cell, 2) + V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) space = FunctionSpace(domain, V) f = Coefficient(space, count=k) g = Coefficient(space, count=k+2) @@ -219,9 +232,10 @@ def test_terminal_hashdata_does_depend_on_argument_number_values(self): def forms(): for rep in range(nreps): for i, cell in enumerate(cells): - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in counts: - V = FiniteElement("CG", cell, 2) + V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) space = FunctionSpace(domain, V) f = Argument(space, k) g = Argument(space, k+2) @@ -248,7 +262,8 @@ def test_domain_signature_data_does_not_depend_on_domain_label_value(self): s1s = set() s2s = set() for i, cell in enumerate(cells): - domain = VectorElement("Lagrange", cell, 1) + d = cell.geometric_dimension() + domain = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) d0 = Mesh(domain) d1 = Mesh(domain, ufl_id=1) d2 = Mesh(domain, ufl_id=2) @@ -270,14 +285,17 @@ def test_terminal_hashdata_does_not_depend_on_domain_label_value(self): hashes = set() ufl_ids = [1, 2] cells = [triangle, quadrilateral] - domains = [Mesh(cell, ufl_id=ufl_id) for cell in cells for ufl_id in ufl_ids] + domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1), + ufl_id=ufl_id) for cell in cells for ufl_id in ufl_ids] nreps = 2 num_exprs = 2 def forms(): for rep in range(nreps): for domain in domains: - V = FunctionSpace(domain, FiniteElement("CG", domain.ufl_cell(), 2)) + V = FunctionSpace(domain, FiniteElement("Lagrange", domain.ufl_cell(), 2, (), + identity_pullback, H1)) f = Coefficient(V, count=0) v = TestFunction(V) x = SpatialCoordinate(domain) @@ -416,11 +434,12 @@ def check_unique_signatures(forms): def test_signature_is_affected_by_element_properties(self): def forms(): - for family in ("CG", "DG"): + for family, sobolev in (("Lagrange", H1), ("Discontinuous Lagrange", L2)): for cell in (triangle, tetrahedron, quadrilateral): - domain = Mesh(VectorElement("Lagrange", cell, 1)) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) for degree in (1, 2): - V = FiniteElement(family, cell, degree) + V = FiniteElement(family, cell, degree, (), identity_pullback, sobolev) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) @@ -435,11 +454,12 @@ def forms(): def test_signature_is_affected_by_domains(self): def forms(): for cell in (triangle, tetrahedron): - domain = Mesh(VectorElement("Lagrange", cell, 1)) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) for di in (1, 2): for dj in (1, 2): for dk in (1, 2): - V = FiniteElement("CG", cell, 1) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, V) u = Coefficient(space) a = u*dx(di) + 2*u*dx(dj) + 3*u*ds(dk) @@ -450,10 +470,12 @@ def forms(): def test_signature_of_forms_with_diff(self): def forms(): for i, cell in enumerate([triangle, tetrahedron]): - domain = Mesh(VectorElement("Lagrange", cell, 1), ufl_id=i) + d = cell.geometric_dimension() + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in (1, 2, 3): - V = FiniteElement("CG", cell, 1) - W = VectorElement("CG", cell, 1) + d = cell.geometric_dimension() + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) u = Coefficient(v_space) @@ -470,8 +492,8 @@ def forms(): def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self): cell = triangle - V = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) g = Coefficient(space) @@ -486,8 +508,9 @@ def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self def test_signature_of_forms_change_with_operators(self): def forms(): for cell in (triangle, tetrahedron): - V = FiniteElement("CG", cell, 1) - domain = Mesh(VectorElement("Lagrange", cell, 1)) + d = cell.geometric_dimension() + V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = Coefficient(space) @@ -495,8 +518,8 @@ def forms(): (u+v)+(u/v), (u+v)*(u/v), (u*v)*(u*v), - (u+v)*(u*v), # (!) same - # (u*v)*(u+v), # (!) same + (u+v)*(u*v), # H1 same + # (u*v)*(u+v), # H1 same (u*v)+(u+v), ] for f in fs: diff --git a/test/test_simplify.py b/test/test_simplify.py index faa9d4e6e..66a176d9d 100755 --- a/test/test_simplify.py +++ b/test/test_simplify.py @@ -1,15 +1,17 @@ import math -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, - VectorElement, acos, as_tensor, as_ufl, asin, atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, - outer, sin, sinh, tan, tanh, triangle) +from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, acos, as_tensor, as_ufl, + asin, atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, outer, sin, sinh, tan, tanh, triangle) from ufl.algorithms import compute_form_data +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def xtest_zero_times_argument(self): # FIXME: Allow zero forms - element = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -22,8 +24,8 @@ def xtest_zero_times_argument(self): def test_divisions(self): - element = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) @@ -49,8 +51,8 @@ def test_divisions(self): def test_products(self): - element = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) g = Coefficient(space) @@ -71,8 +73,8 @@ def test_products(self): def test_sums(self): - element = FiniteElement("CG", triangle, 1) - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) g = Coefficient(space) @@ -122,7 +124,7 @@ def test_mathfunctions(self): def test_indexing(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) u = VectorConstant(domain) v = VectorConstant(domain) diff --git a/test/test_sobolevspace.py b/test/test_sobolevspace.py index ba4eee118..28e42a24d 100755 --- a/test/test_sobolevspace.py +++ b/test/test_sobolevspace.py @@ -3,8 +3,9 @@ from math import inf -from ufl import (H1, H2, L2, EnrichedElement, FiniteElement, HCurl, HDiv, HInf, TensorProductElement, interval, - quadrilateral, triangle) +from ufl import H1, H2, L2, HCurl, HDiv, HInf, triangle +from ufl.finiteelement import FiniteElement +from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback from ufl.sobolevspace import SobolevSpace # noqa: F401 from ufl.sobolevspace import DirectionalSobolevSpace @@ -62,18 +63,9 @@ def xtest_contains_mixed(): def test_contains_l2(): l2_elements = [ - FiniteElement("DG", triangle, 0), - FiniteElement("DG", triangle, 1), - FiniteElement("DG", triangle, 2), - FiniteElement("CR", triangle, 1), - # Tensor product elements: - TensorProductElement(FiniteElement("DG", interval, 1), - FiniteElement("DG", interval, 1)), - TensorProductElement(FiniteElement("DG", interval, 1), - FiniteElement("CG", interval, 2)), - # Enriched element: - EnrichedElement(FiniteElement("DG", triangle, 1), - FiniteElement("B", triangle, 3)) + FiniteElement("Discontinuous Lagrange", triangle, 0, (), identity_pullback, L2), + FiniteElement("Discontinuous Lagrange", triangle, 1, (), identity_pullback, L2), + FiniteElement("Discontinuous Lagrange", triangle, 2, (), identity_pullback, L2), ] for l2_element in l2_elements: assert l2_element in L2 @@ -89,19 +81,11 @@ def test_contains_l2(): def test_contains_h1(): h1_elements = [ # Standard Lagrange elements: - FiniteElement("CG", triangle, 1), - FiniteElement("CG", triangle, 2), + FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1), + FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1), # Some special elements: - FiniteElement("HER", triangle), - FiniteElement("MTW", triangle), - # Tensor product elements: - TensorProductElement(FiniteElement("CG", interval, 1), - FiniteElement("CG", interval, 1)), - TensorProductElement(FiniteElement("CG", interval, 2), - FiniteElement("CG", interval, 2)), - # Enriched elements: - EnrichedElement(FiniteElement("CG", triangle, 2), - FiniteElement("B", triangle, 3)) + FiniteElement("MTW", triangle, 3, (2, ), contravariant_piola, H1), + FiniteElement("Hermite", triangle, 3, (), "custom", H1), ] for h1_element in h1_elements: assert h1_element in H1 @@ -116,8 +100,8 @@ def test_contains_h1(): def test_contains_h2(): h2_elements = [ - FiniteElement("ARG", triangle, 5), - FiniteElement("MOR", triangle, 2), + FiniteElement("ARG", triangle, 5, (), "custom", H2), + FiniteElement("MOR", triangle, 2, (), "custom", H2), ] for h2_element in h2_elements: assert h2_element in H2 @@ -132,7 +116,7 @@ def test_contains_h2(): def test_contains_hinf(): hinf_elements = [ - FiniteElement("R", triangle, 0) + FiniteElement("Real", triangle, 0, (), identity_pullback, HInf) ] for hinf_element in hinf_elements: assert hinf_element in HInf @@ -148,16 +132,9 @@ def test_contains_hinf(): def test_contains_hdiv(): hdiv_elements = [ - FiniteElement("RT", triangle, 1), - FiniteElement("BDM", triangle, 1), - FiniteElement("BDFM", triangle, 2), - # HDiv elements: - HDiv(TensorProductElement(FiniteElement("DG", triangle, 1), - FiniteElement("CG", interval, 2))), - HDiv(TensorProductElement(FiniteElement("RT", triangle, 1), - FiniteElement("DG", interval, 1))), - HDiv(TensorProductElement(FiniteElement("N1curl", triangle, 1), - FiniteElement("DG", interval, 1))) + FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv), + FiniteElement("BDM", triangle, 1, (2, ), contravariant_piola, HDiv), + FiniteElement("BDFM", triangle, 2, (2, ), contravariant_piola, HDiv), ] for hdiv_element in hdiv_elements: assert hdiv_element in HDiv @@ -172,15 +149,8 @@ def test_contains_hdiv(): def test_contains_hcurl(): hcurl_elements = [ - FiniteElement("N1curl", triangle, 1), - FiniteElement("N2curl", triangle, 1), - # HCurl elements: - HCurl(TensorProductElement(FiniteElement("CG", triangle, 1), - FiniteElement("DG", interval, 1))), - HCurl(TensorProductElement(FiniteElement("N1curl", triangle, 1), - FiniteElement("CG", interval, 1))), - HCurl(TensorProductElement(FiniteElement("RT", triangle, 1), - FiniteElement("CG", interval, 1))) + FiniteElement("N1curl", triangle, 1, (2, ), covariant_piola, HCurl), + FiniteElement("N2curl", triangle, 1, (2, ), covariant_piola, HCurl), ] for hcurl_element in hcurl_elements: assert hcurl_element in HCurl @@ -191,79 +161,3 @@ def test_contains_hcurl(): assert hcurl_element not in HDiv assert hcurl_element not in H2 assert hcurl_element not in H2dx2dy - - -def test_enriched_elements_hdiv(): - A = FiniteElement("CG", interval, 1) - B = FiniteElement("DG", interval, 0) - AxB = TensorProductElement(A, B) - BxA = TensorProductElement(B, A) - C = FiniteElement("RTCF", quadrilateral, 1) - D = FiniteElement("DQ", quadrilateral, 0) - Q1 = TensorProductElement(C, B) - Q2 = TensorProductElement(D, A) - hdiv_elements = [ - EnrichedElement(HDiv(AxB), HDiv(BxA)), - EnrichedElement(HDiv(Q1), HDiv(Q2)) - ] - for hdiv_element in hdiv_elements: - assert hdiv_element in HDiv - assert hdiv_element in L2 - assert hdiv_element in H0dx0dy - assert hdiv_element not in H1 - assert hdiv_element not in H1dx1dy - assert hdiv_element not in HCurl - assert hdiv_element not in H2 - assert hdiv_element not in H2dx2dy - - -def test_enriched_elements_hcurl(): - A = FiniteElement("CG", interval, 1) - B = FiniteElement("DG", interval, 0) - AxB = TensorProductElement(A, B) - BxA = TensorProductElement(B, A) - C = FiniteElement("RTCE", quadrilateral, 1) - D = FiniteElement("DQ", quadrilateral, 0) - Q1 = TensorProductElement(C, B) - Q2 = TensorProductElement(D, A) - hcurl_elements = [ - EnrichedElement(HCurl(AxB), HCurl(BxA)), - EnrichedElement(HCurl(Q1), HCurl(Q2)) - ] - for hcurl_element in hcurl_elements: - assert hcurl_element in HCurl - assert hcurl_element in L2 - assert hcurl_element in H0dx0dy - assert hcurl_element not in H1 - assert hcurl_element not in H1dx1dy - assert hcurl_element not in HDiv - assert hcurl_element not in H2 - assert hcurl_element not in H2dx2dy - - -def test_varying_continuity_elements(): - P1DG_t = FiniteElement("DG", triangle, 1) - P1DG_i = FiniteElement("DG", interval, 1) - P1 = FiniteElement("CG", interval, 1) - P2 = FiniteElement("CG", interval, 2) - P3 = FiniteElement("CG", interval, 3) - RT1 = FiniteElement("RT", triangle, 1) - ARG = FiniteElement("ARG", triangle, 5) - - # Tensor product elements - P1DGP2 = TensorProductElement(P1DG_t, P2) - P1P1DG = TensorProductElement(P1, P1DG_i) - P1DGP1 = TensorProductElement(P1DG_i, P1) - RT1DG1 = TensorProductElement(RT1, P1DG_i) - P2P3 = TensorProductElement(P2, P3) - ARGP3 = TensorProductElement(ARG, P3) - - assert P1DGP2 in H1dz and P1DGP2 in L2 - assert P1DGP2 not in H1dh - assert P1DGP1 in H1dy and P1DGP2 in L2 - assert P1P1DG in H1dx and P1P1DG in L2 - assert P1P1DG not in H1dx1dy - assert RT1DG1 in H000 and RT1DG1 in L2 - assert P2P3 in H1dx1dy and P2P3 in H1 - assert ARG in H2dx2dy - assert ARGP3 in H2dhH1dz diff --git a/test/test_split.py b/test/test_split.py index bce2dbf03..7a16e4a84 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -1,28 +1,31 @@ __authors__ = "Martin Sandve Alnæs" __date__ = "2009-03-14 -- 2009-03-14" -from ufl import (Coefficient, FiniteElement, FunctionSpace, Mesh, MixedElement, TensorElement, TestFunction, - VectorElement, as_vector, product, split, triangle) +from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, as_vector, product, split, triangle +from ufl.finiteelement import FiniteElement, MixedElement, SymmetricElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_split(self): cell = triangle - domain = Mesh(VectorElement("Lagrange", cell, 1)) d = cell.geometric_dimension() - f = FiniteElement("CG", cell, 1) - v = VectorElement("CG", cell, 1) - w = VectorElement("CG", cell, 1, dim=d+1) - t = TensorElement("CG", cell, 1) - s = TensorElement("CG", cell, 1, symmetry=True) - r = TensorElement("CG", cell, 1, symmetry={(1, 0): (0, 1)}, shape=(d, d)) - m = MixedElement(f, v, w, t, s, r) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + f = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + v = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1, + sub_elements=[f for _ in range(d)]) + w = FiniteElement("Lagrange", cell, 1, (d+1, ), identity_pullback, H1, + sub_elements=[f for _ in range(d + 1)]) + t = FiniteElement("Lagrange", cell, 1, (d, d), identity_pullback, H1, + sub_elements=[f for _ in range(d ** 2)]) + s = SymmetricElement({(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, [f for _ in range(3)]) + m = MixedElement([f, v, w, t, s, s]) f_space = FunctionSpace(domain, f) v_space = FunctionSpace(domain, v) w_space = FunctionSpace(domain, w) t_space = FunctionSpace(domain, t) s_space = FunctionSpace(domain, s) - r_space = FunctionSpace(domain, r) m_space = FunctionSpace(domain, m) # Check that shapes of all these functions are correct: @@ -31,7 +34,6 @@ def test_split(self): assert (d+1,) == Coefficient(w_space).ufl_shape assert (d, d) == Coefficient(t_space).ufl_shape assert (d, d) == Coefficient(s_space).ufl_shape - assert (d, d) == Coefficient(r_space).ufl_shape # sum of value sizes, not accounting for symmetries: assert (3*d*d + 2*d + 2,) == Coefficient(m_space).ufl_shape @@ -43,8 +45,8 @@ def test_split(self): assert s == 0 # Mixed elements of non-scalar subelements are flattened - v2 = MixedElement(v, v) - m2 = MixedElement(t, t) + v2 = MixedElement([v, v]) + m2 = MixedElement([t, t]) v2_space = FunctionSpace(domain, v2) m2_space = FunctionSpace(domain, m2) # assert d == 2 @@ -58,13 +60,13 @@ def test_split(self): # Split twice on nested mixed elements gets # the innermost scalar subcomponents - t = TestFunction(FunctionSpace(domain, f*v)) + t = TestFunction(FunctionSpace(domain, MixedElement([f, v]))) assert split(t) == (t[0], as_vector((t[1], t[2]))) assert split(split(t)[1]) == (t[1], t[2]) - t = TestFunction(FunctionSpace(domain, f*(f*v))) + t = TestFunction(FunctionSpace(domain, MixedElement([f, [f, v]]))) assert split(t) == (t[0], as_vector((t[1], t[2], t[3]))) assert split(split(t)[1]) == (t[1], as_vector((t[2], t[3]))) - t = TestFunction(FunctionSpace(domain, (v*f)*(f*v))) + t = TestFunction(FunctionSpace(domain, MixedElement([[v, f], [f, v]]))) assert split(t) == (as_vector((t[0], t[1], t[2])), as_vector((t[3], t[4], t[5]))) assert split(split(t)[0]) == (as_vector((t[0], t[1])), t[2]) diff --git a/test/test_str.py b/test/test_str.py index 96683ae38..df80d8215 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -1,6 +1,9 @@ -from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FiniteElement, FunctionSpace, Index, - Mesh, SpatialCoordinate, TestFunction, TrialFunction, VectorElement, as_matrix, as_ufl, as_vector, - quadrilateral, tetrahedron, triangle) +from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FunctionSpace, Index, Mesh, + SpatialCoordinate, TestFunction, TrialFunction, as_matrix, as_ufl, as_vector, quadrilateral, + tetrahedron, triangle) +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 def test_str_int_value(self): @@ -12,7 +15,7 @@ def test_str_float_value(self): def test_str_zero(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) assert str(as_ufl(0)) == "0" assert str(0*x) == "0 (shape (2,))" @@ -25,41 +28,41 @@ def test_str_index(self): def test_str_coordinate(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(SpatialCoordinate(domain)) == "x" assert str(SpatialCoordinate(domain)[0]) == "x[0]" def test_str_normal(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(FacetNormal(domain)) == "n" assert str(FacetNormal(domain)[0]) == "n[0]" def test_str_circumradius(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(Circumradius(domain)) == "circumradius" def test_str_diameter(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(CellDiameter(domain)) == "diameter" def test_str_facetarea(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(FacetArea(domain)) == "facetarea" def test_str_volume(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) assert str(CellVolume(domain)) == "volume" def test_str_scalar_argument(self): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) - v = TestFunction(FunctionSpace(domain, FiniteElement("CG", triangle, 1))) - u = TrialFunction(FunctionSpace(domain, FiniteElement("CG", triangle, 1))) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + v = TestFunction(FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) + u = TrialFunction(FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) assert str(v) == "v_0" assert str(u) == "v_1" @@ -72,21 +75,21 @@ def test_str_scalar_argument(self): def test_str_list_vector(): - domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) v = as_vector((x, y, z)) assert str(v) == ("[%s, %s, %s]" % (x, y, z)) def test_str_list_vector_with_zero(): - domain = Mesh(VectorElement("Lagrange", tetrahedron, 1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) v = as_vector((x, 0, 0)) assert str(v) == ("[%s, 0, 0]" % (x,)) def test_str_list_matrix(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x, y = SpatialCoordinate(domain) v = as_matrix(((2*x, 3*y), (4*x, 5*y))) @@ -98,7 +101,7 @@ def test_str_list_matrix(): def test_str_list_matrix_with_zero(): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) x, y = SpatialCoordinate(domain) v = as_matrix(((2*x, 3*y), (0, 0))) @@ -113,5 +116,6 @@ def test_str_list_matrix_with_zero(): def test_str_element(): - elem = FiniteElement("Q", quadrilateral, 1) + elem = FiniteElement("Q", quadrilateral, 1, (), identity_pullback, H1) + assert repr(elem) == "ufl.finiteelement.FiniteElement(\"Q\", quadrilateral, 1, (), IdentityPullback(), H1)" assert str(elem) == "" diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index 3e8a77608..9a74ac506 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -1,11 +1,13 @@ import gc import sys -from ufl import (Coefficient, Constant, FiniteElement, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, - inner, triangle) +from ufl import Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle from ufl.algorithms import replace_terminal_data, strip_terminal_data from ufl.core.ufl_id import attach_ufl_id from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 MIN_REF_COUNT = 2 """The minimum value returned by sys.getrefcount.""" @@ -51,8 +53,9 @@ def test_strip_form_arguments_strips_data_refs(): assert sys.getrefcount(const_data) == MIN_REF_COUNT cell = triangle - domain = AugmentedMesh(cell, data=mesh_data) - element = FiniteElement("Lagrange", cell, 1) + domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1), data=mesh_data) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) v = TestFunction(V) @@ -88,8 +91,9 @@ def test_strip_form_arguments_does_not_change_form(): const_data = object() cell = triangle - domain = AugmentedMesh(cell, data=mesh_data) - element = FiniteElement("Lagrange", cell, 1) + domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + identity_pullback, H1), data=mesh_data) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) v = TestFunction(V) diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index d070a3dd4..bdb6ad406 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -2,9 +2,12 @@ import pytest -from ufl import (FacetNormal, Mesh, VectorElement, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, - diag_vector, dot, inner, inv, outer, perp, skew, sym, tr, transpose, triangle, zero) +from ufl import (FacetNormal, Mesh, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, diag_vector, dot, + inner, inv, outer, perp, skew, sym, tr, transpose, triangle, zero) from ufl.algorithms.remove_complex_nodes import remove_complex_nodes +from ufl.finiteelement import FiniteElement +from ufl.pullback import identity_pullback +from ufl.sobolevspace import H1 @pytest.fixture(scope="module") @@ -63,7 +66,7 @@ def test_inner(self, A, B, u, v): def test_pow2_inner(self, A, u): - domain = Mesh(VectorElement("Lagrange", triangle, 1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) f = FacetNormal(domain)[0] f2 = f*f assert f2 == remove_complex_nodes(inner(f, f)) diff --git a/ufl/__init__.py b/ufl/__init__.py index 40821e8b7..59c19e61f 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -63,7 +63,6 @@ - AbstractDomain - Mesh - MeshView - - TensorProductMesh * Sobolev spaces:: @@ -76,19 +75,15 @@ - HEin - HDivDiv -* Elements:: - - FiniteElement - - MixedElement - - VectorElement - - TensorElement - - EnrichedElement - - NodalEnrichedElement - - RestrictedElement - - TensorProductElement - - HDivElement - - HCurlElement - - BrokenElement +* Pull backs:: + + - identity_pullback + - contravariant_piola + - covariant_piola + - l2_piola + - double_contravariant_piola + - double_covariant_piola * Function spaces:: @@ -248,135 +243,62 @@ __version__ = importlib.metadata.version("fenics-ufl") -# README -# Imports here should be what the user sees -# which means we should _not_ import f.ex. "Grad", but "grad". -# This way we expose the language, the operation "grad", but less -# of the implementation, the particular class "Grad". -########## - -# Utility functions (product is the counterpart of the built-in -# python function sum, can be useful for users as well?) -from ufl.utils.sequences import product - -# Types for geometric quantities - -from ufl.cell import as_cell, AbstractCell, Cell, TensorProductCell -from ufl.domain import as_domain, AbstractDomain, Mesh, MeshView, TensorProductMesh -from ufl.geometry import ( - SpatialCoordinate, - FacetNormal, CellNormal, - CellVolume, CellDiameter, Circumradius, MinCellEdgeLength, MaxCellEdgeLength, - FacetArea, MinFacetEdgeLength, MaxFacetEdgeLength, - Jacobian, JacobianDeterminant, JacobianInverse -) - -# Sobolev spaces -from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf - -# Finite elements classes -from ufl.finiteelement import ( - FiniteElementBase, FiniteElement, MixedElement, VectorElement, TensorElement, EnrichedElement, - NodalEnrichedElement, RestrictedElement, TensorProductElement, HDivElement, HCurlElement, BrokenElement, - WithMapping) - -# Hook to extend predefined element families -from ufl.finiteelement.elementlist import register_element, show_elements - -# Function spaces -from ufl.functionspace import FunctionSpace, MixedFunctionSpace - -# Arguments -from ufl.argument import Argument, Coargument, TestFunction, TrialFunction, Arguments, TestFunctions, TrialFunctions - -# Coefficients -from ufl.coefficient import Coefficient, Cofunction, Coefficients -from ufl.constant import Constant, VectorConstant, TensorConstant - -# Matrices -from ufl.matrix import Matrix - -# Adjoints -from ufl.adjoint import Adjoint +from math import e, pi -# Actions +import ufl.exproperators as __exproperators from ufl.action import Action - -# Interpolates +from ufl.adjoint import Adjoint +from ufl.argument import Argument, Arguments, Coargument, TestFunction, TestFunctions, TrialFunction, TrialFunctions +from ufl.cell import AbstractCell, Cell, TensorProductCell, as_cell +from ufl.coefficient import Coefficient, Coefficients, Cofunction +from ufl.constant import Constant, TensorConstant, VectorConstant +from ufl.constantvalue import Identity, PermutationSymbol, as_ufl, zero +from ufl.core.external_operator import ExternalOperator from ufl.core.interpolate import Interpolate, interpolate - -# Split function -from ufl.split_functions import split - -# Literal constants -from ufl.constantvalue import PermutationSymbol, Identity, zero, as_ufl - -# Indexing of tensor expressions from ufl.core.multiindex import Index, indices - -# Special functions for expression base classes -# (ensure this is imported, since it attaches operators to Expr) -import ufl.exproperators as __exproperators # noqa: F401 - -# Containers for expressions with value rank > 0 -from ufl.tensors import as_tensor, as_vector, as_matrix -from ufl.tensors import unit_vector, unit_vectors, unit_matrix, unit_matrices - -# Operators -from ufl.operators import ( - rank, shape, conj, real, imag, outer, inner, dot, cross, perp, - det, inv, cofac, transpose, tr, diag, diag_vector, dev, skew, sym, - sqrt, exp, ln, erf, cos, sin, tan, acos, asin, atan, atan2, cosh, sinh, tanh, - bessel_J, bessel_Y, bessel_I, bessel_K, eq, ne, le, ge, lt, gt, And, Or, Not, - conditional, sign, max_value, min_value, variable, diff, - Dx, grad, div, curl, rot, nabla_grad, nabla_div, Dn, exterior_derivative, - jump, avg, cell_avg, facet_avg, elem_mult, elem_div, elem_pow, elem_op) - -# External Operator -from ufl.core.external_operator import ExternalOperator - -# Measure classes -from ufl.measure import Measure, register_integral_type, integral_types, custom_integral_types - -# Form class -from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm - -# Integral classes +from ufl.domain import AbstractDomain, Mesh, MeshView +from ufl.finiteelement import AbstractFiniteElement +from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm +from ufl.formoperators import (action, adjoint, derivative, energy_norm, extract_blocks, functional, lhs, replace, rhs, + sensitivity_rhs, system) +from ufl.functionspace import FunctionSpace, MixedFunctionSpace +from ufl.geometry import (CellDiameter, CellNormal, CellVolume, Circumradius, FacetArea, FacetNormal, Jacobian, + JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, MaxFacetEdgeLength, + MinCellEdgeLength, MinFacetEdgeLength, SpatialCoordinate) from ufl.integral import Integral - -# Representations of transformed forms -from ufl.formoperators import (replace, derivative, action, energy_norm, rhs, lhs, - system, functional, adjoint, sensitivity_rhs, extract_blocks) - -# Predefined convenience objects -from ufl.objects import ( - vertex, interval, triangle, tetrahedron, pentatope, tesseract, - quadrilateral, hexahedron, prism, pyramid, facet, - i, j, k, l, p, q, r, s, - dx, ds, dS, dP, - dc, dC, dO, dI, dX, - ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v) - -# Useful constants -from math import e, pi +from ufl.matrix import Matrix +from ufl.measure import Measure, custom_integral_types, integral_types, register_integral_type +from ufl.objects import (dc, dC, dI, dO, dP, ds, dS, ds_b, dS_h, ds_t, ds_tb, ds_v, dS_v, dx, dX, facet, hexahedron, i, + interval, j, k, l, p, pentatope, prism, pyramid, q, quadrilateral, r, s, tesseract, + tetrahedron, triangle, vertex) +from ufl.operators import (And, Dn, Dx, Not, Or, acos, asin, atan, atan2, avg, bessel_I, bessel_J, bessel_K, bessel_Y, + cell_avg, cofac, conditional, conj, cos, cosh, cross, curl, det, dev, diag, diag_vector, + diff, div, dot, elem_div, elem_mult, elem_op, elem_pow, eq, erf, exp, exterior_derivative, + facet_avg, ge, grad, gt, imag, inner, inv, jump, le, ln, lt, max_value, min_value, nabla_div, + nabla_grad, ne, outer, perp, rank, real, rot, shape, sign, sin, sinh, skew, sqrt, sym, tan, + tanh, tr, transpose, variable) +from ufl.pullback import (AbstractPullback, MixedPullback, SymmetricPullback, contravariant_piola, covariant_piola, + double_contravariant_piola, double_covariant_piola, identity_pullback, l2_piola) +from ufl.sobolevspace import H1, H2, L2, HCurl, HDiv, HDivDiv, HEin, HInf +from ufl.split_functions import split +from ufl.tensors import as_matrix, as_tensor, as_vector, unit_matrices, unit_matrix, unit_vector, unit_vectors +from ufl.utils.sequences import product __all__ = [ 'product', 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', - 'as_domain', 'AbstractDomain', 'Mesh', 'MeshView', 'TensorProductMesh', + 'AbstractDomain', 'Mesh', 'MeshView', 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'HInf', 'HEin', 'HDivDiv', + 'identity_pullback', 'l2_piola', 'contravariant_piola', 'covariant_piola', + 'double_contravariant_piola', 'double_covariant_piola', + 'l2_piola', 'MixedPullback', 'SymmetricPullback', 'AbstractPullback', 'SpatialCoordinate', 'CellVolume', 'CellDiameter', 'Circumradius', 'MinCellEdgeLength', 'MaxCellEdgeLength', 'FacetArea', 'MinFacetEdgeLength', 'MaxFacetEdgeLength', 'FacetNormal', 'CellNormal', 'Jacobian', 'JacobianDeterminant', 'JacobianInverse', - 'FiniteElementBase', 'FiniteElement', - 'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement', - 'NodalEnrichedElement', 'RestrictedElement', 'TensorProductElement', - 'HDivElement', 'HCurlElement', - 'BrokenElement', "WithMapping", - 'register_element', 'show_elements', + 'AbstractFiniteElement', 'FunctionSpace', 'MixedFunctionSpace', 'Argument', 'Coargument', 'TestFunction', 'TrialFunction', 'Arguments', 'TestFunctions', 'TrialFunctions', diff --git a/ufl/action.py b/ufl/action.py index 6051b4b7c..5d6004994 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -7,14 +7,14 @@ # # Modified by Nacime Bouziani, 2021-2022. -from ufl.form import BaseForm, FormSum, Form, ZeroBaseForm -from ufl.core.ufl_type import ufl_type from ufl.algebra import Sum -from ufl.constantvalue import Zero from ufl.argument import Argument, Coargument from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction -from ufl.differentiation import CoefficientDerivative +from ufl.constantvalue import Zero from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.ufl_type import ufl_type +from ufl.differentiation import CoefficientDerivative +from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm from ufl.matrix import Matrix # --- The Action class represents the action of a numerical object that needs @@ -120,6 +120,7 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in Action.""" from ufl.domain import join_domains + # Collect unique domains self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) diff --git a/ufl/adjoint.py b/ufl/adjoint.py index e0f86ec22..38f003870 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -8,9 +8,10 @@ # # Modified by Nacime Bouziani, 2021-2022. -from ufl.form import BaseForm, FormSum, ZeroBaseForm from ufl.argument import Coargument from ufl.core.ufl_type import ufl_type +from ufl.form import BaseForm, FormSum, ZeroBaseForm + # --- The Adjoint class represents the adjoint of a numerical object that # needs to be computed at assembly time --- @@ -87,6 +88,7 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in Adjoint.""" from ufl.domain import join_domains + # Collect unique domains self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) diff --git a/ufl/algebra.py b/ufl/algebra.py index 1bd1493be..376c16d02 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -7,14 +7,14 @@ # # Modified by Anders Logg, 2008 -from ufl.core.ufl_type import ufl_type +from ufl.checks import is_true_ufl_scalar, is_ufl_scalar +from ufl.constantvalue import ComplexValue, IntValue, ScalarValue, Zero, as_ufl, zero from ufl.core.expr import ufl_err_str from ufl.core.operator import Operator -from ufl.constantvalue import Zero, zero, ScalarValue, IntValue, ComplexValue, as_ufl -from ufl.checks import is_ufl_scalar, is_true_ufl_scalar +from ufl.core.ufl_type import ufl_type from ufl.index_combination_utils import merge_unique_indices -from ufl.sorting import sorted_expr from ufl.precedence import parstr +from ufl.sorting import sorted_expr # --- Algebraic operators --- diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 690b237f8..8e3ce5a1a 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -56,74 +56,25 @@ "load_forms", ] -# Utilities for traversing over expression trees in different ways -# from ufl.algorithms.traversal import iter_expressions - -# Keeping these imports here for backwards compatibility, doesn't cost -# anything. Prefer importing from ufl.corealg.traversal in future -# code. -# from ufl.corealg.traversal import pre_traversal -from ufl.corealg.traversal import post_traversal -# from ufl.corealg.traversal import traverse_terminals, traverse_unique_terminals - - -# Utilities for extracting information from forms and expressions -from ufl.algorithms.analysis import ( - extract_type, - extract_arguments, - extract_coefficients, - # extract_arguments_and_coefficients, - extract_base_form_operators, - extract_elements, - extract_unique_elements, - extract_sub_elements, - sort_elements, -) - - -# Preprocessing a form to extract various meta data -# from ufl.algorithms.formdata import FormData -from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form - -# Utilities for checking properties of forms -from ufl.algorithms.signature import compute_form_signature - -# Utilities for error checking of forms -from ufl.algorithms.checks import validate_form - -# Utilites for modifying expressions and forms -from ufl.corealg.multifunction import MultiFunction -from ufl.algorithms.transformer import Transformer, ReuseTransformer -# from ufl.algorithms.transformer import is_post_handler -from ufl.algorithms.transformer import apply_transformer -from ufl.algorithms.transformer import strip_variables -from ufl.algorithms.strip_terminal_data import strip_terminal_data -from ufl.algorithms.strip_terminal_data import replace_terminal_data -# from ufl.algorithms.replace import Replacer -from ufl.algorithms.replace import replace +from ufl.algorithms.ad import expand_derivatives +from ufl.algorithms.analysis import (extract_arguments, extract_base_form_operators, extract_coefficients, + extract_elements, extract_sub_elements, extract_type, extract_unique_elements, + sort_elements) from ufl.algorithms.change_to_reference import change_to_reference_grad -from ufl.algorithms.expand_compounds import expand_compounds -# from ufl.algorithms.estimate_degrees import SumDegreeEstimator +from ufl.algorithms.checks import validate_form +from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree +from ufl.algorithms.expand_compounds import expand_compounds from ufl.algorithms.expand_indices import expand_indices - -# Utilities for transforming complete Forms into other Forms -from ufl.algorithms.formtransformations import compute_form_adjoint -from ufl.algorithms.formtransformations import compute_form_action -from ufl.algorithms.formtransformations import compute_energy_norm -from ufl.algorithms.formtransformations import compute_form_lhs -from ufl.algorithms.formtransformations import compute_form_rhs -from ufl.algorithms.formtransformations import compute_form_functional -from ufl.algorithms.formtransformations import compute_form_arities - +from ufl.algorithms.formfiles import load_forms, load_ufl_file, read_ufl_file from ufl.algorithms.formsplitter import FormSplitter - -# Utilities for Automatic Functional Differentiation -from ufl.algorithms.ad import expand_derivatives - -# Utilities for form file handling -from ufl.algorithms.formfiles import read_ufl_file -from ufl.algorithms.formfiles import load_ufl_file -from ufl.algorithms.formfiles import load_forms - +from ufl.algorithms.formtransformations import (compute_energy_norm, compute_form_action, compute_form_adjoint, + compute_form_arities, compute_form_functional, compute_form_lhs, + compute_form_rhs) +from ufl.algorithms.replace import replace +from ufl.algorithms.signature import compute_form_signature +from ufl.algorithms.strip_terminal_data import replace_terminal_data, strip_terminal_data +from ufl.algorithms.transformer import ReuseTransformer, Transformer, apply_transformer, strip_variables +from ufl.corealg.multifunction import MultiFunction +from ufl.corealg.traversal import post_traversal from ufl.utils.formatting import tree_format diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index a758351ce..ba3afe6c6 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -11,21 +11,20 @@ from itertools import chain -from ufl.utils.sorting import sorted_by_count, topological_sorting - -from ufl.core.terminal import Terminal -from ufl.core.base_form_operator import BaseFormOperator +from ufl.algorithms.traversal import iter_expressions from ufl.argument import BaseArgument, Coargument from ufl.coefficient import BaseCoefficient from ufl.constant import Constant +from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.terminal import Terminal +from ufl.corealg.traversal import traverse_unique_terminals, unique_pre_traversal from ufl.form import BaseForm, Form -from ufl.algorithms.traversal import iter_expressions -from ufl.corealg.traversal import unique_pre_traversal, traverse_unique_terminals - +from ufl.utils.sorting import sorted_by_count, topological_sorting # TODO: Some of these can possibly be optimised by implementing # inlined stack based traversal algorithms + def _sorted_by_number_and_part(seq): """Sort items by number and part.""" return sorted(seq, key=lambda x: (x.number(), x.part())) @@ -237,7 +236,7 @@ def extract_unique_elements(form): def extract_sub_elements(elements): """Build sorted tuple of all sub elements (including parent element).""" - sub_elements = tuple(chain(*[e.sub_elements() for e in elements])) + sub_elements = tuple(chain(*[e.sub_elements for e in elements])) if not sub_elements: return tuple(elements) return tuple(elements) + extract_sub_elements(sub_elements) @@ -253,12 +252,12 @@ def sort_elements(elements): The ordering is based on sorting a directed acyclic graph. """ # Set nodes - nodes = sorted(elements) + nodes = list(elements) # Set edges edges = dict((node, []) for node in nodes) for element in elements: - for sub_element in element.sub_elements(): + for sub_element in element.sub_elements: edges[element].append(sub_element) # Sort graph diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index 998a139e6..e7dc4cd46 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -8,14 +8,12 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.classes import Product, Grad, Conj -from ufl.core.multiindex import indices, Index -from ufl.tensors import as_tensor, as_matrix, as_vector - -from ufl.compound_expressions import deviatoric_expr, determinant_expr, cofactor_expr, inverse_expr - -from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.classes import Conj, Grad, Product +from ufl.compound_expressions import cofactor_expr, determinant_expr, deviatoric_expr, inverse_expr +from ufl.core.multiindex import Index, indices +from ufl.corealg.multifunction import MultiFunction +from ufl.tensors import as_matrix, as_tensor, as_vector class LowerCompoundAlgebra(MultiFunction): diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index d34540904..7ef8a0a5f 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -10,34 +10,30 @@ from collections import defaultdict from math import pi +from ufl.action import Action from ufl.algorithms.analysis import extract_arguments from ufl.algorithms.map_integrands import map_integrand_dags from ufl.algorithms.replace_derivative_nodes import replace_derivative_nodes +from ufl.argument import BaseArgument from ufl.checks import is_cellwise_constant -from ufl.classes import (Coefficient, ComponentTensor, Conj, ConstantValue, - ExprList, ExprMapping, FloatValue, FormArgument, Grad, - Identity, Imag, Indexed, IndexSum, JacobianInverse, - ListTensor, Product, Real, ReferenceGrad, - ReferenceValue, SpatialCoordinate, Sum, Variable, - Zero) +from ufl.classes import (Coefficient, ComponentTensor, Conj, ConstantValue, ExprList, ExprMapping, FloatValue, + FormArgument, Grad, Identity, Imag, Indexed, IndexSum, JacobianInverse, ListTensor, Product, + Real, ReferenceGrad, ReferenceValue, SpatialCoordinate, Sum, Variable, Zero) from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar +from ufl.core.base_form_operator import BaseFormOperator from ufl.core.expr import ufl_err_str from ufl.core.multiindex import FixedIndex, MultiIndex, indices from ufl.core.terminal import Terminal from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction -from ufl.differentiation import CoordinateDerivative, BaseFormCoordinateDerivative, BaseFormOperatorDerivative +from ufl.differentiation import BaseFormCoordinateDerivative, BaseFormOperatorDerivative, CoordinateDerivative from ufl.domain import extract_unique_domain -from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, - conditional, cos, cosh, exp, facet_avg, ln, sign, - sin, sinh, sqrt) -from ufl.tensors import (as_scalar, as_scalars, as_tensor, unit_indexed_tensor, - unwrap_list_tensor) - -from ufl.argument import BaseArgument -from ufl.action import Action from ufl.form import Form, ZeroBaseForm -from ufl.core.base_form_operator import BaseFormOperator +from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, conditional, cos, cosh, exp, facet_avg, ln, + sign, sin, sinh, sqrt) +from ufl.pullback import CustomPullback, PhysicalPullback +from ufl.tensors import as_scalar, as_scalars, as_tensor, unit_indexed_tensor, unwrap_list_tensor + # TODO: Add more rulesets? # - DivRuleset # - CurlRuleset @@ -597,7 +593,7 @@ def reference_value(self, o): """Differentiate a reference_value.""" # grad(o) == grad(rv(f)) -> K_ji*rgrad(rv(f))_rj f = o.ufl_operands[0] - if f.ufl_element().mapping() == "physical": + if isinstance(f.ufl_element().pullback, PhysicalPullback): # TODO: Do we need to be more careful for immersed things? return ReferenceGrad(o) @@ -836,7 +832,7 @@ def reference_value(self, o): # d/dv(o) == d/dv(rv(f)) = 0 if v is not f, or rv(dv/df) v = self._variable if isinstance(v, Coefficient) and o.ufl_operands[0] == v: - if v.ufl_element().mapping() != "identity": + if not v.ufl_element().pullback.is_identity: # FIXME: This is a bit tricky, instead of Identity it is # actually inverse(transform), or we should rather not # convert to reference frame in the first place @@ -1641,7 +1637,7 @@ def coordinate_derivative(self, o, f, w, v, cd): """Apply to a coordinate_derivative.""" from ufl.algorithms import extract_unique_elements for space in extract_unique_elements(o): - if space.mapping() == "custom": + if isinstance(space.pullback, CustomPullback): raise NotImplementedError( "CoordinateDerivative is not supported for elements with custom pull back.") diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index ebd66866d..a8c1b8acf 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -6,152 +6,9 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from itertools import accumulate, chain, repeat - -import numpy - from ufl.algorithms.map_integrands import map_integrand_dags -from ufl.classes import (Jacobian, JacobianDeterminant, JacobianInverse, - ReferenceValue) -from ufl.core.multiindex import indices +from ufl.classes import ReferenceValue from ufl.corealg.multifunction import MultiFunction, memoized_handler -from ufl.domain import extract_unique_domain -from ufl.tensors import as_tensor, as_vector -from ufl.utils.sequences import product - - -def sub_elements_with_mappings(element): - """Return an ordered list of the largest subelements that have a defined mapping.""" - if element.mapping() != "undefined": - return [element] - elements = [] - for subelm in element.sub_elements(): - if subelm.mapping() != "undefined": - elements.append(subelm) - else: - elements.extend(sub_elements_with_mappings(subelm)) - return elements - - -def apply_known_single_pullback(r, element): - """Apply pullback with given mapping. - - Args: - r: Expression wrapped in ReferenceValue - element: The element defining the mapping - """ - # Need to pass in r rather than the physical space thing, because - # the latter may be a ListTensor or similar, rather than a - # Coefficient/Argument (in the case of mixed elements, see below - # in apply_single_function_pullbacks), to which we cannot apply ReferenceValue - mapping = element.mapping() - domain = extract_unique_domain(r) - if mapping == "physical": - return r - elif mapping == "identity" or mapping == "custom": - return r - elif mapping == "contravariant Piola": - J = Jacobian(domain) - detJ = JacobianDeterminant(J) - transform = (1.0 / detJ) * J - # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) - *k, i, j = indices(len(r.ufl_shape) + 1) - kj = (*k, j) - f = as_tensor(transform[i, j] * r[kj], (*k, i)) - return f - elif mapping == "covariant Piola": - K = JacobianInverse(domain) - # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) - *k, i, j = indices(len(r.ufl_shape) + 1) - kj = (*k, j) - f = as_tensor(K[j, i] * r[kj], (*k, i)) - return f - elif mapping == "L2 Piola": - detJ = JacobianDeterminant(domain) - return r / detJ - elif mapping == "double contravariant Piola": - J = Jacobian(domain) - detJ = JacobianDeterminant(J) - transform = (1.0 / detJ) * J - # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) - *k, i, j, m, n = indices(len(r.ufl_shape) + 2) - kmn = (*k, m, n) - f = as_tensor((1.0 / detJ)**2 * J[i, m] * r[kmn] * J[j, n], (*k, i, j)) - return f - elif mapping == "double covariant Piola": - K = JacobianInverse(domain) - # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) - *k, i, j, m, n = indices(len(r.ufl_shape) + 2) - kmn = (*k, m, n) - f = as_tensor(K[m, i] * r[kmn] * K[n, j], (*k, i, j)) - return f - else: - raise ValueError(f"Unsupported mapping: {mapping}.") - - -def apply_single_function_pullbacks(r, element): - """Apply an appropriate pullback to something in physical space. - - Args: - r: An expression wrapped in ReferenceValue. - element: The element this expression lives in. - - Returns: - a pulled back expression. - """ - mapping = element.mapping() - if r.ufl_shape != element.reference_value_shape(): - raise ValueError( - f"Expecting reference space expression with shape '{element.reference_value_shape()}', " - f"got '{r.ufl_shape}'") - if mapping in {"physical", "identity", - "contravariant Piola", "covariant Piola", - "double contravariant Piola", "double covariant Piola", - "L2 Piola", "custom"}: - # Base case in recursion through elements. If the element - # advertises a mapping we know how to handle, do that - # directly. - f = apply_known_single_pullback(r, element) - if f.ufl_shape != element.value_shape(): - raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', " - f"got '{f.ufl_shape}'") - return f - elif mapping in {"symmetries", "undefined"}: - # Need to pull back each unique piece of the reference space thing - gsh = element.value_shape() - rsh = r.ufl_shape - if mapping == "symmetries": - subelem = element.sub_elements()[0] - fcm = element.flattened_sub_element_mapping() - offsets = (product(subelem.reference_value_shape()) * i for i in fcm) - elements = repeat(subelem) - else: - elements = sub_elements_with_mappings(element) - # Python >= 3.8 has an initial keyword argument to - # accumulate, but 3.7 does not. - offsets = chain([0], - accumulate(product(e.reference_value_shape()) - for e in elements)) - rflat = as_vector([r[idx] for idx in numpy.ndindex(rsh)]) - g_components = [] - # For each unique piece in reference space, apply the appropriate pullback - for offset, subelem in zip(offsets, elements): - sub_rsh = subelem.reference_value_shape() - rm = product(sub_rsh) - rsub = [rflat[offset + i] for i in range(rm)] - rsub = as_tensor(numpy.asarray(rsub).reshape(sub_rsh)) - rmapped = apply_single_function_pullbacks(rsub, subelem) - # Flatten into the pulled back expression for the whole thing - g_components.extend([rmapped[idx] - for idx in numpy.ndindex(rmapped.ufl_shape)]) - # And reshape appropriately - f = as_tensor(numpy.asarray(g_components).reshape(gsh)) - if f.ufl_shape != element.value_shape(): - raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape()}', " - f"got '{f.ufl_shape}'") - return f - else: - raise ValueError(f"Unsupported mapping type: {mapping}") class FunctionPullbackApplier(MultiFunction): @@ -172,7 +29,18 @@ def form_argument(self, o): """Apply to a form_argument.""" # Represent 0-derivatives of form arguments on reference # element - f = apply_single_function_pullbacks(ReferenceValue(o), o.ufl_element()) + r = ReferenceValue(o) + element = o.ufl_element() + + if r.ufl_shape != element.reference_value_shape: + raise ValueError( + f"Expecting reference space expression with shape '{element.reference_value_shape}', " + f"got '{r.ufl_shape}'") + f = element.pullback.apply(r) + if f.ufl_shape != element.value_shape: + raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape}', " + f"got '{f.ufl_shape}'") + assert f.ufl_shape == o.ufl_shape return f diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 21ea99f3c..c1a39d863 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -14,14 +14,10 @@ from functools import reduce from itertools import combinations -from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, - CellOrientation, CellOrigin, CellVertices, CellVolume, - Expr, FacetEdgeVectors, FacetJacobian, - FacetJacobianDeterminant, FloatValue, Form, Integral, - Jacobian, JacobianDeterminant, JacobianInverse, - MaxCellEdgeLength, ReferenceCellVolume, - ReferenceFacetVolume, ReferenceGrad, ReferenceNormal, - SpatialCoordinate) +from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, CellOrigin, CellVertices, + CellVolume, Expr, FacetEdgeVectors, FacetJacobian, FacetJacobianDeterminant, FloatValue, Form, + Integral, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, + ReferenceCellVolume, ReferenceFacetVolume, ReferenceGrad, ReferenceNormal, SpatialCoordinate) from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr from ufl.core.multiindex import Index, indices from ufl.corealg.map_dag import map_expr_dag @@ -54,7 +50,7 @@ def jacobian(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = extract_unique_domain(o) - if domain.ufl_coordinate_element().mapping() != "identity": + if not domain.ufl_coordinate_element().pullback.is_identity: raise ValueError("Piola mapped coordinates are not implemented.") # Note: No longer supporting domain.coordinates(), always # preserving SpatialCoordinate object. However if Jacobians @@ -155,7 +151,7 @@ def spatial_coordinate(self, o): """ if self._preserve_types[o._ufl_typecode_]: return o - if extract_unique_domain(o).ufl_coordinate_element().mapping() != "identity": + if not extract_unique_domain(o).ufl_coordinate_element().pullback.is_identity: raise ValueError("Piola mapped coordinates are not implemented.") # No longer supporting domain.coordinates(), always preserving # SpatialCoordinate object. @@ -284,7 +280,7 @@ def _reduce_cell_edge_length(self, o, reduction_op): domain = extract_unique_domain(o) - if not domain.ufl_coordinate_element().degree() == 1: + if domain.ufl_coordinate_element().embedded_superdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o @@ -309,7 +305,7 @@ def cell_diameter(self, o): domain = extract_unique_domain(o) - if not domain.ufl_coordinate_element().degree() in {1, (1, 1)}: + if domain.ufl_coordinate_element().embedded_superdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute cell diameter of P1 or Q1 cell.") return o @@ -346,7 +342,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): if domain.ufl_cell().topological_dimension() < 3: raise ValueError("Facet edge lengths only make sense for topological dimension >= 3.") - elif not domain.ufl_coordinate_element().degree() == 1: + elif domain.ufl_coordinate_element().embedded_superdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute facet edge lengths of P1 or Q1 cell.") return o diff --git a/ufl/algorithms/apply_integral_scaling.py b/ufl/algorithms/apply_integral_scaling.py index de5cc9fa5..7e5e35ff2 100644 --- a/ufl/algorithms/apply_integral_scaling.py +++ b/ufl/algorithms/apply_integral_scaling.py @@ -6,11 +6,11 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.classes import JacobianDeterminant, FacetJacobianDeterminant, QuadratureWeight, Form, Integral -from ufl.measure import custom_integral_types, point_integral_types -from ufl.differentiation import CoordinateDerivative from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree +from ufl.classes import FacetJacobianDeterminant, Form, Integral, JacobianDeterminant, QuadratureWeight +from ufl.differentiation import CoordinateDerivative +from ufl.measure import custom_integral_types, point_integral_types def compute_integrand_scaling_factor(integral): diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index cd98315ae..8f788a009 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -157,7 +157,7 @@ def facet_normal(self, o): gd = D.geometric_dimension() td = D.topological_dimension() - if e._is_linear() and gd == td: + if e.embedded_superdegree <= 1 and e in H1 and gd == td: # For meshes with a continuous linear non-manifold # coordinate field, the facet normal from side - points in # the opposite direction of the one from side +. We must diff --git a/ufl/algorithms/balancing.py b/ufl/algorithms/balancing.py index fdb6682c5..477ec3f6f 100644 --- a/ufl/algorithms/balancing.py +++ b/ufl/algorithms/balancing.py @@ -6,8 +6,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.classes import (CellAvg, FacetAvg, Grad, Indexed, NegativeRestricted, - PositiveRestricted, ReferenceGrad, ReferenceValue) +from ufl.classes import (CellAvg, FacetAvg, Grad, Indexed, NegativeRestricted, PositiveRestricted, ReferenceGrad, + ReferenceValue) from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index 452f0d896..c232d3c7b 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -6,18 +6,14 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.core.multiindex import indices -from ufl.corealg.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dag - -from ufl.classes import ReferenceGrad, Grad, Restricted, ReferenceValue, JacobianInverse - -from ufl.tensors import as_tensor - from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.checks import is_cellwise_constant - +from ufl.classes import Grad, JacobianInverse, ReferenceGrad, ReferenceValue, Restricted +from ufl.core.multiindex import indices +from ufl.corealg.map_dag import map_expr_dag +from ufl.corealg.multifunction import MultiFunction +from ufl.tensors import as_tensor """ # Some notes: diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 80a917e03..c93727ad8 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -1,10 +1,10 @@ """Check arities.""" from itertools import chain -from ufl.corealg.traversal import traverse_unique_terminals -from ufl.corealg.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dag from ufl.classes import Argument, Zero +from ufl.corealg.map_dag import map_expr_dag +from ufl.corealg.multifunction import MultiFunction +from ufl.corealg.traversal import traverse_unique_terminals class ArityMismatch(BaseException): diff --git a/ufl/algorithms/check_restrictions.py b/ufl/algorithms/check_restrictions.py index d2c4ecd7e..df75e279e 100644 --- a/ufl/algorithms/check_restrictions.py +++ b/ufl/algorithms/check_restrictions.py @@ -6,8 +6,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.corealg.multifunction import MultiFunction from ufl.corealg.map_dag import map_expr_dag +from ufl.corealg.multifunction import MultiFunction class RestrictionChecker(MultiFunction): diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index b5f6aa109..9257b160d 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -9,18 +9,17 @@ # Modified by Anders Logg, 2008-2009. # Modified by Mehdi Nikbakht, 2010. -# UFL classes -from ufl.core.expr import ufl_err_str -from ufl.form import Form +from ufl.algorithms.check_restrictions import check_restrictions +# UFL algorithms +from ufl.algorithms.traversal import iter_expressions from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import is_true_ufl_scalar - -# UFL algorithms -from ufl.algorithms.traversal import iter_expressions +# UFL classes +from ufl.core.expr import ufl_err_str from ufl.corealg.traversal import traverse_unique_terminals -from ufl.algorithms.check_restrictions import check_restrictions from ufl.domain import extract_unique_domain +from ufl.form import Form def validate_form(form): # TODO: Can we make this return a list of errors instead of raising exception? diff --git a/ufl/algorithms/comparison_checker.py b/ufl/algorithms/comparison_checker.py index 32a9098fc..e61aeecf3 100644 --- a/ufl/algorithms/comparison_checker.py +++ b/ufl/algorithms/comparison_checker.py @@ -1,10 +1,10 @@ """Algorithm to check for 'comparison' nodes in a form when the user is in 'complex mode'.""" -from ufl.corealg.multifunction import MultiFunction -from ufl.algorithms.map_integrands import map_integrand_dags from ufl.algebra import Real -from ufl.constantvalue import RealValue, Zero +from ufl.algorithms.map_integrands import map_integrand_dags from ufl.argument import Argument +from ufl.constantvalue import RealValue, Zero +from ufl.corealg.multifunction import MultiFunction from ufl.geometry import GeometricQuantity diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index 5a393987a..f8c9df4ac 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -11,30 +11,26 @@ from itertools import chain -from ufl.utils.sequences import max_degree - -from ufl.classes import GeometricFacetQuantity, Coefficient, Form, FunctionSpace -from ufl.corealg.traversal import traverse_unique_terminals from ufl.algorithms.analysis import extract_coefficients, extract_sub_elements, unique_tuple -from ufl.algorithms.formdata import FormData -from ufl.algorithms.formtransformations import compute_form_arities -from ufl.algorithms.check_arities import check_form_arity - +from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering +from ufl.algorithms.apply_derivatives import apply_coordinate_derivatives, apply_derivatives # These are the main symbolic processing steps: from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks -from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering -from ufl.algorithms.apply_derivatives import apply_derivatives, apply_coordinate_derivatives -from ufl.algorithms.apply_integral_scaling import apply_integral_scaling from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering -from ufl.algorithms.apply_restrictions import apply_restrictions, apply_default_restrictions -from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree -from ufl.algorithms.remove_complex_nodes import remove_complex_nodes +from ufl.algorithms.apply_integral_scaling import apply_integral_scaling +from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions +from ufl.algorithms.check_arities import check_form_arity from ufl.algorithms.comparison_checker import do_comparison_check - # See TODOs at the call sites of these below: -from ufl.algorithms.domain_analysis import build_integral_data -from ufl.algorithms.domain_analysis import reconstruct_form_from_integral_data -from ufl.algorithms.domain_analysis import group_form_integrals +from ufl.algorithms.domain_analysis import (build_integral_data, group_form_integrals, + reconstruct_form_from_integral_data) +from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree +from ufl.algorithms.formdata import FormData +from ufl.algorithms.formtransformations import compute_form_arities +from ufl.algorithms.remove_complex_nodes import remove_complex_nodes +from ufl.classes import Coefficient, Form, FunctionSpace, GeometricFacetQuantity +from ufl.corealg.traversal import traverse_unique_terminals +from ufl.utils.sequences import max_degree def _auto_select_degree(elements): @@ -46,7 +42,7 @@ def _auto_select_degree(elements): """ # Use max degree of all elements, at least 1 (to work with # Lagrange elements) - return max_degree({e.degree() for e in elements} - {None} | {1}) + return max_degree({e.embedded_superdegree for e in elements} - {None} | {1}) def _compute_element_mapping(form): @@ -74,7 +70,7 @@ def _compute_element_mapping(form): reconstruct = False # Set cell - cell = element.cell() + cell = element.cell if cell is None: domains = form.ufl_domains() if not all(domains[0].ufl_cell() == d.ufl_cell() @@ -84,7 +80,7 @@ def _compute_element_mapping(form): reconstruct = True # Set degree - degree = element.degree() + degree = element.embedded_superdegree if degree is None: degree = common_degree reconstruct = True @@ -138,7 +134,7 @@ def _check_elements(form_data): """Check elements.""" for element in chain(form_data.unique_elements, form_data.unique_sub_elements): - if element.cell() is None: + if element.cell is None: raise ValueError(f"Found element with undefined cell: {element}") diff --git a/ufl/algorithms/coordinate_derivative_helpers.py b/ufl/algorithms/coordinate_derivative_helpers.py index d94a8eed1..77b1c19bf 100644 --- a/ufl/algorithms/coordinate_derivative_helpers.py +++ b/ufl/algorithms/coordinate_derivative_helpers.py @@ -9,10 +9,10 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.differentiation import CoordinateDerivative -from ufl.corealg.multifunction import MultiFunction -from ufl.corealg.map_dag import map_expr_dags from ufl.classes import Integral +from ufl.corealg.map_dag import map_expr_dags +from ufl.corealg.multifunction import MultiFunction +from ufl.differentiation import CoordinateDerivative class CoordinateDerivativeIsOutermostChecker(MultiFunction): diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 86fc603d8..3a11b123a 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -6,18 +6,17 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +import numbers +import typing from collections import defaultdict import ufl -from ufl.integral import Integral +from ufl.algorithms.coordinate_derivative_helpers import attach_coordinate_derivatives, strip_coordinate_derivatives from ufl.form import Form +from ufl.integral import Integral from ufl.protocols import id_or_none from ufl.sorting import cmp_expr, sorted_expr from ufl.utils.sorting import canonicalize_metadata, sorted_by_key -from ufl.algorithms.coordinate_derivative_helpers import ( - attach_coordinate_derivatives, strip_coordinate_derivatives) -import numbers -import typing class IntegralData(object): diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index a5889fe2c..ccaf64c7e 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -11,10 +11,10 @@ import warnings -from ufl.corealg.multifunction import MultiFunction from ufl.checks import is_cellwise_constant from ufl.constantvalue import IntValue from ufl.corealg.map_dag import map_expr_dags +from ufl.corealg.multifunction import MultiFunction from ufl.domain import extract_unique_domain from ufl.form import Form from ufl.integral import Integral @@ -52,14 +52,14 @@ def geometric_quantity(self, v): return 0 else: # As a heuristic, just returning domain degree to bump up degree somewhat - return extract_unique_domain(v).ufl_coordinate_element().degree() + return extract_unique_domain(v).ufl_coordinate_element().embedded_superdegree def spatial_coordinate(self, v): """Apply to spatial_coordinate. A coordinate provides additional degrees depending on coordinate field of domain. """ - return extract_unique_domain(v).ufl_coordinate_element().degree() + return extract_unique_domain(v).ufl_coordinate_element().embedded_superdegree def cell_coordinate(self, v): """Apply to cell_coordinate. @@ -74,7 +74,7 @@ def argument(self, v): A form argument provides a degree depending on the element, or the default degree if the element has no degree. """ - return v.ufl_element().degree() # FIXME: Use component to improve accuracy for mixed elements + return v.ufl_element().embedded_superdegree # FIXME: Use component to improve accuracy for mixed elements def coefficient(self, v): """Apply to coefficient. @@ -84,7 +84,7 @@ def coefficient(self, v): """ e = v.ufl_element() e = self.element_replace_map.get(e, e) - d = e.degree() # FIXME: Use component to improve accuracy for mixed elements + d = e.embedded_superdegree # FIXME: Use component to improve accuracy for mixed elements if d is None: d = self.default_degree return d diff --git a/ufl/algorithms/expand_compounds.py b/ufl/algorithms/expand_compounds.py index 04290729b..97eecc499 100644 --- a/ufl/algorithms/expand_compounds.py +++ b/ufl/algorithms/expand_compounds.py @@ -9,6 +9,7 @@ # Modified by Anders Logg, 2009-2010 import warnings + from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index b36923ac5..1523bd4c4 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -11,12 +11,12 @@ # # Modified by Anders Logg, 2009. -from ufl.utils.stacks import Stack, StackDict +from ufl.algorithms.transformer import ReuseTransformer, apply_transformer from ufl.classes import Terminal from ufl.constantvalue import Zero -from ufl.core.multiindex import Index, FixedIndex, MultiIndex +from ufl.core.multiindex import FixedIndex, Index, MultiIndex from ufl.differentiation import Grad -from ufl.algorithms.transformer import ReuseTransformer, apply_transformer +from ufl.utils.stacks import Stack, StackDict class IndexExpander(ReuseTransformer): @@ -58,10 +58,10 @@ def form_argument(self, x): raise ValueError("Component size mismatch.") # Map it through an eventual symmetry mapping - s = e.symmetry() - c = s.get(c, c) - if r != len(c): - raise ValueError("Component size mismatch after symmetry mapping.") + if len(e.components) > 1: + c = min(i for i, j in e.components.items() if j == e.components[c]) + if r != len(c): + raise ValueError("Component size mismatch after symmetry mapping.") return x[c] diff --git a/ufl/algorithms/formdata.py b/ufl/algorithms/formdata.py index c5162c140..6f1048aec 100644 --- a/ufl/algorithms/formdata.py +++ b/ufl/algorithms/formdata.py @@ -8,7 +8,7 @@ # # Modified by Anders Logg, 2008. -from ufl.utils.formatting import lstr, tstr, estr +from ufl.utils.formatting import estr, lstr, tstr class FormData(object): diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index 0bb4f38a5..7927f3a11 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -12,13 +12,14 @@ import io import os import re -from ufl.utils.sorting import sorted_by_key -from ufl.form import Form -from ufl.finiteelement import FiniteElementBase -from ufl.core.expr import Expr -from ufl.constant import Constant + from ufl.argument import Argument from ufl.coefficient import Coefficient +from ufl.constant import Constant +from ufl.core.expr import Expr +from ufl.finiteelement import AbstractFiniteElement +from ufl.form import Form +from ufl.utils.sorting import sorted_by_key class FileData(object): @@ -93,7 +94,7 @@ def interpret_ufl_namespace(namespace): # Object to hold all returned data ufd = FileData() - # Extract object names for Form, Coefficient and FiniteElementBase objects + # Extract object names for Form, Coefficient and AbstractFiniteElement objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. @@ -106,7 +107,7 @@ def interpret_ufl_namespace(namespace): # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value - elif isinstance(value, (FiniteElementBase, Coefficient, Constant, Argument, Form, Expr)): + elif isinstance(value, (AbstractFiniteElement, Coefficient, Constant, Argument, Form, Expr)): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name @@ -149,8 +150,8 @@ def get_form(name): # Validate types if not isinstance(ufd.elements, (list, tuple)): raise ValueError(f"Expecting 'elements' to be a list or tuple, not '{type(ufd.elements)}''.") - if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): - raise ValueError("Expecting 'elements' to be a list of FiniteElementBase instances.") + if not all(isinstance(e, AbstractFiniteElement) for e in ufd.elements): + raise ValueError("Expecting 'elements' to be a list of AbstractFiniteElement instances.") # Get list of exported coefficients functions = [] diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index 755b479df..ef9b2bbef 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -8,12 +8,12 @@ # # Modified by Cecile Daversin-Catty, 2018 -from ufl.corealg.multifunction import MultiFunction from ufl.algorithms.map_integrands import map_integrand_dags -from ufl.constantvalue import Zero -from ufl.tensors import as_vector from ufl.argument import Argument +from ufl.constantvalue import Zero +from ufl.corealg.multifunction import MultiFunction from ufl.functionspace import FunctionSpace +from ufl.tensors import as_vector class FormSplitter(MultiFunction): diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 6a473c71a..58d168c14 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -13,17 +13,16 @@ import warnings from logging import debug -# All classes: -from ufl.core.expr import ufl_err_str -from ufl.argument import Argument -from ufl.coefficient import Coefficient -from ufl.constantvalue import Zero from ufl.algebra import Conj - # Other algorithms: from ufl.algorithms.map_integrands import map_integrands -from ufl.algorithms.transformer import Transformer from ufl.algorithms.replace import replace +from ufl.algorithms.transformer import Transformer +from ufl.argument import Argument +from ufl.coefficient import Coefficient +from ufl.constantvalue import Zero +# All classes: +from ufl.core.expr import ufl_err_str # FIXME: Don't use this below, it makes partextracter more expensive than necessary diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 250360f83..55266dfb9 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -10,13 +10,13 @@ # as part of a careful refactoring process, and this file depends on ufl.form # which drags in a lot of stuff. -from ufl.core.expr import Expr -from ufl.corealg.map_dag import map_expr_dag -from ufl.integral import Integral -from ufl.form import Form, BaseForm, FormSum, ZeroBaseForm from ufl.action import Action from ufl.adjoint import Adjoint from ufl.constantvalue import Zero +from ufl.core.expr import Expr +from ufl.corealg.map_dag import map_expr_dag +from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm +from ufl.integral import Integral def map_integrands(function, form, only_integral_type=None): diff --git a/ufl/algorithms/remove_complex_nodes.py b/ufl/algorithms/remove_complex_nodes.py index 12a5b34e1..7d97a5731 100644 --- a/ufl/algorithms/remove_complex_nodes.py +++ b/ufl/algorithms/remove_complex_nodes.py @@ -1,8 +1,8 @@ """Algorithm for removing conj, real, and imag nodes from a form for when the user is in 'real mode'.""" -from ufl.corealg.multifunction import MultiFunction -from ufl.constantvalue import ComplexValue from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.constantvalue import ComplexValue +from ufl.corealg.multifunction import MultiFunction class ComplexNodeRemoval(MultiFunction): diff --git a/ufl/algorithms/renumbering.py b/ufl/algorithms/renumbering.py index 2f3d94a21..303457dd6 100644 --- a/ufl/algorithms/renumbering.py +++ b/ufl/algorithms/renumbering.py @@ -5,11 +5,11 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.core.expr import Expr -from ufl.core.multiindex import Index, FixedIndex, MultiIndex -from ufl.variable import Label, Variable from ufl.algorithms.transformer import ReuseTransformer, apply_transformer from ufl.classes import Zero +from ufl.core.expr import Expr +from ufl.core.multiindex import FixedIndex, Index, MultiIndex +from ufl.variable import Label, Variable class VariableRenumberingTransformer(ReuseTransformer): diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index fb62095cd..e96ecc577 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -8,11 +8,13 @@ # # Modified by Anders Logg, 2009-2010 -from ufl.classes import CoefficientDerivative, Interpolate, ExternalOperator, Form +from ufl.algorithms.analysis import has_exact_type +from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.classes import CoefficientDerivative, Form from ufl.constantvalue import as_ufl +from ufl.core.external_operator import ExternalOperator +from ufl.core.interpolate import Interpolate from ufl.corealg.multifunction import MultiFunction -from ufl.algorithms.map_integrands import map_integrand_dags -from ufl.algorithms.analysis import has_exact_type class Replacer(MultiFunction): diff --git a/ufl/algorithms/replace_derivative_nodes.py b/ufl/algorithms/replace_derivative_nodes.py index d779a790f..9c822fafb 100644 --- a/ufl/algorithms/replace_derivative_nodes.py +++ b/ufl/algorithms/replace_derivative_nodes.py @@ -1,11 +1,11 @@ """Algorithm for replacing derivative nodes in a BaseForm or Expr.""" import ufl -from ufl.corealg.multifunction import MultiFunction -from ufl.algorithms.map_integrands import map_integrand_dags from ufl.algorithms.analysis import extract_arguments -from ufl.tensors import ListTensor +from ufl.algorithms.map_integrands import map_integrand_dags from ufl.constantvalue import as_ufl +from ufl.corealg.multifunction import MultiFunction +from ufl.tensors import ListTensor class DerivativeNodeReplacer(MultiFunction): diff --git a/ufl/algorithms/signature.py b/ufl/algorithms/signature.py index da3c516dc..7f9dca8b8 100644 --- a/ufl/algorithms/signature.py +++ b/ufl/algorithms/signature.py @@ -6,13 +6,11 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import hashlib -from ufl.classes import (Label, - Index, MultiIndex, - Coefficient, Argument, - GeometricQuantity, ConstantValue, Constant, - ExprList, ExprMapping) -from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal + from ufl.algorithms.domain_analysis import canonicalize_metadata +from ufl.classes import (Argument, Coefficient, Constant, ConstantValue, ExprList, ExprMapping, GeometricQuantity, + Index, Label, MultiIndex) +from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal def compute_multiindex_hashdata(expr, index_numbering): diff --git a/ufl/algorithms/strip_terminal_data.py b/ufl/algorithms/strip_terminal_data.py index 04e48336d..4909febe4 100644 --- a/ufl/algorithms/strip_terminal_data.py +++ b/ufl/algorithms/strip_terminal_data.py @@ -3,11 +3,9 @@ In the stripped version, any data-carrying objects have been extracted to a mapping. """ -from ufl.classes import Form, Integral -from ufl.classes import Argument, Coefficient, Constant -from ufl.classes import FunctionSpace, TensorProductFunctionSpace, MixedFunctionSpace -from ufl.classes import Mesh, MeshView, TensorProductMesh from ufl.algorithms.replace import replace +from ufl.classes import (Argument, Coefficient, Constant, Form, FunctionSpace, Integral, Mesh, MeshView, + MixedFunctionSpace, TensorProductFunctionSpace) from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction @@ -123,8 +121,5 @@ def strip_domain(domain): elif isinstance(domain, MeshView): return MeshView(strip_domain(domain.ufl_mesh()), domain.topological_dimension(), domain.ufl_id()) - elif isinstance(domain, TensorProductMesh): - meshes = [strip_domain(mesh) for mesh in domain.ufl_meshes()] - return TensorProductMesh(meshes, domain.ufl_id()) else: raise NotImplementedError(f"{type(domain)} cannot be stripped") diff --git a/ufl/algorithms/traversal.py b/ufl/algorithms/traversal.py index 7a5c39918..78a0f1b2f 100644 --- a/ufl/algorithms/traversal.py +++ b/ufl/algorithms/traversal.py @@ -8,14 +8,12 @@ # # Modified by Anders Logg, 2008 -from ufl.core.expr import Expr -from ufl.integral import Integral from ufl.action import Action from ufl.adjoint import Adjoint -from ufl.form import Form, FormSum, BaseForm - +from ufl.core.expr import Expr +from ufl.form import BaseForm, Form, FormSum +from ufl.integral import Integral -# --- Traversal utilities --- def iter_expressions(a): """Utility function to handle Form, Integral and any Expr the same way when inspecting expressions. diff --git a/ufl/argument.py b/ufl/argument.py index 876354301..81b94b9ec 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -14,17 +14,14 @@ # Modified by Cecile Daversin-Catty, 2018. # Modified by Ignacia Fierro-Piccardo 2023. -import warnings import numbers -from ufl.core.ufl_type import ufl_type from ufl.core.terminal import FormArgument -from ufl.split_functions import split -from ufl.finiteelement import FiniteElementBase -from ufl.domain import default_domain +from ufl.core.ufl_type import ufl_type +from ufl.duals import is_dual, is_primal from ufl.form import BaseForm -from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace -from ufl.duals import is_primal, is_dual +from ufl.functionspace import AbstractFunctionSpace, MixedFunctionSpace +from ufl.split_functions import split # Export list for ufl.classes (TODO: not actually classes: drop? these are in ufl.*) __all_classes__ = ["TestFunction", "TrialFunction", "TestFunctions", "TrialFunctions"] @@ -44,19 +41,11 @@ def __getnewargs__(self): def __init__(self, function_space, number, part=None): """initialise.""" - if isinstance(function_space, FiniteElementBase): - # For legacy support for UFL files using cells, we map the cell to - # the default Mesh - element = function_space - domain = default_domain(element.cell()) - function_space = FunctionSpace(domain, element) - warnings.warn("The use of FiniteElement as an input to Argument will be deprecated by December 2023. " - "Please, use FunctionSpace instead", FutureWarning) - elif not isinstance(function_space, AbstractFunctionSpace): - raise ValueError("Expecting a FunctionSpace or FiniteElement.") + if not isinstance(function_space, AbstractFunctionSpace): + raise ValueError("Expecting a FunctionSpace.") self._ufl_function_space = function_space - self._ufl_shape = function_space.ufl_element().value_shape() + self._ufl_shape = function_space.ufl_element().value_shape if not isinstance(number, numbers.Integral): raise ValueError(f"Expecting an int for number, not {number}") diff --git a/ufl/averaging.py b/ufl/averaging.py index dc6801cc6..899cc3ca8 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -6,9 +6,9 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.constantvalue import ConstantValue from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import ConstantValue @ufl_type(inherit_shape_from_operand=0, diff --git a/ufl/cell.py b/ufl/cell.py index 85a556474..e7df446fe 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -7,14 +7,14 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from __future__ import annotations + import functools import numbers import typing import weakref - -from ufl.core.ufl_type import UFLObject from abc import abstractmethod +from ufl.core.ufl_type import UFLObject __all_classes__ = ["AbstractCell", "Cell", "TensorProductCell"] diff --git a/ufl/checks.py b/ufl/checks.py index cd47a272c..5b4dc8ce1 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -9,7 +9,10 @@ # Modified by Anders Logg, 2008-2009 from ufl.core.expr import Expr +from ufl.core.terminal import FormArgument from ufl.corealg.traversal import traverse_unique_terminals +from ufl.geometry import GeometricQuantity +from ufl.sobolevspace import H1 def is_python_scalar(expression): @@ -30,32 +33,23 @@ def is_true_ufl_scalar(expression): def is_cellwise_constant(expr): """Return whether expression is constant over a single cell.""" # TODO: Implement more accurately considering e.g. derivatives? - return all(t.is_cellwise_constant() for t in traverse_unique_terminals(expr)) + return all(e.is_cellwise_constant() for e in traverse_unique_terminals(expr)) -def is_globally_constant(expr): - """Check if an expression is globally constant. +def is_scalar_constant_expression(expr): + """Check if an expression is a globally constant scalar expression.""" + if is_python_scalar(expr): + return True + if expr.ufl_shape: + return False - This includes spatially independent constant coefficients that - are not known before assembly time. - """ # TODO: This does not consider gradients of coefficients, so false # negatives are possible. - # from ufl.argument import Argument - # from ufl.coefficient import Coefficient - from ufl.core.terminal import FormArgument - from ufl.geometry import GeometricQuantity for e in traverse_unique_terminals(expr): # Return False if any single terminal is not constant - if e._ufl_is_literal_: - # Accept literals first, they are the most common - # terminals - continue - elif isinstance(e, FormArgument): - # Accept only Real valued Arguments and Coefficients - if e.ufl_element()._is_globally_constant(): - continue - else: + if isinstance(e, FormArgument): + # Accept only globally constant Arguments and Coefficients + if e.ufl_element().embedded_superdegree > 0 or e.ufl_element() not in H1: return False elif isinstance(e, GeometricQuantity): # Reject all geometric quantities, they all vary over @@ -64,12 +58,3 @@ def is_globally_constant(expr): # All terminals passed constant check return True - - -def is_scalar_constant_expression(expr): - """Check if an expression is a globally constant scalar expression.""" - if is_python_scalar(expr): - return True - if expr.ufl_shape: - return False - return is_globally_constant(expr) diff --git a/ufl/classes.py b/ufl/classes.py index 64d12284e..b65c802d4 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -19,42 +19,42 @@ # This will be populated part by part below __all__ = [] - # Import all submodules, triggering execution of the ufl_type class # decorator for each Expr class. -# Base classes of Expr type hierarchy -import ufl.core.expr -import ufl.core.terminal -import ufl.core.operator - -# Terminal types -import ufl.constantvalue +import ufl.algebra import ufl.argument +import ufl.averaging +import ufl.cell import ufl.coefficient +import ufl.conditional +import ufl.constantvalue +import ufl.core.expr +import ufl.core.multiindex +import ufl.core.operator +import ufl.core.terminal +import ufl.differentiation +import ufl.domain +import ufl.equation +import ufl.exprcontainers +import ufl.finiteelement +import ufl.form +import ufl.functionspace import ufl.geometry - -# Operator types -import ufl.averaging import ufl.indexed import ufl.indexsum -import ufl.variable -import ufl.tensors -import ufl.algebra -import ufl.tensoralgebra +import ufl.integral import ufl.mathfunctions -import ufl.differentiation -import ufl.conditional -import ufl.restriction -import ufl.exprcontainers +import ufl.measure +import ufl.pullback import ufl.referencevalue - -# Make sure we import exproperators which attaches special functions -# to Expr +import ufl.restriction +import ufl.sobolevspace +import ufl.tensoralgebra +import ufl.tensors +import ufl.variable from ufl import exproperators as __exproperators -# Make sure to import modules with new Expr subclasses here! - # Collect all classes in sets automatically classified by some properties all_ufl_classes = set(ufl.core.expr.Expr._ufl_all_classes_) abstract_classes = set(c for c in all_ufl_classes if c._ufl_is_abstract_) @@ -96,32 +96,15 @@ def populate_namespace_with_module_classes(mod, loc): return names -import ufl.cell # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.cell, locals()) - -import ufl.finiteelement # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.finiteelement, locals()) - -import ufl.domain # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.domain, locals()) - -import ufl.functionspace # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.functionspace, locals()) - -import ufl.core.multiindex # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.core.multiindex, locals()) - -import ufl.argument # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.argument, locals()) - -import ufl.measure # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.measure, locals()) - -import ufl.integral # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.integral, locals()) - -import ufl.form # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.form, locals()) - -import ufl.equation # noqa E401 __all__ += populate_namespace_with_module_classes(ufl.equation, locals()) +__all__ += populate_namespace_with_module_classes(ufl.pullback, locals()) +__all__ += populate_namespace_with_module_classes(ufl.sobolevspace, locals()) diff --git a/ufl/coefficient.py b/ufl/coefficient.py index c5ba8dac7..584d06c3f 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -10,19 +10,15 @@ # Modified by Massimiliano Leoni, 2016. # Modified by Cecile Daversin-Catty, 2018. # Modified by Ignacia Fierro-Piccardo 2023. -import warnings -from ufl.core.ufl_type import ufl_type -from ufl.core.terminal import FormArgument from ufl.argument import Argument -from ufl.finiteelement import FiniteElementBase -from ufl.domain import default_domain -from ufl.functionspace import AbstractFunctionSpace, FunctionSpace, MixedFunctionSpace +from ufl.core.terminal import FormArgument +from ufl.core.ufl_type import ufl_type +from ufl.duals import is_dual, is_primal from ufl.form import BaseForm +from ufl.functionspace import AbstractFunctionSpace, MixedFunctionSpace from ufl.split_functions import split from ufl.utils.counted import Counted -from ufl.duals import is_primal, is_dual - # --- The Coefficient class represents a coefficient in a form --- @@ -45,19 +41,11 @@ def __init__(self, function_space, count=None): """Initalise.""" Counted.__init__(self, count, Coefficient) - if isinstance(function_space, FiniteElementBase): - # For legacy support for .ufl files using cells, we map - # the cell to The Default Mesh - element = function_space - domain = default_domain(element.cell()) - function_space = FunctionSpace(domain, element) - warnings.warn("The use of FiniteElement as an input to Coefficient will be deprecated by December 2023. " - "Please, use FunctionSpace instead", FutureWarning) - elif not isinstance(function_space, AbstractFunctionSpace): - raise ValueError("Expecting a FunctionSpace or FiniteElement.") + if not isinstance(function_space, AbstractFunctionSpace): + raise ValueError("Expecting a FunctionSpace.") self._ufl_function_space = function_space - self._ufl_shape = function_space.ufl_element().value_shape() + self._ufl_shape = function_space.ufl_element().value_shape self._repr = "BaseCoefficient(%s, %s)" % ( repr(self._ufl_function_space), repr(self._count)) diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 246ce765f..6b9c63402 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -9,10 +9,10 @@ import warnings -from ufl.core.multiindex import indices, Index -from ufl.tensors import as_tensor, as_matrix, as_vector -from ufl.operators import sqrt from ufl.constantvalue import Zero, zero +from ufl.core.multiindex import Index, indices +from ufl.operators import sqrt +from ufl.tensors import as_matrix, as_tensor, as_vector # Note: To avoid typing errors, the expressions for cofactor and # deviatoric parts below were created with the script diff --git a/ufl/conditional.py b/ufl/conditional.py index 422be6ff2..b9f9bda16 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -7,13 +7,13 @@ import warnings +from ufl.checks import is_true_ufl_scalar +from ufl.constantvalue import as_ufl from ufl.core.expr import ufl_err_str -from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator -from ufl.constantvalue import as_ufl -from ufl.precedence import parstr +from ufl.core.ufl_type import ufl_type from ufl.exprequals import expr_equals -from ufl.checks import is_true_ufl_scalar +from ufl.precedence import parstr # --- Condition classes --- diff --git a/ufl/constant.py b/ufl/constant.py index 2edb21c8e..74eb81932 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -6,8 +6,8 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal +from ufl.core.ufl_type import ufl_type from ufl.domain import as_domain from ufl.utils.counted import Counted diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 64a44276a..82d9ed84e 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -12,15 +12,13 @@ from math import atan2 import ufl +# --- Helper functions imported here for compatibility--- +from ufl.checks import is_python_scalar, is_true_ufl_scalar, is_ufl_scalar # noqa: F401 from ufl.core.expr import Expr +from ufl.core.multiindex import FixedIndex, Index from ufl.core.terminal import Terminal -from ufl.core.multiindex import Index, FixedIndex from ufl.core.ufl_type import ufl_type -# --- Helper functions imported here for compatibility--- -from ufl.checks import is_python_scalar, is_ufl_scalar, is_true_ufl_scalar # noqa: F401 - - # Precision for float formatting precision = None diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index e2aa2475d..a136244cb 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -15,10 +15,10 @@ from collections import OrderedDict from ufl.argument import Argument, Coargument +from ufl.constantvalue import as_ufl from ufl.core.operator import Operator -from ufl.form import BaseForm from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import as_ufl +from ufl.form import BaseForm from ufl.functionspace import AbstractFunctionSpace from ufl.utils.counted import Counted diff --git a/ufl/core/expr.py b/ufl/core/expr.py index 9f1851643..e6b2ce3d8 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -21,8 +21,6 @@ from ufl.core.ufl_type import UFLType, update_ufl_type_attributes -# --- The base object for all UFL expression tree nodes --- - class Expr(object, metaclass=UFLType): """Base class for all UFL expression types. @@ -208,10 +206,6 @@ def __init__(self): # "__str__", # "__repr__", - - # TODO: Add checks for methods/properties of terminals only? - # Required for terminals: - # "is_cellwise_constant", # TODO: Rename to ufl_is_cellwise_constant? ) # --- Global variables for collecting all types --- diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index e751c66c9..86198c680 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -8,14 +8,14 @@ # # Modified by Nacime Bouziani, 2021-2022 -from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import as_ufl -from ufl.functionspace import AbstractFunctionSpace -from ufl.argument import Coargument, Argument +from ufl.argument import Argument, Coargument from ufl.coefficient import Cofunction -from ufl.form import Form +from ufl.constantvalue import as_ufl from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.ufl_type import ufl_type from ufl.duals import is_dual +from ufl.form import Form +from ufl.functionspace import AbstractFunctionSpace @ufl_type(num_ops="varying", is_differential=True) diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index ca66a619a..81409ce7d 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -9,9 +9,9 @@ # Modified by Massimiliano Leoni, 2016. -from ufl.utils.counted import Counted -from ufl.core.ufl_type import ufl_type from ufl.core.terminal import Terminal +from ufl.core.ufl_type import ufl_type +from ufl.utils.counted import Counted # Export list for ufl.classes __all_classes__ = ["IndexBase", "FixedIndex", "Index"] diff --git a/ufl/core/operator.py b/ufl/core/operator.py index 7a8de1cc1..c2e31b87e 100644 --- a/ufl/core/operator.py +++ b/ufl/core/operator.py @@ -12,8 +12,6 @@ from ufl.core.ufl_type import ufl_type -# --- Base class for operator objects --- - @ufl_type(is_abstract=True, is_terminal=False) class Operator(Expr): """Base class for all operators, i.e. non-terminal expression types.""" diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index b83d30094..b3bfa0bd6 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -9,14 +9,15 @@ # Modified by Matthew Scroggs, 2023 from __future__ import annotations + import typing import warnings - -from ufl.core.compute_expr_hash import compute_expr_hash -from ufl.utils.formatting import camel2underscore from abc import ABC, abstractmethod + # Avoid circular import import ufl.core as core +from ufl.core.compute_expr_hash import compute_expr_hash +from ufl.utils.formatting import camel2underscore class UFLObject(ABC): diff --git a/ufl/corealg/map_dag.py b/ufl/corealg/map_dag.py index 946a86ff8..f299130c1 100644 --- a/ufl/corealg/map_dag.py +++ b/ufl/corealg/map_dag.py @@ -8,8 +8,8 @@ # Modified by Massimiliano Leoni, 2016 from ufl.core.expr import Expr -from ufl.corealg.traversal import unique_post_traversal, cutoff_unique_post_traversal from ufl.corealg.multifunction import MultiFunction +from ufl.corealg.traversal import cutoff_unique_post_traversal, unique_post_traversal def map_expr_dag(function, expression, compress=True, vcache=None, rcache=None): diff --git a/ufl/differentiation.py b/ufl/differentiation.py index e3820603a..34c003b85 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -6,13 +6,13 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.argument import Argument, Coargument from ufl.checks import is_cellwise_constant from ufl.coefficient import Coefficient -from ufl.argument import Argument, Coargument from ufl.constantvalue import Zero +from ufl.core.base_form_operator import BaseFormOperator from ufl.core.expr import Expr from ufl.core.operator import Operator -from ufl.core.base_form_operator import BaseFormOperator from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import extract_unique_domain, find_geometric_dimension @@ -101,7 +101,7 @@ def __init__(self, base_form, coefficients, arguments, def _analyze_form_arguments(self): """Collect the arguments of the corresponding BaseForm.""" - from ufl.algorithms.analysis import extract_type, extract_coefficients + from ufl.algorithms.analysis import extract_coefficients, extract_type base_form, _, arguments, _ = self.ufl_operands def arg_type(x): diff --git a/ufl/domain.py b/ufl/domain.py index 59de4f121..ecdea376a 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -9,14 +9,14 @@ import numbers import warnings -from ufl.cell import AbstractCell, TensorProductCell, as_cell +from ufl.cell import AbstractCell from ufl.core.ufl_id import attach_ufl_id from ufl.core.ufl_type import attach_operators_from_hash_data from ufl.corealg.traversal import traverse_unique_terminals -from ufl.finiteelement.tensorproductelement import TensorProductElement +from ufl.sobolevspace import H1 # Export list for ufl.classes -__all_classes__ = ["AbstractDomain", "Mesh", "MeshView", "TensorProductMesh"] +__all_classes__ = ["AbstractDomain", "Mesh", "MeshView"] class AbstractDomain(object): @@ -68,22 +68,15 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): # No longer accepting coordinates provided as a Coefficient from ufl.coefficient import Coefficient - if isinstance(coordinate_element, Coefficient): + if isinstance(coordinate_element, (Coefficient, AbstractCell)): raise ValueError("Expecting a coordinate element in the ufl.Mesh construct.") - # Accept a cell in place of an element for brevity Mesh(triangle) - if isinstance(coordinate_element, AbstractCell): - from ufl.finiteelement import VectorElement - cell = coordinate_element - coordinate_element = VectorElement("Lagrange", cell, 1, - dim=cell.geometric_dimension()) - # Store coordinate element self._ufl_coordinate_element = coordinate_element # Derive dimensions from element - gdim, = coordinate_element.value_shape() - tdim = coordinate_element.cell().topological_dimension() + gdim, = coordinate_element.value_shape + tdim = coordinate_element.cell.topological_dimension() AbstractDomain.__init__(self, tdim, gdim) def ufl_cargo(self): @@ -96,11 +89,12 @@ def ufl_coordinate_element(self): def ufl_cell(self): """Get the cell.""" - return self._ufl_coordinate_element.cell() + return self._ufl_coordinate_element.cell def is_piecewise_linear_simplex_domain(self): """Check if the domain is a piecewise linear simplex.""" - return (self._ufl_coordinate_element.degree() == 1) and self.ufl_cell().is_simplex() + ce = self._ufl_coordinate_element + return ce.embedded_superdegree <= 1 and ce in H1 and self.ufl_cell().is_simplex() def __repr__(self): """Representation.""" @@ -142,8 +136,8 @@ def __init__(self, mesh, topological_dimension, ufl_id=None): # Derive dimensions from element coordinate_element = mesh.ufl_coordinate_element() - gdim, = coordinate_element.value_shape() - tdim = coordinate_element.cell().topological_dimension() + gdim, = coordinate_element.value_shape + tdim = coordinate_element.cell.topological_dimension() AbstractDomain.__init__(self, tdim, gdim) def ufl_mesh(self): @@ -187,107 +181,6 @@ def _ufl_sort_key_(self): "MeshView", typespecific) -@attach_operators_from_hash_data -@attach_ufl_id -class TensorProductMesh(AbstractDomain): - """Symbolic representation of a mesh.""" - - def __init__(self, meshes, ufl_id=None): - """Initialise.""" - self._ufl_id = self._init_ufl_id(ufl_id) - - # TODO: Error checking of meshes - self._ufl_meshes = meshes - - # TODO: Is this what we want to do? - # Build cell from mesh cells - self._ufl_cell = TensorProductCell(*[mesh.ufl_cell() for mesh in meshes]) - - # TODO: Is this what we want to do? - # Build coordinate element from mesh coordinate elements - self._ufl_coordinate_element = TensorProductElement([mesh.ufl_coordinate_element() for mesh in meshes]) - - # Derive dimensions from meshes - gdim = sum(mesh.geometric_dimension() for mesh in meshes) - tdim = sum(mesh.topological_dimension() for mesh in meshes) - - AbstractDomain.__init__(self, tdim, gdim) - - def ufl_coordinate_element(self): - """Get the coordinate element.""" - return self._ufl_coordinate_element - - def ufl_cell(self): - """Get the cell.""" - return self._ufl_cell - - def ufl_meshes(self): - """Get the UFL meshes.""" - return self._ufl_meshes - - def is_piecewise_linear_simplex_domain(self): - """Check if the domain is a piecewise linear simplex.""" - return False # TODO: Any cases this is True - - def __repr__(self): - """Representation.""" - r = "TensorProductMesh(%s, %s)" % (repr(self._ufl_meshes), repr(self._ufl_id)) - return r - - def __str__(self): - """Format as a string.""" - return "" % ( - self._ufl_id, self._ufl_meshes) - - def _ufl_hash_data_(self): - """UFL hash data.""" - return (self._ufl_id,) + tuple(mesh._ufl_hash_data_() for mesh in self._ufl_meshes) - - def _ufl_signature_data_(self, renumbering): - """UFL signature data.""" - return ("TensorProductMesh",) + tuple(mesh._ufl_signature_data_(renumbering) for mesh in self._ufl_meshes) - - # NB! Dropped __lt__ here, don't want users to write 'mesh1 < - # mesh2'. - def _ufl_sort_key_(self): - """UFL sort key.""" - typespecific = (self._ufl_id, tuple(mesh._ufl_sort_key_() for mesh in self._ufl_meshes)) - return (self.geometric_dimension(), self.topological_dimension(), - "TensorProductMesh", typespecific) - - -# --- Utility conversion functions - -def affine_mesh(cell, ufl_id=None): - """Create a Mesh over a given cell type with an affine geometric parameterization.""" - from ufl.finiteelement import VectorElement - cell = as_cell(cell) - gdim = cell.geometric_dimension() - degree = 1 - coordinate_element = VectorElement("Lagrange", cell, degree, dim=gdim) - return Mesh(coordinate_element, ufl_id=ufl_id) - - -_default_domains = {} - - -def default_domain(cell): - """Create a singular default Mesh from a cell, always returning the same Mesh object for the same cell.""" - global _default_domains - - warnings.warn("default_domain is deprecated.", FutureWarning) - - assert isinstance(cell, AbstractCell) - domain = _default_domains.get(cell) - if domain is None: - # Create one and only one affine Mesh with a negative ufl_id - # to avoid id collision - ufl_id = -(len(_default_domains) + 1) - domain = affine_mesh(cell, ufl_id=ufl_id) - _default_domains[cell] = domain - return domain - - def as_domain(domain): """Convert any valid object to an AbstractDomain type.""" if isinstance(domain, AbstractDomain): @@ -297,15 +190,7 @@ def as_domain(domain): try: return extract_unique_domain(domain) except AttributeError: - try: - # Legacy UFL files - # TODO: Make this conversion in the relevant constructors - # closer to the user interface? - # TODO: Make this configurable to be an error from the dolfin side? - cell = as_cell(domain) - return default_domain(cell) - except ValueError: - return domain.ufl_domain() + return domain.ufl_domain() def sort_domains(domains): @@ -388,7 +273,7 @@ def find_geometric_dimension(expr): if hasattr(t, "ufl_element"): element = t.ufl_element() if element is not None: - cell = element.cell() + cell = element.cell if cell is not None: gdims.add(cell.geometric_dimension()) diff --git a/ufl/exprcontainers.py b/ufl/exprcontainers.py index 60ae13f6f..8da36299c 100644 --- a/ufl/exprcontainers.py +++ b/ufl/exprcontainers.py @@ -5,15 +5,15 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.argument import Coargument +from ufl.coefficient import Cofunction from ufl.core.expr import Expr from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type -from ufl.coefficient import Cofunction -from ufl.argument import Coargument - # --- Non-tensor types --- + @ufl_type(num_ops="varying") class ExprList(Operator): """List of Expr objects. For internal use, never to be created by end users.""" diff --git a/ufl/exproperators.py b/ufl/exproperators.py index f0b38ec79..60eb87566 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -14,25 +14,23 @@ import numbers -from ufl.utils.stacks import StackDict -from ufl.core.expr import Expr +from ufl.algebra import Abs, Division, Power, Product, Sum +from ufl.conditional import GE, GT, LE, LT from ufl.constantvalue import Zero, as_ufl -from ufl.algebra import Sum, Product, Division, Power, Abs -from ufl.tensoralgebra import Transposed, Inner -from ufl.core.multiindex import MultiIndex, Index, indices -from ufl.indexed import Indexed -from ufl.indexsum import IndexSum -from ufl.tensors import as_tensor, ComponentTensor -from ufl.restriction import PositiveRestricted, NegativeRestricted +from ufl.core.expr import Expr +from ufl.core.multiindex import Index, MultiIndex, indices from ufl.differentiation import Grad -from ufl.index_combination_utils import create_slice_indices, merge_overlapping_indices - from ufl.exprequals import expr_equals +from ufl.index_combination_utils import create_slice_indices, merge_overlapping_indices +from ufl.indexed import Indexed +from ufl.indexsum import IndexSum +from ufl.restriction import NegativeRestricted, PositiveRestricted +from ufl.tensoralgebra import Inner, Transposed +from ufl.tensors import ComponentTensor, as_tensor +from ufl.utils.stacks import StackDict # --- Boolean operators --- -from ufl.conditional import LE, GE, LT, GT - def _le(left, right): """A boolean expresion (left <= right) for use with conditional.""" diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py new file mode 100644 index 000000000..9a4e43d6e --- /dev/null +++ b/ufl/finiteelement.py @@ -0,0 +1,368 @@ +"""This module defines the UFL finite element classes.""" +# Copyright (C) 2008-2016 Martin Sandve Alnæs +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Kristian B. Oelgaard +# Modified by Marie E. Rognes 2010, 2012 +# Modified by Massimiliano Leoni, 2016 +# Modified by Matthew Scroggs, 2023 + +from __future__ import annotations + +import abc as _abc +import typing as _typing + +import numpy as np + +from ufl.cell import Cell as _Cell +from ufl.pullback import AbstractPullback as _AbstractPullback +from ufl.pullback import IdentityPullback as _IdentityPullback +from ufl.pullback import MixedPullback as _MixedPullback +from ufl.pullback import SymmetricPullback as _SymmetricPullback +from ufl.sobolevspace import SobolevSpace as _SobolevSpace +from ufl.utils.sequences import product + +__all_classes__ = ["AbstractFiniteElement", "FiniteElement", "MixedElement", "SymmetricElement"] + + +class AbstractFiniteElement(_abc.ABC): + """Base class for all finite elements. + + To make your element library compatible with UFL, you should make a subclass of AbstractFiniteElement + and provide implementions of all the abstract methods and properties. All methods and properties + that are not marked as abstract are implemented here and should not need to be overwritten in your + subclass. + + An example of how the methods in your subclass could be implemented can be found in Basix; see + https://github.com/FEniCS/basix/blob/main/python/basix/ufl.py + """ + + @_abc.abstractmethod + def __repr__(self) -> str: + """Format as string for evaluation as Python object.""" + + @_abc.abstractmethod + def __str__(self) -> str: + """Format as string for nice printing.""" + + @_abc.abstractmethod + def __hash__(self) -> int: + """Return a hash.""" + + @_abc.abstractmethod + def __eq__(self, other: AbstractFiniteElement) -> bool: + """Check if this element is equal to another element.""" + + @_abc.abstractproperty + def sobolev_space(self) -> _SobolevSpace: + """Return the underlying Sobolev space.""" + + @_abc.abstractproperty + def pullback(self) -> _AbstractPullback: + """Return the pullback for this element.""" + + @_abc.abstractproperty + def embedded_superdegree(self) -> _typing.Union[int, None]: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + + @_abc.abstractproperty + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + + @_abc.abstractproperty + def cell(self) -> _Cell: + """Return the cell of the finite element.""" + + @_abc.abstractproperty + def reference_value_shape(self) -> _typing.Tuple[int, ...]: + """Return the shape of the value space on the reference cell.""" + + @_abc.abstractproperty + def sub_elements(self) -> _typing.List: + """Return list of sub-elements. + + This function does not recurse: ie it does not extract the sub-elements + of sub-elements. + """ + + def __ne__(self, other: AbstractFiniteElement) -> bool: + """Check if this element is different to another element.""" + return not self.__eq__(other) + + def is_cellwise_constant(self) -> bool: + """Check whether this element is spatially constant over each cell.""" + return self.embedded_superdegree == 0 + + def _ufl_hash_data_(self) -> str: + """Return UFL hash data.""" + return repr(self) + + def _ufl_signature_data_(self) -> str: + """Return UFL signature data.""" + return repr(self) + + @property + def components(self) -> _typing.Dict[_typing.Tuple[int, ...], int]: + """Get the numbering of the components of the element. + + Returns: + A map from the components of the values on a physical cell (eg (0, 1)) + to flat component numbers on the reference cell (eg 1) + """ + if isinstance(self.pullback, _SymmetricPullback): + return self.pullback._symmetry + + if len(self.sub_elements) == 0: + return {(): 0} + + components = {} + offset = 0 + c_offset = 0 + for e in self.sub_elements: + for i, j in enumerate(np.ndindex(e.value_shape)): + components[(offset + i, )] = c_offset + e.components[j] + c_offset += max(e.components.values()) + 1 + offset += e.value_size + return components + + @property + def value_shape(self) -> _typing.Tuple[int, ...]: + """Return the shape of the value space on the physical domain.""" + return self.pullback.physical_value_shape(self) + + @property + def value_size(self) -> int: + """Return the integer product of the value shape.""" + return product(self.value_shape) + + @property + def reference_value_size(self) -> int: + """Return the integer product of the reference value shape.""" + return product(self.reference_value_shape) + + @property + def num_sub_elements(self) -> int: + """Return number of sub-elements. + + This function does not recurse: ie it does not count the sub-elements of + sub-elements. + """ + return len(self.sub_elements) + + +class FiniteElement(AbstractFiniteElement): + """A directly defined finite element.""" + __slots__ = ("_repr", "_str", "_family", "_cell", "_degree", + "_reference_value_shape", "_pullback", "_sobolev_space", + "_sub_elements", "_subdegree") + + def __init__( + self, family: str, cell: _Cell, degree: int, + reference_value_shape: _typing.Tuple[int, ...], pullback: _AbstractPullback, + sobolev_space: _SobolevSpace, sub_elements=[], + _repr: _typing.Optional[str] = None, _str: _typing.Optional[str] = None, + subdegree: _typing.Optional[int] = None, + ): + """Initialise a finite element. + + This class should only be used for testing + + Args: + family: The family name of the element + cell: The cell on which the element is defined + degree: The polynomial degree of the element + reference_value_shape: The reference value shape of the element + pullback: The pullback to use + sobolev_space: The Sobolev space containing this element + sub_elements: Sub elements of this element + _repr: A string representation of this elements + _str: A string for printing + subdegree: The embedded subdegree of this element + """ + if subdegree is None: + self._subdegree = degree + else: + self._subdegree = subdegree + if _repr is None: + if len(sub_elements) > 0: + self._repr = ( + f"ufl.finiteelement.FiniteElement(\"{family}\", {cell}, {degree}, " + f"{reference_value_shape}, {pullback}, {sobolev_space}, {sub_elements!r})") + else: + self._repr = ( + f"ufl.finiteelement.FiniteElement(\"{family}\", {cell}, {degree}, " + f"{reference_value_shape}, {pullback}, {sobolev_space})") + else: + self._repr = _repr + if _str is None: + self._str = f"<{family}{degree} on a {cell}>" + else: + self._str = _str + self._family = family + self._cell = cell + self._degree = degree + self._reference_value_shape = reference_value_shape + self._pullback = pullback + self._sobolev_space = sobolev_space + self._sub_elements = sub_elements + + def __repr__(self) -> str: + """Format as string for evaluation as Python object.""" + return self._repr + + def __str__(self) -> str: + """Format as string for nice printing.""" + return self._str + + def __hash__(self) -> int: + """Return a hash.""" + return hash(f"{self!r}") + + def __eq__(self, other) -> bool: + """Check if this element is equal to another element.""" + return type(self) is type(other) and repr(self) == repr(other) + + @property + def sobolev_space(self) -> _SobolevSpace: + """Return the underlying Sobolev space.""" + return self._sobolev_space + + @property + def pullback(self) -> _AbstractPullback: + """Return the pullback for this element.""" + return self._pullback + + @property + def embedded_superdegree(self) -> _typing.Union[int, None]: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self._degree + + @property + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self._subdegree + + @property + def cell(self) -> _Cell: + """Return the cell of the finite element.""" + return self._cell + + @property + def reference_value_shape(self) -> _typing.Tuple[int, ...]: + """Return the shape of the value space on the reference cell.""" + return self._reference_value_shape + + @property + def sub_elements(self) -> _typing.List: + """Return list of sub-elements. + + This function does not recurse: ie it does not extract the sub-elements + of sub-elements. + """ + return self._sub_elements + + +class SymmetricElement(FiniteElement): + """A symmetric finite element.""" + + def __init__( + self, + symmetry: _typing.Dict[_typing.Tuple[int, ...], int], + sub_elements: _typing.List[AbstractFiniteElement] + ): + """Initialise a symmetric element. + + This class should only be used for testing + + Args: + symmetry: Map from physical components to reference components + sub_elements: Sub-elements of this element + """ + self._sub_elements = sub_elements + pullback = _SymmetricPullback(self, symmetry) + reference_value_shape = (sum(e.reference_value_size for e in sub_elements), ) + degree = max(e.embedded_superdegree for e in sub_elements) + cell = sub_elements[0].cell + for e in sub_elements: + if e.cell != cell: + raise ValueError("All sub-elements must be defined on the same cell") + sobolev_space = max(e.sobolev_space for e in sub_elements) + + super().__init__( + "Symmetric element", cell, degree, reference_value_shape, pullback, + sobolev_space, sub_elements=sub_elements, + _repr=(f"ufl.finiteelement.SymmetricElement({symmetry!r}, {sub_elements!r})"), + _str=f"") + + +class MixedElement(FiniteElement): + """A mixed element.""" + + def __init__(self, sub_elements): + """Initialise a mixed element. + + This class should only be used for testing + + Args: + sub_elements: Sub-elements of this element + """ + sub_elements = [MixedElement(e) if isinstance(e, list) else e for e in sub_elements] + cell = sub_elements[0].cell + for e in sub_elements: + assert e.cell == cell + degree = max(e.embedded_superdegree for e in sub_elements) + reference_value_shape = (sum(e.reference_value_size for e in sub_elements), ) + if all(isinstance(e.pullback, _IdentityPullback) for e in sub_elements): + pullback = _IdentityPullback() + else: + pullback = _MixedPullback(self) + sobolev_space = max(e.sobolev_space for e in sub_elements) + + super().__init__( + "Mixed element", cell, degree, reference_value_shape, pullback, sobolev_space, + sub_elements=sub_elements, + _repr=f"ufl.finiteelement.MixedElement({sub_elements!r})", + _str=f"") diff --git a/ufl/finiteelement/__init__.py b/ufl/finiteelement/__init__.py deleted file mode 100644 index f7a7c94a3..000000000 --- a/ufl/finiteelement/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# flake8: noqa -"""This module defines the UFL finite element classes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Andrew T. T. McRae 2014 -# Modified by Lawrence Mitchell 2014 - -from ufl.finiteelement.finiteelementbase import FiniteElementBase -from ufl.finiteelement.finiteelement import FiniteElement -from ufl.finiteelement.mixedelement import MixedElement -from ufl.finiteelement.mixedelement import VectorElement -from ufl.finiteelement.mixedelement import TensorElement -from ufl.finiteelement.enrichedelement import EnrichedElement -from ufl.finiteelement.enrichedelement import NodalEnrichedElement -from ufl.finiteelement.restrictedelement import RestrictedElement -from ufl.finiteelement.tensorproductelement import TensorProductElement -from ufl.finiteelement.hdivcurl import HDivElement, HCurlElement, WithMapping -from ufl.finiteelement.brokenelement import BrokenElement - -# Export list for ufl.classes -__all_classes__ = [ - "FiniteElementBase", - "FiniteElement", - "MixedElement", - "VectorElement", - "TensorElement", - "EnrichedElement", - "NodalEnrichedElement", - "RestrictedElement", - "TensorProductElement", - "HDivElement", - "HCurlElement", - "BrokenElement", - "WithMapping" - ] diff --git a/ufl/form.py b/ufl/form.py index ecbd4b17d..4fb474f1c 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -831,6 +831,7 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in FormSum.""" from ufl.domain import join_domains + # Collect unique domains self._domains = join_domains([component.ufl_domain() for component in self._components]) diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 842e78e3f..90a5d7bf7 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -3,11 +3,11 @@ import numbers import ufl -from ufl.corealg.multifunction import MultiFunction +from ufl.algorithms import compute_form_data +from ufl.core.multiindex import FixedIndex, Index from ufl.corealg.map_dag import map_expr_dag -from ufl.core.multiindex import Index, FixedIndex +from ufl.corealg.multifunction import MultiFunction from ufl.form import Form -from ufl.algorithms import compute_form_data try: import colorama diff --git a/ufl/formoperators.py b/ufl/formoperators.py index b71acd9d9..d5cc31117 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -9,37 +9,29 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -from ufl.form import Form, FormSum, BaseForm, ZeroBaseForm, as_form -from ufl.core.expr import Expr, ufl_err_str -from ufl.core.base_form_operator import BaseFormOperator -from ufl.split_functions import split -from ufl.exprcontainers import ExprList, ExprMapping -from ufl.variable import Variable -from ufl.finiteelement import MixedElement +from ufl.action import Action +from ufl.adjoint import Adjoint +from ufl.algorithms import replace # noqa: F401 +from ufl.algorithms import (compute_energy_norm, compute_form_action, compute_form_adjoint, compute_form_functional, + compute_form_lhs, compute_form_rhs, expand_derivatives, extract_arguments, formsplitter) from ufl.argument import Argument from ufl.coefficient import Coefficient, Cofunction -from ufl.adjoint import Adjoint -from ufl.action import Action -from ufl.differentiation import (CoefficientDerivative, CoordinateDerivative, - BaseFormDerivative, BaseFormCoordinateDerivative, - BaseFormOperatorDerivative, BaseFormOperatorCoordinateDerivative) -from ufl.constantvalue import is_true_ufl_scalar, as_ufl -from ufl.indexed import Indexed +from ufl.constantvalue import as_ufl, is_true_ufl_scalar +from ufl.core.base_form_operator import BaseFormOperator +from ufl.core.expr import Expr, ufl_err_str from ufl.core.multiindex import FixedIndex, MultiIndex -from ufl.tensors import as_tensor, ListTensor -from ufl.sorting import sorted_expr +from ufl.differentiation import (BaseFormCoordinateDerivative, BaseFormDerivative, BaseFormOperatorCoordinateDerivative, + BaseFormOperatorDerivative, CoefficientDerivative, CoordinateDerivative) +from ufl.exprcontainers import ExprList, ExprMapping +from ufl.finiteelement import MixedElement +from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm, as_form from ufl.functionspace import FunctionSpace from ufl.geometry import SpatialCoordinate - -# An exception to the rule that ufl.* does not depend on ufl.algorithms.* ... -from ufl.algorithms import compute_form_adjoint, compute_form_action -from ufl.algorithms import compute_energy_norm -from ufl.algorithms import compute_form_lhs, compute_form_rhs, compute_form_functional -from ufl.algorithms import expand_derivatives, extract_arguments -from ufl.algorithms import formsplitter - -# Part of the external interface -from ufl.algorithms import replace # noqa +from ufl.indexed import Indexed +from ufl.sorting import sorted_expr +from ufl.split_functions import split +from ufl.tensors import ListTensor, as_tensor +from ufl.variable import Variable def extract_blocks(form, i=None, j=None): @@ -223,14 +215,14 @@ def _handle_derivative_arguments(form, coefficient, argument): # Create argument and split it if in a mixed space function_spaces = [c.ufl_function_space() for c in coefficients] - domains = [fs.ufl_domain() for fs in function_spaces] - elements = [fs.ufl_element() for fs in function_spaces] if len(function_spaces) == 1: arguments = (Argument(function_spaces[0], number, part),) else: # Create in mixed space over assumed (for now) same domain + domains = [fs.ufl_domain() for fs in function_spaces] + elements = [fs.ufl_element() for fs in function_spaces] assert all(fs.ufl_domain() == domains[0] for fs in function_spaces) - elm = MixedElement(*elements) + elm = MixedElement(elements) fs = FunctionSpace(domains[0], elm) arguments = split(Argument(fs, number, part)) else: diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 7c3cfda43..e2cc86dbc 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -42,7 +42,7 @@ def __init__(self, domain, element): """Initialise.""" if domain is None: # DOLFIN hack - # TODO: Is anything expected from element.cell() in this case? + # TODO: Is anything expected from element.cell in this case? pass else: try: @@ -50,7 +50,7 @@ def __init__(self, domain, element): except AttributeError: raise ValueError("Expected non-abstract domain for initalization of function space.") else: - if element.cell() != domain_cell: + if element.cell != domain_cell: raise ValueError("Non-matching cell of finite element and domain.") AbstractFunctionSpace.__init__(self) diff --git a/ufl/geometry.py b/ufl/geometry.py index e06ec7793..a8f6c7720 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -9,6 +9,7 @@ from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.domain import as_domain, extract_unique_domain +from ufl.sobolevspace import H1 """ Possible coordinate bootstrapping: @@ -675,7 +676,8 @@ def is_cellwise_constant(self): # facets. Seems like too much work to fix right now. Only # true for a piecewise linear coordinate field with simplex # _facets_. - is_piecewise_linear = self._domain.ufl_coordinate_element().degree() == 1 + ce = self._domain.ufl_coordinate_element() + is_piecewise_linear = ce.embedded_superdegree <= 1 and ce in H1 return is_piecewise_linear and self._domain.ufl_cell().has_simplex_facets() diff --git a/ufl/index_combination_utils.py b/ufl/index_combination_utils.py index 6d820244e..1f7682ae0 100644 --- a/ufl/index_combination_utils.py +++ b/ufl/index_combination_utils.py @@ -7,10 +7,10 @@ from ufl.core.multiindex import FixedIndex, Index, indices - # FIXME: Some of these might be merged into one function, some might # be optimized + def unique_sorted_indices(indices): """Get unique sorted indices. diff --git a/ufl/indexed.py b/ufl/indexed.py index 43bae7f10..b22f3d7ef 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -8,18 +8,16 @@ from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str -from ufl.core.ufl_type import ufl_type +from ufl.core.multiindex import FixedIndex, Index, MultiIndex from ufl.core.operator import Operator -from ufl.core.multiindex import Index, FixedIndex, MultiIndex +from ufl.core.ufl_type import ufl_type from ufl.index_combination_utils import unique_sorted_indices from ufl.precedence import parstr -# --- Indexed expression --- - @ufl_type(is_shaping=True, num_ops=2, is_terminal_modifier=True) class Indexed(Operator): - """Indexed.""" + """Indexed expression.""" __slots__ = ( "ufl_free_indices", diff --git a/ufl/indexsum.py b/ufl/indexsum.py index 3df273255..b13829484 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -7,16 +7,16 @@ # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.core.ufl_type import ufl_type +from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str -from ufl.core.operator import Operator from ufl.core.multiindex import MultiIndex +from ufl.core.operator import Operator +from ufl.core.ufl_type import ufl_type from ufl.precedence import parstr -from ufl.constantvalue import Zero - # --- Sum over an index --- + @ufl_type(num_ops=2) class IndexSum(Operator): """Index sum.""" diff --git a/ufl/integral.py b/ufl/integral.py index f92aefbc5..d680f5a77 100644 --- a/ufl/integral.py +++ b/ufl/integral.py @@ -10,8 +10,8 @@ # Modified by Massimiliano Leoni, 2016. import ufl -from ufl.core.expr import Expr from ufl.checks import is_python_scalar, is_scalar_constant_expression +from ufl.core.expr import Expr from ufl.measure import Measure # noqa from ufl.protocols import id_or_none diff --git a/ufl/legacy/__init__.py b/ufl/legacy/__init__.py new file mode 100644 index 000000000..06a0b12ca --- /dev/null +++ b/ufl/legacy/__init__.py @@ -0,0 +1,25 @@ +"""Legacy UFL features.""" +# Copyright (C) 2008-2016 Martin Sandve Alnæs +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# Modified by Kristian B. Oelgaard +# Modified by Marie E. Rognes 2010, 2012 +# Modified by Andrew T. T. McRae 2014 +# Modified by Lawrence Mitchell 2014 + +import warnings as _warnings + +from ufl.legacy.brokenelement import BrokenElement +from ufl.legacy.enrichedelement import EnrichedElement, NodalEnrichedElement +from ufl.legacy.finiteelement import FiniteElement +from ufl.legacy.finiteelementbase import FiniteElementBase +from ufl.legacy.hdivcurl import HCurlElement, HDivElement, WithMapping +from ufl.legacy.mixedelement import MixedElement, TensorElement, VectorElement +from ufl.legacy.restrictedelement import RestrictedElement +from ufl.legacy.tensorproductelement import TensorProductElement + +_warnings.warn("The features in ufl.legacy are deprecated and will be removed in a future version.", + FutureWarning) diff --git a/ufl/finiteelement/brokenelement.py b/ufl/legacy/brokenelement.py similarity index 86% rename from ufl/finiteelement/brokenelement.py rename to ufl/legacy/brokenelement.py index 2467e9d0b..2563b868f 100644 --- a/ufl/finiteelement/brokenelement.py +++ b/ufl/legacy/brokenelement.py @@ -8,7 +8,7 @@ # # Modified by Massimiliano Leoni, 2016 -from ufl.finiteelement.finiteelementbase import FiniteElementBase +from ufl.legacy.finiteelementbase import FiniteElementBase from ufl.sobolevspace import L2 @@ -19,11 +19,11 @@ def __init__(self, element): self._element = element family = "BrokenElement" - cell = element.cell() + cell = element.cell degree = element.degree() quad_scheme = element.quadrature_scheme() - value_shape = element.value_shape() - reference_value_shape = element.reference_value_shape() + value_shape = element.value_shape + reference_value_shape = element.reference_value_shape FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) @@ -35,6 +35,7 @@ def mapping(self): """Doc.""" return self._element.mapping() + @property def sobolev_space(self): """Return the underlying Sobolev space.""" return L2 diff --git a/ufl/finiteelement/elementlist.py b/ufl/legacy/elementlist.py similarity index 99% rename from ufl/finiteelement/elementlist.py rename to ufl/legacy/elementlist.py index 90c783d4f..712b94bb1 100644 --- a/ufl/finiteelement/elementlist.py +++ b/ufl/legacy/elementlist.py @@ -16,12 +16,12 @@ # Modified by Robert Kloefkorn, 2022 import warnings + from numpy import asarray -from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl, HEin, HDivDiv, HInf -from ufl.utils.formatting import istr from ufl.cell import Cell, TensorProductCell - +from ufl.sobolevspace import H1, H2, L2, HCurl, HDiv, HDivDiv, HEin, HInf +from ufl.utils.formatting import istr # List of valid elements ufl_elements = {} diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/legacy/enrichedelement.py similarity index 83% rename from ufl/finiteelement/enrichedelement.py rename to ufl/legacy/enrichedelement.py index 81e2d88cc..76420f00b 100644 --- a/ufl/finiteelement/enrichedelement.py +++ b/ufl/legacy/enrichedelement.py @@ -10,7 +10,7 @@ # Modified by Marie E. Rognes 2010, 2012 # Modified by Massimiliano Leoni, 2016 -from ufl.finiteelement.finiteelementbase import FiniteElementBase +from ufl.legacy.finiteelementbase import FiniteElementBase class EnrichedElementBase(FiniteElementBase): @@ -20,8 +20,8 @@ def __init__(self, *elements): """Doc.""" self._elements = elements - cell = elements[0].cell() - if not all(e.cell() == cell for e in elements[1:]): + cell = elements[0].cell + if not all(e.cell == cell for e in elements[1:]): raise ValueError("Cell mismatch for sub elements of enriched element.") if isinstance(elements[0].degree(), int): @@ -38,12 +38,12 @@ def __init__(self, *elements): if not all(qs == quad_scheme for qs in quad_schemes): raise ValueError("Quadrature scheme mismatch.") - value_shape = elements[0].value_shape() - if not all(e.value_shape() == value_shape for e in elements[1:]): + value_shape = elements[0].value_shape + if not all(e.value_shape == value_shape for e in elements[1:]): raise ValueError("Element value shape mismatch.") - reference_value_shape = elements[0].reference_value_shape() - if not all(e.reference_value_shape() == reference_value_shape for e in elements[1:]): + reference_value_shape = elements[0].reference_value_shape + if not all(e.reference_value_shape == reference_value_shape for e in elements[1:]): raise ValueError("Element reference value shape mismatch.") # mapping = elements[0].mapping() # FIXME: This fails for a mixed subelement here. @@ -62,15 +62,16 @@ def mapping(self): """Doc.""" return self._elements[0].mapping() + @property def sobolev_space(self): """Return the underlying Sobolev space.""" elements = [e for e in self._elements] - if all(e.sobolev_space() == elements[0].sobolev_space() + if all(e.sobolev_space == elements[0].sobolev_space for e in elements): - return elements[0].sobolev_space() + return elements[0].sobolev_space else: # Find smallest shared Sobolev space over all sub elements - spaces = [e.sobolev_space() for e in elements] + spaces = [e.sobolev_space for e in elements] superspaces = [{s} | set(s.parents) for s in spaces] intersect = set.intersection(*superspaces) for s in intersect.copy(): @@ -92,6 +93,22 @@ def reconstruct(self, **kwargs): """Doc.""" return type(self)(*[e.reconstruct(**kwargs) for e in self._elements]) + @property + def embedded_subdegree(self): + """Return embedded subdegree.""" + if isinstance(self._degree, int): + return self._degree + else: + return min(e.embedded_subdegree for e in self._elements) + + @property + def embedded_superdegree(self): + """Return embedded superdegree.""" + if isinstance(self._degree, int): + return self._degree + else: + return max(e.embedded_superdegree for e in self._elements) + class EnrichedElement(EnrichedElementBase): r"""The vector sum of several finite element spaces. diff --git a/ufl/finiteelement/finiteelement.py b/ufl/legacy/finiteelement.py similarity index 93% rename from ufl/finiteelement/finiteelement.py rename to ufl/legacy/finiteelement.py index 0962e82a3..1166ed741 100644 --- a/ufl/finiteelement/finiteelement.py +++ b/ufl/legacy/finiteelement.py @@ -10,12 +10,10 @@ # Modified by Anders Logg 2014 # Modified by Massimiliano Leoni, 2016 +from ufl.cell import TensorProductCell, as_cell +from ufl.legacy.elementlist import canonical_element_description, simplices +from ufl.legacy.finiteelementbase import FiniteElementBase from ufl.utils.formatting import istr -from ufl.cell import as_cell - -from ufl.cell import TensorProductCell -from ufl.finiteelement.elementlist import canonical_element_description, simplices -from ufl.finiteelement.finiteelementbase import FiniteElementBase class FiniteElement(FiniteElementBase): @@ -37,9 +35,10 @@ def __new__(cls, if isinstance(cell, TensorProductCell): # Delay import to avoid circular dependency at module load time - from ufl.finiteelement.tensorproductelement import TensorProductElement - from ufl.finiteelement.enrichedelement import EnrichedElement - from ufl.finiteelement.hdivcurl import HDivElement as HDiv, HCurlElement as HCurl + from ufl.legacy.enrichedelement import EnrichedElement + from ufl.legacy.hdivcurl import HCurlElement as HCurl + from ufl.legacy.hdivcurl import HDivElement as HDiv + from ufl.legacy.tensorproductelement import TensorProductElement family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \ canonical_element_description(family, cell, degree, form_degree) @@ -171,7 +170,7 @@ def __init__(self, else: var_str = ", variant=%s" % repr(v) self._repr = "FiniteElement(%s, %s, %s%s%s)" % ( - repr(self.family()), repr(self.cell()), repr(self.degree()), quad_str, var_str) + repr(self.family()), repr(self.cell), repr(self.degree()), quad_str, var_str) assert '"' not in self._repr def __repr__(self): @@ -190,6 +189,7 @@ def mapping(self): """Return the mapping type for this element .""" return self._mapping + @property def sobolev_space(self): """Return the underlying Sobolev space.""" return self._sobolev_space @@ -203,7 +203,7 @@ def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, var if family is None: family = self.family() if cell is None: - cell = self.cell() + cell = self.cell if degree is None: degree = self.degree() if quad_scheme is None: @@ -219,7 +219,7 @@ def __str__(self): v = self.variant() v = "" if v is None else "(%s)" % v return "<%s%s%s%s on a %s>" % (self._short_name, istr(self.degree()), - qs, v, self.cell()) + qs, v, self.cell) def shortstr(self): """Format as string for pretty printing.""" @@ -228,7 +228,7 @@ def shortstr(self): def __getnewargs__(self): """Return the arguments which pickle needs to recreate the object.""" return (self.family(), - self.cell(), + self.cell, self.degree(), None, self.quadrature_scheme(), diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/legacy/finiteelementbase.py similarity index 81% rename from ufl/finiteelement/finiteelementbase.py rename to ufl/legacy/finiteelementbase.py index 7f34252fc..88eba6c8c 100644 --- a/ufl/finiteelement/finiteelementbase.py +++ b/ufl/legacy/finiteelementbase.py @@ -10,15 +10,18 @@ # Modified by Marie E. Rognes 2010, 2012 # Modified by Massimiliano Leoni, 2016 -from ufl.utils.sequences import product +from abc import abstractmethod, abstractproperty + +from ufl import pullback from ufl.cell import AbstractCell, as_cell -from abc import ABC, abstractmethod +from ufl.finiteelement import AbstractFiniteElement +from ufl.utils.sequences import product -class FiniteElementBase(ABC): +class FiniteElementBase(AbstractFiniteElement): """Base class for all finite elements.""" __slots__ = ("_family", "_cell", "_degree", "_quad_scheme", - "_value_shape", "_reference_value_shape", "__weakref__") + "_value_shape", "_reference_value_shape") # TODO: Not all these should be in the base class! In particular # family, degree, and quad_scheme do not belong here. @@ -49,7 +52,7 @@ def __repr__(self): """Format as string for evaluation as Python object.""" pass - @abstractmethod + @abstractproperty def sobolev_space(self): """Return the underlying Sobolev space.""" pass @@ -104,14 +107,13 @@ def variant(self): def degree(self, component=None): """Return polynomial degree of finite element.""" - # FIXME: Consider embedded_degree concept for more accurate - # degree, see blueprint return self._degree def quadrature_scheme(self): """Return quadrature scheme of finite element.""" return self._quad_scheme + @property def cell(self): """Return cell of finite element.""" return self._cell @@ -120,21 +122,25 @@ def is_cellwise_constant(self, component=None): """Return whether the basis functions of this element is spatially constant over each cell.""" return self._is_globally_constant() or self.degree() == 0 + @property def value_shape(self): """Return the shape of the value space on the global domain.""" return self._value_shape + @property def reference_value_shape(self): """Return the shape of the value space on the reference cell.""" return self._reference_value_shape + @property def value_size(self): """Return the integer product of the value shape.""" - return product(self.value_shape()) + return product(self.value_shape) + @property def reference_value_size(self): """Return the integer product of the reference value shape.""" - return product(self.reference_value_shape()) + return product(self.reference_value_shape) def symmetry(self): # FIXME: different approach r"""Return the symmetry dict. @@ -148,7 +154,7 @@ def symmetry(self): # FIXME: different approach def _check_component(self, i): """Check that component index i is valid.""" - sh = self.value_shape() + sh = self.value_shape r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): raise ValueError( @@ -174,7 +180,7 @@ def extract_component(self, i): def _check_reference_component(self, i): """Check that reference component index i is valid.""" - sh = self.value_shape() + sh = self.value_shape r = len(sh) if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): raise ValueError( @@ -201,10 +207,12 @@ def extract_reference_component(self, i): self._check_reference_component(i) return (i, self) + @property def num_sub_elements(self): """Return number of sub-elements.""" return 0 + @property def sub_elements(self): """Return list of sub-elements.""" return [] @@ -213,20 +221,20 @@ def __add__(self, other): """Add two elements, creating an enriched element.""" if not isinstance(other, FiniteElementBase): raise ValueError(f"Can't add element and {other.__class__}.") - from ufl.finiteelement import EnrichedElement + from ufl.legacy import EnrichedElement return EnrichedElement(self, other) def __mul__(self, other): """Multiply two elements, creating a mixed element.""" if not isinstance(other, FiniteElementBase): raise ValueError("Can't multiply element and {other.__class__}.") - from ufl.finiteelement import MixedElement + from ufl.legacy import MixedElement return MixedElement(self, other) def __getitem__(self, index): """Restrict finite element to a subdomain, subcomponent or topology (cell).""" if index in ("facet", "interior"): - from ufl.finiteelement import RestrictedElement + from ufl.legacy import RestrictedElement return RestrictedElement(self, index) else: raise KeyError(f"Invalid index for restriction: {repr(index)}") @@ -234,3 +242,35 @@ def __getitem__(self, index): def __iter__(self): """Iter.""" raise TypeError(f"'{type(self).__name__}' object is not iterable") + + @property + def embedded_superdegree(self): + """Doc.""" + return self.degree() + + @property + def embedded_subdegree(self): + """Doc.""" + return self.degree() + + @property + def pullback(self): + """Get the pull back.""" + if self.mapping() == "identity": + return pullback.identity_pullback + elif self.mapping() == "L2 Piola": + return pullback.l2_piola + elif self.mapping() == "covariant Piola": + return pullback.covariant_piola + elif self.mapping() == "contravariant Piola": + return pullback.contravariant_piola + elif self.mapping() == "double covariant Piola": + return pullback.double_covariant_piola + elif self.mapping() == "double contravariant Piola": + return pullback.double_contravariant_piola + elif self.mapping() == "custom": + return pullback.custom_pullback + elif self.mapping() == "physical": + return pullback.physical_pullback + + raise ValueError(f"Unsupported mapping: {self.mapping()}") diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/legacy/hdivcurl.py similarity index 81% rename from ufl/finiteelement/hdivcurl.py rename to ufl/legacy/hdivcurl.py index fde6ff33b..3f693017e 100644 --- a/ufl/finiteelement/hdivcurl.py +++ b/ufl/legacy/hdivcurl.py @@ -7,8 +7,8 @@ # # Modified by Massimiliano Leoni, 2016 -from ufl.finiteelement.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import HDiv, HCurl, L2 +from ufl.legacy.finiteelementbase import FiniteElementBase +from ufl.sobolevspace import L2, HCurl, HDiv class HDivElement(FiniteElementBase): @@ -20,11 +20,11 @@ def __init__(self, element): self._element = element family = "TensorProductElement" - cell = element.cell() + cell = element.cell degree = element.degree() quad_scheme = element.quadrature_scheme() - value_shape = (element.cell().geometric_dimension(),) - reference_value_shape = (element.cell().topological_dimension(),) + value_shape = (element.cell.geometric_dimension(),) + reference_value_shape = (element.cell.topological_dimension(),) # Skipping TensorProductElement constructor! Bad code smell, refactor to avoid this non-inheritance somehow. FiniteElementBase.__init__(self, family, cell, degree, @@ -38,6 +38,7 @@ def mapping(self): """Doc.""" return "contravariant Piola" + @property def sobolev_space(self): """Return the underlying Sobolev space.""" return HDiv @@ -58,6 +59,16 @@ def shortstr(self): """Format as string for pretty printing.""" return f"HDivElement({self._element.shortstr()})" + @property + def embedded_subdegree(self): + """Return embedded subdegree.""" + return self._element.embedded_subdegree + + @property + def embedded_superdegree(self): + """Return embedded superdegree.""" + return self._element.embedded_superdegree + class HCurlElement(FiniteElementBase): """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" @@ -68,10 +79,10 @@ def __init__(self, element): self._element = element family = "TensorProductElement" - cell = element.cell() + cell = element.cell degree = element.degree() quad_scheme = element.quadrature_scheme() - cell = element.cell() + cell = element.cell value_shape = (cell.geometric_dimension(),) reference_value_shape = (cell.topological_dimension(),) # TODO: Is this right? # Skipping TensorProductElement constructor! Bad code smell, @@ -87,6 +98,7 @@ def mapping(self): """Doc.""" return "covariant Piola" + @property def sobolev_space(self): """Return the underlying Sobolev space.""" return HCurl @@ -136,36 +148,39 @@ def __repr__(self): """Doc.""" return f"WithMapping({repr(self.wrapee)}, '{self._mapping}')" + @property def value_shape(self): """Doc.""" - gdim = self.cell().geometric_dimension() + gdim = self.cell.geometric_dimension() mapping = self.mapping() if mapping in {"covariant Piola", "contravariant Piola"}: return (gdim,) elif mapping in {"double covariant Piola", "double contravariant Piola"}: return (gdim, gdim) else: - return self.wrapee.value_shape() + return self.wrapee.value_shape + @property def reference_value_shape(self): """Doc.""" - tdim = self.cell().topological_dimension() + tdim = self.cell.topological_dimension() mapping = self.mapping() if mapping in {"covariant Piola", "contravariant Piola"}: return (tdim,) elif mapping in {"double covariant Piola", "double contravariant Piola"}: return (tdim, tdim) else: - return self.wrapee.reference_value_shape() + return self.wrapee.reference_value_shape def mapping(self): """Doc.""" return self._mapping + @property def sobolev_space(self): """Return the underlying Sobolev space.""" if self.wrapee.mapping() == self.mapping(): - return self.wrapee.sobolev_space() + return self.wrapee.sobolev_space else: return L2 @@ -186,3 +201,13 @@ def __str__(self): def shortstr(self): """Doc.""" return f"WithMapping({self.wrapee.shortstr()}, {self._mapping})" + + @property + def embedded_subdegree(self): + """Return embedded subdegree.""" + return self._element.embedded_subdegree + + @property + def embedded_superdegree(self): + """Return embedded superdegree.""" + return self._element.embedded_superdegree diff --git a/ufl/finiteelement/mixedelement.py b/ufl/legacy/mixedelement.py similarity index 86% rename from ufl/finiteelement/mixedelement.py rename to ufl/legacy/mixedelement.py index ca16a3d80..97f194e55 100644 --- a/ufl/finiteelement/mixedelement.py +++ b/ufl/legacy/mixedelement.py @@ -10,13 +10,15 @@ # Modified by Anders Logg 2014 # Modified by Massimiliano Leoni, 2016 -from ufl.permutation import compute_indices -from ufl.utils.sequences import product, max_degree -from ufl.utils.indexflattening import flatten_multiindex, unflatten_index, shape_to_strides -from ufl.cell import as_cell +import numpy as np -from ufl.finiteelement.finiteelementbase import FiniteElementBase -from ufl.finiteelement.finiteelement import FiniteElement +from ufl.cell import as_cell +from ufl.legacy.finiteelement import FiniteElement +from ufl.legacy.finiteelementbase import FiniteElementBase +from ufl.permutation import compute_indices +from ufl.pullback import IdentityPullback, MixedPullback, SymmetricPullback +from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides, unflatten_index +from ufl.utils.sequences import max_degree, product class MixedElement(FiniteElementBase): @@ -38,7 +40,7 @@ def __init__(self, *elements, **kwargs): self._sub_elements = elements # Pick the first cell, for now all should be equal - cells = tuple(sorted(set(element.cell() for element in elements) - set([None]))) + cells = tuple(sorted(set(element.cell for element in elements) - set([None]))) self._cells = cells if cells: cell = cells[0] @@ -58,8 +60,8 @@ def __init__(self, *elements, **kwargs): raise ValueError("Quadrature scheme mismatch for sub elements of mixed element.") # Compute value sizes in global and reference configurations - value_size_sum = sum(product(s.value_shape()) for s in self._sub_elements) - reference_value_size_sum = sum(product(s.reference_value_shape()) for s in self._sub_elements) + value_size_sum = sum(product(s.value_shape) for s in self._sub_elements) + reference_value_size_sum = sum(product(s.reference_value_shape) for s in self._sub_elements) # Default value shape: Treated simply as all subelement values # unpacked in a vector. @@ -110,7 +112,7 @@ def symmetry(self): # Base index of the current subelement into mixed value j = 0 for e in self._sub_elements: - sh = e.value_shape() + sh = e.value_shape st = shape_to_strides(sh) # Map symmetries of subelement into index space of this # element @@ -120,13 +122,14 @@ def symmetry(self): sm[(j0,)] = (j1,) # Update base index for next element j += product(sh) - if j != product(self.value_shape()): + if j != product(self.value_shape): raise ValueError("Size mismatch in symmetry algorithm.") return sm or {} + @property def sobolev_space(self): """Doc.""" - return max(e.sobolev_space() for e in self._sub_elements) + return max(e.sobolev_space for e in self._sub_elements) def mapping(self): """Doc.""" @@ -135,10 +138,12 @@ def mapping(self): else: return "undefined" + @property def num_sub_elements(self): """Return number of sub elements.""" return len(self._sub_elements) + @property def sub_elements(self): """Return list of sub elements.""" return self._sub_elements @@ -153,14 +158,14 @@ def extract_subelement_component(self, i): self._check_component(i) # Select between indexing modes - if len(self.value_shape()) == 1: + if len(self.value_shape) == 1: # Indexing into a long vector of flattened subelement # shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): - sh = e.value_shape() + sh = e.value_shape si = product(sh) if j < si: break @@ -198,13 +203,13 @@ def extract_subelement_reference_component(self, i): self._check_reference_component(i) # Select between indexing modes - assert len(self.reference_value_shape()) == 1 + assert len(self.reference_value_shape) == 1 # Indexing into a long vector of flattened subelement shapes j, = i # Find subelement for this index for sub_element_index, e in enumerate(self._sub_elements): - sh = e.reference_value_shape() + sh = e.reference_value_shape si = product(sh) if j < si: break @@ -228,7 +233,7 @@ def extract_reference_component(self, i): def is_cellwise_constant(self, component=None): """Return whether the basis functions of this element is spatially constant over each cell.""" if component is None: - return all(e.is_cellwise_constant() for e in self.sub_elements()) + return all(e.is_cellwise_constant() for e in self.sub_elements) else: i, e = self.extract_component(component) return e.is_cellwise_constant() @@ -241,14 +246,30 @@ def degree(self, component=None): i, e = self.extract_component(component) return e.degree() + @property + def embedded_subdegree(self): + """Return embedded subdegree.""" + if isinstance(self._degree, int): + return self._degree + else: + return min(e.embedded_subdegree for e in self.sub_elements) + + @property + def embedded_superdegree(self): + """Return embedded superdegree.""" + if isinstance(self._degree, int): + return self._degree + else: + return max(e.embedded_superdegree for e in self.sub_elements) + def reconstruct(self, **kwargs): """Doc.""" - return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()]) + return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements]) def variant(self): """Doc.""" try: - variant, = {e.variant() for e in self.sub_elements()} + variant, = {e.variant() for e in self.sub_elements} return variant except ValueError: return None @@ -263,6 +284,14 @@ def shortstr(self): tmp = ", ".join(element.shortstr() for element in self._sub_elements) return "Mixed<" + tmp + ">" + @property + def pullback(self): + """Get the pull back.""" + for e in self.sub_elements: + if not isinstance(e.pullback, IdentityPullback): + return MixedPullback(self) + return IdentityPullback() + class VectorElement(MixedElement): """A special case of a mixed finite element where all elements are equal.""" @@ -274,7 +303,7 @@ def __init__(self, family, cell=None, degree=None, dim=None, """Create vector element (repeated mixed element).""" if isinstance(family, FiniteElementBase): sub_element = family - cell = sub_element.cell() + cell = sub_element.cell variant = sub_element.variant() else: if cell is not None: @@ -296,14 +325,14 @@ def __init__(self, family, cell=None, degree=None, dim=None, sub_elements = [sub_element] * dim # Compute value shapes - value_shape = (dim,) + sub_element.value_shape() - reference_value_shape = (dim,) + sub_element.reference_value_shape() + value_shape = (dim,) + sub_element.value_shape + reference_value_shape = (dim,) + sub_element.reference_value_shape # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) - FiniteElementBase.__init__(self, sub_element.family(), sub_element.cell(), sub_element.degree(), + FiniteElementBase.__init__(self, sub_element.family(), sub_element.cell, sub_element.degree(), sub_element.quadrature_scheme(), value_shape, reference_value_shape) self._sub_element = sub_element @@ -323,7 +352,7 @@ def __repr__(self): def reconstruct(self, **kwargs): """Doc.""" sub_element = self._sub_element.reconstruct(**kwargs) - return VectorElement(sub_element, dim=len(self.sub_elements())) + return VectorElement(sub_element, dim=len(self.sub_elements)) def variant(self): """Return the variant used to initialise the element.""" @@ -356,7 +385,7 @@ def __init__(self, family, cell=None, degree=None, shape=None, """Create tensor element (repeated mixed element with optional symmetries).""" if isinstance(family, FiniteElementBase): sub_element = family - cell = sub_element.cell() + cell = sub_element.cell variant = sub_element.variant() else: if cell is not None: @@ -423,8 +452,8 @@ def __init__(self, family, cell=None, degree=None, shape=None, reference_value_shape = shape self._mapping = sub_element.mapping() - value_shape = value_shape + sub_element.value_shape() - reference_value_shape = reference_value_shape + sub_element.reference_value_shape() + value_shape = value_shape + sub_element.value_shape + reference_value_shape = reference_value_shape + sub_element.reference_value_shape # Initialize element data MixedElement.__init__(self, sub_elements, value_shape=value_shape, reference_value_shape=reference_value_shape) @@ -445,6 +474,25 @@ def __init__(self, family, cell=None, degree=None, shape=None, self._repr = (f"TensorElement({repr(sub_element)}, shape={shape}, " f"symmetry={symmetry}{var_str})") + @property + def pullback(self): + """Get pull back.""" + if len(self._symmetry) > 0: + sub_element_value_shape = self.sub_elements[0].value_shape + for e in self.sub_elements: + if e.value_shape != sub_element_value_shape: + raise ValueError("Sub-elements must all have the same value size") + symmetry = {} + n = 0 + for i in np.ndindex(self.value_shape[:len(self.value_shape)-len(sub_element_value_shape)]): + if i in self._symmetry and self._symmetry[i] in symmetry: + symmetry[i] = symmetry[self._symmetry[i]] + else: + symmetry[i] = n + n += 1 + return SymmetricPullback(self, symmetry) + return super().pullback + def __repr__(self): """Doc.""" return self._repr @@ -501,7 +549,7 @@ def __str__(self): else: sym = "" return ("" % - (self.value_shape(), self._sub_element, sym)) + (self.value_shape, self._sub_element, sym)) def shortstr(self): """Format as string for pretty printing.""" @@ -510,5 +558,5 @@ def shortstr(self): sym = " with symmetries (%s)" % tmp else: sym = "" - return "Tensor<%s x %s%s>" % (self.value_shape(), + return "Tensor<%s x %s%s>" % (self.value_shape, self._sub_element.shortstr(), sym) diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/legacy/restrictedelement.py similarity index 91% rename from ufl/finiteelement/restrictedelement.py rename to ufl/legacy/restrictedelement.py index 7ad71b3a7..767444087 100644 --- a/ufl/finiteelement/restrictedelement.py +++ b/ufl/legacy/restrictedelement.py @@ -10,7 +10,7 @@ # Modified by Marie E. Rognes 2010, 2012 # Modified by Massimiliano Leoni, 2016 -from ufl.finiteelement.finiteelementbase import FiniteElementBase +from ufl.legacy.finiteelementbase import FiniteElementBase from ufl.sobolevspace import L2 valid_restriction_domains = ("interior", "facet", "face", "edge", "vertex") @@ -26,11 +26,11 @@ def __init__(self, element, restriction_domain): if restriction_domain not in valid_restriction_domains: raise ValueError(f"Expecting one of the strings: {valid_restriction_domains}") - FiniteElementBase.__init__(self, "RestrictedElement", element.cell(), + FiniteElementBase.__init__(self, "RestrictedElement", element.cell, element.degree(), element.quadrature_scheme(), - element.value_shape(), - element.reference_value_shape()) + element.value_shape, + element.reference_value_shape) self._element = element @@ -40,12 +40,13 @@ def __repr__(self): """Doc.""" return f"RestrictedElement({repr(self._element)}, {repr(self._restriction_domain)})" + @property def sobolev_space(self): """Doc.""" if self._restriction_domain == "interior": return L2 else: - return self._element.sobolev_space() + return self._element.sobolev_space def is_cellwise_constant(self): """Return whether the basis functions of this element is spatially constant over each cell.""" @@ -89,13 +90,15 @@ def symmetry(self): """ return self._element.symmetry() + @property def num_sub_elements(self): """Return number of sub elements.""" - return self._element.num_sub_elements() + return self._element.num_sub_elements + @property def sub_elements(self): """Return list of sub elements.""" - return self._element.sub_elements() + return self._element.sub_elements def num_restricted_sub_elements(self): """Return number of restricted sub elements.""" diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/legacy/tensorproductelement.py similarity index 83% rename from ufl/finiteelement/tensorproductelement.py rename to ufl/legacy/tensorproductelement.py index 07aad6e8f..d9f1ff1bd 100644 --- a/ufl/finiteelement/tensorproductelement.py +++ b/ufl/legacy/tensorproductelement.py @@ -13,10 +13,9 @@ from itertools import chain from ufl.cell import TensorProductCell, as_cell +from ufl.legacy.finiteelementbase import FiniteElementBase from ufl.sobolevspace import DirectionalSobolevSpace -from ufl.finiteelement.finiteelementbase import FiniteElementBase - class TensorProductElement(FiniteElementBase): r"""The tensor product of :math:`d` element spaces. @@ -43,7 +42,7 @@ def __init__(self, *elements, **kwargs): if cell is None: # Define cell as the product of each elements cell - cell = TensorProductCell(*[e.cell() for e in elements]) + cell = TensorProductCell(*[e.cell for e in elements]) else: cell = as_cell(cell) @@ -54,8 +53,8 @@ def __init__(self, *elements, **kwargs): quad_scheme = None # match FIAT implementation - value_shape = tuple(chain(*[e.value_shape() for e in elements])) - reference_value_shape = tuple(chain(*[e.reference_value_shape() for e in elements])) + value_shape = tuple(chain(*[e.value_shape for e in elements])) + reference_value_shape = tuple(chain(*[e.reference_value_shape for e in elements])) if len(value_shape) > 1: raise ValueError("Product of vector-valued elements not supported") if len(reference_value_shape) > 1: @@ -80,39 +79,42 @@ def mapping(self): else: return "undefined" + @property def sobolev_space(self): """Return the underlying Sobolev space of the TensorProductElement.""" elements = self._sub_elements - if all(e.sobolev_space() == elements[0].sobolev_space() + if all(e.sobolev_space == elements[0].sobolev_space for e in elements): - return elements[0].sobolev_space() + return elements[0].sobolev_space else: # Generate a DirectionalSobolevSpace which contains # continuity information parametrized by spatial index orders = [] for e in elements: - e_dim = e.cell().geometric_dimension() - e_order = (e.sobolev_space()._order,) * e_dim + e_dim = e.cell.geometric_dimension() + e_order = (e.sobolev_space._order,) * e_dim orders.extend(e_order) return DirectionalSobolevSpace(orders) + @property def num_sub_elements(self): """Return number of subelements.""" return len(self._sub_elements) + @property def sub_elements(self): """Return subelements (factors).""" return self._sub_elements def reconstruct(self, **kwargs): """Doc.""" - cell = kwargs.pop("cell", self.cell()) - return TensorProductElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()], cell=cell) + cell = kwargs.pop("cell", self.cell) + return TensorProductElement(*[e.reconstruct(**kwargs) for e in self.sub_elements], cell=cell) def variant(self): """Doc.""" try: - variant, = {e.variant() for e in self.sub_elements()} + variant, = {e.variant() for e in self.sub_elements} return variant except ValueError: return None @@ -126,3 +128,13 @@ def shortstr(self): """Short pretty-print.""" return "TensorProductElement(%s, cell=%s)" \ % (', '.join([e.shortstr() for e in self._sub_elements]), str(self._cell)) + + @property + def embedded_superdegree(self): + """Doc.""" + return sum(self.degree()) + + @property + def embedded_subdegree(self): + """Doc.""" + return min(self.degree()) diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 24ab0354d..704304d6c 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -8,15 +8,15 @@ # Modified by Anders Logg, 2008 # Modified by Kristian B. Oelgaard, 2011 -import math import cmath +import math import numbers import warnings +from ufl.constantvalue import (ComplexValue, ConstantValue, FloatValue, IntValue, RealValue, Zero, as_ufl, + is_true_ufl_scalar) from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import (is_true_ufl_scalar, Zero, RealValue, FloatValue, IntValue, ComplexValue, - ConstantValue, as_ufl) """ TODO: Include additional functions available in (need derivatives as well): diff --git a/ufl/matrix.py b/ufl/matrix.py index 7cf8e55bd..7fb1f5c07 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -7,15 +7,15 @@ # # Modified by Nacime Bouziani, 2021-2022. -from ufl.form import BaseForm -from ufl.core.ufl_type import ufl_type from ufl.argument import Argument +from ufl.core.ufl_type import ufl_type +from ufl.form import BaseForm from ufl.functionspace import AbstractFunctionSpace from ufl.utils.counted import Counted - # --- The Matrix class represents a matrix, an assembled two form --- + @ufl_type() class Matrix(BaseForm, Counted): """An assemble linear operator between two function spaces.""" @@ -68,6 +68,7 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in a Matrix.""" from ufl.domain import join_domains + # Collect unique domains self._domains = join_domains([fs.ufl_domain() for fs in self._ufl_function_spaces]) diff --git a/ufl/measure.py b/ufl/measure.py index e457842ab..97d719501 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -10,16 +10,14 @@ # Modified by Massimiliano Leoni, 2016. import numbers - from itertools import chain -from ufl.core.expr import Expr from ufl.checks import is_true_ufl_scalar from ufl.constantvalue import as_ufl -from ufl.domain import as_domain, AbstractDomain, extract_domains +from ufl.core.expr import Expr +from ufl.domain import AbstractDomain, as_domain, extract_domains from ufl.protocols import id_or_none - # Export list for ufl.classes __all_classes__ = ["Measure", "MeasureSum", "MeasureProduct"] @@ -123,9 +121,11 @@ def __init__(self, self._integral_type = as_integral_type(integral_type) # Check that we either have a proper AbstractDomain or none - self._domain = None if domain is None else as_domain(domain) - if not (self._domain is None or isinstance(self._domain, AbstractDomain)): - raise ValueError("Invalid domain.") + if domain is not None: + domain = as_domain(domain) + if not isinstance(domain, AbstractDomain): + raise ValueError("Invalid domain.") + self._domain = domain # Store subdomain data self._subdomain_data = subdomain_data @@ -231,11 +231,11 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, # over entire domain. To do this we need to hijack the first # argument: if subdomain_id is not None and ( - isinstance(subdomain_id, AbstractDomain) or hasattr(subdomain_id, 'ufl_domain') + isinstance(subdomain_id, AbstractDomain) or hasattr(subdomain_id, "ufl_domain") ): if domain is not None: raise ValueError("Ambiguous: setting domain both as keyword argument and first argument.") - subdomain_id, domain = "everywhere", as_domain(subdomain_id) + subdomain_id, domain = "everywhere", subdomain_id # If degree or scheme is set, inject into metadata. This is a # quick fix to enable the dx(..., degree=3) notation. @@ -344,8 +344,8 @@ def __rmul__(self, integrand): Integration properties are taken from this Measure object. """ # Avoid circular imports - from ufl.integral import Integral from ufl.form import Form + from ufl.integral import Integral # Allow python literals: 1*dx and 1.0*dx if isinstance(integrand, (int, float)): diff --git a/ufl/objects.py b/ufl/objects.py index 7e740cf72..fc57ea2ae 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -8,10 +8,9 @@ # Modified by Anders Logg, 2008 # Modified by Kristian Oelgaard, 2009 -from ufl.core.multiindex import indices from ufl.cell import Cell -from ufl.measure import Measure -from ufl.measure import integral_type_to_measure_name +from ufl.core.multiindex import indices +from ufl.measure import Measure, integral_type_to_measure_name # Default indices i, j, k, l = indices(4) # noqa: E741 diff --git a/ufl/operators.py b/ufl/operators.py index 8f99d501b..9dc5add03 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -14,30 +14,27 @@ # Modified by Kristian B. Oelgaard, 2011 # Modified by Massimiliano Leoni, 2016. -import warnings import operator +import warnings -from ufl.form import Form -from ufl.constantvalue import Zero, RealValue, ComplexValue, as_ufl -from ufl.differentiation import VariableDerivative, Grad, Div, Curl, NablaGrad, NablaDiv -from ufl.tensoralgebra import ( - Transposed, Inner, Outer, Dot, Cross, Perp, - Determinant, Inverse, Cofactor, Trace, Deviatoric, Skew, Sym) -from ufl.coefficient import Coefficient -from ufl.variable import Variable -from ufl.tensors import as_tensor, as_matrix, as_vector, ListTensor -from ufl.conditional import ( - EQ, NE, AndCondition, OrCondition, NotCondition, Conditional, MaxValue, MinValue) -from ufl.algebra import Conj, Real, Imag -from ufl.mathfunctions import ( - Sqrt, Exp, Ln, Erf, Cos, Sin, Tan, Cosh, Sinh, Tanh, Acos, Asin, Atan, Atan2, - BesselJ, BesselY, BesselI, BesselK) +from ufl import sobolevspace +from ufl.algebra import Conj, Imag, Real from ufl.averaging import CellAvg, FacetAvg -from ufl.indexed import Indexed -from ufl.geometry import SpatialCoordinate, FacetNormal from ufl.checks import is_cellwise_constant +from ufl.coefficient import Coefficient +from ufl.conditional import EQ, NE, AndCondition, Conditional, MaxValue, MinValue, NotCondition, OrCondition +from ufl.constantvalue import ComplexValue, RealValue, Zero, as_ufl +from ufl.differentiation import Curl, Div, Grad, NablaDiv, NablaGrad, VariableDerivative from ufl.domain import extract_domains -from ufl import sobolevspace +from ufl.form import Form +from ufl.geometry import FacetNormal, SpatialCoordinate +from ufl.indexed import Indexed +from ufl.mathfunctions import (Acos, Asin, Atan, Atan2, BesselI, BesselJ, BesselK, BesselY, Cos, Cosh, Erf, Exp, Ln, + Sin, Sinh, Sqrt, Tan, Tanh) +from ufl.tensoralgebra import (Cofactor, Cross, Determinant, Deviatoric, Dot, Inner, Inverse, Outer, Perp, Skew, Sym, + Trace, Transposed) +from ufl.tensors import ListTensor, as_matrix, as_tensor, as_vector +from ufl.variable import Variable # --- Basic operators --- @@ -671,7 +668,12 @@ def exterior_derivative(f): raise NotImplementedError index = int(indices[0]) element = expression.ufl_element() - element = element.extract_component(index)[1] + while index != 0: + for e in element.sub_elements: + if e.value_size > index: + element = e + break + index -= e.value_size elif isinstance(f, ListTensor): f0 = f.ufl_operands[0] f0expr, f0indices = f0.ufl_operands # FIXME: Assumption on type of f0!!! @@ -679,7 +681,12 @@ def exterior_derivative(f): raise NotImplementedError index = int(f0indices[0]) element = f0expr.ufl_element() - element = element.extract_component(index)[1] + while index != 0: + for e in element.sub_elements: + if e.value_size > index: + element = e + break + index -= e.value_size else: try: element = f.ufl_element() @@ -687,7 +694,7 @@ def exterior_derivative(f): raise ValueError(f"Unable to determine element from {f}") gdim = element.cell().geometric_dimension() - space = element.sobolev_space() + space = element.sobolev_space if space == sobolevspace.L2: return f diff --git a/ufl/precedence.py b/ufl/precedence.py index 2c3b705a1..0aea48b20 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -8,9 +8,9 @@ import warnings - # FIXME: This code is crap... + def parstr(child, parent, pre="(", post=")", format=str): """Parstr.""" # Execute when needed instead of on import, which leads to all @@ -41,8 +41,8 @@ def parstr(child, parent, pre="(", post=")", format=str): def build_precedence_list(): """Build precedence list.""" - from ufl.classes import (Operator, Terminal, Sum, IndexSum, Product, Division, Power, - MathFunction, BesselFunction, Abs, Indexed) + from ufl.classes import (Abs, BesselFunction, Division, Indexed, IndexSum, MathFunction, Operator, Power, Product, + Sum, Terminal) # TODO: Fill in other types... # Power <= Transposed @@ -74,7 +74,7 @@ def build_precedence_mapping(precedence_list): Utility function used by some external code. """ - from ufl.classes import Expr, all_ufl_classes, abstract_classes + from ufl.classes import Expr, abstract_classes, all_ufl_classes pm = {} missing = set() # Assign integer values for each precedence level diff --git a/ufl/pullback.py b/ufl/pullback.py new file mode 100644 index 000000000..dee12f0ce --- /dev/null +++ b/ufl/pullback.py @@ -0,0 +1,552 @@ +"""Pull back and push forward maps.""" +# Copyright (C) 2023 Matthew Scroggs, David Ham, Garth Wells +# +# This file is part of UFL (https://www.fenicsproject.org) +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +from __future__ import annotations + +import typing +from abc import ABC, abstractmethod, abstractproperty +from typing import TYPE_CHECKING + +import numpy as np + +from ufl.core.expr import Expr +from ufl.core.multiindex import indices +from ufl.domain import extract_unique_domain +from ufl.tensors import as_tensor + +if TYPE_CHECKING: + from ufl.finiteelement import AbstractFiniteElement as _AbstractFiniteElement + +__all_classes__ = ["NonStandardPullbackException", "AbstractPullback", "IdentityPullback", + "ContravariantPiola", "CovariantPiola", "L2Piola", "DoubleContravariantPiola", + "DoubleCovariantPiola", "MixedPullback", "SymmetricPullback", + "PhysicalPullback", "CustomPullback", "UndefinedPullback"] + + +class NonStandardPullbackException(BaseException): + """Exception to raise if a map is non-standard.""" + pass + + +class AbstractPullback(ABC): + """An abstract pull back.""" + + @abstractmethod + def __repr__(self) -> str: + """Return a representation of the object.""" + + @abstractmethod + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + + @abstractproperty + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + + def apply(self, expr: Expr) -> Expr: + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + raise NonStandardPullbackException() + + +class IdentityPullback(AbstractPullback): + """The identity pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "IdentityPullback()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return True + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + return expr + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + return element.reference_value_shape + + +class ContravariantPiola(AbstractPullback): + """The contravariant Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "ContravariantPiola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import Jacobian, JacobianDeterminant + + domain = extract_unique_domain(expr) + J = Jacobian(domain) + detJ = JacobianDeterminant(J) + transform = (1.0 / detJ) * J + # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) + *k, i, j = indices(len(expr.ufl_shape) + 1) + kj = (*k, j) + return as_tensor(transform[i, j] * expr[kj], (*k, i)) + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + gdim = element.cell.geometric_dimension() + return (gdim, ) + element.reference_value_shape[1:] + + +class CovariantPiola(AbstractPullback): + """The covariant Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "CovariantPiola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import JacobianInverse + + domain = extract_unique_domain(expr) + K = JacobianInverse(domain) + # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) + *k, i, j = indices(len(expr.ufl_shape) + 1) + kj = (*k, j) + return as_tensor(K[j, i] * expr[kj], (*k, i)) + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + gdim = element.cell.geometric_dimension() + return (gdim, ) + element.reference_value_shape[1:] + + +class L2Piola(AbstractPullback): + """The L2 Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "L2Piola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import JacobianDeterminant + + domain = extract_unique_domain(expr) + detJ = JacobianDeterminant(domain) + return expr / detJ + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + return element.reference_value_shape + + +class DoubleContravariantPiola(AbstractPullback): + """The double contravariant Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "DoubleContravariantPiola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import Jacobian, JacobianDeterminant + + domain = extract_unique_domain(expr) + J = Jacobian(domain) + detJ = JacobianDeterminant(J) + # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) + *k, i, j, m, n = indices(len(expr.ufl_shape) + 2) + kmn = (*k, m, n) + return as_tensor((1.0 / detJ)**2 * J[i, m] * expr[kmn] * J[j, n], (*k, i, j)) + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + gdim = element.cell.geometric_dimension() + return (gdim, gdim) + + +class DoubleCovariantPiola(AbstractPullback): + """The double covariant Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "DoubleCovariantPiola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import JacobianInverse + + domain = extract_unique_domain(expr) + K = JacobianInverse(domain) + # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) + *k, i, j, m, n = indices(len(expr.ufl_shape) + 2) + kmn = (*k, m, n) + return as_tensor(K[m, i] * expr[kmn] * K[n, j], (*k, i, j)) + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + gdim = element.cell.geometric_dimension() + return (gdim, gdim) + + +class MixedPullback(AbstractPullback): + """Pull back for a mixed element.""" + + def __init__(self, element: _AbstractFiniteElement): + """Initalise. + + Args: + element: The mixed element + """ + self._element = element + + def __repr__(self) -> str: + """Return a representation of the object.""" + return f"MixedPullback({self._element!r})" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return all(e.pullback.is_identity for e in self._element.sub_elements) + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] + g_components = [] + offset = 0 + # For each unique piece in reference space, apply the appropriate pullback + for subelem in self._element.sub_elements: + rsub = as_tensor(np.asarray( + rflat[offset: offset + subelem.reference_value_size] + ).reshape(subelem.reference_value_shape)) + rmapped = subelem.pullback.apply(rsub) + # Flatten into the pulled back expression for the whole thing + g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) + offset += subelem.reference_value_size + # And reshape appropriately + f = as_tensor(np.asarray(g_components).reshape(self._element.value_shape)) + if f.ufl_shape != self._element.value_shape: + raise ValueError("Expecting pulled back expression with shape " + f"'{self._element.value_shape}', got '{f.ufl_shape}'") + return f + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + assert element == self._element + dim = sum(e.value_size for e in self._element.sub_elements) + return (dim, ) + + +class SymmetricPullback(AbstractPullback): + """Pull back for an element with symmetry.""" + + def __init__(self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing.tuple[int, ...], int]): + """Initalise. + + Args: + element: The element + symmetry: A dictionary mapping from the component in physical space to the local component + """ + self._element = element + self._symmetry = symmetry + + self._sub_element_value_shape = element.sub_elements[0].value_shape + for e in element.sub_elements: + if e.value_shape != self._sub_element_value_shape: + raise ValueError("Sub-elements must all have the same value shape.") + self._block_shape = tuple(i + 1 for i in max(symmetry.keys())) + + def __repr__(self) -> str: + """Return a representation of the object.""" + return f"SymmetricPullback({self._element!r}, {self._symmetry!r})" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return all(e.pullback.is_identity for e in self._element.sub_elements) + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] + g_components = [] + offsets = [0] + for subelem in self._element.sub_elements: + offsets.append(offsets[-1] + subelem.reference_value_size) + # For each unique piece in reference space, apply the appropriate pullback + for component in np.ndindex(self._block_shape): + i = self._symmetry[component] + subelem = self._element.sub_elements[i] + rsub = as_tensor(np.asarray( + rflat[offsets[i]:offsets[i+1]] + ).reshape(subelem.reference_value_shape)) + rmapped = subelem.pullback.apply(rsub) + # Flatten into the pulled back expression for the whole thing + g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) + # And reshape appropriately + f = as_tensor(np.asarray(g_components).reshape(self._element.value_shape)) + if f.ufl_shape != self._element.value_shape: + raise ValueError(f"Expecting pulled back expression with shape " + f"'{self._element.value_shape}', got '{f.ufl_shape}'") + return f + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + assert element == self._element + return tuple(i + 1 for i in max(self._symmetry.keys())) + + +class PhysicalPullback(AbstractPullback): + """Physical pull back. + + This should probably be removed. + """ + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "PhysicalPullback()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return True + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + return expr + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + raise NotImplementedError() + + +class CustomPullback(AbstractPullback): + """Custom pull back. + + This should probably be removed. + """ + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "CustomPullback()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return True + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + return expr + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + raise NotImplementedError() + + +class UndefinedPullback(AbstractPullback): + """Undefined pull back. + + This should probably be removed. + """ + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "UndefinedPullback()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return True + + def physical_value_shape(self, element) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + + Returns: + The value shape when the pull back is applied to the given element + """ + raise NotImplementedError() + + +identity_pullback = IdentityPullback() +covariant_piola = CovariantPiola() +contravariant_piola = ContravariantPiola() +l2_piola = L2Piola() +double_covariant_piola = DoubleCovariantPiola() +double_contravariant_piola = DoubleContravariantPiola() +physical_pullback = PhysicalPullback() +custom_pullback = CustomPullback() +undefined_pullback = UndefinedPullback() diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index c1d13bcf1..5a4c5bb11 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -5,9 +5,9 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.core.ufl_type import ufl_type from ufl.core.operator import Operator from ufl.core.terminal import FormArgument +from ufl.core.ufl_type import ufl_type @ufl_type(num_ops=1, @@ -28,7 +28,7 @@ def __init__(self, f): @property def ufl_shape(self): """Get the UFL shape.""" - return self.ufl_operands[0].ufl_element().reference_value_shape() + return self.ufl_operands[0].ufl_element().reference_value_shape def evaluate(self, x, mapping, component, index_values, derivatives=()): """Get child from mapping and return the component asked for.""" diff --git a/ufl/restriction.py b/ufl/restriction.py index 77b03c71a..2871cd53f 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -6,12 +6,12 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from ufl.core.operator import Operator -from ufl.precedence import parstr from ufl.core.ufl_type import ufl_type - +from ufl.precedence import parstr # --- Restriction operators --- + @ufl_type(is_abstract=True, num_ops=1, inherit_shape_from_operand=0, diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index b697a0685..9500ae422 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -18,6 +18,8 @@ from functools import total_ordering from math import inf, isinf +__all_classes__ = ["SobolevSpace", "DirectionalSobolevSpace"] + @total_ordering class SobolevSpace(object): @@ -83,24 +85,13 @@ def __contains__(self, other): raise TypeError("Unable to test for inclusion of a " "SobolevSpace in another SobolevSpace. " "Did you mean to use <= instead?") - return other.sobolev_space() == self or self in other.sobolev_space().parents + return (other.sobolev_space == self or + self in other.sobolev_space.parents) def __lt__(self, other): """In common with intrinsic Python sets, < indicates "is a proper subset of".""" return other in self.parents - def __call__(self, element): - """Syntax shortcut to create a HDivElement or HCurlElement.""" - if self.name == "HDiv": - from ufl.finiteelement import HDivElement - return HDivElement(element) - elif self.name == "HCurl": - from ufl.finiteelement import HCurlElement - return HCurlElement(element) - raise NotImplementedError( - "SobolevSpace has no call operator (only the specific HDiv and HCurl instances)." - ) - @total_ordering class DirectionalSobolevSpace(SobolevSpace): @@ -140,9 +131,9 @@ def __contains__(self, other): raise TypeError("Unable to test for inclusion of a " "SobolevSpace in another SobolevSpace. " "Did you mean to use <= instead?") - return (other.sobolev_space() == self or all( - self[i] in other.sobolev_space().parents - for i in self._spatial_indices)) + return (other.sobolev_space == self or + all(self[i] in other.sobolev_space.parents + for i in self._spatial_indices)) def __eq__(self, other): """Check equality.""" diff --git a/ufl/sorting.py b/ufl/sorting.py index 445fa0739..5efe2a44a 100644 --- a/ufl/sorting.py +++ b/ufl/sorting.py @@ -14,7 +14,6 @@ from functools import cmp_to_key -from ufl.core.expr import Expr from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.core.multiindex import FixedIndex, MultiIndex @@ -98,7 +97,7 @@ def _cmp_terminal_by_repr(a, b): # Hack up a MultiFunction-like type dispatch for terminal comparisons -_terminal_cmps = [_cmp_terminal_by_repr] * Expr._ufl_num_typecodes_ +_terminal_cmps = {} _terminal_cmps[MultiIndex._ufl_typecode_] = _cmp_multi_index _terminal_cmps[Argument._ufl_typecode_] = _cmp_argument _terminal_cmps[Coefficient._ufl_typecode_] = _cmp_coefficient @@ -120,7 +119,11 @@ def cmp_expr(a, b): # Now we know that the type is the same, check further based # on type specific properties. if a._ufl_is_terminal_: - c = _terminal_cmps[x](a, b) + if x in _terminal_cmps: + c = _terminal_cmps[x](a, b) + else: + c = _cmp_terminal_by_repr(a, b) + if c: return c else: diff --git a/ufl/split_functions.py b/ufl/split_functions.py index f285f8ea5..886c98915 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -7,12 +7,11 @@ # # Modified by Anders Logg, 2008 -from ufl.utils.sequences import product -from ufl.finiteelement import TensorElement -from ufl.tensors import as_vector, as_matrix, ListTensor from ufl.indexed import Indexed from ufl.permutation import compute_indices +from ufl.tensors import ListTensor, as_matrix, as_vector from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides +from ufl.utils.sequences import product def split(v): @@ -50,19 +49,15 @@ def split(v): # Special case: simple element, just return function in a tuple element = v.ufl_element() - if element.num_sub_elements() == 0: + if element.num_sub_elements == 0: assert end is None return (v,) - if isinstance(element, TensorElement): - if element.symmetry(): - raise ValueError("Split not implemented for symmetric tensor elements.") - if len(v.ufl_shape) != 1: raise ValueError("Don't know how to split tensor valued mixed functions without flattened index space.") # Compute value size and set default range end - value_size = product(element.value_shape()) + value_size = element.value_size if end is None: end = value_size else: @@ -70,19 +65,22 @@ def split(v): # corresponding to beginning of range j = begin while True: - sub_i, j = element.extract_subelement_component(j) - element = element.sub_elements()[sub_i] + for e in element.sub_elements: + if j < e.value_size: + element = e + break + j -= e.value_size # Then break when we find the subelement that covers the whole range - if product(element.value_shape()) == (end - begin): + if element.value_size == (end - begin): break # Build expressions representing the subfunction of v for each subelement offset = begin sub_functions = [] - for i, e in enumerate(element.sub_elements()): + for i, e in enumerate(element.sub_elements): # Get shape, size, indices, and v components # corresponding to subelement value - shape = e.value_shape() + shape = e.value_shape strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index a0291d060..733b24388 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -5,13 +5,13 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later +from ufl.algebra import Conj, Operator +from ufl.constantvalue import Zero from ufl.core.expr import ufl_err_str from ufl.core.ufl_type import ufl_type -from ufl.constantvalue import Zero -from ufl.algebra import Operator, Conj +from ufl.index_combination_utils import merge_nonoverlapping_indices from ufl.precedence import parstr from ufl.sorting import sorted_expr -from ufl.index_combination_utils import merge_nonoverlapping_indices # Algebraic operations on tensors: # FloatValues: diff --git a/ufl/tensors.py b/ufl/tensors.py index d89045ce9..2fe829990 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -7,17 +7,17 @@ # # Modified by Massimiliano Leoni, 2016. -from ufl.core.ufl_type import ufl_type +from ufl.constantvalue import Zero, as_ufl from ufl.core.expr import Expr +from ufl.core.multiindex import FixedIndex, Index, MultiIndex, indices from ufl.core.operator import Operator -from ufl.constantvalue import as_ufl, Zero -from ufl.core.multiindex import Index, FixedIndex, MultiIndex, indices -from ufl.indexed import Indexed +from ufl.core.ufl_type import ufl_type from ufl.index_combination_utils import remove_indices - +from ufl.indexed import Indexed # --- Classes representing tensors of UFL expressions --- + @ufl_type(is_shaping=True, num_ops="varying", inherit_indices_from_operand=0) class ListTensor(Operator): """Wraps a list of expressions into a tensor valued expression of one higher rank.""" diff --git a/ufl/utils/sequences.py b/ufl/utils/sequences.py index 20ebe1dc9..9904287c4 100644 --- a/ufl/utils/sequences.py +++ b/ufl/utils/sequences.py @@ -7,6 +7,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later from functools import reduce + import numpy diff --git a/ufl/variable.py b/ufl/variable.py index ee8587119..84a795434 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -8,12 +8,12 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.utils.counted import Counted +from ufl.constantvalue import as_ufl from ufl.core.expr import Expr -from ufl.core.ufl_type import ufl_type -from ufl.core.terminal import Terminal from ufl.core.operator import Operator -from ufl.constantvalue import as_ufl +from ufl.core.terminal import Terminal +from ufl.core.ufl_type import ufl_type +from ufl.utils.counted import Counted @ufl_type() From 4b6142a7c69f5360af10897c98aa9fec0f7d96ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 16 Oct 2023 16:39:34 +0200 Subject: [PATCH 066/136] Remove deprecated functionality (attach_operators_from_hash_data). (#220) * Remove deprecated functionality (attach_operators_from_hash_data). The deprecation was introduced 4 months ago (https://github.com/FEniCS/ufl/pull/168). Since all the core objects of UFL is using it (Mesh and FunctionSpace) it means that pytest is throwing tons of deprecation warnings at import: ```python python3 -W error::DeprecationWarning -c "import ufl.Mesh" Traceback (most recent call last): File "", line 1, in File "/root/shared/ufl/__init__.py", line 265, in from ufl.domain import as_domain, AbstractDomain, Mesh, MeshView, TensorProductMesh File "/root/shared/ufl/domain.py", line 190, in class TensorProductMesh(AbstractDomain): File "/root/shared/ufl/core/ufl_type.py", line 56, in attach_operators_from_hash_data warnings.warn("attach_operators_from_hash_data deprecated, please use UFLObject instead.", DeprecationWarning) DeprecationWarning: attach_operators_from_hash_data deprecated, please use UFLObject instead. ``` * Flake8 * implement __str__ for function spaces --------- Co-authored-by: Matthew Scroggs --- test/test_strip_forms.py | 8 +++--- ufl/core/ufl_id.py | 2 -- ufl/core/ufl_type.py | 29 ---------------------- ufl/domain.py | 8 +++--- ufl/functionspace.py | 53 +++++++++++++++++++++------------------- 5 files changed, 34 insertions(+), 66 deletions(-) diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index 9a74ac506..27e75869d 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -4,7 +4,7 @@ from ufl import Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle from ufl.algorithms import replace_terminal_data, strip_terminal_data from ufl.core.ufl_id import attach_ufl_id -from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.core.ufl_type import UFLObject from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -13,16 +13,14 @@ """The minimum value returned by sys.getrefcount.""" -@attach_operators_from_hash_data @attach_ufl_id -class AugmentedMesh(Mesh): +class AugmentedMesh(Mesh, UFLObject): def __init__(self, *args, data): super().__init__(*args) self.data = data -@attach_operators_from_hash_data -class AugmentedFunctionSpace(FunctionSpace): +class AugmentedFunctionSpace(FunctionSpace, UFLObject): def __init__(self, *args, data): super().__init__(*args) self.data = data diff --git a/ufl/core/ufl_id.py b/ufl/core/ufl_id.py index 1af849ba1..bc1206761 100644 --- a/ufl/core/ufl_id.py +++ b/ufl/core/ufl_id.py @@ -54,8 +54,6 @@ def init_ufl_id(self, ufl_id): return init_ufl_id # Modify class: - if hasattr(cls, "__slots__"): - assert "_ufl_id" in cls.__slots__ cls._ufl_global_id = 0 cls.ufl_id = _get_ufl_id cls._init_ufl_id = _init_ufl_id(cls) diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index b3bfa0bd6..e19a9c340 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -11,10 +11,8 @@ from __future__ import annotations import typing -import warnings from abc import ABC, abstractmethod -# Avoid circular import import ufl.core as core from ufl.core.compute_expr_hash import compute_expr_hash from ufl.utils.formatting import camel2underscore @@ -48,33 +46,6 @@ def __ne__(self, other): return not self.__eq__(other) -def attach_operators_from_hash_data(cls): - """Class decorator to attach ``__hash__``, ``__eq__`` and ``__ne__`` implementations. - - These are implemented in terms of a ``._ufl_hash_data()`` method on the class, - which should return a tuple or hashable and comparable data. - """ - warnings.warn("attach_operators_from_hash_data deprecated, please use UFLObject instead.", DeprecationWarning) - assert hasattr(cls, "_ufl_hash_data_") - - def __hash__(self): - """__hash__ implementation attached in attach_operators_from_hash_data.""" - return hash(self._ufl_hash_data_()) - cls.__hash__ = __hash__ - - def __eq__(self, other): - """__eq__ implementation attached in attach_operators_from_hash_data.""" - return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() - cls.__eq__ = __eq__ - - def __ne__(self, other): - """__ne__ implementation attached in attach_operators_from_hash_data.""" - return not self.__eq__(other) - cls.__ne__ = __ne__ - - return cls - - def get_base_attr(cls, name): """Return first non-``None`` attribute of given name among base classes.""" for base in cls.mro(): diff --git a/ufl/domain.py b/ufl/domain.py index ecdea376a..f438006d0 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -11,7 +11,7 @@ from ufl.cell import AbstractCell from ufl.core.ufl_id import attach_ufl_id -from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.core.ufl_type import UFLObject from ufl.corealg.traversal import traverse_unique_terminals from ufl.sobolevspace import H1 @@ -52,9 +52,8 @@ def topological_dimension(self): # AbstractDomain.__init__(self, geometric_dimension, geometric_dimension) -@attach_operators_from_hash_data @attach_ufl_id -class Mesh(AbstractDomain): +class Mesh(AbstractDomain, UFLObject): """Symbolic representation of a mesh.""" def __init__(self, coordinate_element, ufl_id=None, cargo=None): @@ -122,9 +121,8 @@ def _ufl_sort_key_(self): "Mesh", typespecific) -@attach_operators_from_hash_data @attach_ufl_id -class MeshView(AbstractDomain): +class MeshView(AbstractDomain, UFLObject): """Symbolic representation of a mesh.""" def __init__(self, mesh, topological_dimension, ufl_id=None): diff --git a/ufl/functionspace.py b/ufl/functionspace.py index e2cc86dbc..17de9d1a8 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -9,7 +9,7 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 -from ufl.core.ufl_type import attach_operators_from_hash_data +from ufl.core.ufl_type import UFLObject from ufl.domain import join_domains from ufl.duals import is_dual, is_primal @@ -29,13 +29,11 @@ class AbstractFunctionSpace(object): def ufl_sub_spaces(self): """Return ufl sub spaces.""" raise NotImplementedError( - "Missing implementation of IFunctionSpace.ufl_sub_spaces in %s." - % self.__class__.__name__ + f"Missing implementation of ufl_sub_spaces in {self.__class__.__name__}." ) -@attach_operators_from_hash_data -class BaseFunctionSpace(AbstractFunctionSpace): +class BaseFunctionSpace(AbstractFunctionSpace, UFLObject): """Base function space.""" def __init__(self, domain, element): @@ -109,13 +107,10 @@ def _ufl_signature_data_(self, renumbering, name=None): def __repr__(self): """Representation.""" - r = "BaseFunctionSpace(%s, %s)" % (repr(self._ufl_domain), - repr(self._ufl_element)) - return r + return f"BaseFunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})" -@attach_operators_from_hash_data -class FunctionSpace(BaseFunctionSpace): +class FunctionSpace(BaseFunctionSpace, UFLObject): """Representation of a Function space.""" _primal = True @@ -135,13 +130,14 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - r = "FunctionSpace(%s, %s)" % (repr(self._ufl_domain), - repr(self._ufl_element)) - return r + return f"FunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})" + def __str__(self): + """String.""" + return f"FunctionSpace({self._ufl_domain}, {self._ufl_element})" -@attach_operators_from_hash_data -class DualSpace(BaseFunctionSpace): + +class DualSpace(BaseFunctionSpace, UFLObject): """Representation of a Dual space.""" _primal = False @@ -165,13 +161,14 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - r = "DualSpace(%s, %s)" % (repr(self._ufl_domain), - repr(self._ufl_element)) - return r + return f"DualSpace({self._ufl_domain!r}, {self._ufl_element!r})" + + def __str__(self): + """String.""" + return f"DualSpace({self._ufl_domain}, {self._ufl_element})" -@attach_operators_from_hash_data -class TensorProductFunctionSpace(AbstractFunctionSpace): +class TensorProductFunctionSpace(AbstractFunctionSpace, UFLObject): """Tensor product function space.""" def __init__(self, *function_spaces): @@ -196,12 +193,14 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - r = "TensorProductFunctionSpace(*%s)" % repr(self._ufl_function_spaces) - return r + return f"TensorProductFunctionSpace(*{self._ufl_function_spaces!r})" + def __str__(self): + """String.""" + return self.__repr__() -@attach_operators_from_hash_data -class MixedFunctionSpace(AbstractFunctionSpace): + +class MixedFunctionSpace(AbstractFunctionSpace, UFLObject): """Mixed function space.""" def __init__(self, *args): @@ -297,4 +296,8 @@ def _ufl_signature_data_(self, renumbering): def __repr__(self): """Representation.""" - return f"MixedFunctionSpace(*{self._ufl_function_spaces})" + return f"MixedFunctionSpace(*{self._ufl_function_spaces!r})" + + def __str__(self): + """String.""" + return self.__repr__() From f132188759a248d6edfc6eaf05e623b898bfcffd Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 16 Oct 2023 17:22:16 +0100 Subject: [PATCH 067/136] Remove `setup.cfg` (#223) * Remove setup.cfg * Fixes * Update doc check * CI fix * Update --- .flake8 | 6 +++ .github/workflows/pythonapp.yml | 2 +- pyproject.toml | 50 ++++++++++++++++++++++- setup.cfg | 71 --------------------------------- 4 files changed, 56 insertions(+), 73 deletions(-) create mode 100644 .flake8 delete mode 100644 setup.cfg diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..cd8e21ed8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +max-line-length = 120 +builtins = ufl +exclude = doc/sphinx/source/conf.py +per-file-ignores = + */__init__.py: F401 diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 1d916b7b5..032bec747 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -34,7 +34,7 @@ jobs: flake8 --statistics . - name: Check documentation style run: | - python -m pip install pydocstyle + python -m pip install pydocstyle[toml] python -m pydocstyle ufl/ - name: Install UFL run: python -m pip install .[ci] diff --git a/pyproject.toml b/pyproject.toml index cfcfb19c9..4fcaab701 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,52 @@ [build-system] requires = ["setuptools>=62", "wheel"] - build-backend = "setuptools.build_meta" + +[project] +name = "fenics-ufl" +version = "2023.3.0.dev0" +authors = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project"}] +maintainers = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project Steering Council"}] +description = "Unified Form Language" +readme = "README.rst" +license = {file = "LICENSE"} +requires-python = ">=3.8.0" +dependencies = ["numpy"] + +[project.urls] +homepage = "https://fenicsproject.org" +repository = "https://github.com/fenics/ufl.git" +documentation = "https://docs.fenicsproject.org" +issues = "https://github.com/FEniCS/ufl/issues" +funding = "https://numfocus.org/donate" + +[project.optional-dependencies] +lint = ["flake8", "pydocstyle[toml]"] +docs = ["sphinx", "sphinx_rtd_theme"] +test = ["pytest"] +ci = [ + "coveralls", + "coverage", + "pytest-cov", + "pytest-xdist", + "fenics-ufl[docs]", + "fenics-ufl[lint]", + "fenics-ufl[test]", +] + +[tool.setuptools] +packages = [ + "ufl", + "ufl.algorithms", + "ufl.core", + "ufl.corealg", + "ufl.formatting", + "ufl.legacy", + "ufl.utils", +] + +[tool.pydocstyle] +convention = "google" + +[itool.sort] +line_length = 120 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4bbece177..000000000 --- a/setup.cfg +++ /dev/null @@ -1,71 +0,0 @@ -# Setuptools does not yet support modern pyproject.toml but will do so in the -# future -[metadata] -name = fenics-ufl -version = 2023.3.0.dev0 -author = FEniCS Project Contributors -email = fenics-dev@googlegroups.com -maintainer = FEniCS Project Steering Council -description = Unified Form Language -url = https://github.com/FEniCS/ufl -project_urls = - Homepage = https://fenicsproject.org - Documentation = https://fenics.readthedocs.io/projects/ufl - Issues = https://github.com/FEniCS/ufl/issues - Funding = https://numfocus.org/donate -long_description = file: README.rst -long_description_content_type = text/x-rst -license=LGPL-3.0-or-later -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - Intended Audience :: Science/Research - License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) - Operating System :: POSIX - Operating System :: POSIX :: Linux - Operating System :: MacOS :: MacOS X - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Scientific/Engineering :: Mathematics - Topic :: Software Development :: Libraries :: Python Modules - -[options] -packages = find: -include_package_data = True -zip_safe = False -python_requires = >= 3.8 -setup_requires = - setuptools >= 62 - wheel -install_requires = - numpy - -[options.extras_require] -docs = sphinx; sphinx_rtd_theme -lint = flake8; pydocstyle[toml] -test = pytest -ci = - coverage - coveralls - pytest-cov - pytest-xdist - fenics-ufl[docs] - fenics-ufl[lint] - fenics-ufl[test] - -[flake8] -max-line-length = 120 -builtins = ufl -exclude = doc/sphinx/source/conf.py -per-file-ignores = - */__init__.py: F401 - -[pydocstyle] -convention = google - -[isort] -line_length = 120 From e1e8e5627b0c7db17687bdb32857820ac4ddc072 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 18 Oct 2023 14:46:03 +0100 Subject: [PATCH 068/136] Fix coefficients optional kwarg when calling a Form (#226) * None * fix typo --- ufl/form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/form.py b/ufl/form.py index 4fb474f1c..ed3040477 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -227,7 +227,7 @@ def __call__(self, *args, **kwargs): raise ValueError(f"Need {len(arguments)} arguments to form(), got {len(args)}.") repdict.update(zip(arguments, args)) - coefficients = kwargs.pop("coefficients") + coefficients = kwargs.pop("coefficients", None) if kwargs: raise ValueError(f"Unknown kwargs {list(kwargs)}.") From 1bb16223c594f615ccad9406a726452f14920535 Mon Sep 17 00:00:00 2001 From: Connor Pierce Date: Thu, 26 Oct 2023 05:09:39 -0500 Subject: [PATCH 069/136] Replace ineffective `continue` with `break` (#229) --- ufl/utils/sorting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index cb177737d..bf02cfdf6 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -22,7 +22,7 @@ def topological_sorting(nodes, edges): for es in edges.values(): if node in es and node in S: S.remove(node) - continue + break while S: node = S.pop(0) From a4c7280574789b7bda9b779fff65bc7d3bf32644 Mon Sep 17 00:00:00 2001 From: Francesco Ballarin Date: Fri, 27 Oct 2023 11:48:23 +0200 Subject: [PATCH 070/136] Fix isort section in pyproject.toml (#231) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4fcaab701..ec823e1d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,5 +48,5 @@ packages = [ [tool.pydocstyle] convention = "google" -[itool.sort] +[tool.isort] line_length = 120 From 6cf3e61dfa3449b848a714a57247580fd34a7c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Fri, 27 Oct 2023 11:56:34 +0200 Subject: [PATCH 071/136] We do require pip>=22.3 for editable installs (PEP 660). (#227) This does not quite resolve issues with users trying to install with an outdated pip version, as the upgrade happens at runtime, and doesn't change the current version of pip that is executed. Co-authored-by: Jack S. Hale --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ec823e1d9..28cf84116 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=62", "wheel"] +requires = ["setuptools>=62", "wheel", "pip>=22.3"] build-backend = "setuptools.build_meta" [project] From 1c1bfbe514ec664218cb1a5cdffcdbe22a89ee3e Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Fri, 27 Oct 2023 12:02:05 +0200 Subject: [PATCH 072/136] Update wheel builder action. (#230) --- .github/workflows/build-wheels.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index e7f65cb77..f1d86945d 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout UFL - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: ${{ github.event.inputs.ufl_ref }} @@ -50,7 +50,7 @@ jobs: - name: Build sdist and wheel run: python -m build . - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: dist/* @@ -65,7 +65,7 @@ jobs: path: dist - name: Push to PyPI - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 if: ${{ github.event.inputs.pypi_publish == 'true' }} with: user: __token__ @@ -73,7 +73,7 @@ jobs: repository_url: https://upload.pypi.org/legacy/ - name: Push to Test PyPI - uses: pypa/gh-action-pypi-publish@v1.5.0 + uses: pypa/gh-action-pypi-publish@release/v1 if: ${{ github.event.inputs.test_pypi_publish == 'true' }} with: user: __token__ From 41f30d0970de4b84d5743ce537640cb082fd6e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Delaporte-Mathurin?= <40028739+RemDelaporteMathurin@users.noreply.github.com> Date: Thu, 2 Nov 2023 03:31:52 -0400 Subject: [PATCH 073/136] fixed typo (#233) --- ufl/conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/conditional.py b/ufl/conditional.py index b9f9bda16..5d0fb910d 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -267,7 +267,7 @@ class Conditional(Operator): def __init__(self, condition, true_value, false_value): """Initialise.""" if not isinstance(condition, Condition): - raise ValueError("Expectiong condition as first argument.") + raise ValueError("Expecting condition as first argument.") true_value = as_ufl(true_value) false_value = as_ufl(false_value) tsh = true_value.ufl_shape From b210795ce798e93be27f3fb72b476d930d6522d3 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 2 Nov 2023 07:32:53 +0000 Subject: [PATCH 074/136] Remove ufl.legacy (#224) * remove ufl.legacy * finat branch * remove test of legacy elements * remove .legacy --- .github/workflows/tsfc-tests.yml | 4 +- pyproject.toml | 1 - test/test_legacy.py | 254 ------------- ufl/legacy/__init__.py | 25 -- ufl/legacy/brokenelement.py | 53 --- ufl/legacy/elementlist.py | 481 ------------------------ ufl/legacy/enrichedelement.py | 164 --------- ufl/legacy/finiteelement.py | 235 ------------ ufl/legacy/finiteelementbase.py | 276 -------------- ufl/legacy/hdivcurl.py | 213 ----------- ufl/legacy/mixedelement.py | 562 ----------------------------- ufl/legacy/restrictedelement.py | 113 ------ ufl/legacy/tensorproductelement.py | 140 ------- 13 files changed, 2 insertions(+), 2519 deletions(-) delete mode 100644 test/test_legacy.py delete mode 100644 ufl/legacy/__init__.py delete mode 100644 ufl/legacy/brokenelement.py delete mode 100644 ufl/legacy/elementlist.py delete mode 100644 ufl/legacy/enrichedelement.py delete mode 100644 ufl/legacy/finiteelement.py delete mode 100644 ufl/legacy/finiteelementbase.py delete mode 100644 ufl/legacy/hdivcurl.py delete mode 100644 ufl/legacy/mixedelement.py delete mode 100644 ufl/legacy/restrictedelement.py delete mode 100644 ufl/legacy/tensorproductelement.py diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index a861b4728..45752de0c 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -32,14 +32,14 @@ jobs: with: path: ./tsfc repository: firedrakeproject/tsfc - ref: mscroggs/newfl-legacy + ref: mscroggs/newfl-legacy2 - name: Install tsfc run: | cd tsfc pip install -r requirements-ext.txt pip install git+https://github.com/coneoproject/COFFEE.git#egg=coffee pip install git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat - pip install git+https://github.com/FInAT/FInAT.git#egg=finat + pip install git+https://github.com/FInAT/FInAT.git@mscroggs/ufl-elements pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy pip install .[ci] pip install pytest diff --git a/pyproject.toml b/pyproject.toml index 28cf84116..b3751e708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ packages = [ "ufl.core", "ufl.corealg", "ufl.formatting", - "ufl.legacy", "ufl.utils", ] diff --git a/test/test_legacy.py b/test/test_legacy.py deleted file mode 100644 index fd3de052f..000000000 --- a/test/test_legacy.py +++ /dev/null @@ -1,254 +0,0 @@ -from ufl import (H1, Coefficient, FunctionSpace, Mesh, dx, hexahedron, identity_pullback, inner, interval, - quadrilateral, tetrahedron, triangle) -from ufl.legacy import FiniteElement, MixedElement, TensorElement, VectorElement, WithMapping - -all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) - - -def test_legacy_vs_new(): - from ufl.finiteelement import FiniteElement as NewFiniteElement - e = FiniteElement("Lagrange", triangle, 1) - new_e = NewFiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - assert e.sobolev_space == new_e.sobolev_space - assert e.pullback == new_e.pullback - assert e.embedded_superdegree == new_e.embedded_superdegree - assert e.embedded_subdegree == new_e.embedded_subdegree - assert e.cell == new_e.cell - assert e.reference_value_shape == new_e.reference_value_shape - assert e.value_shape == new_e.value_shape - assert e.reference_value_size == new_e.reference_value_size - assert e.value_size == new_e.value_size - assert e.num_sub_elements == new_e.num_sub_elements - assert e.sub_elements == new_e.sub_elements - assert e.is_cellwise_constant() == new_e.is_cellwise_constant() - - -def test_scalar_galerkin(): - for cell in all_cells: - for p in range(1, 10): - for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): - element = FiniteElement(family, cell, p) - assert element.value_shape == () - assert element == eval(repr(element)) - for p in range(1, 10): - for family in ("TDG", "Discontinuous Taylor"): - element = FiniteElement(family, interval, p) - assert element.value_shape == () - - -def test_vector_galerkin(): - for cell in all_cells: - dim = cell.geometric_dimension() - # shape = () if dim == 1 else (dim,) - shape = (dim,) - for p in range(1, 10): - for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): - element = VectorElement(family, cell, p) - assert element.value_shape == shape - assert element == eval(repr(element)) - for i in range(dim): - c = element.extract_component(i) - assert c[0] == () - - -def test_tensor_galerkin(): - for cell in all_cells: - dim = cell.geometric_dimension() - # shape = () if dim == 1 else (dim,dim) - shape = (dim, dim) - for p in range(1, 10): - for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): - element = TensorElement(family, cell, p) - assert element.value_shape == shape - assert element == eval(repr(element)) - for i in range(dim): - for j in range(dim): - c = element.extract_component((i, j)) - assert c[0] == () - - -def test_tensor_symmetry(): - for cell in all_cells: - dim = cell.geometric_dimension() - for p in range(1, 10): - for s in (None, True, {(0, 1): (1, 0)}): - # Symmetry dict is invalid for interval cell - if isinstance(s, dict) and cell == interval: - continue - - for family in ("Lagrange", "CG", "Discontinuous Lagrange", "DG", "Discontinuous Lagrange L2", "DG L2"): - if isinstance(s, dict): - element = TensorElement( - family, cell, p, shape=(dim, dim), symmetry=s) - else: - element = TensorElement(family, cell, p, symmetry=s) - assert element.value_shape, (dim == dim) - assert element == eval(repr(element)) - for i in range(dim): - for j in range(dim): - c = element.extract_component((i, j)) - assert c[0] == () - - -def test_mixed_tensor_symmetries(): - from ufl.algorithms import expand_compounds, expand_indices - - S = FiniteElement('CG', triangle, 1) - V = VectorElement('CG', triangle, 1) - T = TensorElement('CG', triangle, 1, symmetry=True) - - print(T.pullback) - - # M has dimension 4+1, symmetries are 2->1 - M = T * S - domain = Mesh(VectorElement("Lagrange", triangle, 1)) - m_space = FunctionSpace(domain, M) - P = Coefficient(m_space) - M = inner(P, P) * dx - - M2 = expand_indices(expand_compounds(M)) - assert '[1]' in str(M2) - assert '[2]' not in str(M2) - - # M has dimension 2+(1+4), symmetries are 5->4 - M = V * (S * T) - m_space = FunctionSpace(domain, M) - P = Coefficient(m_space) - M = inner(P, P) * dx - - M2 = expand_indices(expand_compounds(M)) - assert '[4]' in str(M2) - assert '[5]' not in str(M2) - - -def test_bdm(): - for cell in (triangle, tetrahedron): - dim = cell.geometric_dimension() - element = FiniteElement("BDM", cell, 1) - assert element.value_shape == (dim,) - assert element == eval(repr(element)) - - -def test_vector_bdm(): - for cell in (triangle, tetrahedron): - dim = cell.geometric_dimension() - element = VectorElement("BDM", cell, 1) - assert element.value_shape, (dim == dim) - assert element == eval(repr(element)) - - -def test_mtw(): - cell = triangle - element = FiniteElement("MTW", cell, 3) - assert element.value_shape == (cell.geometric_dimension(), ) - assert element == eval(repr(element)) - assert element.mapping() == "contravariant Piola" - - -def test_mixed(): - for cell in (triangle, tetrahedron): - dim = cell.geometric_dimension() - velement = VectorElement("CG", cell, 2) - pelement = FiniteElement("CG", cell, 1) - TH1 = MixedElement(velement, pelement) - TH2 = velement * pelement - assert TH1.value_shape == (dim + 1,) - assert TH2.value_shape == (dim + 1,) - assert repr(TH1) == repr(TH2) - assert TH1 == eval(repr(TH2)) - assert TH2 == eval(repr(TH1)) - - -def test_nested_mixed(): - for cell in (triangle, tetrahedron): - dim = cell.geometric_dimension() - velement = VectorElement("CG", cell, 2) - pelement = FiniteElement("CG", cell, 1) - TH1 = MixedElement((velement, pelement), pelement) - TH2 = velement * pelement * pelement - assert TH1.value_shape == (dim + 2,) - assert TH2.value_shape == (dim + 2,) - assert repr(TH1) == repr(TH2) - assert TH1 == eval(repr(TH2)) - assert TH2 == eval(repr(TH1)) - - -def test_quadrature_scheme(): - for cell in (triangle, tetrahedron): - for q in (None, 1, 2, 3): - element = FiniteElement("CG", cell, 1, quad_scheme=q) - assert element.quadrature_scheme() == q - assert element == eval(repr(element)) - - -def test_missing_cell(): - # These special cases are here to allow missing - # cell in PyDOLFIN Constant and Expression - for cell in (triangle, None): - element = FiniteElement("Real", cell, 0) - assert element == eval(repr(element)) - element = FiniteElement("Undefined", cell, None) - assert element == eval(repr(element)) - element = VectorElement("Lagrange", cell, 1, dim=2) - assert element == eval(repr(element)) - element = TensorElement("DG", cell, 1, shape=(2, 2)) - assert element == eval(repr(element)) - element = TensorElement("DG L2", cell, 1, shape=(2, 2)) - assert element == eval(repr(element)) - - -def test_invalid_degree(): - cell = triangle - for degree in (1, None): - element = FiniteElement("CG", cell, degree) - assert element == eval(repr(element)) - element = VectorElement("CG", cell, degree) - assert element == eval(repr(element)) - - -def test_lobatto(): - cell = interval - for degree in (1, 2, None): - element = FiniteElement("Lob", cell, degree) - assert element == eval(repr(element)) - - element = FiniteElement("Lobatto", cell, degree) - assert element == eval(repr(element)) - - -def test_radau(): - cell = interval - for degree in (0, 1, 2, None): - element = FiniteElement("Rad", cell, degree) - assert element == eval(repr(element)) - - element = FiniteElement("Radau", cell, degree) - assert element == eval(repr(element)) - - -def test_mse(): - for degree in (2, 3, 4, 5): - element = FiniteElement('EGL', interval, degree) - assert element == eval(repr(element)) - - element = FiniteElement('EGL-Edge', interval, degree - 1) - assert element == eval(repr(element)) - - element = FiniteElement('EGL-Edge L2', interval, degree - 1) - assert element == eval(repr(element)) - - for degree in (1, 2, 3, 4, 5): - element = FiniteElement('GLL', interval, degree) - assert element == eval(repr(element)) - - element = FiniteElement('GLL-Edge', interval, degree - 1) - assert element == eval(repr(element)) - - element = FiniteElement('GLL-Edge L2', interval, degree - 1) - assert element == eval(repr(element)) - - -def test_withmapping(): - base = FiniteElement("CG", interval, 1) - element = WithMapping(base, "identity") - assert element == eval(repr(element)) diff --git a/ufl/legacy/__init__.py b/ufl/legacy/__init__.py deleted file mode 100644 index 06a0b12ca..000000000 --- a/ufl/legacy/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Legacy UFL features.""" -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Andrew T. T. McRae 2014 -# Modified by Lawrence Mitchell 2014 - -import warnings as _warnings - -from ufl.legacy.brokenelement import BrokenElement -from ufl.legacy.enrichedelement import EnrichedElement, NodalEnrichedElement -from ufl.legacy.finiteelement import FiniteElement -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.legacy.hdivcurl import HCurlElement, HDivElement, WithMapping -from ufl.legacy.mixedelement import MixedElement, TensorElement, VectorElement -from ufl.legacy.restrictedelement import RestrictedElement -from ufl.legacy.tensorproductelement import TensorProductElement - -_warnings.warn("The features in ufl.legacy are deprecated and will be removed in a future version.", - FutureWarning) diff --git a/ufl/legacy/brokenelement.py b/ufl/legacy/brokenelement.py deleted file mode 100644 index 2563b868f..000000000 --- a/ufl/legacy/brokenelement.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Element.""" -# -*- coding: utf-8 -*- -# Copyright (C) 2014 Andrew T. T. McRae -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Massimiliano Leoni, 2016 - -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import L2 - - -class BrokenElement(FiniteElementBase): - """The discontinuous version of an existing Finite Element space.""" - def __init__(self, element): - """Init.""" - self._element = element - - family = "BrokenElement" - cell = element.cell - degree = element.degree() - quad_scheme = element.quadrature_scheme() - value_shape = element.value_shape - reference_value_shape = element.reference_value_shape - FiniteElementBase.__init__(self, family, cell, degree, - quad_scheme, value_shape, reference_value_shape) - - def __repr__(self): - """Doc.""" - return f"BrokenElement({repr(self._element)})" - - def mapping(self): - """Doc.""" - return self._element.mapping() - - @property - def sobolev_space(self): - """Return the underlying Sobolev space.""" - return L2 - - def reconstruct(self, **kwargs): - """Doc.""" - return BrokenElement(self._element.reconstruct(**kwargs)) - - def __str__(self): - """Doc.""" - return f"BrokenElement({repr(self._element)})" - - def shortstr(self): - """Format as string for pretty printing.""" - return f"BrokenElement({repr(self._element)})" diff --git a/ufl/legacy/elementlist.py b/ufl/legacy/elementlist.py deleted file mode 100644 index 712b94bb1..000000000 --- a/ufl/legacy/elementlist.py +++ /dev/null @@ -1,481 +0,0 @@ -"""Element. - -This module provides an extensive list of predefined finite element -families. Users or, more likely, form compilers, may register new -elements by calling the function register_element. -""" -# Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Marie E. Rognes , 2010 -# Modified by Lizao Li , 2015, 2016 -# Modified by Massimiliano Leoni, 2016 -# Modified by Robert Kloefkorn, 2022 - -import warnings - -from numpy import asarray - -from ufl.cell import Cell, TensorProductCell -from ufl.sobolevspace import H1, H2, L2, HCurl, HDiv, HDivDiv, HEin, HInf -from ufl.utils.formatting import istr - -# List of valid elements -ufl_elements = {} - -# Aliases: aliases[name] (...) -> (standard_name, ...) -aliases = {} - - -# Function for registering new elements -def register_element(family, short_name, value_rank, sobolev_space, mapping, - degree_range, cellnames): - """Register new finite element family.""" - if family in ufl_elements: - raise ValueError(f"Finite element '{family}%s' has already been registered.") - ufl_elements[family] = (family, short_name, value_rank, sobolev_space, - mapping, degree_range, cellnames) - if short_name is not None: - ufl_elements[short_name] = (family, short_name, value_rank, sobolev_space, - mapping, degree_range, cellnames) - - -def register_alias(alias, to): - """Doc.""" - aliases[alias] = to - - -def show_elements(): - """Shows all registered elements.""" - print("Showing all registered elements:") - print("================================") - shown = set() - for k in sorted(ufl_elements.keys()): - data = ufl_elements[k] - if data in shown: - continue - shown.add(data) - (family, short_name, value_rank, sobolev_space, mapping, degree_range, cellnames) = data - print(f"Finite element family: '{family}', '{short_name}'") - print(f"Sobolev space: {sobolev_space}%s") - print(f"Mapping: {mapping}") - print(f"Degree range: {degree_range}") - print(f"Value rank: {value_rank}") - print(f"Defined on cellnames: {cellnames}") - print() - - -# FIXME: Consider cleanup of element names. Use notation from periodic -# table as the main, keep old names as compatibility aliases. - -# NOTE: Any element with polynomial degree 0 will be considered L2, -# independent of the space passed to register_element. - -# NOTE: The mapping of the element basis functions -# from reference to physical representation is -# chosen based on the sobolev space: -# HDiv = contravariant Piola, -# HCurl = covariant Piola, -# H1/L2 = no mapping. - -# TODO: If determining mapping from sobolev_space isn't sufficient in -# the future, add mapping name as another element property. - -# Cell groups -simplices = ("interval", "triangle", "tetrahedron", "pentatope") -cubes = ("interval", "quadrilateral", "hexahedron", "tesseract") -any_cell = (None, - "vertex", "interval", - "triangle", "tetrahedron", "prism", - "pyramid", "quadrilateral", "hexahedron", "pentatope", "tesseract") - -# Elements in the periodic table # TODO: Register these as aliases of -# periodic table element description instead of the other way around -register_element("Lagrange", "CG", 0, H1, "identity", (1, None), - any_cell) # "P" -register_element("Brezzi-Douglas-Marini", "BDM", 1, HDiv, - "contravariant Piola", (1, None), simplices[1:]) # "BDMF" (2d), "N2F" (3d) -register_element("Discontinuous Lagrange", "DG", 0, L2, "identity", (0, None), - any_cell) # "DP" -register_element("Discontinuous Taylor", "TDG", 0, L2, "identity", (0, None), simplices) -register_element("Nedelec 1st kind H(curl)", "N1curl", 1, HCurl, - "covariant Piola", (1, None), simplices[1:]) # "RTE" (2d), "N1E" (3d) -register_element("Nedelec 2nd kind H(curl)", "N2curl", 1, HCurl, - "covariant Piola", (1, None), simplices[1:]) # "BDME" (2d), "N2E" (3d) -register_element("Raviart-Thomas", "RT", 1, HDiv, "contravariant Piola", - (1, None), simplices[1:]) # "RTF" (2d), "N1F" (3d) - -# Elements not in the periodic table -register_element("Argyris", "ARG", 0, H2, "custom", (5, 5), ("triangle",)) -register_element("Bell", "BELL", 0, H2, "custom", (5, 5), ("triangle",)) -register_element("Brezzi-Douglas-Fortin-Marini", "BDFM", 1, HDiv, - "contravariant Piola", (1, None), simplices[1:]) -register_element("Crouzeix-Raviart", "CR", 0, L2, "identity", (1, 1), - simplices[1:]) -# TODO: Implement generic Tear operator for elements instead of this: -register_element("Discontinuous Raviart-Thomas", "DRT", 1, L2, - "contravariant Piola", (1, None), simplices[1:]) -register_element("Hermite", "HER", 0, H1, "custom", (3, 3), simplices) -register_element("Kong-Mulder-Veldhuizen", "KMV", 0, H1, "identity", (1, None), - simplices[1:]) -register_element("Mardal-Tai-Winther", "MTW", 1, H1, "contravariant Piola", (3, 3), - ("triangle",)) -register_element("Morley", "MOR", 0, H2, "custom", (2, 2), ("triangle",)) - -# Special elements -register_element("Boundary Quadrature", "BQ", 0, L2, "identity", (0, None), - any_cell) -register_element("Bubble", "B", 0, H1, "identity", (2, None), simplices) -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, 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",)) -register_element("Regge", "Regge", 2, HEin, "double covariant Piola", - (0, None), simplices[1:]) -register_element("HDiv Trace", "HDivT", 0, L2, "identity", (0, None), any_cell) -register_element("Hellan-Herrmann-Johnson", "HHJ", 2, HDivDiv, - "double contravariant Piola", (0, None), ("triangle",)) -register_element("Nonconforming Arnold-Winther", "AWnc", 2, HDivDiv, - "double contravariant Piola", (2, 2), ("triangle", "tetrahedron")) -register_element("Conforming Arnold-Winther", "AWc", 2, HDivDiv, - "double contravariant Piola", (3, None), ("triangle", "tetrahedron")) -# Spectral elements. -register_element("Gauss-Legendre", "GL", 0, L2, "identity", (0, None), - ("interval",)) -register_element("Gauss-Lobatto-Legendre", "GLL", 0, H1, "identity", (1, None), - ("interval",)) -register_alias("Lobatto", - lambda family, dim, order, degree: ("Gauss-Lobatto-Legendre", order)) -register_alias("Lob", - lambda family, dim, order, degree: ("Gauss-Lobatto-Legendre", order)) - -register_element("Bernstein", None, 0, H1, "identity", (1, None), simplices) - - -# Let Nedelec H(div) elements be aliases to BDMs/RTs -register_alias("Nedelec 1st kind H(div)", - lambda family, dim, order, degree: ("Raviart-Thomas", order)) -register_alias("N1div", - lambda family, dim, order, degree: ("Raviart-Thomas", order)) - -register_alias("Nedelec 2nd kind H(div)", - lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", - order)) -register_alias("N2div", - lambda family, dim, order, degree: ("Brezzi-Douglas-Marini", - order)) - -# Let Discontinuous Lagrange Trace element be alias to HDiv Trace -register_alias("Discontinuous Lagrange Trace", - lambda family, dim, order, degree: ("HDiv Trace", order)) -register_alias("DGT", - lambda family, dim, order, degree: ("HDiv Trace", order)) - -# New elements introduced for the periodic table 2014 -register_element("Q", None, 0, H1, "identity", (1, None), cubes) -register_element("DQ", None, 0, L2, "identity", (0, None), cubes) -register_element("RTCE", None, 1, HCurl, "covariant Piola", (1, None), - ("quadrilateral",)) -register_element("RTCF", None, 1, HDiv, "contravariant Piola", (1, None), - ("quadrilateral",)) -register_element("NCE", None, 1, HCurl, "covariant Piola", (1, None), - ("hexahedron",)) -register_element("NCF", None, 1, HDiv, "contravariant Piola", (1, None), - ("hexahedron",)) - -register_element("S", None, 0, H1, "identity", (1, None), cubes) -register_element("DPC", None, 0, L2, "identity", (0, None), cubes) -register_element("BDMCE", None, 1, HCurl, "covariant Piola", (1, None), - ("quadrilateral",)) -register_element("BDMCF", None, 1, HDiv, "contravariant Piola", (1, None), - ("quadrilateral",)) -register_element("SminusE", "SminusE", 1, HCurl, "covariant Piola", (1, None), cubes[1:3]) -register_element("SminusF", "SminusF", 1, HDiv, "contravariant Piola", (1, None), cubes[1:2]) -register_element("SminusDiv", "SminusDiv", 1, HDiv, "contravariant Piola", (1, None), cubes[1:3]) -register_element("SminusCurl", "SminusCurl", 1, HCurl, "covariant Piola", (1, None), cubes[1:3]) -register_element("AAE", None, 1, HCurl, "covariant Piola", (1, None), - ("hexahedron",)) -register_element("AAF", None, 1, HDiv, "contravariant Piola", (1, None), - ("hexahedron",)) - -# New aliases introduced for the periodic table 2014 -register_alias("P", lambda family, dim, order, degree: ("Lagrange", order)) -register_alias("DP", lambda family, dim, order, - degree: ("Discontinuous Lagrange", order)) -register_alias("RTE", lambda family, dim, order, - degree: ("Nedelec 1st kind H(curl)", order)) -register_alias("RTF", lambda family, dim, order, - degree: ("Raviart-Thomas", order)) -register_alias("N1E", lambda family, dim, order, - degree: ("Nedelec 1st kind H(curl)", order)) -register_alias("N1F", lambda family, dim, order, degree: ("Raviart-Thomas", - order)) - -register_alias("BDME", lambda family, dim, order, - degree: ("Nedelec 2nd kind H(curl)", order)) -register_alias("BDMF", lambda family, dim, order, - degree: ("Brezzi-Douglas-Marini", order)) -register_alias("N2E", lambda family, dim, order, - degree: ("Nedelec 2nd kind H(curl)", order)) -register_alias("N2F", lambda family, dim, order, - degree: ("Brezzi-Douglas-Marini", order)) - -# discontinuous elements using l2 pullbacks -register_element("DPC L2", None, 0, L2, "L2 Piola", (1, None), cubes) -register_element("DQ L2", None, 0, L2, "L2 Piola", (0, None), cubes) -register_element("Gauss-Legendre L2", "GL L2", 0, L2, "L2 Piola", (0, None), - ("interval",)) -register_element("Discontinuous Lagrange L2", "DG L2", 0, L2, "L2 Piola", (0, None), - any_cell) # "DP" - -register_alias("DP L2", lambda family, dim, order, - degree: ("Discontinuous Lagrange L2", order)) - -register_alias("P- Lambda L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) -register_alias("P Lambda L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) -register_alias("Q- Lambda L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) -register_alias("S Lambda L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) - -register_alias("P- L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) -register_alias("Q- L2", lambda family, dim, order, - degree: feec_element_l2(family, dim, order, degree)) - -# mimetic spectral elements - primal and dual complexs -register_element("Extended-Gauss-Legendre", "EGL", 0, H1, "identity", (2, None), - ("interval",)) -register_element("Extended-Gauss-Legendre Edge", "EGL-Edge", 0, L2, "identity", (1, None), - ("interval",)) -register_element("Extended-Gauss-Legendre Edge L2", "EGL-Edge L2", 0, L2, "L2 Piola", (1, None), - ("interval",)) -register_element("Gauss-Lobatto-Legendre Edge", "GLL-Edge", 0, L2, "identity", (0, None), - ("interval",)) -register_element("Gauss-Lobatto-Legendre Edge L2", "GLL-Edge L2", 0, L2, "L2 Piola", (0, None), - ("interval",)) - -# directly-defined serendipity elements ala Arbogast -# currently the theory is only really worked out for quads. -register_element("Direct Serendipity", "Sdirect", 0, H1, "physical", (1, None), - ("quadrilateral",)) -register_element("Direct Serendipity Full H(div)", "Sdirect H(div)", 1, HDiv, "physical", (1, None), - ("quadrilateral",)) -register_element("Direct Serendipity Reduced H(div)", "Sdirect H(div) red", 1, HDiv, "physical", (1, None), - ("quadrilateral",)) - - -# NOTE- the edge elements for primal mimetic spectral elements are accessed by using -# variant='mse' in the appropriate places - -def feec_element(family, n, r, k): - """Finite element exterior calculus notation. - - n = topological dimension of domain - r = polynomial order - k = form_degree - """ - # Note: We always map to edge elements in 2D, don't know how to - # differentiate otherwise? - - # Mapping from (feec name, domain dimension, form degree) to - # (family name, polynomial order) - _feec_elements = { - "P- Lambda": ( - (("P", r), ("DP", r - 1)), - (("P", r), ("RTE", r), ("DP", r - 1)), - (("P", r), ("N1E", r), ("N1F", r), ("DP", r - 1)), - ), - "P Lambda": ( - (("P", r), ("DP", r)), - (("P", r), ("BDME", r), ("DP", r)), - (("P", r), ("N2E", r), ("N2F", r), ("DP", r)), - ), - "Q- Lambda": ( - (("Q", r), ("DQ", r - 1)), - (("Q", r), ("RTCE", r), ("DQ", r - 1)), - (("Q", r), ("NCE", r), ("NCF", r), ("DQ", r - 1)), - ), - "S Lambda": ( - (("S", r), ("DPC", r)), - (("S", r), ("BDMCE", r), ("DPC", r)), - (("S", r), ("AAE", r), ("AAF", r), ("DPC", r)), - ), - } - - # New notation, old verbose notation (including "Lambda") might be - # removed - _feec_elements["P-"] = _feec_elements["P- Lambda"] - _feec_elements["P"] = _feec_elements["P Lambda"] - _feec_elements["Q-"] = _feec_elements["Q- Lambda"] - _feec_elements["S"] = _feec_elements["S Lambda"] - - family, r = _feec_elements[family][n - 1][k] - - return family, r - - -def feec_element_l2(family, n, r, k): - """Finite element exterior calculus notation. - - n = topological dimension of domain - r = polynomial order - k = form_degree - """ - # Note: We always map to edge elements in 2D, don't know how to - # differentiate otherwise? - - # Mapping from (feec name, domain dimension, form degree) to - # (family name, polynomial order) - _feec_elements = { - "P- Lambda L2": ( - (("P", r), ("DP L2", r - 1)), - (("P", r), ("RTE", r), ("DP L2", r - 1)), - (("P", r), ("N1E", r), ("N1F", r), ("DP L2", r - 1)), - ), - "P Lambda L2": ( - (("P", r), ("DP L2", r)), - (("P", r), ("BDME", r), ("DP L2", r)), - (("P", r), ("N2E", r), ("N2F", r), ("DP L2", r)), - ), - "Q- Lambda L2": ( - (("Q", r), ("DQ L2", r - 1)), - (("Q", r), ("RTCE", r), ("DQ L2", r - 1)), - (("Q", r), ("NCE", r), ("NCF", r), ("DQ L2", r - 1)), - ), - "S Lambda L2": ( - (("S", r), ("DPC L2", r)), - (("S", r), ("BDMCE", r), ("DPC L2", r)), - (("S", r), ("AAE", r), ("AAF", r), ("DPC L2", r)), - ), - } - - # New notation, old verbose notation (including "Lambda") might be - # removed - _feec_elements["P- L2"] = _feec_elements["P- Lambda L2"] - _feec_elements["P L2"] = _feec_elements["P Lambda L2"] - _feec_elements["Q- L2"] = _feec_elements["Q- Lambda L2"] - _feec_elements["S L2"] = _feec_elements["S Lambda L2"] - - family, r = _feec_elements[family][n - 1][k] - - return family, r - - -# General FEEC notation, old verbose (can be removed) -register_alias("P- Lambda", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) -register_alias("P Lambda", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) -register_alias("Q- Lambda", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) -register_alias("S Lambda", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) - -# General FEEC notation, new compact notation -register_alias("P-", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) -register_alias("Q-", lambda family, dim, order, - degree: feec_element(family, dim, order, degree)) - - -def canonical_element_description(family, cell, order, form_degree): - """Given basic element information, return corresponding element information on canonical form. - - Input: family, cell, (polynomial) order, form_degree - Output: family (canonical), short_name (for printing), order, value shape, - reference value shape, sobolev_space. - - This is used by the FiniteElement constructor to ved input - data against the element list and aliases defined in ufl. - """ - # Get domain dimensions - if cell is not None: - tdim = cell.topological_dimension() - gdim = cell.geometric_dimension() - if isinstance(cell, Cell): - cellname = cell.cellname() - else: - cellname = None - else: - tdim = None - gdim = None - cellname = None - - # Catch general FEEC notation "P" and "S" - if form_degree is not None and family in ("P", "S"): - family, order = feec_element(family, tdim, order, form_degree) - - if form_degree is not None and family in ("P L2", "S L2"): - family, order = feec_element_l2(family, tdim, order, form_degree) - - # Check whether this family is an alias for something else - while family in aliases: - if tdim is None: - raise ValueError("Need dimension to handle element aliases.") - (family, order) = aliases[family](family, tdim, order, form_degree) - - # Check that the element family exists - if family not in ufl_elements: - raise ValueError(f"Unknown finite element '{family}'.") - - # Check that element data is valid (and also get common family - # name) - (family, short_name, value_rank, sobolev_space, mapping, krange, cellnames) = ufl_elements[family] - - # Accept CG/DG on all kind of cells, but use Q/DQ on "product" cells - if cellname in set(cubes) - set(simplices) or isinstance(cell, TensorProductCell): - if family == "Lagrange": - family = "Q" - elif family == "Discontinuous Lagrange": - if order >= 1: - warnings.warn("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) - family = "DQ" - elif family == "Discontinuous Lagrange L2": - if order >= 1: - warnings.warn(f"Discontinuous Lagrange L2 element requested on {cell.cellname()}, " - "creating DQ L2 element.") - family = "DQ L2" - - # Validate cellname if a valid cell is specified - if not (cellname is None or cellname in cellnames): - raise ValueError(f"Cellname '{cellname}' invalid for '{family}' finite element.") - - # Validate order if specified - if order is not None: - if krange is None: - raise ValueError(f"Order {order} invalid for '{family}' finite element, should be None.") - kmin, kmax = krange - if not (kmin is None or (asarray(order) >= kmin).all()): - raise ValueError(f"Order {order} invalid for '{family}' finite element.") - if not (kmax is None or (asarray(order) <= kmax).all()): - raise ValueError(f"Order {istr(order)} invalid for '{family}' finite element.") - - if value_rank == 2: - # Tensor valued fundamental elements in HEin have this shape - if gdim is None or tdim is None: - raise ValueError("Cannot infer shape of element without topological and geometric dimensions.") - reference_value_shape = (tdim, tdim) - value_shape = (gdim, gdim) - elif value_rank == 1: - # Vector valued fundamental elements in HDiv and HCurl have a shape - if gdim is None or tdim is None: - raise ValueError("Cannot infer shape of element without topological and geometric dimensions.") - reference_value_shape = (tdim,) - value_shape = (gdim,) - elif value_rank == 0: - # All other elements are scalar values - reference_value_shape = () - value_shape = () - else: - raise ValueError(f"Invalid value rank {value_rank}.") - - return family, short_name, order, value_shape, reference_value_shape, sobolev_space, mapping diff --git a/ufl/legacy/enrichedelement.py b/ufl/legacy/enrichedelement.py deleted file mode 100644 index 76420f00b..000000000 --- a/ufl/legacy/enrichedelement.py +++ /dev/null @@ -1,164 +0,0 @@ -"""This module defines the UFL finite element classes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Massimiliano Leoni, 2016 - -from ufl.legacy.finiteelementbase import FiniteElementBase - - -class EnrichedElementBase(FiniteElementBase): - """The vector sum of several finite element spaces.""" - - def __init__(self, *elements): - """Doc.""" - self._elements = elements - - cell = elements[0].cell - if not all(e.cell == cell for e in elements[1:]): - raise ValueError("Cell mismatch for sub elements of enriched element.") - - if isinstance(elements[0].degree(), int): - degrees = {e.degree() for e in elements} - {None} - degree = max(degrees) if degrees else None - else: - degree = tuple(map(max, zip(*[e.degree() for e in elements]))) - - # We can allow the scheme not to be defined, but all defined - # should be equal - quad_schemes = [e.quadrature_scheme() for e in elements] - quad_schemes = [qs for qs in quad_schemes if qs is not None] - quad_scheme = quad_schemes[0] if quad_schemes else None - if not all(qs == quad_scheme for qs in quad_schemes): - raise ValueError("Quadrature scheme mismatch.") - - value_shape = elements[0].value_shape - if not all(e.value_shape == value_shape for e in elements[1:]): - raise ValueError("Element value shape mismatch.") - - reference_value_shape = elements[0].reference_value_shape - if not all(e.reference_value_shape == reference_value_shape for e in elements[1:]): - raise ValueError("Element reference value shape mismatch.") - - # mapping = elements[0].mapping() # FIXME: This fails for a mixed subelement here. - # if not all(e.mapping() == mapping for e in elements[1:]): - # raise ValueError("Element mapping mismatch.") - - # Get name of subclass: EnrichedElement or NodalEnrichedElement - class_name = self.__class__.__name__ - - # Initialize element data - FiniteElementBase.__init__(self, class_name, cell, degree, - quad_scheme, value_shape, - reference_value_shape) - - def mapping(self): - """Doc.""" - return self._elements[0].mapping() - - @property - def sobolev_space(self): - """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): - return elements[0].sobolev_space - else: - # Find smallest shared Sobolev space over all sub elements - spaces = [e.sobolev_space for e in elements] - superspaces = [{s} | set(s.parents) for s in spaces] - intersect = set.intersection(*superspaces) - for s in intersect.copy(): - for parent in s.parents: - intersect.discard(parent) - - sobolev_space, = intersect - return sobolev_space - - def variant(self): - """Doc.""" - try: - variant, = {e.variant() for e in self._elements} - return variant - except ValueError: - return None - - def reconstruct(self, **kwargs): - """Doc.""" - return type(self)(*[e.reconstruct(**kwargs) for e in self._elements]) - - @property - def embedded_subdegree(self): - """Return embedded subdegree.""" - if isinstance(self._degree, int): - return self._degree - else: - return min(e.embedded_subdegree for e in self._elements) - - @property - def embedded_superdegree(self): - """Return embedded superdegree.""" - if isinstance(self._degree, int): - return self._degree - else: - return max(e.embedded_superdegree for e in self._elements) - - -class EnrichedElement(EnrichedElementBase): - r"""The vector sum of several finite element spaces. - - .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. - - Dual basis is a concatenation of subelements dual bases; - primal basis is a concatenation of subelements primal bases; - resulting element is not nodal even when subelements are. - Structured basis may be exploited in form compilers. - """ - - def is_cellwise_constant(self): - """Return whether the basis functions of this element is spatially constant over each cell.""" - return all(e.is_cellwise_constant() for e in self._elements) - - def __repr__(self): - """Doc.""" - 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) - - def shortstr(self): - """Format as string for pretty printing.""" - return "<%s>" % " + ".join(e.shortstr() for e in self._elements) - - -class NodalEnrichedElement(EnrichedElementBase): - r"""The vector sum of several finite element spaces. - - .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}. - - Primal basis is reorthogonalized to dual basis which is - a concatenation of subelements dual bases; resulting - element is nodal. - """ - def is_cellwise_constant(self): - """Return whether the basis functions of this element is spatially constant over each cell.""" - return False - - def __repr__(self): - """Doc.""" - 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) - - def shortstr(self): - """Format as string for pretty printing.""" - return "NodalEnriched(%s)" % ", ".join(e.shortstr() for e in self._elements) diff --git a/ufl/legacy/finiteelement.py b/ufl/legacy/finiteelement.py deleted file mode 100644 index 1166ed741..000000000 --- a/ufl/legacy/finiteelement.py +++ /dev/null @@ -1,235 +0,0 @@ -"""This module defines the UFL finite element classes.""" -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Anders Logg 2014 -# Modified by Massimiliano Leoni, 2016 - -from ufl.cell import TensorProductCell, as_cell -from ufl.legacy.elementlist import canonical_element_description, simplices -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.utils.formatting import istr - - -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", "_repr") - - def __new__(cls, - family, - cell=None, - degree=None, - form_degree=None, - quad_scheme=None, - variant=None): - """Intercepts construction to expand CG, DG, RTCE and RTCF spaces on TensorProductCells.""" - if cell is not None: - cell = as_cell(cell) - - if isinstance(cell, TensorProductCell): - # Delay import to avoid circular dependency at module load time - from ufl.legacy.enrichedelement import EnrichedElement - from ufl.legacy.hdivcurl import HCurlElement as HCurl - from ufl.legacy.hdivcurl import HDivElement as HDiv - from ufl.legacy.tensorproductelement import TensorProductElement - - family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \ - canonical_element_description(family, cell, degree, form_degree) - - if family in ["RTCF", "RTCE"]: - cell_h, cell_v = cell.sub_cells() - if cell_h.cellname() != "interval": - raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") - if cell_v.cellname() != "interval": - raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") - - C_elt = FiniteElement("CG", "interval", degree, variant=variant) - D_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) - - CxD_elt = TensorProductElement(C_elt, D_elt, cell=cell) - DxC_elt = TensorProductElement(D_elt, C_elt, cell=cell) - - if family == "RTCF": - return EnrichedElement(HDiv(CxD_elt), HDiv(DxC_elt)) - if family == "RTCE": - return EnrichedElement(HCurl(CxD_elt), HCurl(DxC_elt)) - - elif family == "NCF": - cell_h, cell_v = cell.sub_cells() - if cell_h.cellname() != "quadrilateral": - raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") - if cell_v.cellname() != "interval": - raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") - - Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, variant=variant) - Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, variant=variant) - - Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) - Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) - - return EnrichedElement(HDiv(TensorProductElement(Qc_elt, Id_elt, cell=cell)), - HDiv(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) - - elif family == "NCE": - cell_h, cell_v = cell.sub_cells() - if cell_h.cellname() != "quadrilateral": - raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") - if cell_v.cellname() != "interval": - raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") - - Qc_elt = FiniteElement("Q", "quadrilateral", degree, variant=variant) - Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, variant=variant) - - Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) - Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) - - return EnrichedElement(HCurl(TensorProductElement(Qc_elt, Id_elt, cell=cell)), - HCurl(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) - - elif family == "Q": - return TensorProductElement(*[FiniteElement("CG", c, degree, variant=variant) - for c in cell.sub_cells()], - cell=cell) - - elif family == "DQ": - def dq_family(cell): - """Doc.""" - return "DG" if cell.cellname() in simplices else "DQ" - return TensorProductElement(*[FiniteElement(dq_family(c), c, degree, variant=variant) - for c in cell.sub_cells()], - cell=cell) - - elif family == "DQ L2": - def dq_family_l2(cell): - """Doc.""" - return "DG L2" if cell.cellname() in simplices else "DQ L2" - return TensorProductElement(*[FiniteElement(dq_family_l2(c), c, degree, variant=variant) - for c in cell.sub_cells()], - cell=cell) - - return super(FiniteElement, cls).__new__(cls) - - def __init__(self, - family, - cell=None, - degree=None, - form_degree=None, - quad_scheme=None, - variant=None): - """Create finite element. - - Args: - family: The finite element family - cell: The geometric cell - degree: The polynomial degree (optional) - form_degree: The form degree (FEEC notation, used when field is - viewed as k-form) - quad_scheme: The quadrature scheme (optional) - variant: Hint for the local basis function variant (optional) - """ - # Note: Unfortunately, dolfin sometimes passes None for - # cell. Until this is fixed, allow it: - if cell is not None: - cell = as_cell(cell) - - ( - family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping - ) = canonical_element_description(family, cell, degree, form_degree) - - # TODO: Move these to base? Might be better to instead - # simplify base though. - self._sobolev_space = sobolev_space - self._mapping = mapping - self._short_name = short_name or family - self._variant = variant - - # Type check variant - if variant is not None and not isinstance(variant, str): - raise ValueError("Illegal variant: must be string or None") - - # Initialize element data - FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, - value_shape, reference_value_shape) - - # Cache repr string - qs = self.quadrature_scheme() - if qs is None: - quad_str = "" - else: - quad_str = ", quad_scheme=%s" % repr(qs) - v = self.variant() - if v is None: - var_str = "" - else: - var_str = ", variant=%s" % repr(v) - self._repr = "FiniteElement(%s, %s, %s%s%s)" % ( - 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 _is_globally_constant(self): - """Doc.""" - return self.family() == "Real" - - def _is_linear(self): - """Doc.""" - return self.family() == "Lagrange" and self.degree() == 1 - - def mapping(self): - """Return the mapping type for this element .""" - return self._mapping - - @property - def sobolev_space(self): - """Return the underlying Sobolev space.""" - return self._sobolev_space - - def variant(self): - """Return the variant used to initialise the element.""" - return self._variant - - def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, variant=None): - """Construct a new FiniteElement object with some properties replaced with new values.""" - if family is None: - family = self.family() - if cell is None: - cell = self.cell - if degree is None: - degree = self.degree() - if quad_scheme is None: - quad_scheme = self.quadrature_scheme() - if variant is None: - variant = self.variant() - return FiniteElement(family, cell, degree, quad_scheme=quad_scheme, variant=variant) - - def __str__(self): - """Format as string for pretty printing.""" - qs = self.quadrature_scheme() - qs = "" if qs is None else "(%s)" % qs - v = self.variant() - v = "" if v is None else "(%s)" % v - return "<%s%s%s%s on a %s>" % (self._short_name, istr(self.degree()), - qs, v, self.cell) - - def shortstr(self): - """Format as string for pretty printing.""" - return f"{self._short_name}{istr(self.degree())}({self.quadrature_scheme()},{istr(self.variant())})" - - def __getnewargs__(self): - """Return the arguments which pickle needs to recreate the object.""" - return (self.family(), - self.cell, - self.degree(), - None, - self.quadrature_scheme(), - self.variant()) diff --git a/ufl/legacy/finiteelementbase.py b/ufl/legacy/finiteelementbase.py deleted file mode 100644 index 88eba6c8c..000000000 --- a/ufl/legacy/finiteelementbase.py +++ /dev/null @@ -1,276 +0,0 @@ -"""This module defines the UFL finite element classes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Massimiliano Leoni, 2016 - -from abc import abstractmethod, abstractproperty - -from ufl import pullback -from ufl.cell import AbstractCell, as_cell -from ufl.finiteelement import AbstractFiniteElement -from ufl.utils.sequences import product - - -class FiniteElementBase(AbstractFiniteElement): - """Base class for all finite elements.""" - __slots__ = ("_family", "_cell", "_degree", "_quad_scheme", - "_value_shape", "_reference_value_shape") - - # 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.""" - if not (degree is None or isinstance(degree, (int, tuple))): - raise ValueError("Invalid degree type.") - if not isinstance(value_shape, tuple): - raise ValueError("Invalid value_shape type.") - if not isinstance(reference_value_shape, tuple): - raise ValueError("Invalid reference_value_shape type.") - - if cell is not None: - cell = as_cell(cell) - if not isinstance(cell, AbstractCell): - raise ValueError("Invalid cell type.") - - self._family = family - self._cell = cell - self._degree = degree - self._value_shape = 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.""" - pass - - @abstractproperty - def sobolev_space(self): - """Return the underlying Sobolev space.""" - pass - - @abstractmethod - def mapping(self): - """Return the mapping type for this element.""" - pass - - def _is_globally_constant(self): - """Check if the element is a global constant. - - For Real elements, this should return True. - """ - return False - - def _is_linear(self): - """Check if the element is Lagrange degree 1.""" - return False - - def _ufl_hash_data_(self): - """Doc.""" - return repr(self) - - def _ufl_signature_data_(self): - """Doc.""" - return repr(self) - - def __hash__(self): - """Compute hash code for insertion in hashmaps.""" - return hash(self._ufl_hash_data_()) - - def __eq__(self, other): - """Compute element equality for insertion in hashmaps.""" - return type(self) is type(other) and self._ufl_hash_data_() == other._ufl_hash_data_() - - def __ne__(self, other): - """Compute element inequality for insertion in hashmaps.""" - return not self.__eq__(other) - - def __lt__(self, other): - """Compare elements by repr, to give a natural stable sorting.""" - return repr(self) < repr(other) - - def family(self): # FIXME: Undefined for base? - """Return finite element family.""" - return self._family - - def variant(self): - """Return the variant used to initialise the element.""" - return None - - def degree(self, component=None): - """Return polynomial degree of finite element.""" - return self._degree - - def quadrature_scheme(self): - """Return quadrature scheme of finite element.""" - return self._quad_scheme - - @property - def cell(self): - """Return cell of finite element.""" - return self._cell - - def is_cellwise_constant(self, component=None): - """Return whether the basis functions of this element is spatially constant over each cell.""" - return self._is_globally_constant() or self.degree() == 0 - - @property - def value_shape(self): - """Return the shape of the value space on the global domain.""" - return self._value_shape - - @property - def reference_value_shape(self): - """Return the shape of the value space on the reference cell.""" - return self._reference_value_shape - - @property - def value_size(self): - """Return the integer product of the value shape.""" - return product(self.value_shape) - - @property - def reference_value_size(self): - """Return the integer product of the reference value shape.""" - return product(self.reference_value_shape) - - def symmetry(self): # FIXME: different approach - r"""Return the symmetry dict. - - This is a mapping :math:`c_0 \\to c_1` - meaning that component :math:`c_0` is represented by component - :math:`c_1`. - A component is a tuple of one or more ints. - """ - return {} - - def _check_component(self, i): - """Check that component index i is valid.""" - sh = self.value_shape - r = len(sh) - if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): - raise ValueError( - f"Illegal component index {i} (value rank {len(i)}) " - f"for element (value rank {r}).") - - def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative component index for a given component index.""" - if isinstance(i, int): - i = (i,) - self._check_component(i) - return (None, i) - - def extract_component(self, i): - """Recursively extract component index relative to a (simple) element. - - and that element for given value component index. - """ - if isinstance(i, int): - i = (i,) - self._check_component(i) - return (i, self) - - def _check_reference_component(self, i): - """Check that reference component index i is valid.""" - sh = self.value_shape - r = len(sh) - if not (len(i) == r and all(j < k for (j, k) in zip(i, sh))): - raise ValueError( - f"Illegal component index {i} (value rank {len(i)}) " - f"for element (value rank {r}).") - - def extract_subelement_reference_component(self, i): - """Extract direct subelement index and subelement relative. - - reference component index for a given reference component index. - """ - if isinstance(i, int): - i = (i,) - self._check_reference_component(i) - return (None, i) - - def extract_reference_component(self, i): - """Recursively extract reference component index relative to a (simple) element. - - and that element for given reference value component index. - """ - if isinstance(i, int): - i = (i,) - self._check_reference_component(i) - return (i, self) - - @property - def num_sub_elements(self): - """Return number of sub-elements.""" - return 0 - - @property - def sub_elements(self): - """Return list of sub-elements.""" - return [] - - def __add__(self, other): - """Add two elements, creating an enriched element.""" - if not isinstance(other, FiniteElementBase): - raise ValueError(f"Can't add element and {other.__class__}.") - from ufl.legacy import EnrichedElement - return EnrichedElement(self, other) - - def __mul__(self, other): - """Multiply two elements, creating a mixed element.""" - if not isinstance(other, FiniteElementBase): - raise ValueError("Can't multiply element and {other.__class__}.") - from ufl.legacy import MixedElement - return MixedElement(self, other) - - def __getitem__(self, index): - """Restrict finite element to a subdomain, subcomponent or topology (cell).""" - if index in ("facet", "interior"): - from ufl.legacy import RestrictedElement - return RestrictedElement(self, index) - else: - raise KeyError(f"Invalid index for restriction: {repr(index)}") - - def __iter__(self): - """Iter.""" - raise TypeError(f"'{type(self).__name__}' object is not iterable") - - @property - def embedded_superdegree(self): - """Doc.""" - return self.degree() - - @property - def embedded_subdegree(self): - """Doc.""" - return self.degree() - - @property - def pullback(self): - """Get the pull back.""" - if self.mapping() == "identity": - return pullback.identity_pullback - elif self.mapping() == "L2 Piola": - return pullback.l2_piola - elif self.mapping() == "covariant Piola": - return pullback.covariant_piola - elif self.mapping() == "contravariant Piola": - return pullback.contravariant_piola - elif self.mapping() == "double covariant Piola": - return pullback.double_covariant_piola - elif self.mapping() == "double contravariant Piola": - return pullback.double_contravariant_piola - elif self.mapping() == "custom": - return pullback.custom_pullback - elif self.mapping() == "physical": - return pullback.physical_pullback - - raise ValueError(f"Unsupported mapping: {self.mapping()}") diff --git a/ufl/legacy/hdivcurl.py b/ufl/legacy/hdivcurl.py deleted file mode 100644 index 3f693017e..000000000 --- a/ufl/legacy/hdivcurl.py +++ /dev/null @@ -1,213 +0,0 @@ -"""Doc.""" -# Copyright (C) 2008-2016 Andrew T. T. McRae -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Massimiliano Leoni, 2016 - -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import L2, HCurl, HDiv - - -class HDivElement(FiniteElementBase): - """A div-conforming version of an outer product element, assuming this makes mathematical sense.""" - __slots__ = ("_element", ) - - def __init__(self, element): - """Doc.""" - self._element = element - - family = "TensorProductElement" - cell = element.cell - degree = element.degree() - quad_scheme = element.quadrature_scheme() - value_shape = (element.cell.geometric_dimension(),) - reference_value_shape = (element.cell.topological_dimension(),) - - # Skipping TensorProductElement constructor! Bad code smell, refactor to avoid this non-inheritance somehow. - FiniteElementBase.__init__(self, family, cell, degree, - quad_scheme, value_shape, reference_value_shape) - - def __repr__(self): - """Doc.""" - return f"HDivElement({repr(self._element)})" - - def mapping(self): - """Doc.""" - return "contravariant Piola" - - @property - def sobolev_space(self): - """Return the underlying Sobolev space.""" - return HDiv - - def reconstruct(self, **kwargs): - """Doc.""" - return HDivElement(self._element.reconstruct(**kwargs)) - - def variant(self): - """Doc.""" - return self._element.variant() - - def __str__(self): - """Doc.""" - return f"HDivElement({repr(self._element)})" - - def shortstr(self): - """Format as string for pretty printing.""" - return f"HDivElement({self._element.shortstr()})" - - @property - def embedded_subdegree(self): - """Return embedded subdegree.""" - return self._element.embedded_subdegree - - @property - def embedded_superdegree(self): - """Return embedded superdegree.""" - return self._element.embedded_superdegree - - -class HCurlElement(FiniteElementBase): - """A curl-conforming version of an outer product element, assuming this makes mathematical sense.""" - __slots__ = ("_element",) - - def __init__(self, element): - """Doc.""" - self._element = element - - family = "TensorProductElement" - cell = element.cell - degree = element.degree() - quad_scheme = element.quadrature_scheme() - cell = element.cell - value_shape = (cell.geometric_dimension(),) - reference_value_shape = (cell.topological_dimension(),) # TODO: Is this right? - # Skipping TensorProductElement constructor! Bad code smell, - # refactor to avoid this non-inheritance somehow. - FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, - value_shape, reference_value_shape) - - def __repr__(self): - """Doc.""" - return f"HCurlElement({repr(self._element)})" - - def mapping(self): - """Doc.""" - return "covariant Piola" - - @property - def sobolev_space(self): - """Return the underlying Sobolev space.""" - return HCurl - - def reconstruct(self, **kwargs): - """Doc.""" - return HCurlElement(self._element.reconstruct(**kwargs)) - - def variant(self): - """Doc.""" - return self._element.variant() - - def __str__(self): - """Doc.""" - return f"HCurlElement({repr(self._element)})" - - def shortstr(self): - """Format as string for pretty printing.""" - return f"HCurlElement({self._element.shortstr()})" - - -class WithMapping(FiniteElementBase): - """Specify an alternative mapping for the wrappee. - - For example, - to use identity mapping instead of Piola map with an element E, - write - remapped = WithMapping(E, "identity") - """ - - def __init__(self, wrapee, mapping): - """Doc.""" - if mapping == "symmetries": - raise ValueError("Can't change mapping to 'symmetries'") - self._mapping = mapping - self.wrapee = wrapee - - def __getattr__(self, attr): - """Doc.""" - try: - return getattr(self.wrapee, attr) - except AttributeError: - raise AttributeError("'%s' object has no attribute '%s'" % - (type(self).__name__, attr)) - - def __repr__(self): - """Doc.""" - return f"WithMapping({repr(self.wrapee)}, '{self._mapping}')" - - @property - def value_shape(self): - """Doc.""" - gdim = self.cell.geometric_dimension() - mapping = self.mapping() - if mapping in {"covariant Piola", "contravariant Piola"}: - return (gdim,) - elif mapping in {"double covariant Piola", "double contravariant Piola"}: - return (gdim, gdim) - else: - return self.wrapee.value_shape - - @property - def reference_value_shape(self): - """Doc.""" - tdim = self.cell.topological_dimension() - mapping = self.mapping() - if mapping in {"covariant Piola", "contravariant Piola"}: - return (tdim,) - elif mapping in {"double covariant Piola", "double contravariant Piola"}: - return (tdim, tdim) - else: - return self.wrapee.reference_value_shape - - def mapping(self): - """Doc.""" - return self._mapping - - @property - 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): - """Doc.""" - mapping = kwargs.pop("mapping", self._mapping) - wrapee = self.wrapee.reconstruct(**kwargs) - return type(self)(wrapee, mapping) - - def variant(self): - """Doc.""" - return self.wrapee.variant() - - def __str__(self): - """Doc.""" - return f"WithMapping({repr(self.wrapee)}, {self._mapping})" - - def shortstr(self): - """Doc.""" - return f"WithMapping({self.wrapee.shortstr()}, {self._mapping})" - - @property - def embedded_subdegree(self): - """Return embedded subdegree.""" - return self._element.embedded_subdegree - - @property - def embedded_superdegree(self): - """Return embedded superdegree.""" - return self._element.embedded_superdegree diff --git a/ufl/legacy/mixedelement.py b/ufl/legacy/mixedelement.py deleted file mode 100644 index 97f194e55..000000000 --- a/ufl/legacy/mixedelement.py +++ /dev/null @@ -1,562 +0,0 @@ -"""This module defines the UFL finite element classes.""" -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Anders Logg 2014 -# Modified by Massimiliano Leoni, 2016 - -import numpy as np - -from ufl.cell import as_cell -from ufl.legacy.finiteelement import FiniteElement -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.permutation import compute_indices -from ufl.pullback import IdentityPullback, MixedPullback, SymmetricPullback -from ufl.utils.indexflattening import flatten_multiindex, shape_to_strides, unflatten_index -from ufl.utils.sequences import max_degree, product - - -class MixedElement(FiniteElementBase): - """A finite element composed of a nested hierarchy of mixed or simple elements.""" - __slots__ = ("_sub_elements", "_cells") - - def __init__(self, *elements, **kwargs): - """Create mixed finite element from given list of elements.""" - if type(self) is MixedElement: - if kwargs: - raise ValueError("Not expecting keyword arguments to MixedElement constructor.") - - # Un-nest arguments if we get a single argument with a list of elements - if len(elements) == 1 and isinstance(elements[0], (tuple, list)): - elements = elements[0] - # Interpret nested tuples as sub-mixedelements recursively - elements = [MixedElement(e) if isinstance(e, (tuple, list)) else e - for e in elements] - self._sub_elements = elements - - # Pick the first cell, for now all should be equal - cells = tuple(sorted(set(element.cell for element in elements) - set([None]))) - self._cells = cells - if cells: - cell = cells[0] - # Require that all elements are defined on the same cell - if not all(c == cell for c in cells[1:]): - raise ValueError("Sub elements must live on the same cell.") - else: - cell = None - - # Check that all elements use the same quadrature scheme TODO: - # We can allow the scheme not to be defined. - if len(elements) == 0: - quad_scheme = None - else: - quad_scheme = elements[0].quadrature_scheme() - if not all(e.quadrature_scheme() == quad_scheme for e in elements): - raise ValueError("Quadrature scheme mismatch for sub elements of mixed element.") - - # Compute value sizes in global and reference configurations - value_size_sum = sum(product(s.value_shape) for s in self._sub_elements) - reference_value_size_sum = sum(product(s.reference_value_shape) for s in self._sub_elements) - - # Default value shape: Treated simply as all subelement values - # unpacked in a vector. - value_shape = kwargs.get('value_shape', (value_size_sum,)) - - # Default reference value shape: Treated simply as all - # subelement reference values unpacked in a vector. - reference_value_shape = kwargs.get('reference_value_shape', (reference_value_size_sum,)) - - # Validate value_shape (deliberately not for subclasses - # VectorElement and TensorElement) - if type(self) is MixedElement: - # This is not valid for tensor elements with symmetries, - # assume subclasses deal with their own validation - if product(value_shape) != value_size_sum: - raise ValueError("Provided value_shape doesn't match the " - "total value size of all subelements.") - - # Initialize element data - degrees = {e.degree() for e in self._sub_elements} - {None} - degree = max_degree(degrees) if degrees else None - FiniteElementBase.__init__(self, "Mixed", cell, degree, quad_scheme, - value_shape, reference_value_shape) - - def __repr__(self): - """Doc.""" - return "MixedElement(" + ", ".join(repr(e) for e in self._sub_elements) + ")" - - def _is_linear(self): - """Doc.""" - return all(i._is_linear() for i in self._sub_elements) - - def reconstruct_from_elements(self, *elements): - """Reconstruct a mixed element from new subelements.""" - if all(a == b for (a, b) in zip(elements, self._sub_elements)): - return self - return MixedElement(*elements) - - def symmetry(self): - r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. - - meaning that component :math:`c_0` is represented by component - :math:`c_1`. - A component is a tuple of one or more ints. - """ - # Build symmetry map from symmetries of subelements - sm = {} - # Base index of the current subelement into mixed value - j = 0 - for e in self._sub_elements: - sh = e.value_shape - st = shape_to_strides(sh) - # Map symmetries of subelement into index space of this - # element - for c0, c1 in e.symmetry().items(): - j0 = flatten_multiindex(c0, st) + j - j1 = flatten_multiindex(c1, st) + j - sm[(j0,)] = (j1,) - # Update base index for next element - j += product(sh) - if j != product(self.value_shape): - raise ValueError("Size mismatch in symmetry algorithm.") - return sm or {} - - @property - def sobolev_space(self): - """Doc.""" - return max(e.sobolev_space for e in self._sub_elements) - - def mapping(self): - """Doc.""" - if all(e.mapping() == "identity" for e in self._sub_elements): - return "identity" - else: - return "undefined" - - @property - def num_sub_elements(self): - """Return number of sub elements.""" - return len(self._sub_elements) - - @property - def sub_elements(self): - """Return list of sub elements.""" - return self._sub_elements - - def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative. - - component index for a given component index. - """ - if isinstance(i, int): - i = (i,) - self._check_component(i) - - # Select between indexing modes - if len(self.value_shape) == 1: - # Indexing into a long vector of flattened subelement - # shapes - j, = i - - # Find subelement for this index - for sub_element_index, e in enumerate(self._sub_elements): - sh = e.value_shape - si = product(sh) - if j < si: - break - j -= si - if j < 0: - raise ValueError("Moved past last value component!") - - # Convert index into a shape tuple - st = shape_to_strides(sh) - component = unflatten_index(j, st) - else: - # Indexing into a multidimensional tensor where subelement - # index is first axis - sub_element_index = i[0] - if sub_element_index >= len(self._sub_elements): - raise ValueError(f"Illegal component index (dimension {sub_element_index}).") - component = i[1:] - return (sub_element_index, component) - - def extract_component(self, i): - """Recursively extract component index relative to a (simple) element. - - and that element for given value component index. - """ - sub_element_index, component = self.extract_subelement_component(i) - return self._sub_elements[sub_element_index].extract_component(component) - - def extract_subelement_reference_component(self, i): - """Extract direct subelement index and subelement relative. - - reference_component index for a given reference_component index. - """ - if isinstance(i, int): - i = (i,) - self._check_reference_component(i) - - # Select between indexing modes - assert len(self.reference_value_shape) == 1 - # Indexing into a long vector of flattened subelement shapes - j, = i - - # Find subelement for this index - for sub_element_index, e in enumerate(self._sub_elements): - sh = e.reference_value_shape - si = product(sh) - if j < si: - break - j -= si - if j < 0: - raise ValueError("Moved past last value reference_component!") - - # Convert index into a shape tuple - st = shape_to_strides(sh) - reference_component = unflatten_index(j, st) - return (sub_element_index, reference_component) - - def extract_reference_component(self, i): - """Recursively extract reference_component index relative to a (simple) element. - - and that element for given value reference_component index. - """ - sub_element_index, reference_component = self.extract_subelement_reference_component(i) - return self._sub_elements[sub_element_index].extract_reference_component(reference_component) - - def is_cellwise_constant(self, component=None): - """Return whether the basis functions of this element is spatially constant over each cell.""" - if component is None: - return all(e.is_cellwise_constant() for e in self.sub_elements) - else: - i, e = self.extract_component(component) - return e.is_cellwise_constant() - - def degree(self, component=None): - """Return polynomial degree of finite element.""" - if component is None: - return self._degree # from FiniteElementBase, computed as max of subelements in __init__ - else: - i, e = self.extract_component(component) - return e.degree() - - @property - def embedded_subdegree(self): - """Return embedded subdegree.""" - if isinstance(self._degree, int): - return self._degree - else: - return min(e.embedded_subdegree for e in self.sub_elements) - - @property - def embedded_superdegree(self): - """Return embedded superdegree.""" - if isinstance(self._degree, int): - return self._degree - else: - return max(e.embedded_superdegree for e in self.sub_elements) - - def reconstruct(self, **kwargs): - """Doc.""" - return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements]) - - def variant(self): - """Doc.""" - try: - variant, = {e.variant() for e in self.sub_elements} - return variant - except ValueError: - return None - - def __str__(self): - """Format as string for pretty printing.""" - tmp = ", ".join(str(element) for element in self._sub_elements) - return "" - - def shortstr(self): - """Format as string for pretty printing.""" - tmp = ", ".join(element.shortstr() for element in self._sub_elements) - return "Mixed<" + tmp + ">" - - @property - def pullback(self): - """Get the pull back.""" - for e in self.sub_elements: - if not isinstance(e.pullback, IdentityPullback): - return MixedPullback(self) - return IdentityPullback() - - -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): - """Create vector element (repeated mixed element).""" - if isinstance(family, FiniteElementBase): - sub_element = family - cell = sub_element.cell - variant = sub_element.variant() - else: - if cell is not None: - cell = as_cell(cell) - # Create sub element - sub_element = FiniteElement(family, cell, degree, - form_degree=form_degree, - quad_scheme=quad_scheme, - variant=variant) - - # Set default size if not specified - if dim is None: - if cell is None: - raise ValueError("Cannot infer vector dimension without a cell.") - dim = cell.geometric_dimension() - - self._mapping = sub_element.mapping() - # Create list of sub elements for mixed element constructor - sub_elements = [sub_element] * dim - - # Compute value shapes - value_shape = (dim,) + sub_element.value_shape - reference_value_shape = (dim,) + sub_element.reference_value_shape - - # Initialize element data - MixedElement.__init__(self, sub_elements, value_shape=value_shape, - reference_value_shape=reference_value_shape) - - FiniteElementBase.__init__(self, sub_element.family(), sub_element.cell, sub_element.degree(), - sub_element.quadrature_scheme(), value_shape, reference_value_shape) - - self._sub_element = sub_element - - if variant is None: - var_str = "" - else: - var_str = ", variant='" + variant + "'" - - # Cache repr string - self._repr = f"VectorElement({repr(sub_element)}, dim={dim}{var_str})" - - def __repr__(self): - """Doc.""" - return self._repr - - def reconstruct(self, **kwargs): - """Doc.""" - sub_element = self._sub_element.reconstruct(**kwargs) - return VectorElement(sub_element, dim=len(self.sub_elements)) - - def variant(self): - """Return the variant used to initialise the element.""" - return self._sub_element.variant() - - def mapping(self): - """Doc.""" - return self._mapping - - def __str__(self): - """Format as string for pretty printing.""" - return ("" % - (len(self._sub_elements), self._sub_element)) - - def shortstr(self): - """Format as string for pretty printing.""" - return "Vector<%d x %s>" % (len(self._sub_elements), - self._sub_element.shortstr()) - - -class TensorElement(MixedElement): - """A special case of a mixed finite element where all elements are equal.""" - __slots__ = ("_sub_element", "_shape", "_symmetry", - "_sub_element_mapping", - "_flattened_sub_element_mapping", - "_mapping", "_repr") - - def __init__(self, family, cell=None, degree=None, shape=None, - symmetry=None, quad_scheme=None, variant=None): - """Create tensor element (repeated mixed element with optional symmetries).""" - if isinstance(family, FiniteElementBase): - sub_element = family - cell = sub_element.cell - variant = sub_element.variant() - else: - if cell is not None: - cell = as_cell(cell) - # Create scalar sub element - sub_element = FiniteElement(family, cell, degree, quad_scheme=quad_scheme, - variant=variant) - - # Set default shape if not specified - if shape is None: - if cell is None: - raise ValueError("Cannot infer tensor shape without a cell.") - dim = cell.geometric_dimension() - shape = (dim, dim) - - if symmetry is None: - symmetry = {} - elif symmetry is True: - # Construct default symmetry dict for matrix elements - if not (len(shape) == 2 and shape[0] == shape[1]): - raise ValueError("Cannot set automatic symmetry for non-square tensor.") - symmetry = dict(((i, j), (j, i)) for i in range(shape[0]) - for j in range(shape[1]) if i > j) - else: - if not isinstance(symmetry, dict): - raise ValueError("Expecting symmetry to be None (unset), True, or dict.") - - # Validate indices in symmetry dict - for i, j in symmetry.items(): - if len(i) != len(j): - raise ValueError("Non-matching length of symmetry index tuples.") - for k in range(len(i)): - if not (i[k] >= 0 and j[k] >= 0 and i[k] < shape[k] and j[k] < shape[k]): - raise ValueError("Symmetry dimensions out of bounds.") - - # Compute all index combinations for given shape - indices = compute_indices(shape) - - # Compute mapping from indices to sub element number, - # accounting for symmetry - sub_elements = [] - sub_element_mapping = {} - for index in indices: - if index in symmetry: - continue - sub_element_mapping[index] = len(sub_elements) - sub_elements += [sub_element] - - # Update mapping for symmetry - for index in indices: - if index in symmetry: - sub_element_mapping[index] = sub_element_mapping[symmetry[index]] - flattened_sub_element_mapping = [sub_element_mapping[index] for i, - index in enumerate(indices)] - - # Compute value shape - value_shape = shape - - # Compute reference value shape based on symmetries - if symmetry: - reference_value_shape = (product(shape) - len(symmetry),) - self._mapping = "symmetries" - else: - reference_value_shape = shape - self._mapping = sub_element.mapping() - - value_shape = value_shape + sub_element.value_shape - reference_value_shape = reference_value_shape + sub_element.reference_value_shape - # Initialize element data - MixedElement.__init__(self, sub_elements, value_shape=value_shape, - reference_value_shape=reference_value_shape) - self._family = sub_element.family() - self._degree = sub_element.degree() - self._sub_element = sub_element - self._shape = shape - self._symmetry = symmetry - self._sub_element_mapping = sub_element_mapping - self._flattened_sub_element_mapping = flattened_sub_element_mapping - - if variant is None: - var_str = "" - else: - var_str = ", variant='" + variant + "'" - - # Cache repr string - self._repr = (f"TensorElement({repr(sub_element)}, shape={shape}, " - f"symmetry={symmetry}{var_str})") - - @property - def pullback(self): - """Get pull back.""" - if len(self._symmetry) > 0: - sub_element_value_shape = self.sub_elements[0].value_shape - for e in self.sub_elements: - if e.value_shape != sub_element_value_shape: - raise ValueError("Sub-elements must all have the same value size") - symmetry = {} - n = 0 - for i in np.ndindex(self.value_shape[:len(self.value_shape)-len(sub_element_value_shape)]): - if i in self._symmetry and self._symmetry[i] in symmetry: - symmetry[i] = symmetry[self._symmetry[i]] - else: - symmetry[i] = n - n += 1 - return SymmetricPullback(self, symmetry) - return super().pullback - - def __repr__(self): - """Doc.""" - return self._repr - - def variant(self): - """Return the variant used to initialise the element.""" - return self._sub_element.variant() - - def mapping(self): - """Doc.""" - return self._mapping - - def flattened_sub_element_mapping(self): - """Doc.""" - return self._flattened_sub_element_mapping - - def extract_subelement_component(self, i): - """Extract direct subelement index and subelement relative. - - component index for a given component index. - """ - if isinstance(i, int): - i = (i,) - self._check_component(i) - - i = self.symmetry().get(i, i) - l = len(self._shape) # noqa: E741 - ii = i[:l] - jj = i[l:] - if ii not in self._sub_element_mapping: - raise ValueError(f"Illegal component index {i}.") - k = self._sub_element_mapping[ii] - return (k, jj) - - def symmetry(self): - r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. - - meaning that component :math:`c_0` is represented by component - :math:`c_1`. - A component is a tuple of one or more ints. - """ - return self._symmetry - - def reconstruct(self, **kwargs): - """Doc.""" - sub_element = self._sub_element.reconstruct(**kwargs) - return TensorElement(sub_element, shape=self._shape, symmetry=self._symmetry) - - def __str__(self): - """Format as string for pretty printing.""" - if self._symmetry: - tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in self._symmetry.items()) - sym = " with symmetries (%s)" % tmp - else: - sym = "" - return ("" % - (self.value_shape, self._sub_element, sym)) - - def shortstr(self): - """Format as string for pretty printing.""" - if self._symmetry: - tmp = ", ".join("%s -> %s" % (a, b) for (a, b) in self._symmetry.items()) - sym = " with symmetries (%s)" % tmp - else: - sym = "" - return "Tensor<%s x %s%s>" % (self.value_shape, - self._sub_element.shortstr(), sym) diff --git a/ufl/legacy/restrictedelement.py b/ufl/legacy/restrictedelement.py deleted file mode 100644 index 767444087..000000000 --- a/ufl/legacy/restrictedelement.py +++ /dev/null @@ -1,113 +0,0 @@ -"""This module defines the UFL finite element classes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Massimiliano Leoni, 2016 - -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import L2 - -valid_restriction_domains = ("interior", "facet", "face", "edge", "vertex") - - -class RestrictedElement(FiniteElementBase): - """Represents the restriction of a finite element to a type of cell entity.""" - - def __init__(self, element, restriction_domain): - """Doc.""" - if not isinstance(element, FiniteElementBase): - raise ValueError("Expecting a finite element instance.") - if restriction_domain not in valid_restriction_domains: - raise ValueError(f"Expecting one of the strings: {valid_restriction_domains}") - - FiniteElementBase.__init__(self, "RestrictedElement", element.cell, - element.degree(), - element.quadrature_scheme(), - element.value_shape, - element.reference_value_shape) - - self._element = element - - self._restriction_domain = restriction_domain - - def __repr__(self): - """Doc.""" - return f"RestrictedElement({repr(self._element)}, {repr(self._restriction_domain)})" - - @property - def sobolev_space(self): - """Doc.""" - if self._restriction_domain == "interior": - return L2 - else: - return self._element.sobolev_space - - def is_cellwise_constant(self): - """Return whether the basis functions of this element is spatially constant over each cell.""" - return self._element.is_cellwise_constant() - - def _is_linear(self): - """Doc.""" - return self._element._is_linear() - - def sub_element(self): - """Return the element which is restricted.""" - return self._element - - def mapping(self): - """Doc.""" - return self._element.mapping() - - def restriction_domain(self): - """Return the domain onto which the element is restricted.""" - return self._restriction_domain - - def reconstruct(self, **kwargs): - """Doc.""" - element = self._element.reconstruct(**kwargs) - return RestrictedElement(element, self._restriction_domain) - - def __str__(self): - """Format as string for pretty printing.""" - return "<%s>|_{%s}" % (self._element, self._restriction_domain) - - def shortstr(self): - """Format as string for pretty printing.""" - return "<%s>|_{%s}" % (self._element.shortstr(), - self._restriction_domain) - - def symmetry(self): - r"""Return the symmetry dict, which is a mapping :math:`c_0 \\to c_1`. - - meaning that component :math:`c_0` is represented by component - :math:`c_1`. A component is a tuple of one or more ints. - """ - return self._element.symmetry() - - @property - def num_sub_elements(self): - """Return number of sub elements.""" - return self._element.num_sub_elements - - @property - def sub_elements(self): - """Return list of sub elements.""" - return self._element.sub_elements - - def num_restricted_sub_elements(self): - """Return number of restricted sub elements.""" - return 1 - - def restricted_sub_elements(self): - """Return list of restricted sub elements.""" - return (self._element,) - - def variant(self): - """Doc.""" - return self._element.variant() diff --git a/ufl/legacy/tensorproductelement.py b/ufl/legacy/tensorproductelement.py deleted file mode 100644 index d9f1ff1bd..000000000 --- a/ufl/legacy/tensorproductelement.py +++ /dev/null @@ -1,140 +0,0 @@ -"""This module defines the UFL finite element classes.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Kristian B. Oelgaard -# Modified by Marie E. Rognes 2010, 2012 -# Modified by Massimiliano Leoni, 2016 - -from itertools import chain - -from ufl.cell import TensorProductCell, as_cell -from ufl.legacy.finiteelementbase import FiniteElementBase -from ufl.sobolevspace import DirectionalSobolevSpace - - -class TensorProductElement(FiniteElementBase): - r"""The tensor product of :math:`d` element spaces. - - .. math:: V = V_1 \otimes V_2 \otimes ... \otimes V_d - - Given bases :math:`\{\phi_{j_i}\}` of the spaces :math:`V_i` for :math:`i = 1, ...., d`, - :math:`\{ \phi_{j_1} \otimes \phi_{j_2} \otimes \cdots \otimes \phi_{j_d} - \}` forms a basis for :math:`V`. - """ - __slots__ = ("_sub_elements", "_cell") - - def __init__(self, *elements, **kwargs): - """Create TensorProductElement from a given list of elements.""" - if not elements: - raise ValueError("Cannot create TensorProductElement from empty list.") - - keywords = list(kwargs.keys()) - if keywords and keywords != ["cell"]: - raise ValueError("TensorProductElement got an unexpected keyword argument '%s'" % keywords[0]) - cell = kwargs.get("cell") - - family = "TensorProductElement" - - if cell is None: - # Define cell as the product of each elements cell - cell = TensorProductCell(*[e.cell for e in elements]) - else: - cell = as_cell(cell) - - # Define polynomial degree as a tuple of sub-degrees - degree = tuple(e.degree() for e in elements) - - # No quadrature scheme defined - quad_scheme = None - - # match FIAT implementation - value_shape = tuple(chain(*[e.value_shape for e in elements])) - reference_value_shape = tuple(chain(*[e.reference_value_shape for e in elements])) - if len(value_shape) > 1: - raise ValueError("Product of vector-valued elements not supported") - if len(reference_value_shape) > 1: - raise ValueError("Product of vector-valued elements not supported") - - FiniteElementBase.__init__(self, family, cell, degree, - quad_scheme, value_shape, - reference_value_shape) - self._sub_elements = elements - self._cell = cell - - def __repr__(self): - """Doc.""" - return "TensorProductElement(" + ", ".join(repr(e) for e in self._sub_elements) + f", cell={repr(self._cell)})" - - def mapping(self): - """Doc.""" - if all(e.mapping() == "identity" for e in self._sub_elements): - return "identity" - elif all(e.mapping() == "L2 Piola" for e in self._sub_elements): - return "L2 Piola" - else: - return "undefined" - - @property - def sobolev_space(self): - """Return the underlying Sobolev space of the TensorProductElement.""" - elements = self._sub_elements - if all(e.sobolev_space == elements[0].sobolev_space - for e in elements): - return elements[0].sobolev_space - else: - # Generate a DirectionalSobolevSpace which contains - # continuity information parametrized by spatial index - orders = [] - for e in elements: - e_dim = e.cell.geometric_dimension() - e_order = (e.sobolev_space._order,) * e_dim - orders.extend(e_order) - return DirectionalSobolevSpace(orders) - - @property - def num_sub_elements(self): - """Return number of subelements.""" - return len(self._sub_elements) - - @property - def sub_elements(self): - """Return subelements (factors).""" - return self._sub_elements - - def reconstruct(self, **kwargs): - """Doc.""" - cell = kwargs.pop("cell", self.cell) - return TensorProductElement(*[e.reconstruct(**kwargs) for e in self.sub_elements], cell=cell) - - def variant(self): - """Doc.""" - 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)" \ - % (', '.join([str(e) for e in self._sub_elements]), str(self._cell)) - - def shortstr(self): - """Short pretty-print.""" - return "TensorProductElement(%s, cell=%s)" \ - % (', '.join([e.shortstr() for e in self._sub_elements]), str(self._cell)) - - @property - def embedded_superdegree(self): - """Doc.""" - return sum(self.degree()) - - @property - def embedded_subdegree(self): - """Doc.""" - return min(self.degree()) From 23c383e59dae0728df75c572d67b595d8a244f06 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Fri, 3 Nov 2023 06:31:09 +0100 Subject: [PATCH 075/136] Change DOLFINx integration test image (#235) * Change DOLFINx integration test image * Update PETSC_ARCH to new scheme * Update pip install. * Update fenicsx-tests.yml * Update fenicsx-tests.yml --- .github/workflows/fenicsx-tests.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 2075498b0..b2a47af67 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -37,7 +37,7 @@ jobs: python3 -m pip install git+https://github.com/FEniCS/basix.git - name: Clone FFCx - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ./ffcx repository: FEniCS/ffcx @@ -53,11 +53,10 @@ jobs: dolfinx-tests: name: Run DOLFINx tests runs-on: ubuntu-latest - container: fenicsproject/test-env:nightly-openmpi + container: ghcr.io/fenics/test-env:current-openmpi env: - - PETSC_ARCH: linux-gnu-complex-32 + PETSC_ARCH: linux-gnu-complex128-32 OMPI_ALLOW_RUN_AS_ROOT: 1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 OMPI_MCA_rmaps_base_oversubscribe: 1 @@ -67,10 +66,7 @@ jobs: OMPI_MCA_hwloc_base_binding_policy: none steps: - - uses: actions/checkout@v3 - - name: Install dependencies (Python) - run: | - python3 -m pip install --upgrade pip + - uses: actions/checkout@v4 - name: Install UFL run: | @@ -82,7 +78,7 @@ jobs: python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ./dolfinx repository: FEniCS/dolfinx @@ -92,6 +88,6 @@ jobs: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ cmake --build build cmake --install build - pip3 -v install --global-option build --global-option --debug dolfinx/python/ + python3 -m pip -v install --no-build-isolation --check-build-dependencies --config-settings=cmake.build-type="Developer" dolfinx/python/ - name: Run DOLFINx unit tests run: python3 -m pytest -n auto dolfinx/python/test/unit From 372a93b2c875ceaf0651e79a1a099f0567eeb9a0 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:20:47 +0000 Subject: [PATCH 076/136] Update BaseForm's __call__ (#232) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names * Fix BaseForm's call * Add test * Update test with new FiniteElement interface --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: David A. Ham Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward Co-authored-by: Matthew Scroggs --- test/test_duals.py | 11 +++++++++++ ufl/form.py | 49 ++++------------------------------------------ 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/test/test_duals.py b/test/test_duals.py index b82736e0f..53e230f3f 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -305,3 +305,14 @@ def test_zero_base_form_mult(): Zu = Z * u assert Zu == action(Z, u) assert action(Zu, u) == ZeroBaseForm(()) + + +def test_base_form_call(): + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) + V = FunctionSpace(domain_2d, f_2d) + + # Check duality pairing + f = Coefficient(V) + c = Cofunction(V.dual()) + assert c(f) == action(c, f) diff --git a/ufl/form.py b/ufl/form.py index ed3040477..f4b546b41 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -198,51 +198,10 @@ def __ne__(self, other): """Immediately evaluate the != operator (as opposed to the == operator).""" return not self.equals(other) - def __call__(self, *args, **kwargs): - """Evaluate form by replacing arguments and coefficients. - - Replaces form.arguments() with given positional arguments in - same number and ordering. Number of positional arguments must - be 0 or equal to the number of Arguments in the form. - - The optional keyword argument coefficients can be set to a dict - to replace Coefficients with expressions of matching shapes. - - Example: - V = FiniteElement("CG", triangle, 1) - v = TestFunction(V) - u = TrialFunction(V) - f = Coefficient(V) - g = Coefficient(V) - a = g*inner(grad(u), grad(v))*dx - M = a(f, f, coefficients={ g: 1 }) - - Is equivalent to M == grad(f)**2*dx. - """ - repdict = {} - - if args: - arguments = self.arguments() - if len(arguments) != len(args): - raise ValueError(f"Need {len(arguments)} arguments to form(), got {len(args)}.") - repdict.update(zip(arguments, args)) - - coefficients = kwargs.pop("coefficients", None) - if kwargs: - raise ValueError(f"Unknown kwargs {list(kwargs)}.") - - if coefficients is not None: - coeffs = self.coefficients() - for f in coefficients: - if f in coeffs: - repdict[f] = coefficients[f] - else: - warnings("Coefficient %s is not in form." % ufl_err_str(f)) - if repdict: - from ufl.formoperators import replace - return replace(self, repdict) - else: - return self + def __call__(self, x): + """Take the action of this form on ``x``.""" + from ufl.formoperators import action + return action(self, x) def _ufl_compute_hash_(self): """Compute the hash.""" From 0fe782c97bcd1f7cc054a21861a45d7eaf1761ed Mon Sep 17 00:00:00 2001 From: Daiane Iglesia Dolci <63597005+Ig-dolci@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:21:01 +0000 Subject: [PATCH 077/136] ufl2unicode fail (#237) * In a recent change to UFL, subdomain_id was transformed from a single value to a tuple. This broke some assumptions in ufl2unicode that I have fixed here. * Update ufl/formatting/ufl2unicode.py Co-authored-by: Connor Ward * Update ufl/formatting/ufl2unicode.py Co-authored-by: Connor Ward * Update ufl/formatting/ufl2unicode.py Co-authored-by: Connor Ward * Update ufl/formatting/ufl2unicode.py Co-authored-by: Connor Ward --------- Co-authored-by: Connor Ward --- ufl/formatting/ufl2unicode.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 90a5d7bf7..0a5f0549f 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -300,14 +300,15 @@ def get_integral_symbol(integral_type, domain, subdomain_id): # TODO: Render domain description - if isinstance(subdomain_id, numbers.Integral): - istr += subscript_number(int(subdomain_id)) - elif subdomain_id == "everywhere": - pass - elif subdomain_id == "otherwise": - istr += "[rest of domain]" - elif isinstance(subdomain_id, tuple): - istr += ",".join([subscript_number(int(i)) for i in subdomain_id]) + subdomain_strs = [] + for subdomain in subdomain_id: + if isinstance(subdomain, numbers.Integral): + subdomain_strs.append(subscript_number(int(subdomain))) + elif subdomain == "everywhere": + pass + elif subdomain == "otherwise": + subdomain_strs.append("[rest of domain]") + istr += ",".join(subdomain_strs) dxstr = ufl.measure.integral_type_to_measure_name[integral_type] dxstr = measure_font(dxstr) From 27869b44b4a407bc9fac3a42fd7d76ba67733ca3 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 13 Nov 2023 15:19:35 +0000 Subject: [PATCH 078/136] Convert rst to md (#239) * move README.rst to README.md * move changelog to md * update styling * indentation? --- ChangeLog.rst => ChangeLog.md | 177 ++++++++++++++-------------------- README.md | 35 +++++++ README.rst | 47 --------- 3 files changed, 108 insertions(+), 151 deletions(-) rename ChangeLog.rst => ChangeLog.md (79%) create mode 100644 README.md delete mode 100644 README.rst diff --git a/ChangeLog.rst b/ChangeLog.md similarity index 79% rename from ChangeLog.rst rename to ChangeLog.md index 28559668d..75d1ab629 100644 --- a/ChangeLog.rst +++ b/ChangeLog.md @@ -1,97 +1,91 @@ -Changelog -========= +# Changelog -2021.1.0 -------------- +## 2021.1.0 - Mixed dimensional domain support -2019.1.0 (2019-04-17) ---------------------- +## 2019.1.0 (2019-04-17) - Remove scripts - Remove LaTeX support (not functional) - Add support for complex valued elements; complex mode - is chosen by ``compute_form_data(form, complex_mode=True)`` typically + is chosen by `compute_form_data(form, complex_mode=True)` typically by a form compiler; otherwise UFL language is agnostic to the choice of real/complex domain -2018.1.0 (2018-06-14) ---------------------- +## 2018.1.0 (2018-06-14) - Remove python2 support -2017.2.0 (2017-12-05) ---------------------- +## 2017.2.0 (2017-12-05) -- Add geometric quantity ``CellDiameter`` defined as a set diameter +- Add geometric quantity `CellDiameter` defined as a set diameter of the cell, i.e., maximal distance between any two points of the cell; implemented on simplices and quads/hexes - Rename internally used reference quantities - ``(Cell|Facet)EdgeVectors`` to ``Reference(Cell|Facet)EdgeVectors`` -- Add internally used quantites ``CellVertices``, - ``(Cell|Facet)EdgeVectors`` which are physical-coordinates-valued; + `(Cell|Facet)EdgeVectors` to `Reference(Cell|Facet)EdgeVectors` +- Add internally used quantites `CellVertices`, + `(Cell|Facet)EdgeVectors` which are physical-coordinates-valued; will be useful for further geometry lowering implementations for quads/hexes -- Implement geometry lowering of ``(Min|Max)(Cell|Facet)EdgeLength`` +- Implement geometry lowering of `(Min|Max)(Cell|Facet)EdgeLength` for quads and hexes -2017.1.0.post1 (2017-09-12) ---------------------------- +## 2017.1.0.post1 (2017-09-12) - Change PyPI package name to fenics-ufl. -2017.1.0 (2017-05-09) ---------------------- +## 2017.1.0 (2017-05-09) -- Add the ``DirectionalSobolevSpace`` subclass of ``SobolevSpace``. This +- Add the `DirectionalSobolevSpace` subclass of `SobolevSpace`. This allows one to use spaces where elements have varying continuity in different spatial directions. -- Add ``sobolev_space`` methods for ``HDiv`` and ``HCurl`` finite +- Add `sobolev_space` methods for `HDiv` and `HCurl` finite elements. -- Add ``sobolev_space`` methods for ``TensorProductElement`` and - ``EnrichedElement``. The smallest shared Sobolev space will be +- Add `sobolev_space` methods for `TensorProductElement` and + `EnrichedElement`. The smallest shared Sobolev space will be returned for enriched elements. For the tensor product elements, a - ``DirectionalSobolevSpace`` is returned depending on the order of the + `DirectionalSobolevSpace` is returned depending on the order of the spaces associated with the component elements. -2016.2.0 (2016-11-30) ---------------------- +## 2016.2.0 (2016-11-30) -- Add call operator syntax to ``Form`` to replace arguments and +- Add call operator syntax to `Form` to replace arguments and coefficients. This makes it easier to e.g. express the norm - defined by a bilinear form as a functional. Example usage:: - + defined by a bilinear form as a functional. Example usage: + ```python # Equivalent to replace(a, {u: f, v: f}) M = a(f, f) # Equivalent to replace(a, {f:1}) c = a(coefficients={f:1}) -- Add call operator syntax to ``Form`` to replace arguments and - coefficients:: - + ``` +- Add call operator syntax to `Form` to replace arguments and + coefficients: + ```python a(f, g) == replace(a, {u: f, v: g}) a(coefficients={f:1}) == replace(a, {f:1}) -- Add ``@`` operator to ``Form``: ``form @ f == action(form, f)`` + ``` +- Add `@` operator to `Form`: `form @ f == action(form, f)` (python 3.5+ only) -- Reduce noise in Mesh str such that ``print(form)`` gets more short and +- Reduce noise in Mesh str such that `print(form)` gets more short and readable -- Fix repeated ``split(function)`` for arbitrary nested elements -- EnrichedElement: Remove ``+/*`` warning - - In the distant past, ``A + B => MixedElement([A, B])``. The change - that ``A + B => EnrichedElement([A, B])`` was made in ``d622c74`` (22 - March 2010). A warning was introduced in ``fcbc5ff`` (26 March 2010) - that the meaning of ``+`` had changed, and that users wanting a - ``MixedElement`` should use ``*`` instead. People have, presumably, +- Fix repeated `split(function)` for arbitrary nested elements +- EnrichedElement: Remove `+/*` warning + + In the distant past, `A + B => MixedElement([A, B])`. The change + that `A + B => EnrichedElement([A, B])` was made in `d622c74` (22 + March 2010). A warning was introduced in `fcbc5ff` (26 March 2010) + that the meaning of `+` had changed, and that users wanting a + `MixedElement` should use `*` instead. People have, presumably, been seeing this warning for 6 1/2 years by now, so it's probably safe to remove. -- Rework ``TensorProductElement`` implementation, replaces - ``OuterProductElement`` -- Rework ``TensorProductCell`` implementation, replaces - ``OuterProductCell`` -- Remove ``OuterProductVectorElement`` and ``OuterProductTensorElement`` -- Add ``FacetElement`` and ``InteriorElement`` -- Add ``Hellan-Herrmann-Johnson`` element +- Rework `TensorProductElement` implementation, replaces + `OuterProductElement` +- Rework `TensorProductCell` implementation, replaces + `OuterProductCell` +- Remove `OuterProductVectorElement` and `OuterProductTensorElement` +- Add `FacetElement` and `InteriorElement` +- Add `Hellan-Herrmann-Johnson` element - Add support for double covariant and contravariant mappings in mixed elements - Support discontinuous Taylor elements on all simplices @@ -103,8 +97,7 @@ Changelog - Add bitbucket pipelines testing - Improve documentation -2016.1.0 (2016-06-23) ---------------------- +## 2016.1.0 (2016-06-23) - Add operator A^(i,j) := as_tensor(A, (i,j)) - Updates to old manual for publishing on fenics-ufl.readthedocs.org @@ -127,8 +120,7 @@ Changelog - Large reworking of symbolic geometry pipeline - Implement symbolic Piola mappings -1.6.0 (2015-07-28) ------------------- +## 1.6.0 (2015-07-28) - Change approach to attaching __hash__ implementation to accomodate python 3 - Implement new non-recursive traversal based hash computation @@ -147,13 +139,12 @@ Changelog - Remove Measure constants - Remove cell2D and cell3D - Implement reference_value in apply_restrictions -- Rename point integral to vertex integral and kept ``*dP`` syntax +- Rename point integral to vertex integral and kept `*dP` syntax - Replace lambda functions in ufl_type with named functions for nicer stack traces - Minor bugfixes, removal of unused code and cleanups -1.5.0 (2015-01-12) ------------------- +## 1.5.0 (2015-01-12) - Require Python 2.7 - Python 3 support @@ -192,10 +183,9 @@ Changelog - Fix signature stability w.r.t. metadata dicts - Minor bugfixes, removal of unused code and cleanups -1.4.0 (2014-06-02) ------------------- +## 1.4.0 (2014-06-02) -- New integral type custom_integral (``*dc``) +- New integral type custom_integral (`*dc`) - Add analysis of which coefficients each integral actually uses to optimize assembly - Improved svg rendering of cells and sobolevspaces in ipython notebook - Add sobolev spaces, use notation "element in HCurl" (HCurl, HDiv, H1, H2, L2) @@ -225,8 +215,7 @@ Changelog - Various minor bugfixes - Various docstring improvements -1.3.0 (2014-01-07) ------------------- +## 1.3.0 (2014-01-07) - Add cell_avg and facet_avg operators, can be applied to a Coefficient or Argument or restrictions thereof - Fix bug in cofactor: now it is transposed the correct way. @@ -236,8 +225,7 @@ Changelog - Add atan2 function - Allow form+0 -> form -1.2.0 (2013-03-24) ------------------- +## 1.2.0 (2013-03-24) - NB! Using shapes such as (1,) and (1,1) instead of () for 1D tensor quantities I, x, grad(f) - Add cell.facet_diameter @@ -249,8 +237,7 @@ Changelog - Generalize jump(v,n) for rank(v) > 2 - Fix some minor bugs -1.1.0 (2013-01-07) ------------------- +## 1.1.0 (2013-01-07) - Add support for pickling of expressions (thanks to Graham Markall) - Add shorthand notation A**2 == inner(A, A), special cased for power 2. @@ -260,8 +247,7 @@ Changelog - Bugfix in quadrature degree estimation, never returning <0 now - Remove use of cmp to accomodate removal from python 3 -1.1-alpha-prerelease (2012-11-18) ---------------------------------- +## 1.1-alpha-prerelease (2012-11-18) (Not released, snapshot archived with submission of UFL journal paper) - Support adding 0 to forms, allowing sum([a]) @@ -273,13 +259,11 @@ Changelog - Add simplification f/f -> 1 - Add operators <,>,<=,>= in place of lt,gt,le,ge -1.0.0 (2011-12-07) ------------------- +## 1.0.0 (2011-12-07) - No changes since rc1. -1.0-rc1 (2011-11-22) --------------------- +## 1.0-rc1 (2011-11-22) - Added tests covering snippets from UFL chapter in FEniCS book - Added more unit tests @@ -288,27 +272,24 @@ Changelog - Fixed rtruediv bug - Fixed bug with derivatives of elements of type Real with unspecified cell -1.0-beta3 (2011-10-26) ----------------------- +## 1.0-beta3 (2011-10-26) - Added nabla_grad and nabla_div operators - Added error function erf(x) - Added bessel functions of first and second kind, normal and modified, bessel_J(nu, x), bessel_Y(nu, x), bessel_I(nu, x), bessel_K(nu, x) - Extended derivative() to allow indexed coefficient(s) as differentiation variable -- Made ``*Constant`` use the ``Real`` space instead of ``DG0`` +- Made `*Constant` use the `Real` space instead of `DG0` - Bugfix in adjoint where test and trial functions were in different spaces - Bugfix in replace where the argument to a grad was replaced with 0 - Bugfix in reconstruction of tensor elements - Some other minor bugfixes -1.0-beta2 (2011-08-11) ----------------------- +## 1.0-beta2 (2011-08-11) - Support c*form where c depends on a coefficient in a Real space -1.0-beta (2011-07-08) ---------------------- +## 1.0-beta (2011-07-08) - Add script ufl-version - Added syntax for associating an arbitrary domain data object with a measure: @@ -319,8 +300,7 @@ Changelog - Fixed support for symmetries in subelements of a mixed element - Add support for specifying derivatives of coefficients to derivative() -0.9.1 (2011-05-16) ------------------- +## 0.9.1 (2011-05-16) - Remove set_foo functions in finite element classes - Change license from GPL v3 or later to LGPL v3 or later @@ -332,23 +312,20 @@ Changelog - Add subdomain member variables to form class - Allow action on forms of arbitrary rank -0.9.0 (2011-02-23) ------------------- +## 0.9.0 (2011-02-23) - Allow jump(Sigma, n) for matrix-valued expression Sigma - Bug fix in scalar curl operator - Bug fix in deviatoric operator -0.5.4 (2010-09-01) ------------------- +## 0.5.4 (2010-09-01) - Bug fixes in PartExtracter - Do not import x for coordinate - Add Circumradius to Cell (Cell.circumradius) - Add CellVolume to Cell (Cell.volume) -0.5.3 (2010-07-01) ------------------- +## 0.5.3 (2010-07-01) - Rename ElementRestriction --> RestrictedElement - Experimental import of x from tetrahedron @@ -357,46 +334,40 @@ Changelog - Rename ElementUnion -> EnrichedElement - Add support for tan() and inverse trigonometric functions -0.5.2 (2010-02-15) ------------------- +## 0.5.2 (2010-02-15) - Attach form data to preprocessed form, accessible by form.form_data() -0.5.1 (2010-02-03) ------------------- +## 0.5.1 (2010-02-03) - Fix bug in propagate_restriction -0.5.0 (2010-02-01) ------------------- +## 0.5.0 (2010-02-01) - Several interface changes in FormData class - Introduce call preprocess(form) to be called at beginning of compilation - Rename BasisFunction --> Argument - Rename Function --> Coefficient -0.4.1 (2009-12-04) ------------------- +## 0.4.1 (2009-12-04) - Redefine grad().T --> grad() - New meaning of estimate_max_polynomial_degree - New function estimate_total_polynomial_degree - Allow degree = None and cell = None for elements -0.4.0 (2009-09-23) ------------------- +## 0.4.0 (2009-09-23) - Extensions for ElementRestriction (restrict FiniteElement to Cell) - Bug fix for lhs/rhs with list tensor types - Add new log function set_prefix - Add new log function log(level, message) -- Added macro cell integral ``*dE`` +- Added macro cell integral `*dE` - Added mechanism to add additional integral types - Added LiftingOperator and LiftingFunction - Added ElementRestriction -0.3.0 (2009-05-28) ------------------- +## 0.3.0 (2009-05-28) - Some critical bugfixes, in particular in differentiation. - Added form operators "system" and "sensitivity_rhs". @@ -408,12 +379,10 @@ Changelog for quadrature degree estimation. - Improved manual. -0.2.0 (2009-04-07) ------------------- +## 0.2.0 (2009-04-07) - Initial release of UFL. -0.1.0 (unreleased) ------------------- +## 0.1.0 (unreleased) - Unreleased development versions of UFL. diff --git a/README.md b/README.md new file mode 100644 index 000000000..5da66e02f --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# UFL - Unified Form Language + +The Unified Form Language (UFL) is a domain specific language for +declaration of finite element discretizations of variational forms. More +precisely, it defines a flexible interface for choosing finite element +spaces and defining expressions for weak forms in a notation close to +mathematical notation. + +UFL is part of the FEniCS Project. For more information, visit +https://www.fenicsproject.org + +[![UFL CI](https://github.com/FEniCS/ufl/workflows/UFL%20CI/badge.svg)](https://github.com/FEniCS/ufl/workflows/UFL%20CI) +[![Coverage Status](https://coveralls.io/repos/github/FEniCS/ufl/badge.svg?branch=master)](https://coveralls.io/github/FEniCS/ufl?branch=master) +[![Documentation Status](https://readthedocs.org/projects/fenics-ufl/badge/?version=latest)](https://fenics.readthedocs.io/projects/ufl/en/latest/?badge=latest) + +## Documentation + +Documentation can be viewed at https://fenics-ufl.readthedocs.org/. + + +## License + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . + diff --git a/README.rst b/README.rst deleted file mode 100644 index 452963331..000000000 --- a/README.rst +++ /dev/null @@ -1,47 +0,0 @@ -=========================== -UFL - Unified Form Language -=========================== - -The Unified Form Language (UFL) is a domain specific language for -declaration of finite element discretizations of variational forms. More -precisely, it defines a flexible interface for choosing finite element -spaces and defining expressions for weak forms in a notation close to -mathematical notation. - -UFL is part of the FEniCS Project. For more information, visit -https://www.fenicsproject.org - -.. image:: https://github.com/FEniCS/ufl/workflows/UFL%20CI/badge.svg - :target: https://github.com/FEniCS/ufl/workflows/UFL%20CI -.. image:: https://github.com/FEniCS/ufl/actions/workflows/spack.yml/badge.svg - :target: https://github.com/FEniCS/ufl/actions/workflows/spack.yml -.. image:: https://coveralls.io/repos/github/FEniCS/ufl/badge.svg?branch=master - :target: https://coveralls.io/github/FEniCS/ufl?branch=master - :alt: Coverage Status -.. image:: https://readthedocs.org/projects/fenics-ufl/badge/?version=latest - :target: https://fenics.readthedocs.io/projects/ufl/en/latest/?badge=latest - :alt: Documentation Status - - -Documentation -============= - -Documentation can be viewed at https://fenics-ufl.readthedocs.org/. - - -License -======= - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program. If not, see . - From d2b4fe69d2f40a2831d749537c7a864bd5bbfb4c Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 15 Nov 2023 13:47:04 +0000 Subject: [PATCH 079/136] use tsfc master on CI (#240) * use tsfc master on CI * another branch name --- .github/workflows/tsfc-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 45752de0c..06b10a13d 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -32,14 +32,14 @@ jobs: with: path: ./tsfc repository: firedrakeproject/tsfc - ref: mscroggs/newfl-legacy2 + ref: master - name: Install tsfc run: | cd tsfc pip install -r requirements-ext.txt pip install git+https://github.com/coneoproject/COFFEE.git#egg=coffee pip install git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat - pip install git+https://github.com/FInAT/FInAT.git@mscroggs/ufl-elements + pip install git+https://github.com/FInAT/FInAT.git#egg=finat pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy pip install .[ci] pip install pytest From 5c08401c8c797eaacc16b9e0e65e6a1e72da5a8b Mon Sep 17 00:00:00 2001 From: Francesco Ballarin Date: Wed, 15 Nov 2023 17:15:12 +0100 Subject: [PATCH 080/136] Update OMPI environment variables in FEniCSx integration test for openmpi v5 (#241) --- .github/workflows/fenicsx-tests.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index b2a47af67..6a8856e47 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -59,11 +59,6 @@ jobs: PETSC_ARCH: linux-gnu-complex128-32 OMPI_ALLOW_RUN_AS_ROOT: 1 OMPI_ALLOW_RUN_AS_ROOT_CONFIRM: 1 - OMPI_MCA_rmaps_base_oversubscribe: 1 - OMPI_MCA_plm: isolated - OMPI_MCA_btl_vader_single_copy_mechanism: none - OMPI_MCA_mpi_yield_when_idle: 1 - OMPI_MCA_hwloc_base_binding_policy: none steps: - uses: actions/checkout@v4 From 3eda4f9dcc6d2d04fd9d4b8e58576b45d60b34fa Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Tue, 21 Nov 2023 16:28:51 +0100 Subject: [PATCH 081/136] Fix license file field. (#244) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b3751e708..9617fef89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project"}] maintainers = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project Steering Council"}] description = "Unified Form Language" readme = "README.rst" -license = {file = "LICENSE"} +license = {file = "COPYING.lesser"} requires-python = ">=3.8.0" dependencies = ["numpy"] From e8d3077b21a3452ff471be383a77de38817b9468 Mon Sep 17 00:00:00 2001 From: JHopeCollins Date: Wed, 22 Nov 2023 16:54:35 +0000 Subject: [PATCH 082/136] FormSum.coefficients should be ordered the same as Form.coefficients (#243) --- ufl/form.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ufl/form.py b/ufl/form.py index f4b546b41..3f85b709d 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -784,8 +784,11 @@ def _analyze_form_arguments(self): for component in self._components: arguments.extend(component.arguments()) coefficients.extend(component.coefficients()) - self._arguments = tuple(set(arguments)) - self._coefficients = tuple(set(coefficients)) + # Define canonical numbering of arguments and coefficients + self._arguments = tuple( + sorted(set(arguments), key=lambda x: x.number())) + self._coefficients = tuple( + sorted(set(coefficients), key=lambda x: x.count())) def _analyze_domains(self): """Analyze which domains can be found in FormSum.""" From 60a1cfc1b8a89c9bf1976d45d86e0334c2bfb70e Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Wed, 29 Nov 2023 16:21:01 +0000 Subject: [PATCH 083/136] Expunge ufl_domain (#246) * Expunge Expr.ufl_domain() * Remove failing assertion * Action: get ufl_domains() from operands --- ufl/action.py | 5 +++-- ufl/algorithms/change_to_reference.py | 3 ++- ufl/algorithms/compute_form_data.py | 6 ++++-- ufl/algorithms/estimate_degrees.py | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ufl/action.py b/ufl/action.py index 5d6004994..1887da201 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -16,6 +16,7 @@ from ufl.differentiation import CoefficientDerivative from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm from ufl.matrix import Matrix +from itertools import chain # --- The Action class represents the action of a numerical object that needs # to be computed at assembly time --- @@ -121,8 +122,8 @@ def _analyze_domains(self): """Analyze which domains can be found in Action.""" from ufl.domain import join_domains - # Collect unique domains - self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) + # Collect domains + self._domains = join_domains(chain.from_iterable(e.ufl_domains() for e in self.ufl_operands)) def equals(self, other): """Check if two Actions are equal.""" diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index c232d3c7b..ed5c22ca2 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -13,6 +13,7 @@ from ufl.core.multiindex import indices from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction +from ufl.domain import extract_unique_domain from ufl.tensors import as_tensor """ @@ -139,7 +140,7 @@ def grad(self, o): f = ReferenceValue(f) # Get domain and create Jacobian inverse object - domain = o.ufl_domain() + domain = extract_unique_domain(o) Jinv = JacobianInverse(domain) if is_cellwise_constant(Jinv): diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index f8c9df4ac..af0ddf68d 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -30,6 +30,7 @@ from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.classes import Coefficient, Form, FunctionSpace, GeometricFacetQuantity from ufl.corealg.traversal import traverse_unique_terminals +from ufl.domain import extract_unique_domain from ufl.utils.sequences import max_degree @@ -180,8 +181,9 @@ def _build_coefficient_replace_map(coefficients, element_mapping=None): # coefficient had a domain, the new one does too. # This should be overhauled with requirement that Expressions # always have a domain. - if f.ufl_domain() is not None: - new_e = FunctionSpace(f.ufl_domain(), new_e) + domain = extract_unique_domain(f) + if domain is not None: + new_e = FunctionSpace(domain, new_e) new_f = Coefficient(new_e, count=i) new_coefficients.append(new_f) replace_map[f] = new_f diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index ccaf64c7e..334041d3f 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -95,7 +95,7 @@ def _reduce_degree(self, v, f): This is used when derivatives are taken. It does not reduce the degree when TensorProduct elements or quadrilateral elements are involved. """ - if isinstance(f, int) and v.ufl_domain().ufl_cell().cellname() not in ["quadrilateral", "hexahedron"]: + if isinstance(f, int) and extract_unique_domain(v).ufl_cell().cellname() not in ["quadrilateral", "hexahedron"]: return max(f - 1, 0) else: return f From 3fc1763197744d50d7b34fa49a4b14897d1c5445 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:07:19 +0000 Subject: [PATCH 084/136] Fix cofunction equality (#247) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names * Update equality inheritance for Cofunctions --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: David A. Ham Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward Co-authored-by: Iglesia Dolci Co-authored-by: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> --- ufl/coefficient.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 584d06c3f..3ff9c34e0 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -116,6 +116,8 @@ class Cofunction(BaseCoefficient, BaseForm): _primal = False _dual = True + __eq__ = BaseForm.__eq__ + def __new__(cls, *args, **kw): """Create a new Cofunction.""" if args[0] and is_primal(args[0]): From 7277fd1bda219853afeb4db0924c0410792efe6b Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 5 Jan 2024 08:44:39 -0600 Subject: [PATCH 085/136] Bug-fix: do not call cell() in exterior_derivative (#248) --- ufl/operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/operators.py b/ufl/operators.py index 9dc5add03..78226d8a1 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -693,7 +693,7 @@ def exterior_derivative(f): except Exception: raise ValueError(f"Unable to determine element from {f}") - gdim = element.cell().geometric_dimension() + gdim = element.cell.geometric_dimension() space = element.sobolev_space if space == sobolevspace.L2: From 5507c896d23be9614a12ffc477bf25fb2f49c3b2 Mon Sep 17 00:00:00 2001 From: Francesco Ballarin Date: Mon, 22 Jan 2024 16:57:26 +0100 Subject: [PATCH 086/136] Drop `fenics-dev@googlegroups.com` in favor of `fenics-steering-council@googlegroups.com` (#250) * Drop fenics-dev@googlegroups.com in favor of fenics-steering-council@googlegroups.com * Be consistent with the naming used in other repos * Change authors field --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9617fef89..468cd0a10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,8 @@ build-backend = "setuptools.build_meta" [project] name = "fenics-ufl" version = "2023.3.0.dev0" -authors = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project"}] -maintainers = [{email="fenics-dev@googlegroups.com"}, {name="FEniCS Project Steering Council"}] +authors = [{name="UFL contributors"}] +maintainers = [{email="fenics-steering-council@googlegroups.com"}, {name="FEniCS Steering Council"}] description = "Unified Form Language" readme = "README.rst" license = {file = "COPYING.lesser"} From 929a679c4ce8e9703c2f194b3ddc75e13e815fc6 Mon Sep 17 00:00:00 2001 From: Francesco Ballarin Date: Thu, 25 Jan 2024 08:15:46 +0100 Subject: [PATCH 087/136] Bump actions/{download-artifact,upload-artifact} to v4 (#251) --- .github/workflows/build-wheels.yml | 4 ++-- .github/workflows/pythonapp.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index f1d86945d..7d0847457 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -50,7 +50,7 @@ jobs: - name: Build sdist and wheel run: python -m build . - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: path: dist/* @@ -59,7 +59,7 @@ jobs: needs: [build] runs-on: ubuntu-latest steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: artifact path: dist diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 032bec747..1d8bd0461 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -53,7 +53,7 @@ jobs: make html - name: Upload documentation artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: doc-${{ matrix.os }}-${{ matrix.python-version }} path: doc/sphinx/build/html/ From 7b85c8e213bba123aa152d1b3e1065ced5c09fcf Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Thu, 1 Feb 2024 11:13:25 +0000 Subject: [PATCH 088/136] Add Python 3.12 test coverage (#253) * Add Python 3.12 test coverage * Sort import --- .github/workflows/fenicsx-tests.yml | 4 ++-- .github/workflows/pythonapp.yml | 6 +++--- ufl/action.py | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 6a8856e47..6363a55bf 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -18,9 +18,9 @@ jobs: PETSC_ARCH: linux-gnu-real-64 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.9 diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 1d8bd0461..bef53ac2e 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -20,12 +20,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Lint with flake8 diff --git a/ufl/action.py b/ufl/action.py index 1887da201..2401e05ea 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -7,6 +7,8 @@ # # Modified by Nacime Bouziani, 2021-2022. +from itertools import chain + from ufl.algebra import Sum from ufl.argument import Argument, Coargument from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction @@ -16,7 +18,6 @@ from ufl.differentiation import CoefficientDerivative from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm from ufl.matrix import Matrix -from itertools import chain # --- The Action class represents the action of a numerical object that needs # to be computed at assembly time --- From b47c9e07819512ef252d7b5aab7ddc29d4bbb005 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Thu, 1 Feb 2024 11:44:03 +0000 Subject: [PATCH 089/136] Github actions update (#254) * Add Python 3.12 test coverage * Sort import * Update an action --- .github/workflows/pythonapp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index bef53ac2e..e6f23ec0b 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -62,7 +62,7 @@ jobs: - name: Checkout FEniCS/docs if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: "FEniCS/docs" path: "docs" From 109aa7d35ddaa7b1cccbf069831266c2cbe10856 Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Fri, 2 Feb 2024 12:03:58 +0000 Subject: [PATCH 090/136] Emmarothwell1/restricted function space (#252) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names * adding label to functionspace * pass label through to dualspace too * removed label from __init__ * change default label to "" * apply pr suggestions * Update ufl/functionspace.py --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: nbouziani Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward Co-authored-by: Iglesia Dolci Co-authored-by: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> Co-authored-by: Josh Hope-Collins Co-authored-by: JHopeCollins Co-authored-by: Emma Rothwell Co-authored-by: Matthew Scroggs --- ufl/functionspace.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 17de9d1a8..756c16aa0 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -36,7 +36,7 @@ def ufl_sub_spaces(self): class BaseFunctionSpace(AbstractFunctionSpace, UFLObject): """Base function space.""" - def __init__(self, domain, element): + def __init__(self, domain, element, label=""): """Initialise.""" if domain is None: # DOLFIN hack @@ -50,11 +50,15 @@ def __init__(self, domain, element): else: if element.cell != domain_cell: raise ValueError("Non-matching cell of finite element and domain.") - AbstractFunctionSpace.__init__(self) + self._label = label self._ufl_domain = domain self._ufl_element = element + def label(self): + """Return label of boundary domains to differentiate restricted and unrestricted.""" + return self._label + def ufl_sub_spaces(self): """Return ufl sub spaces.""" return () @@ -88,7 +92,7 @@ def _ufl_hash_data_(self, name=None): edata = None else: edata = element._ufl_hash_data_() - return (name, ddata, edata) + return (name, ddata, edata, self.label()) def _ufl_signature_data_(self, renumbering, name=None): """UFL signature data.""" @@ -103,7 +107,7 @@ def _ufl_signature_data_(self, renumbering, name=None): edata = None else: edata = element._ufl_signature_data_() - return (name, ddata, edata) + return (name, ddata, edata, self.label()) def __repr__(self): """Representation.""" @@ -118,7 +122,7 @@ class FunctionSpace(BaseFunctionSpace, UFLObject): def dual(self): """Get the dual of the space.""" - return DualSpace(self._ufl_domain, self._ufl_element) + return DualSpace(self._ufl_domain, self._ufl_element, label=self.label()) def _ufl_hash_data_(self): """UFL hash data.""" @@ -143,13 +147,13 @@ class DualSpace(BaseFunctionSpace, UFLObject): _primal = False _dual = True - def __init__(self, domain, element): + def __init__(self, domain, element, label=""): """Initialise.""" - BaseFunctionSpace.__init__(self, domain, element) + BaseFunctionSpace.__init__(self, domain, element, label) def dual(self): """Get the dual of the space.""" - return FunctionSpace(self._ufl_domain, self._ufl_element) + return FunctionSpace(self._ufl_domain, self._ufl_element, label=self.label()) def _ufl_hash_data_(self): """UFL hash data.""" From f3b9865104cdee487cc3e76b037f0a21b54419d7 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Sat, 10 Feb 2024 10:49:23 +0000 Subject: [PATCH 091/136] Remove geometric dimension from cell, move value_shape to FunctionSpace (#249) * remove file containing only xtests * remove geometric dimension from cell * correct typo in test * basix branch * branches and one (domain) * remove more `cell.geometric_dimension()`s * dolfinx branch * tsfc branch * branch * allow passing in physical value size to custom element * Revert "allow passing in physical value size to custom element" This reverts commit cdca5a09e25a8671db31732df68798334de08c06. * move value_shape and value_size to functionspace * flake8 * return () if reference_value_size is () --- .github/workflows/fenicsx-tests.yml | 10 +- .github/workflows/tsfc-tests.yml | 4 +- demo/HyperElasticity.py | 2 +- test/test_apply_function_pullbacks.py | 3 +- test/test_automatic_differentiation.py | 2 +- test/test_cell.py | 1 - test/test_classcoverage.py | 2 +- test/test_derivative.py | 26 ++-- test/test_domains.py | 19 ++- test/test_evaluate.py | 4 +- test/test_grad.py | 133 --------------------- test/test_indices.py | 2 +- test/test_measures.py | 3 +- test/test_new_ad.py | 6 +- test/test_piecewise_checks.py | 28 ++--- test/test_reference_shapes.py | 28 +++-- test/test_signature.py | 24 ++-- test/test_split.py | 2 +- test/test_strip_forms.py | 4 +- ufl/algorithms/apply_function_pullbacks.py | 5 +- ufl/argument.py | 2 +- ufl/cell.py | 103 +++++----------- ufl/coefficient.py | 2 +- ufl/domain.py | 8 +- ufl/finiteelement.py | 10 -- ufl/functionspace.py | 13 ++ ufl/geometry.py | 15 ++- ufl/objects.py | 20 ++-- ufl/operators.py | 4 +- ufl/pullback.py | 93 ++++++++------ ufl/split_functions.py | 14 ++- 31 files changed, 224 insertions(+), 368 deletions(-) delete mode 100755 test/test_grad.py diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 6363a55bf..aa9f3e702 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -34,14 +34,14 @@ jobs: - name: Install Basix run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git + python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/remove-gdim - name: Clone FFCx uses: actions/checkout@v4 with: path: ./ffcx repository: FEniCS/ffcx - ref: main + ref: mscroggs/gdim - name: Install FFCx run: | @@ -69,15 +69,15 @@ jobs: - name: Install Basix and FFCx run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/remove-gdim + python3 -m pip install git+https://github.com/FEniCS/ffcx.git@mscroggs/gdim - name: Clone DOLFINx uses: actions/checkout@v4 with: path: ./dolfinx repository: FEniCS/dolfinx - ref: main + ref: mscroggs/gdim - name: Install DOLFINx run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 06b10a13d..eea8c3860 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -32,14 +32,14 @@ jobs: with: path: ./tsfc repository: firedrakeproject/tsfc - ref: master + ref: mscroggs/gdim - name: Install tsfc run: | cd tsfc pip install -r requirements-ext.txt pip install git+https://github.com/coneoproject/COFFEE.git#egg=coffee pip install git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat - pip install git+https://github.com/FInAT/FInAT.git#egg=finat + pip install git+https://github.com/FInAT/FInAT.git@mscroggs/gdim#egg=finat pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy pip install .[ci] pip install pytest diff --git a/demo/HyperElasticity.py b/demo/HyperElasticity.py index abbc8314c..cdfd58488 100644 --- a/demo/HyperElasticity.py +++ b/demo/HyperElasticity.py @@ -13,7 +13,7 @@ # Cell and its properties cell = tetrahedron domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) -d = cell.geometric_dimension() +d = 3 N = FacetNormal(domain) x = SpatialCoordinate(domain) diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index 3cb73e898..872cfdd88 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -33,8 +33,7 @@ def check_single_function_pullback(g, mappings): def test_apply_single_function_pullbacks_triangle3d(): - triangle3d = Cell("triangle", geometric_dimension=3) - cell = triangle3d + cell = Cell("triangle") domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) UL2 = FiniteElement("Discontinuous Lagrange", cell, 1, (), l2_piola, L2) diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index 37bfd02f1..a1d080c76 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -26,7 +26,7 @@ class ExpressionCollection(object): def __init__(self, cell): self.cell = cell - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) x = SpatialCoordinate(domain) diff --git a/test/test_cell.py b/test/test_cell.py index 5d9472a1d..dc26052a4 100644 --- a/test/test_cell.py +++ b/test/test_cell.py @@ -85,4 +85,3 @@ def test_tensorproductcell(): cell = orig.reconstruct() assert cell.sub_cells() == orig.sub_cells() assert cell.topological_dimension() == orig.topological_dimension() - assert cell.geometric_dimension() == orig.geometric_dimension() diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index 9a61261e3..caeff1b91 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -106,7 +106,7 @@ def testAll(self): # --- Elements: cell = triangle - dim = cell.geometric_dimension() + dim = 2 e0 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) e1 = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) diff --git a/test/test_derivative.py b/test/test_derivative.py index 63528d97f..fc8bfdd76 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -47,14 +47,16 @@ def make_value(c): amapping = dict((c, make_value(c)) for c in chain(ad.original_form.coefficients(), ad.original_form.arguments())) bmapping = dict((c, make_value(c)) for c in chain(bd.original_form.coefficients(), bd.original_form.arguments())) - acell = extract_unique_domain(actual).ufl_cell() - bcell = extract_unique_domain(expected).ufl_cell() + adomain = extract_unique_domain(actual) + bdomain = extract_unique_domain(expected) + acell = adomain.ufl_cell() + bcell = bdomain.ufl_cell() assert acell == bcell - if acell.geometric_dimension() == 1: + if adomain.geometric_dimension() == 1: x = (0.3,) - elif acell.geometric_dimension() == 2: + elif adomain.geometric_dimension() == 2: x = (0.3, 0.4) - elif acell.geometric_dimension() == 3: + elif adomain.geometric_dimension() == 3: x = (0.3, 0.4, 0.5) av = a(x, amapping) bv = b(x, bmapping) @@ -464,7 +466,7 @@ def test_multiple_coefficient_derivative(self): def test_indexed_coefficient_derivative(self): cell = triangle - ident = Identity(cell.geometric_dimension()) + ident = Identity(2) V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) W = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) @@ -522,21 +524,21 @@ def test_segregated_derivative_of_convection(self): Lv = {} Lvu = {} - for a in range(cell.geometric_dimension()): + for a in range(3): Lv[a] = derivative(L, v[a], dv) - for b in range(cell.geometric_dimension()): + for b in range(3): Lvu[a, b] = derivative(Lv[a], u[b], du) - for a in range(cell.geometric_dimension()): - for b in range(cell.geometric_dimension()): + for a in range(3): + for b in range(3): form = Lvu[a, b]*dx fd = compute_form_data(form) pf = fd.preprocessed_form expand_indices(pf) k = Index() - for a in range(cell.geometric_dimension()): - for b in range(cell.geometric_dimension()): + for a in range(3): + for b in range(3): actual = Lvu[a, b] expected = du*u[a].dx(b)*dv + u[k]*du.dx(k)*dv assertEqualBySampling(actual, expected) diff --git a/test/test_domains.py b/test/test_domains.py index cb9df4ca0..112a1613b 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -18,13 +18,13 @@ def test_construct_domains_from_cells(): for cell in all_cells: - d = cell.geometric_dimension() + d = cell.topological_dimension() Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) def test_construct_domains_with_names(): for cell in all_cells: - d = cell.geometric_dimension() + d = cell.topological_dimension() e = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) D2 = Mesh(e, ufl_id=2) D3 = Mesh(e, ufl_id=3) @@ -36,16 +36,15 @@ def test_construct_domains_with_names(): def test_domains_sort_by_name(): # This ordering is rather arbitrary, but at least this shows sorting is # working - domains1 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + domains1 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1), ufl_id=hash(cell.cellname())) for cell in all_cells] - domains2 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + domains2 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1), ufl_id=hash(cell.cellname())) for cell in sorted(all_cells)] - sdomains = sorted(domains1, key=lambda D: (D.geometric_dimension(), - D.topological_dimension(), + sdomains = sorted(domains1, key=lambda D: (D.topological_dimension(), D.ufl_cell(), D.ufl_id())) assert sdomains != domains1 @@ -115,7 +114,7 @@ def test_join_domains(): from ufl.domain import join_domains mesh7 = MockMesh(7) mesh8 = MockMesh(8) - triangle3 = Cell("triangle", geometric_dimension=3) + triangle = Cell("triangle") xa = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) xb = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) @@ -163,11 +162,11 @@ def test_join_domains(): with pytest.raises(BaseException): join_domains([ Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1))]) + Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1))]) with pytest.raises(BaseException): join_domains([ Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), - Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)]) + Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)]) # Cargo and mesh ids must match with pytest.raises(BaseException): Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh8) @@ -181,7 +180,7 @@ def test_join_domains(): Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), None, Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), ufl_id=8)])) assert None not in join_domains([ - Mesh(FiniteElement("Lagrange", triangle3, 1, (3, ), identity_pullback, H1), ufl_id=7), None, + Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1), ufl_id=7), None, Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1), ufl_id=8)]) diff --git a/test/test_evaluate.py b/test/test_evaluate.py index 964740aac..55ca15aeb 100755 --- a/test/test_evaluate.py +++ b/test/test_evaluate.py @@ -28,7 +28,7 @@ def testZero(): def testIdentity(): cell = triangle - ident = Identity(cell.geometric_dimension()) + ident = Identity(cell.topological_dimension()) s = 123 * ident[0, 0] e = s((5, 7)) @@ -118,7 +118,7 @@ def testIndexSum2(): cell = triangle domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) x = SpatialCoordinate(domain) - ident = Identity(cell.geometric_dimension()) + ident = Identity(cell.topological_dimension()) i, j = indices(2) s = (x[i] * x[j]) * ident[i, j] e = s((5, 7)) diff --git a/test/test_grad.py b/test/test_grad.py deleted file mode 100755 index 21b269e2f..000000000 --- a/test/test_grad.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Test use of grad in various situations.""" - -from ufl import (Coefficient, Constant, TensorConstant, VectorConstant, div, dx, grad, indices, inner, interval, - tetrahedron, triangle) -from ufl.algorithms import compute_form_data -from ufl.finiteelement import FiniteElement -from ufl.pullback import identity_pullback -from ufl.sobolevspace import H1 - - -def xtest_grad_div_curl_properties_in_1D(self): - _test_grad_div_curl_properties(self, interval) - - -def xtest_grad_div_curl_properties_in_2D(self): - _test_grad_div_curl_properties(self, triangle) - - -def xtest_grad_div_curl_properties_in_3D(self): - _test_grad_div_curl_properties(self, tetrahedron) - - -def _test_grad_div_curl_properties(self, cell): - d = cell.geometric_dimension() - - S = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - V = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) - T = FiniteElement("Lagrange", cell, 1, (d, d), identity_pullback, H1) - - cs = Constant(cell) - cv = VectorConstant(cell) - ct = TensorConstant(cell) - - s = Coefficient(S) - v = Coefficient(V) - t = Coefficient(T) - - def eval_s(x, derivatives=()): - return sum(derivatives) - - def eval_v(x, derivatives=()): - return tuple(float(k)+sum(derivatives) for k in range(d)) - - def eval_t(x, derivatives=()): - return tuple(tuple(float(i*j)+sum(derivatives) - for i in range(d)) - for j in range(d)) - - mapping = {cs: eval_s, s: eval_s, - cv: eval_v, v: eval_v, - ct: eval_t, t: eval_t, } - x = tuple(1.0+float(k) for k in range(d)) - - assert s.ufl_shape == () - assert v.ufl_shape == (d,) - assert t.ufl_shape == (d, d) - - assert cs.ufl_shape == () - assert cv.ufl_shape == (d,) - assert ct.ufl_shape == (d, d) - - self.assertEqual(s(x, mapping=mapping), eval_s(x)) - self.assertEqual(v(x, mapping=mapping), eval_v(x)) - self.assertEqual(t(x, mapping=mapping), eval_t(x)) - - assert grad(s).ufl_shape == (d,) - assert grad(v).ufl_shape == (d, d) - assert grad(t).ufl_shape == (d, d, d) - - assert grad(cs).ufl_shape == (d,) - assert grad(cv).ufl_shape == (d, d) - assert grad(ct).ufl_shape == (d, d, d) - - self.assertEqual(grad(s)[0](x, mapping=mapping), eval_s(x, (0,))) - self.assertEqual(grad(v)[d-1, d-1](x, mapping=mapping), - eval_v(x, derivatives=(d-1,))[d-1]) - self.assertEqual(grad(t)[d-1, d-1, d-1](x, mapping=mapping), - eval_t(x, derivatives=(d-1,))[d-1][d-1]) - - assert div(grad(cs)).ufl_shape == () - assert div(grad(cv)).ufl_shape == (d,) - assert div(grad(ct)).ufl_shape == (d, d) - - assert s.dx(0).ufl_shape == () - assert v.dx(0).ufl_shape == (d,) - assert t.dx(0).ufl_shape == (d, d) - - assert s.dx(0 == 0).ufl_shape, () - assert v.dx(0 == 0).ufl_shape, (d,) - assert t.dx(0 == 0).ufl_shape, (d, d) - - i, j = indices(2) - assert s.dx(i).ufl_shape == () - assert v.dx(i).ufl_shape == (d,) - assert t.dx(i).ufl_shape == (d, d) - - assert s.dx(i).ufl_free_indices == (i.count(),) - assert v.dx(i).ufl_free_indices == (i.count(),) - assert t.dx(i).ufl_free_indices == (i.count(),) - - self.assertEqual(s.dx(i, j).ufl_shape, ()) - self.assertEqual(v.dx(i, j).ufl_shape, (d,)) - self.assertEqual(t.dx(i, j).ufl_shape, (d, d)) - - self.assertTrue(s.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) - self.assertTrue(v.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) - self.assertTrue(t.dx(i, j).ufl_free_indices == tuple(sorted([i.count(), j.count()]))) - - a0 = s.dx(0)*dx - a1 = s.dx(0)**2*dx - a2 = v.dx(0)**2*dx - a3 = t.dx(0)**2*dx - - a4 = inner(grad(s), grad(s))*dx - a5 = inner(grad(v), grad(v))*dx - a6 = inner(grad(t), grad(t))*dx - - a7 = inner(div(grad(s)), s)*dx - a8 = inner(div(grad(v)), v)*dx - a9 = inner(div(grad(t)), t)*dx - - compute_form_data(a0) - compute_form_data(a1) - compute_form_data(a2) - compute_form_data(a3) - - compute_form_data(a4) - compute_form_data(a5) - compute_form_data(a6) - - compute_form_data(a7) - compute_form_data(a8) - compute_form_data(a9) diff --git a/test/test_indices.py b/test/test_indices.py index 49cf16a1a..52ef28cf1 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -244,7 +244,7 @@ def test_spatial_derivative(self): v = TestFunction(space) u = TrialFunction(space) i, j, k, l = indices(4) # noqa: E741 - d = cell.geometric_dimension() + d = 2 a = v[i].dx(i) self.assertSameIndices(a, ()) diff --git a/test/test_measures.py b/test/test_measures.py index 2a31d51e1..539151672 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -68,13 +68,12 @@ def test_foo(): # Define a manifold domain, allows checking gdim/tdim mixup errors gdim = 3 tdim = 2 - cell = Cell("triangle", gdim) + cell = Cell("triangle") mymesh = MockMesh(9) mydomain = Mesh(FiniteElement("Lagrange", cell, 1, (gdim, ), identity_pullback, H1), ufl_id=9, cargo=mymesh) assert cell.topological_dimension() == tdim - assert cell.geometric_dimension() == gdim assert cell.cellname() == "triangle" assert mydomain.topological_dimension() == tdim assert mydomain.geometric_dimension() == gdim diff --git a/test/test_new_ad.py b/test/test_new_ad.py index be6b6d403..7fc69e850 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -17,7 +17,7 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): cell = triangle - d = cell.geometric_dimension() + d = 2 V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) @@ -63,7 +63,7 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): def test_literal_derivatives_are_zero(): cell = triangle - d = cell.geometric_dimension() + d = 2 # Literals one = as_ufl(1) @@ -110,7 +110,7 @@ def test_literal_derivatives_are_zero(): def test_grad_ruleset(): cell = triangle - d = cell.geometric_dimension() + d = 2 V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index d5d419d37..f0b1955f6 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -22,14 +22,14 @@ def get_domains(): tetrahedron, hexahedron, ] - return [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + return [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1)) for cell in all_cells] def get_nonlinear(): domains_with_quadratic_coordinates = [] for D in get_domains(): - V = FiniteElement("Lagrange", D.ufl_cell(), 2, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 2, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) domains_with_quadratic_coordinates.append(E) @@ -53,7 +53,7 @@ def domains(request): domains = get_domains() domains_with_linear_coordinates = [] for D in domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) domains_with_linear_coordinates.append(E) @@ -69,13 +69,13 @@ def affine_domains(request): triangle, tetrahedron, ] - affine_domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + affine_domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1)) for cell in affine_cells] affine_domains_with_linear_coordinates = [] for D in affine_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) affine_domains_with_linear_coordinates.append(E) @@ -94,11 +94,11 @@ def affine_facet_domains(request): tetrahedron, ] affine_facet_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.geometric_dimension(), ), + "Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1)) for cell in affine_facet_cells] affine_facet_domains_with_linear_coordinates = [] for D in affine_facet_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) affine_facet_domains_with_linear_coordinates.append(E) @@ -116,11 +116,11 @@ def nonaffine_domains(request): hexahedron, ] nonaffine_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.geometric_dimension(), ), + "Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1)) for cell in nonaffine_cells] nonaffine_domains_with_linear_coordinates = [] for D in nonaffine_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) nonaffine_domains_with_linear_coordinates.append(E) @@ -137,11 +137,11 @@ def nonaffine_facet_domains(request): hexahedron, ] nonaffine_facet_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.geometric_dimension(), ), + "Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1)) for cell in nonaffine_facet_cells] nonaffine_facet_domains_with_linear_coordinates = [] for D in nonaffine_facet_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().geometric_dimension(), ), + V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), identity_pullback, H1) E = Mesh(V) nonaffine_facet_domains_with_linear_coordinates.append(E) @@ -177,7 +177,7 @@ def test_coordinates_never_cellwise_constant(domains): def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: - domains = Mesh(FiniteElement("Lagrange", Cell("vertex", 3), 1, (3, ), identity_pullback, H1)) + domains = Mesh(FiniteElement("Lagrange", Cell("vertex"), 1, (3, ), identity_pullback, H1)) assert domains.ufl_cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert is_cellwise_constant(e) @@ -235,7 +235,7 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): assert is_cellwise_constant(e) V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 0, (), identity_pullback, L2) - d = domains_not_linear.ufl_cell().geometric_dimension() + d = domains_not_linear.ufl_cell().topological_dimension() domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) e = Coefficient(space) @@ -256,7 +256,7 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 1, (), identity_pullback, L2) - d = domains_not_linear.ufl_cell().geometric_dimension() + d = domains_not_linear.ufl_cell().topological_dimension() domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) e = Coefficient(space) diff --git a/test/test_reference_shapes.py b/test/test_reference_shapes.py index 12c5028be..69c0a2983 100755 --- a/test/test_reference_shapes.py +++ b/test/test_reference_shapes.py @@ -1,40 +1,48 @@ -from ufl import Cell +from ufl import Cell, Mesh from ufl.finiteelement import FiniteElement, MixedElement, SymmetricElement +from ufl.functionspace import FunctionSpace from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback from ufl.sobolevspace import H1, HCurl, HDiv def test_reference_shapes(): # show_elements() - - cell = Cell("triangle", 3) + cell = Cell("triangle") + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) V = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) - assert V.value_shape == (3,) + Vspace = FunctionSpace(domain, V) + assert Vspace.value_shape == (3,) assert V.reference_value_shape == (2,) U = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) - assert U.value_shape == (3,) + Uspace = FunctionSpace(domain, U) + assert Uspace.value_shape == (3,) assert U.reference_value_shape == (2,) W = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - assert W.value_shape == () + Wspace = FunctionSpace(domain, W) + assert Wspace.value_shape == () assert W.reference_value_shape == () Q = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) - assert Q.value_shape == (3,) + Qspace = FunctionSpace(domain, Q) + assert Qspace.value_shape == (3,) assert Q.reference_value_shape == (3,) T = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) - assert T.value_shape == (3, 3) + Tspace = FunctionSpace(domain, T) + assert Tspace.value_shape == (3, 3) assert T.reference_value_shape == (3, 3) S = SymmetricElement( {(0, 0): 0, (1, 0): 1, (2, 0): 2, (0, 1): 1, (1, 1): 3, (2, 1): 4, (0, 2): 2, (1, 2): 4, (2, 2): 5}, [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)]) - assert S.value_shape == (3, 3) + Sspace = FunctionSpace(domain, S) + assert Sspace.value_shape == (3, 3) assert S.reference_value_shape == (6,) M = MixedElement([V, U, W]) - assert M.value_shape == (7,) + Mspace = FunctionSpace(domain, M) + assert Mspace.value_shape == (7,) assert M.reference_value_shape == (5,) diff --git a/test/test_signature.py b/test/test_signature.py index 4e270d500..660758fe7 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -21,7 +21,7 @@ def domain_numbering(*cells): renumbering = {} for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) renumbering[domain] = i return renumbering @@ -88,7 +88,7 @@ def forms(): i, j = indices(2) cells = (triangle, tetrahedron) for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) x = SpatialCoordinate(domain) @@ -133,7 +133,7 @@ def test_terminal_hashdata_depends_on_form_argument_properties(self): def forms(): for rep in range(nreps): for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for degree in degrees: for family, sobolev in families: @@ -193,7 +193,7 @@ def test_terminal_hashdata_does_not_depend_on_coefficient_count_values_only_orde def forms(): for rep in range(nreps): for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in counts: V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) @@ -232,7 +232,7 @@ def test_terminal_hashdata_does_depend_on_argument_number_values(self): def forms(): for rep in range(nreps): for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in counts: V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) @@ -262,7 +262,7 @@ def test_domain_signature_data_does_not_depend_on_domain_label_value(self): s1s = set() s2s = set() for i, cell in enumerate(cells): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) d0 = Mesh(domain) d1 = Mesh(domain, ufl_id=1) @@ -285,7 +285,7 @@ def test_terminal_hashdata_does_not_depend_on_domain_label_value(self): hashes = set() ufl_ids = [1, 2] cells = [triangle, quadrilateral] - domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), identity_pullback, H1), ufl_id=ufl_id) for cell in cells for ufl_id in ufl_ids] nreps = 2 @@ -436,7 +436,7 @@ def test_signature_is_affected_by_element_properties(self): def forms(): for family, sobolev in (("Lagrange", H1), ("Discontinuous Lagrange", L2)): for cell in (triangle, tetrahedron, quadrilateral): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) for degree in (1, 2): V = FiniteElement(family, cell, degree, (), identity_pullback, sobolev) @@ -454,7 +454,7 @@ def forms(): def test_signature_is_affected_by_domains(self): def forms(): for cell in (triangle, tetrahedron): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) for di in (1, 2): for dj in (1, 2): @@ -470,10 +470,10 @@ def forms(): def test_signature_of_forms_with_diff(self): def forms(): for i, cell in enumerate([triangle, tetrahedron]): - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) for k in (1, 2, 3): - d = cell.geometric_dimension() + d = cell.topological_dimension() V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) W = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) v_space = FunctionSpace(domain, V) @@ -508,7 +508,7 @@ def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self def test_signature_of_forms_change_with_operators(self): def forms(): for cell in (triangle, tetrahedron): - d = cell.geometric_dimension() + d = cell.topological_dimension() V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) space = FunctionSpace(domain, V) diff --git a/test/test_split.py b/test/test_split.py index 7a16e4a84..33b0a5885 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -9,7 +9,7 @@ def test_split(self): cell = triangle - d = cell.geometric_dimension() + d = cell.topological_dimension() domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) f = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) v = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1, diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index 27e75869d..b58eea904 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -51,7 +51,7 @@ def test_strip_form_arguments_strips_data_refs(): assert sys.getrefcount(const_data) == MIN_REF_COUNT cell = triangle - domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1), data=mesh_data) element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) @@ -89,7 +89,7 @@ def test_strip_form_arguments_does_not_change_form(): const_data = object() cell = triangle - domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (cell.geometric_dimension(), ), + domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1), data=mesh_data) element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index a8c1b8acf..44b0e86ba 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -30,6 +30,7 @@ def form_argument(self, o): # Represent 0-derivatives of form arguments on reference # element r = ReferenceValue(o) + space = o.ufl_function_space() element = o.ufl_element() if r.ufl_shape != element.reference_value_shape: @@ -37,8 +38,8 @@ def form_argument(self, o): f"Expecting reference space expression with shape '{element.reference_value_shape}', " f"got '{r.ufl_shape}'") f = element.pullback.apply(r) - if f.ufl_shape != element.value_shape: - raise ValueError(f"Expecting pulled back expression with shape '{element.value_shape}', " + if f.ufl_shape != space.value_shape: + raise ValueError(f"Expecting pulled back expression with shape '{space.value_shape}', " f"got '{f.ufl_shape}'") assert f.ufl_shape == o.ufl_shape diff --git a/ufl/argument.py b/ufl/argument.py index 81b94b9ec..804615b07 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -45,7 +45,7 @@ def __init__(self, function_space, number, part=None): raise ValueError("Expecting a FunctionSpace.") self._ufl_function_space = function_space - self._ufl_shape = function_space.ufl_element().value_shape + self._ufl_shape = function_space.value_shape if not isinstance(number, numbers.Integral): raise ValueError(f"Expecting an int for number, not {number}") diff --git a/ufl/cell.py b/ufl/cell.py index e7df446fe..3eebad7dd 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -26,10 +26,6 @@ class AbstractCell(UFLObject): def topological_dimension(self) -> int: """Return the dimension of the topology of this cell.""" - @abstractmethod - def geometric_dimension(self) -> int: - """Return the dimension of the geometry of this cell.""" - @abstractmethod def is_simplex(self) -> bool: """Return True if this is a simplex cell.""" @@ -65,8 +61,8 @@ def reconstruct(self, **kwargs: typing.Any) -> Cell: def __lt__(self, other: AbstractCell) -> bool: """Define an arbitrarily chosen but fixed sort order for all cells.""" if type(self) is type(other): - s = (self.geometric_dimension(), self.topological_dimension()) - o = (other.geometric_dimension(), other.topological_dimension()) + s = self.topological_dimension() + o = other.topological_dimension() if s != o: return s < o return self._lt(other) @@ -206,15 +202,14 @@ def peak_types(self) -> typing.Tuple[AbstractCell, ...]: class Cell(AbstractCell): """Representation of a named finite element cell with known structure.""" - __slots__ = ("_cellname", "_tdim", "_gdim", "_num_cell_entities", "_sub_entity_types", + __slots__ = ("_cellname", "_tdim", "_num_cell_entities", "_sub_entity_types", "_sub_entities", "_sub_entity_types") - def __init__(self, cellname: str, geometric_dimension: typing.Optional[int] = None): + def __init__(self, cellname: str): """Initialise. Args: cellname: Name of the cell - geometric_dimension: Geometric dimension """ if cellname not in _sub_entity_celltypes: raise ValueError(f"Unsupported cell type: {cellname}") @@ -223,30 +218,21 @@ def __init__(self, cellname: str, geometric_dimension: typing.Optional[int] = No self._cellname = cellname self._tdim = len(self._sub_entity_celltypes) - 1 - self._gdim = self._tdim if geometric_dimension is None else geometric_dimension self._num_cell_entities = [len(i) for i in self._sub_entity_celltypes] - self._sub_entities = [tuple(Cell(t, self._gdim) for t in se_types) + self._sub_entities = [tuple(Cell(t) for t in se_types) for se_types in self._sub_entity_celltypes[:-1]] self._sub_entity_types = [tuple(set(i)) for i in self._sub_entities] self._sub_entities.append((weakref.proxy(self), )) self._sub_entity_types.append((weakref.proxy(self), )) - if not isinstance(self._gdim, numbers.Integral): - raise ValueError("Expecting integer geometric_dimension.") if not isinstance(self._tdim, numbers.Integral): raise ValueError("Expecting integer topological_dimension.") - if self._tdim > self._gdim: - raise ValueError("Topological dimension cannot be larger than geometric dimension.") def topological_dimension(self) -> int: """Return the dimension of the topology of this cell.""" return self._tdim - def geometric_dimension(self) -> int: - """Return the dimension of the geometry of this cell.""" - return self._gdim - def is_simplex(self) -> bool: """Return True if this is a simplex cell.""" return self._cellname in ["vertex", "interval", "triangle", "tetrahedron"] @@ -291,56 +277,40 @@ def cellname(self) -> str: def __str__(self) -> str: """Format as a string.""" - if self._gdim == self._tdim: - return self._cellname - else: - return f"{self._cellname}{self._gdim}D" + return self._cellname def __repr__(self) -> str: """Representation.""" - if self._gdim == self._tdim: - return self._cellname - else: - return f"Cell({self._cellname}, {self._gdim})" + return self._cellname def _ufl_hash_data_(self) -> typing.Hashable: """UFL hash data.""" - return (self._cellname, self._gdim) + return (self._cellname, ) def reconstruct(self, **kwargs: typing.Any) -> Cell: """Reconstruct this cell, overwriting properties by those in kwargs.""" - gdim = self._gdim for key, value in kwargs.items(): - if key == "geometric_dimension": - gdim = value - else: - raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") - return Cell(self._cellname, geometric_dimension=gdim) + raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") + return Cell(self._cellname) class TensorProductCell(AbstractCell): """Tensor product cell.""" - __slots__ = ("_cells", "_tdim", "_gdim") + __slots__ = ("_cells", "_tdim") - def __init__(self, *cells: Cell, geometric_dimension: typing.Optional[int] = None): + def __init__(self, *cells: Cell): """Initialise. Args: cells: Cells to take the tensor product of - geometric_dimension: Geometric dimension """ self._cells = tuple(as_cell(cell) for cell in cells) self._tdim = sum([cell.topological_dimension() for cell in self._cells]) - self._gdim = self._tdim if geometric_dimension is None else geometric_dimension - if not isinstance(self._gdim, numbers.Integral): - raise ValueError("Expecting integer geometric_dimension.") if not isinstance(self._tdim, numbers.Integral): raise ValueError("Expecting integer topological_dimension.") - if self._tdim > self._gdim: - raise ValueError("Topological dimension cannot be larger than geometric dimension.") def sub_cells(self) -> typing.List[AbstractCell]: """Return list of cell factors.""" @@ -350,10 +320,6 @@ def topological_dimension(self) -> int: """Return the dimension of the topology of this cell.""" return self._tdim - def geometric_dimension(self) -> int: - """Return the dimension of the geometry of this cell.""" - return self._gdim - def is_simplex(self) -> bool: """Return True if this is a simplex cell.""" if len(self._cells) == 1: @@ -387,7 +353,7 @@ def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]: if dim < 0 or dim > self._tdim: return [] if dim == 0: - return [Cell("vertex", self._gdim) for i in range(self.num_sub_entities(0))] + return [Cell("vertex") for i in range(self.num_sub_entities(0))] if dim == self._tdim: return [self] raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.") @@ -397,7 +363,7 @@ def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]: if dim < 0 or dim > self._tdim: return [] if dim == 0: - return [Cell("vertex", self._gdim)] + return [Cell("vertex")] if dim == self._tdim: return [self] raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.") @@ -411,12 +377,7 @@ def cellname(self) -> str: def __str__(self) -> str: """Format as a string.""" - s = "TensorProductCell(" - s += ", ".join(f"{c!r}" for c in self._cells) - if self._tdim != self._gdim: - s += f", geometric_dimension={self._gdim}" - s += ")" - return s + return "TensorProductCell(" + ", ".join(f"{c!r}" for c in self._cells) + ")" def __repr__(self) -> str: """Representation.""" @@ -424,46 +385,42 @@ def __repr__(self) -> str: def _ufl_hash_data_(self) -> typing.Hashable: """UFL hash data.""" - return tuple(c._ufl_hash_data_() for c in self._cells) + (self._gdim,) + return tuple(c._ufl_hash_data_() for c in self._cells) def reconstruct(self, **kwargs: typing.Any) -> Cell: """Reconstruct this cell, overwriting properties by those in kwargs.""" - gdim = self._gdim for key, value in kwargs.items(): - if key == "geometric_dimension": - gdim = value - else: - raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") - return TensorProductCell(*self._cells, geometric_dimension=gdim) + raise TypeError(f"reconstruct() got unexpected keyword argument '{key}'") + return TensorProductCell(*self._cells) -def simplex(topological_dimension: int, geometric_dimension: typing.Optional[int] = None): +def simplex(topological_dimension: int): """Return a simplex cell of the given dimension.""" if topological_dimension == 0: - return Cell("vertex", geometric_dimension) + return Cell("vertex") if topological_dimension == 1: - return Cell("interval", geometric_dimension) + return Cell("interval") if topological_dimension == 2: - return Cell("triangle", geometric_dimension) + return Cell("triangle") if topological_dimension == 3: - return Cell("tetrahedron", geometric_dimension) + return Cell("tetrahedron") if topological_dimension == 4: - return Cell("pentatope", geometric_dimension) + return Cell("pentatope") raise ValueError(f"Unsupported topological dimension for simplex: {topological_dimension}") -def hypercube(topological_dimension, geometric_dimension=None): +def hypercube(topological_dimension: int): """Return a hypercube cell of the given dimension.""" if topological_dimension == 0: - return Cell("vertex", geometric_dimension) + return Cell("vertex") if topological_dimension == 1: - return Cell("interval", geometric_dimension) + return Cell("interval") if topological_dimension == 2: - return Cell("quadrilateral", geometric_dimension) + return Cell("quadrilateral") if topological_dimension == 3: - return Cell("hexahedron", geometric_dimension) + return Cell("hexahedron") if topological_dimension == 4: - return Cell("tesseract", geometric_dimension) + return Cell("tesseract") raise ValueError(f"Unsupported topological dimension for hypercube: {topological_dimension}") diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 3ff9c34e0..956cd5159 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -45,7 +45,7 @@ def __init__(self, function_space, count=None): raise ValueError("Expecting a FunctionSpace.") self._ufl_function_space = function_space - self._ufl_shape = function_space.ufl_element().value_shape + self._ufl_shape = function_space.value_shape self._repr = "BaseCoefficient(%s, %s)" % ( repr(self._ufl_function_space), repr(self._count)) diff --git a/ufl/domain.py b/ufl/domain.py index f438006d0..175507b39 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -74,7 +74,7 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): self._ufl_coordinate_element = coordinate_element # Derive dimensions from element - gdim, = coordinate_element.value_shape + gdim, = coordinate_element.reference_value_shape tdim = coordinate_element.cell.topological_dimension() AbstractDomain.__init__(self, tdim, gdim) @@ -268,12 +268,6 @@ def find_geometric_dimension(expr): domain = extract_unique_domain(t) if domain is not None: gdims.add(domain.geometric_dimension()) - if hasattr(t, "ufl_element"): - element = t.ufl_element() - if element is not None: - cell = element.cell - if cell is not None: - gdims.add(cell.geometric_dimension()) if len(gdims) != 1: raise ValueError("Cannot determine geometric dimension from expression.") diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py index 9a4e43d6e..d306002f1 100644 --- a/ufl/finiteelement.py +++ b/ufl/finiteelement.py @@ -148,16 +148,6 @@ def components(self) -> _typing.Dict[_typing.Tuple[int, ...], int]: offset += e.value_size return components - @property - def value_shape(self) -> _typing.Tuple[int, ...]: - """Return the shape of the value space on the physical domain.""" - return self.pullback.physical_value_shape(self) - - @property - def value_size(self) -> int: - """Return the integer product of the value shape.""" - return product(self.value_shape) - @property def reference_value_size(self) -> int: """Return the integer product of the reference value shape.""" diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 756c16aa0..38839bc20 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -9,9 +9,12 @@ # Modified by Massimiliano Leoni, 2016 # Modified by Cecile Daversin-Catty, 2018 +import typing + from ufl.core.ufl_type import UFLObject from ufl.domain import join_domains from ufl.duals import is_dual, is_primal +from ufl.utils.sequences import product # Export list for ufl.classes __all_classes__ = [ @@ -113,6 +116,16 @@ def __repr__(self): """Representation.""" return f"BaseFunctionSpace({self._ufl_domain!r}, {self._ufl_element!r})" + @property + def value_shape(self) -> typing.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) + + @property + def value_size(self) -> int: + """Return the integer product of the value shape on a physical domain.""" + return product(self.value_shape) + class FunctionSpace(BaseFunctionSpace, UFLObject): """Representation of a Function space.""" diff --git a/ufl/geometry.py b/ufl/geometry.py index a8f6c7720..9566e8aa6 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -456,9 +456,10 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - cell = extract_unique_domain(self).ufl_cell() + domain = extract_unique_domain(self) + cell = domain.ufl_cell() nv = cell.num_vertices() - g = cell.geometric_dimension() + g = domain.geometric_dimension() return (nv, g) def is_cellwise_constant(self): @@ -484,9 +485,10 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - cell = extract_unique_domain(self).ufl_cell() + domain = extract_unique_domain(self) + cell = domain.ufl_cell() ne = cell.num_edges() - g = cell.geometric_dimension() + g = domain.geometric_dimension() return (ne, g) def is_cellwise_constant(self): @@ -512,7 +514,8 @@ def __init__(self, domain): @property def ufl_shape(self): """Get the UFL shape.""" - cell = extract_unique_domain(self).ufl_cell() + domain = extract_unique_domain(self) + cell = domain.ufl_cell() facet_types = cell.facet_types() # Raise exception for cells with more than one facet type e.g. prisms @@ -520,7 +523,7 @@ def ufl_shape(self): raise Exception(f"Cell type {cell} not supported.") nfe = facet_types[0].num_edges() - g = cell.geometric_dimension() + g = domain.geometric_dimension() return (nfe, g) def is_cellwise_constant(self): diff --git a/ufl/objects.py b/ufl/objects.py index fc57ea2ae..64704d275 100644 --- a/ufl/objects.py +++ b/ufl/objects.py @@ -26,16 +26,16 @@ dX = dx + dC # noqa: F821 # Create objects for builtin known cell types -vertex = Cell("vertex", 0) -interval = Cell("interval", 1) -triangle = Cell("triangle", 2) -tetrahedron = Cell("tetrahedron", 3) -prism = Cell("prism", 3) -pyramid = Cell("pyramid", 3) -quadrilateral = Cell("quadrilateral", 2) -hexahedron = Cell("hexahedron", 3) -tesseract = Cell("tesseract", 4) -pentatope = Cell("pentatope", 4) +vertex = Cell("vertex") +interval = Cell("interval") +triangle = Cell("triangle") +tetrahedron = Cell("tetrahedron") +prism = Cell("prism") +pyramid = Cell("pyramid") +quadrilateral = Cell("quadrilateral") +hexahedron = Cell("hexahedron") +tesseract = Cell("tesseract") +pentatope = Cell("pentatope") # Facet is just a dummy declaration for RestrictedElement facet = "facet" diff --git a/ufl/operators.py b/ufl/operators.py index 78226d8a1..0394e65eb 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -693,7 +693,9 @@ def exterior_derivative(f): except Exception: raise ValueError(f"Unable to determine element from {f}") - gdim = element.cell.geometric_dimension() + domain = f.ufl_domain() + + gdim = domain.geometric_dimension() space = element.sobolev_space if space == sobolevspace.L2: diff --git a/ufl/pullback.py b/ufl/pullback.py index dee12f0ce..cbc6b53db 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -16,6 +16,7 @@ from ufl.core.expr import Expr from ufl.core.multiindex import indices from ufl.domain import extract_unique_domain +from ufl.functionspace import FunctionSpace from ufl.tensors import as_tensor if TYPE_CHECKING: @@ -40,11 +41,12 @@ def __repr__(self) -> str: """Return a representation of the object.""" @abstractmethod - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element @@ -87,11 +89,12 @@ def apply(self, expr): """ return expr - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element @@ -130,16 +133,17 @@ def apply(self, expr): kj = (*k, j) return as_tensor(transform[i, j] * expr[kj], (*k, i)) - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ - gdim = element.cell.geometric_dimension() + gdim = domain.geometric_dimension() return (gdim, ) + element.reference_value_shape[1:] @@ -172,16 +176,17 @@ def apply(self, expr): kj = (*k, j) return as_tensor(K[j, i] * expr[kj], (*k, i)) - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ - gdim = element.cell.geometric_dimension() + gdim = domain.geometric_dimension() return (gdim, ) + element.reference_value_shape[1:] @@ -211,11 +216,12 @@ def apply(self, expr): detJ = JacobianDeterminant(domain) return expr / detJ - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element @@ -253,16 +259,17 @@ def apply(self, expr): kmn = (*k, m, n) return as_tensor((1.0 / detJ)**2 * J[i, m] * expr[kmn] * J[j, n], (*k, i, j)) - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ - gdim = element.cell.geometric_dimension() + gdim = domain.geometric_dimension() return (gdim, gdim) @@ -295,16 +302,17 @@ def apply(self, expr): kmn = (*k, m, n) return as_tensor(K[m, i] * expr[kmn] * K[n, j], (*k, i, j)) - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ - gdim = element.cell.geometric_dimension() + gdim = domain.geometric_dimension() return (gdim, gdim) @@ -336,6 +344,8 @@ def apply(self, expr): Returns: The function pulled back to the reference cell """ + domain = expr.ufl_domain() + space = FunctionSpace(domain, self._element) rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] g_components = [] offset = 0 @@ -349,23 +359,24 @@ def apply(self, expr): g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) offset += subelem.reference_value_size # And reshape appropriately - f = as_tensor(np.asarray(g_components).reshape(self._element.value_shape)) - if f.ufl_shape != self._element.value_shape: + f = as_tensor(np.asarray(g_components).reshape(space.value_shape)) + if f.ufl_shape != space.value_shape: raise ValueError("Expecting pulled back expression with shape " - f"'{self._element.value_shape}', got '{f.ufl_shape}'") + f"'{space.value_shape}', got '{f.ufl_shape}'") return f - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ assert element == self._element - dim = sum(e.value_size for e in self._element.sub_elements) + dim = sum(FunctionSpace(domain, e).value_size for e in self._element.sub_elements) return (dim, ) @@ -382,9 +393,9 @@ def __init__(self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing self._element = element self._symmetry = symmetry - self._sub_element_value_shape = element.sub_elements[0].value_shape + self._sub_element_value_shape = element.sub_elements[0].reference_value_shape for e in element.sub_elements: - if e.value_shape != self._sub_element_value_shape: + if e.reference_value_shape != self._sub_element_value_shape: raise ValueError("Sub-elements must all have the same value shape.") self._block_shape = tuple(i + 1 for i in max(symmetry.keys())) @@ -405,6 +416,8 @@ def apply(self, expr): Returns: The function pulled back to the reference cell """ + domain = expr.ufl_domain() + space = FunctionSpace(domain, self._element) rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] g_components = [] offsets = [0] @@ -421,17 +434,18 @@ def apply(self, expr): # Flatten into the pulled back expression for the whole thing g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) # And reshape appropriately - f = as_tensor(np.asarray(g_components).reshape(self._element.value_shape)) - if f.ufl_shape != self._element.value_shape: + f = as_tensor(np.asarray(g_components).reshape(space.value_shape)) + if f.ufl_shape != space.value_shape: raise ValueError(f"Expecting pulled back expression with shape " - f"'{self._element.value_shape}', got '{f.ufl_shape}'") + f"'{space.value_shape}', got '{f.ufl_shape}'") return f - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element @@ -465,11 +479,12 @@ def apply(self, expr): """ return expr - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element @@ -502,15 +517,18 @@ def apply(self, expr): """ return expr - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element """ + if element.reference_value_shape == (): + return () raise NotImplementedError() @@ -529,11 +547,12 @@ def is_identity(self) -> bool: """Is this pull back the identity (or the identity applied to mutliple components).""" return True - def physical_value_shape(self, element) -> typing.Tuple[int, ...]: - """Get the physical value shape when this pull back is applied to an element. + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element on a domain. Args: element: The element that the pull back is applied to + domain: The domain Returns: The value shape when the pull back is applied to the given element diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 886c98915..0931b74ae 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -7,6 +7,8 @@ # # Modified by Anders Logg, 2008 +from ufl.functionspace import FunctionSpace + from ufl.indexed import Indexed from ufl.permutation import compute_indices from ufl.tensors import ListTensor, as_matrix, as_vector @@ -20,6 +22,8 @@ def split(v): If v is a Coefficient or Argument in a mixed space, returns a tuple with the function components corresponding to the subelements. """ + domain = v.ufl_domain() + # Default range is all of v begin = 0 end = None @@ -57,7 +61,7 @@ def split(v): raise ValueError("Don't know how to split tensor valued mixed functions without flattened index space.") # Compute value size and set default range end - value_size = element.value_size + value_size = v.ufl_function_space().value_size if end is None: end = value_size else: @@ -66,12 +70,12 @@ def split(v): j = begin while True: for e in element.sub_elements: - if j < e.value_size: + if j < FunctionSpace(domain, e).value_size: element = e break - j -= e.value_size + j -= FunctionSpace(domain, e).value_size # Then break when we find the subelement that covers the whole range - if element.value_size == (end - begin): + if FunctionSpace(domain, element).value_size == (end - begin): break # Build expressions representing the subfunction of v for each subelement @@ -80,7 +84,7 @@ def split(v): for i, e in enumerate(element.sub_elements): # Get shape, size, indices, and v components # corresponding to subelement value - shape = e.value_shape + shape = FunctionSpace(domain, e).value_shape strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) From 49e604fc68423da3ddc08df11cfc6e3404483e90 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Sat, 10 Feb 2024 12:02:00 +0000 Subject: [PATCH 092/136] reset FEniCSx branches (#255) --- .github/workflows/fenicsx-tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index aa9f3e702..6363a55bf 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -34,14 +34,14 @@ jobs: - name: Install Basix run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/remove-gdim + python3 -m pip install git+https://github.com/FEniCS/basix.git - name: Clone FFCx uses: actions/checkout@v4 with: path: ./ffcx repository: FEniCS/ffcx - ref: mscroggs/gdim + ref: main - name: Install FFCx run: | @@ -69,15 +69,15 @@ jobs: - name: Install Basix and FFCx run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git@mscroggs/remove-gdim - python3 -m pip install git+https://github.com/FEniCS/ffcx.git@mscroggs/gdim + python3 -m pip install git+https://github.com/FEniCS/basix.git + python3 -m pip install git+https://github.com/FEniCS/ffcx.git - name: Clone DOLFINx uses: actions/checkout@v4 with: path: ./dolfinx repository: FEniCS/dolfinx - ref: mscroggs/gdim + ref: main - name: Install DOLFINx run: | cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ From b15d8d3fdfea5ad6fe78531ec4ce6059cafeaa89 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Sat, 10 Feb 2024 14:09:11 +0000 Subject: [PATCH 093/136] Apply black formatting (using ruff) and the ruff linter (#256) * ruff format and linting * Reinstate import * Small fixes * Import fixes * Doc fixes --- .flake8 | 6 - .github/workflows/pythonapp.yml | 11 +- demo/Constant.py | 18 +- demo/ConvectionJacobi.py | 16 +- demo/ConvectionJacobi2.py | 4 +- demo/ConvectionVector.py | 4 +- demo/Elasticity.py | 4 +- demo/EnergyNorm.py | 2 +- demo/Equation.py | 16 +- demo/ExplicitConvection.py | 16 +- demo/FunctionOperators.py | 21 +- demo/H1norm.py | 2 +- demo/HarmonicMap.py | 4 +- demo/HarmonicMap2.py | 4 +- demo/Heat.py | 23 +- demo/HornSchunck.py | 20 +- demo/HyperElasticity.py | 45 +- demo/HyperElasticity1D.py | 4 +- demo/L2norm.py | 2 +- demo/Mass.py | 2 +- demo/MassAD.py | 2 +- demo/MixedElasticity.py | 31 +- demo/MixedPoisson.py | 16 +- demo/MixedPoisson2.py | 17 +- demo/NavierStokes.py | 16 +- demo/NeumannProblem.py | 17 +- demo/NonlinearPoisson.py | 17 +- demo/Poisson.py | 14 +- demo/PoissonDG.py | 36 +- demo/PoissonSystem.py | 17 +- demo/PowAD.py | 13 +- demo/ProjectionSystem.py | 2 +- demo/QuadratureElement.py | 22 +- demo/Stiffness.py | 2 +- demo/StiffnessAD.py | 15 +- demo/Stokes.py | 18 +- demo/StokesEquation.py | 21 +- demo/SubDomain.py | 2 +- demo/SubDomains.py | 14 +- demo/TensorWeightedPoisson.py | 14 +- demo/VectorLaplaceGradCurl.py | 23 +- demo/_TensorProductElement.py | 13 +- doc/sphinx/source/conf.py | 159 ++++--- pyproject.toml | 49 +- test/conftest.py | 1 - test/mockobjects.py | 22 +- test/test_algorithms.py | 52 ++- test/test_analyse_demos.py | 5 +- test/test_apply_algebra_lowering.py | 85 ++-- test/test_apply_function_pullbacks.py | 384 +++++++++------ test/test_apply_restrictions.py | 41 +- test/test_arithmetic.py | 35 +- test/test_automatic_differentiation.py | 317 ++++++++----- test/test_change_to_local.py | 24 +- test/test_change_to_reference_frame.py | 23 +- test/test_check_arities.py | 28 +- test/test_classcoverage.py | 257 ++++++---- test/test_complex.py | 88 ++-- test/test_conditionals.py | 4 +- test/test_degree_estimation.py | 49 +- test/test_derivative.py | 358 ++++++++------ test/test_diff.py | 56 ++- test/test_domains.py | 289 ++++++++---- test/test_duals.py | 68 ++- test/test_equals.py | 13 +- test/test_evaluate.py | 133 +++--- test/test_expand_indices.py | 141 +++--- test/test_external_operator.py | 111 +++-- test/test_ffcforms.py | 187 ++++---- test/test_form.py | 45 +- test/test_illegal.py | 4 +- test/test_indexing.py | 2 +- test/test_indices.py | 127 ++--- test/test_interpolate.py | 34 +- test/test_lhs_rhs.py | 27 +- test/test_literals.py | 20 +- test/test_measures.py | 29 +- test/test_mixed_function_space.py | 43 +- test/test_new_ad.py | 61 ++- test/test_pickle.py | 134 ++++-- test/test_piecewise_checks.py | 181 +++++-- test/test_reference_shapes.py | 23 +- test/test_scratch.py | 190 ++++---- test/test_signature.py | 233 +++++---- test/test_simplify.py | 74 ++- test/test_sobolevspace.py | 28 +- test/test_split.py | 30 +- test/test_str.py | 81 ++-- test/test_strip_forms.py | 27 +- test/test_tensoralgebra.py | 53 ++- test/test_utilities.py | 21 +- ufl/__init__.py | 441 +++++++++++++++--- ufl/action.py | 29 +- ufl/adjoint.py | 24 +- ufl/algebra.py | 74 ++- ufl/algorithms/__init__.py | 32 +- ufl/algorithms/analysis.py | 45 +- ufl/algorithms/apply_algebra_lowering.py | 6 +- ufl/algorithms/apply_derivatives.py | 397 ++++++++++------ ufl/algorithms/apply_function_pullbacks.py | 13 +- ufl/algorithms/apply_geometry_lowering.py | 47 +- ufl/algorithms/apply_integral_scaling.py | 20 +- ufl/algorithms/apply_restrictions.py | 44 +- ufl/algorithms/balancing.py | 32 +- ufl/algorithms/change_to_reference.py | 39 +- ufl/algorithms/check_arities.py | 38 +- ufl/algorithms/check_restrictions.py | 2 +- ufl/algorithms/checks.py | 19 +- ufl/algorithms/comparison_checker.py | 18 +- ufl/algorithms/compute_form_data.py | 76 +-- .../coordinate_derivative_helpers.py | 7 +- ufl/algorithms/domain_analysis.py | 111 +++-- ufl/algorithms/estimate_degrees.py | 29 +- ufl/algorithms/expand_compounds.py | 9 +- ufl/algorithms/expand_indices.py | 4 +- ufl/algorithms/formdata.py | 8 +- ufl/algorithms/formfiles.py | 32 +- ufl/algorithms/formsplitter.py | 14 +- ufl/algorithms/formtransformations.py | 47 +- ufl/algorithms/map_integrands.py | 42 +- ufl/algorithms/remove_complex_nodes.py | 11 +- ufl/algorithms/renumbering.py | 8 +- ufl/algorithms/replace.py | 5 +- ufl/algorithms/replace_derivative_nodes.py | 19 +- ufl/algorithms/signature.py | 18 +- ufl/algorithms/strip_terminal_data.py | 33 +- ufl/algorithms/transformer.py | 21 +- ufl/algorithms/traversal.py | 2 +- ufl/argument.py | 62 ++- ufl/averaging.py | 17 +- ufl/cell.py | 99 ++-- ufl/checks.py | 4 +- ufl/classes.py | 1 + ufl/coefficient.py | 34 +- ufl/compound_expressions.py | 366 +++++++++++---- ufl/conditional.py | 33 +- ufl/constant.py | 26 +- ufl/constantvalue.py | 36 +- ufl/core/base_form_operator.py | 67 ++- ufl/core/expr.py | 22 +- ufl/core/external_operator.py | 85 ++-- ufl/core/interpolate.py | 19 +- ufl/core/multiindex.py | 6 +- ufl/core/terminal.py | 8 +- ufl/core/ufl_id.py | 1 + ufl/core/ufl_type.py | 91 ++-- ufl/corealg/map_dag.py | 21 +- ufl/corealg/multifunction.py | 4 +- ufl/corealg/traversal.py | 6 +- ufl/differentiation.py | 173 +++---- ufl/domain.py | 47 +- ufl/duals.py | 4 +- ufl/equation.py | 1 + ufl/exproperators.py | 25 +- ufl/finiteelement.py | 152 +++--- ufl/form.py | 137 +++--- ufl/formatting/ufl2unicode.py | 140 +++--- ufl/formoperators.py | 110 +++-- ufl/functionspace.py | 43 +- ufl/geometry.py | 26 +- ufl/index_combination_utils.py | 7 +- ufl/indexed.py | 20 +- ufl/indexsum.py | 20 +- ufl/integral.py | 60 ++- ufl/mathfunctions.py | 39 +- ufl/matrix.py | 21 +- ufl/measure.py | 148 +++--- ufl/operators.py | 95 +++- ufl/precedence.py | 30 +- ufl/protocols.py | 2 +- ufl/pullback.py | 63 ++- ufl/referencevalue.py | 5 +- ufl/restriction.py | 15 +- ufl/sobolevspace.py | 50 +- ufl/sorting.py | 2 +- ufl/split_functions.py | 30 +- ufl/tensoralgebra.py | 33 +- ufl/tensors.py | 45 +- ufl/utils/formatting.py | 9 +- ufl/utils/indexflattening.py | 2 +- ufl/utils/sequences.py | 10 +- ufl/utils/sorting.py | 8 +- ufl/utils/stacks.py | 6 +- ufl/variable.py | 10 +- 184 files changed, 6253 insertions(+), 3171 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index cd8e21ed8..000000000 --- a/.flake8 +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -max-line-length = 120 -builtins = ufl -exclude = doc/sphinx/source/conf.py -per-file-ignores = - */__init__.py: F401 diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index e6f23ec0b..a3bdfc35a 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -28,14 +28,11 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Lint with flake8 + - name: Lint with ruff run: | - python -m pip install flake8 - flake8 --statistics . - - name: Check documentation style - run: | - python -m pip install pydocstyle[toml] - python -m pydocstyle ufl/ + pip install ruff + ruff check . + ruff format --check . - name: Install UFL run: python -m pip install .[ci] - name: Run unit tests diff --git a/demo/Constant.py b/demo/Constant.py index 96acaf22c..09f0d515f 100644 --- a/demo/Constant.py +++ b/demo/Constant.py @@ -18,15 +18,27 @@ # Modified by Martin Sandve Alnes, 2009 # # Test form for scalar and vector constants. -from ufl import (Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, dot, dx, grad, - inner, triangle) +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + VectorConstant, + dot, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/ConvectionJacobi.py b/demo/ConvectionJacobi.py index 5f3b2da10..f058ca233 100644 --- a/demo/ConvectionJacobi.py +++ b/demo/ConvectionJacobi.py @@ -2,13 +2,23 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/ConvectionJacobi2.py b/demo/ConvectionJacobi2.py index c88108a17..bbe68b292 100644 --- a/demo/ConvectionJacobi2.py +++ b/demo/ConvectionJacobi2.py @@ -7,8 +7,8 @@ from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/ConvectionVector.py b/demo/ConvectionVector.py index e83e60698..59b5e233d 100644 --- a/demo/ConvectionVector.py +++ b/demo/ConvectionVector.py @@ -7,8 +7,8 @@ from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/Elasticity.py b/demo/Elasticity.py index 73513d5d6..324b2cefd 100644 --- a/demo/Elasticity.py +++ b/demo/Elasticity.py @@ -8,8 +8,8 @@ from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/EnergyNorm.py b/demo/EnergyNorm.py index 30f1ade40..de2de347c 100644 --- a/demo/EnergyNorm.py +++ b/demo/EnergyNorm.py @@ -23,7 +23,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) diff --git a/demo/Equation.py b/demo/Equation.py index 33625545b..f440f303d 100644 --- a/demo/Equation.py +++ b/demo/Equation.py @@ -34,13 +34,25 @@ # the unknown u to the right-hand side, all terms may # be listed on one line and left- and right-hand sides # extracted by lhs() and rhs(). -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, lhs, rhs, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + lhs, + rhs, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 diff --git a/demo/ExplicitConvection.py b/demo/ExplicitConvection.py index c56d27376..e5086c1a9 100644 --- a/demo/ExplicitConvection.py +++ b/demo/ExplicitConvection.py @@ -2,13 +2,23 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/FunctionOperators.py b/demo/FunctionOperators.py index 075e7b8c1..4aeb4d621 100644 --- a/demo/FunctionOperators.py +++ b/demo/FunctionOperators.py @@ -16,13 +16,25 @@ # along with UFL. If not, see . # # Test form for operators on Coefficients. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, max_value, sqrt, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + max_value, + sqrt, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -30,4 +42,7 @@ f = Coefficient(space) g = Coefficient(space) -a = sqrt(1 / max_value(1 / f, -1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx +a = ( + sqrt(1 / max_value(1 / f, -1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + + v * u * sqrt(f * g) * g * dx +) diff --git a/demo/H1norm.py b/demo/H1norm.py index 9da6b28e4..769607aff 100644 --- a/demo/H1norm.py +++ b/demo/H1norm.py @@ -8,7 +8,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) diff --git a/demo/HarmonicMap.py b/demo/HarmonicMap.py index 8aa3ee5d2..1d53906c9 100644 --- a/demo/HarmonicMap.py +++ b/demo/HarmonicMap.py @@ -9,9 +9,9 @@ from ufl.sobolevspace import H1 cell = triangle -X = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +X = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) Y = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) X_space = FunctionSpace(domain, X) Y_space = FunctionSpace(domain, Y) diff --git a/demo/HarmonicMap2.py b/demo/HarmonicMap2.py index dfc47c3b1..fc048868f 100644 --- a/demo/HarmonicMap2.py +++ b/demo/HarmonicMap2.py @@ -9,10 +9,10 @@ from ufl.sobolevspace import H1 cell = triangle -X = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) +X = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) Y = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) M = MixedElement([X, Y]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, M) u = Coefficient(space) diff --git a/demo/Heat.py b/demo/Heat.py index f695341b2..2a197b5d1 100644 --- a/demo/Heat.py +++ b/demo/Heat.py @@ -20,22 +20,33 @@ # The bilinear form a(v, u1) and linear form L(v) for # one backward Euler step with the heat equation. # -from ufl import Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) # Test function u1 = TrialFunction(space) # Value at t_n -u0 = Coefficient(space) # Value at t_n-1 -c = Coefficient(space) # Heat conductivity -f = Coefficient(space) # Heat source -k = Constant(domain) # Time step +u0 = Coefficient(space) # Value at t_n-1 +c = Coefficient(space) # Heat conductivity +f = Coefficient(space) # Heat source +k = Constant(domain) # Time step a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx L = v * u0 * dx + k * v * f * dx diff --git a/demo/HornSchunck.py b/demo/HornSchunck.py index d3ec43840..5238ed04e 100644 --- a/demo/HornSchunck.py +++ b/demo/HornSchunck.py @@ -3,7 +3,18 @@ # http://code.google.com/p/debiosee/wiki/DemosOptiocFlowHornSchunck # but not tested so this could contain errors! # -from ufl import Coefficient, Constant, FunctionSpace, Mesh, derivative, dot, dx, grad, inner, triangle +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + derivative, + dot, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -11,8 +22,8 @@ # Finite element spaces for scalar and vector fields cell = triangle S = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) -V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +V = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) S_space = FunctionSpace(domain, S) V_space = FunctionSpace(domain, V) @@ -29,8 +40,7 @@ lamda = Constant(domain) # Coefficiental to minimize -M = (dot(u, grad(I1)) + (I1 - I0))**2 * dx\ - + lamda * inner(grad(u), grad(u)) * dx +M = (dot(u, grad(I1)) + (I1 - I0)) ** 2 * dx + lamda * inner(grad(u), grad(u)) * dx # Derived linear system L = derivative(M, u) diff --git a/demo/HyperElasticity.py b/demo/HyperElasticity.py index cdfd58488..e4882d2b6 100644 --- a/demo/HyperElasticity.py +++ b/demo/HyperElasticity.py @@ -3,22 +3,45 @@ # Date: 2008-12-22 # -from ufl import (Coefficient, Constant, FacetNormal, FunctionSpace, Identity, Mesh, SpatialCoordinate, TestFunction, - TrialFunction, derivative, det, diff, dot, ds, dx, exp, grad, inner, inv, tetrahedron, tr, variable) +from ufl import ( + Coefficient, + Constant, + FacetNormal, + FunctionSpace, + Identity, + Mesh, + SpatialCoordinate, + TestFunction, + TrialFunction, + derivative, + det, + diff, + dot, + ds, + dx, + exp, + grad, + inner, + inv, + tetrahedron, + tr, + variable, +) from ufl.finiteelement import FiniteElement + # Modified by Garth N. Wells, 2009 from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 # Cell and its properties cell = tetrahedron -domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) d = 3 N = FacetNormal(domain) x = SpatialCoordinate(domain) # Elements -u_element = FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1) +u_element = FiniteElement("Lagrange", cell, 2, (3,), identity_pullback, H1) p_element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) A_element = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) @@ -79,7 +102,9 @@ Ef = A * E * A.T # Strain energy function W(Q(Ef)) -Q = c00 * Ef[0, 0]**2 + c11 * Ef[1, 1]**2 + c22 * Ef[2, 2]**2 # FIXME: insert some simple law here +Q = ( + c00 * Ef[0, 0] ** 2 + c11 * Ef[1, 1] ** 2 + c22 * Ef[2, 2] ** 2 +) # FIXME: insert some simple law here W = (K / 2) * (exp(Q) - 1) # + p stuff # First Piola-Kirchoff stress tensor @@ -87,13 +112,15 @@ # Acceleration term discretized with finite differences k = dt / rho -acc = (u - 2 * up + upp) +acc = u - 2 * up + upp # Residual equation # FIXME: Can contain errors, not tested! -a_F = inner(acc, v) * dx \ - + k * inner(P, grad(v)) * dx \ - - k * dot(J * Finv * T, v) * ds(0) \ +a_F = ( + inner(acc, v) * dx + + k * inner(P, grad(v)) * dx + - k * dot(J * Finv * T, v) * ds(0) - k * dot(J * Finv * p0 * N, v) * ds(1) +) # Jacobi matrix of residual equation a_J = derivative(a_F, u, w) diff --git a/demo/HyperElasticity1D.py b/demo/HyperElasticity1D.py index 9dfcbed96..f2cd1ab52 100644 --- a/demo/HyperElasticity1D.py +++ b/demo/HyperElasticity1D.py @@ -9,14 +9,14 @@ cell = interval element = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (1, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (1,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Coefficient(space) b = Constant(domain) K = Constant(domain) -E = u.dx(0) + u.dx(0)**2 / 2 +E = u.dx(0) + u.dx(0) ** 2 / 2 E = variable(E) Q = b * E**2 psi = K * (exp(Q) - 1) diff --git a/demo/L2norm.py b/demo/L2norm.py index 31050a9df..cd492f647 100644 --- a/demo/L2norm.py +++ b/demo/L2norm.py @@ -8,7 +8,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) diff --git a/demo/Mass.py b/demo/Mass.py index 4f083140b..eb20c5afb 100644 --- a/demo/Mass.py +++ b/demo/Mass.py @@ -26,7 +26,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/MassAD.py b/demo/MassAD.py index ed36e8c2c..9f90b5eec 100644 --- a/demo/MassAD.py +++ b/demo/MassAD.py @@ -8,7 +8,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Coefficient(space) diff --git a/demo/MixedElasticity.py b/demo/MixedElasticity.py index 6fe1a96a3..466935be0 100644 --- a/demo/MixedElasticity.py +++ b/demo/MixedElasticity.py @@ -17,8 +17,20 @@ # # First added: 2008-10-03 # Last changed: 2011-07-22 -from ufl import (FunctionSpace, Mesh, TestFunctions, TrialFunctions, as_vector, div, dot, dx, inner, skew, tetrahedron, - tr) +from ufl import ( + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + as_vector, + div, + dot, + dx, + inner, + skew, + tetrahedron, + tr, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import contravariant_piola, identity_pullback from ufl.sobolevspace import H1, L2, HDiv @@ -36,19 +48,22 @@ def skw(tau): # Finite element exterior calculus syntax r = 1 S = FiniteElement("vector BDM", cell, r, (3, 3), contravariant_piola, HDiv) -V = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3, ), identity_pullback, L2) -Q = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3, ), identity_pullback, L2) +V = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3,), identity_pullback, L2) +Q = FiniteElement("Discontinuous Lagrange", cell, r - 1, (3,), identity_pullback, L2) W = MixedElement([S, V, Q]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, W) (sigma, u, gamma) = TrialFunctions(space) (tau, v, eta) = TestFunctions(space) a = ( - inner(sigma, tau) - tr(sigma) * tr(tau) + dot( - div(tau), u - ) - dot(div(sigma), v) + inner(skw(tau), gamma) + inner(skw(sigma), eta) + inner(sigma, tau) + - tr(sigma) * tr(tau) + + dot(div(tau), u) + - dot(div(sigma), v) + + inner(skw(tau), gamma) + + inner(skw(sigma), eta) ) * dx diff --git a/demo/MixedPoisson.py b/demo/MixedPoisson.py index 1580372a3..6e1c1730f 100644 --- a/demo/MixedPoisson.py +++ b/demo/MixedPoisson.py @@ -23,17 +23,27 @@ # a mixed formulation of Poisson's equation with BDM # (Brezzi-Douglas-Marini) elements. # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + div, + dot, + dx, + triangle, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import contravariant_piola, identity_pullback from ufl.sobolevspace import H1, HDiv cell = triangle -BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1, (2, ), contravariant_piola, HDiv) +BDM1 = FiniteElement("Brezzi-Douglas-Marini", cell, 1, (2,), contravariant_piola, HDiv) DG0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, H1) element = MixedElement([BDM1, DG0]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) dg0_space = FunctionSpace(domain, DG0) diff --git a/demo/MixedPoisson2.py b/demo/MixedPoisson2.py index 29268a309..fc9deb84d 100644 --- a/demo/MixedPoisson2.py +++ b/demo/MixedPoisson2.py @@ -3,16 +3,27 @@ # Modified by: Martin Sandve Alnes # Date: 2009-02-12 # -from ufl import FacetNormal, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, ds, dx, tetrahedron +from ufl import ( + FacetNormal, + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + div, + dot, + ds, + dx, + tetrahedron, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import contravariant_piola, identity_pullback from ufl.sobolevspace import H1, HDiv cell = tetrahedron -RT = FiniteElement("Raviart-Thomas", cell, 1, (3, ), contravariant_piola, HDiv) +RT = FiniteElement("Raviart-Thomas", cell, 1, (3,), contravariant_piola, HDiv) DG = FiniteElement("DG", cell, 0, (), identity_pullback, H1) MX = MixedElement([RT, DG]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, MX) (u, p) = TrialFunctions(space) diff --git a/demo/NavierStokes.py b/demo/NavierStokes.py index 14dfa5f56..e1121d9c9 100644 --- a/demo/NavierStokes.py +++ b/demo/NavierStokes.py @@ -21,14 +21,24 @@ # # The bilinear form for the nonlinear term in the # Navier-Stokes equations with fixed convective velocity. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, tetrahedron +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + tetrahedron, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = tetrahedron -element = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/NeumannProblem.py b/demo/NeumannProblem.py index d384c4315..95f255933 100644 --- a/demo/NeumannProblem.py +++ b/demo/NeumannProblem.py @@ -17,13 +17,24 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation with Neumann boundary conditions. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, ds, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + ds, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/NonlinearPoisson.py b/demo/NonlinearPoisson.py index 7604459b5..56ef5cb9a 100644 --- a/demo/NonlinearPoisson.py +++ b/demo/NonlinearPoisson.py @@ -1,10 +1,20 @@ -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -12,6 +22,5 @@ u0 = Coefficient(space) f = Coefficient(space) -a = (1 + u0**2) * dot(grad(v), grad(u)) * dx \ - + 2 * u0 * u * dot(grad(v), grad(u0)) * dx +a = (1 + u0**2) * dot(grad(v), grad(u)) * dx + 2 * u0 * u * dot(grad(v), grad(u0)) * dx L = v * f * dx - (1 + u0**2) * dot(grad(v), grad(u0)) * dx diff --git a/demo/Poisson.py b/demo/Poisson.py index 779273391..bfae70f8b 100644 --- a/demo/Poisson.py +++ b/demo/Poisson.py @@ -21,13 +21,23 @@ # Last changed: 2009-03-02 # # The bilinear form a(v, u) and linear form L(v) for Poisson's equation. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/PoissonDG.py b/demo/PoissonDG.py index fd289213a..bfe01c7a0 100644 --- a/demo/PoissonDG.py +++ b/demo/PoissonDG.py @@ -21,15 +21,31 @@ # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in a discontinuous Galerkin (DG) # formulation. -from ufl import (Coefficient, Constant, FacetNormal, FunctionSpace, Mesh, TestFunction, TrialFunction, avg, dot, dS, ds, - dx, grad, inner, jump, triangle) +from ufl import ( + Coefficient, + Constant, + FacetNormal, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + avg, + dot, + dS, + ds, + dx, + grad, + inner, + jump, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1, L2 cell = triangle element = FiniteElement("Discontinuous Lagrange", cell, 1, (), identity_pullback, L2) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -44,12 +60,14 @@ alpha = 4.0 gamma = 8.0 -a = inner(grad(v), grad(u)) * dx \ - - inner(avg(grad(v)), jump(u, n)) * dS \ - - inner(jump(v, n), avg(grad(u))) * dS \ - + alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS \ - - inner(grad(v), u * n) * ds \ - - inner(v * n, grad(u)) * ds \ +a = ( + inner(grad(v), grad(u)) * dx + - inner(avg(grad(v)), jump(u, n)) * dS + - inner(jump(v, n), avg(grad(u))) * dS + + alpha / h("+") * dot(jump(v, n), jump(u, n)) * dS + - inner(grad(v), u * n) * ds + - inner(v * n, grad(u)) * ds + gamma / h * v * u * ds +) L = v * f * dx + v * gN * ds diff --git a/demo/PoissonSystem.py b/demo/PoissonSystem.py index 29967d025..9bcd06ad3 100644 --- a/demo/PoissonSystem.py +++ b/demo/PoissonSystem.py @@ -21,14 +21,25 @@ # # The bilinear form a(v, u) and linear form L(v) for # Poisson's equation in system form (vector-valued). -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = triangle -element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +element = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/PowAD.py b/demo/PowAD.py index 106f1f799..dde304d99 100644 --- a/demo/PowAD.py +++ b/demo/PowAD.py @@ -2,13 +2,22 @@ # Author: Martin Sandve Alnes # Date: 2008-10-03 # -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, derivative, dx, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + derivative, + dx, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/ProjectionSystem.py b/demo/ProjectionSystem.py index 7548a0b22..5211abb84 100644 --- a/demo/ProjectionSystem.py +++ b/demo/ProjectionSystem.py @@ -4,7 +4,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) diff --git a/demo/QuadratureElement.py b/demo/QuadratureElement.py index 4e01cd31b..5ca50134f 100644 --- a/demo/QuadratureElement.py +++ b/demo/QuadratureElement.py @@ -20,17 +20,28 @@ # # The linearised bilinear form a(u,v) and linear form L(v) for # the nonlinear equation - div (1+u) grad u = f (non-linear Poisson) -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dot, dx, grad, i, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dot, + dx, + grad, + i, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) QE = FiniteElement("Quadrature", triangle, 2, (), identity_pullback, H1) -sig = FiniteElement("Quadrature", triangle, 1, (2, ), identity_pullback, H1) +sig = FiniteElement("Quadrature", triangle, 1, (2,), identity_pullback, H1) qe_space = FunctionSpace(domain, QE) sig_space = FunctionSpace(domain, sig) @@ -42,5 +53,8 @@ sig0 = Coefficient(sig_space) f = Coefficient(space) -a = v.dx(i) * C * u.dx(i) * dx(metadata={"quadrature_degree": 2}) + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx +a = ( + v.dx(i) * C * u.dx(i) * dx(metadata={"quadrature_degree": 2}) + + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx +) L = v * f * dx - dot(grad(v), sig0) * dx(metadata={"quadrature_degree": 1}) diff --git a/demo/Stiffness.py b/demo/Stiffness.py index 32bf00a54..f96fa6e32 100644 --- a/demo/Stiffness.py +++ b/demo/Stiffness.py @@ -8,7 +8,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) diff --git a/demo/StiffnessAD.py b/demo/StiffnessAD.py index 59a8bcba4..c95b94684 100644 --- a/demo/StiffnessAD.py +++ b/demo/StiffnessAD.py @@ -2,13 +2,24 @@ # Author: Martin Sandve Alnes # Date: 2008-10-30 # -from ufl import Coefficient, FunctionSpace, Mesh, action, adjoint, derivative, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + action, + adjoint, + derivative, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) w = Coefficient(space) diff --git a/demo/Stokes.py b/demo/Stokes.py index b4d240ffd..4a2c185ac 100644 --- a/demo/Stokes.py +++ b/demo/Stokes.py @@ -20,16 +20,28 @@ # # The bilinear form a(v, u) and Linear form L(v) for the Stokes # equations using a mixed formulation (Taylor-Hood elements). -from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + div, + dot, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = triangle -P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) +P2 = FiniteElement("Lagrange", cell, 2, (2,), identity_pullback, H1) P1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) TH = MixedElement([P2, P1]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) diff --git a/demo/StokesEquation.py b/demo/StokesEquation.py index 949551846..667ca729b 100644 --- a/demo/StokesEquation.py +++ b/demo/StokesEquation.py @@ -19,17 +19,30 @@ # equations using a mixed formulation (Taylor-Hood elements) in # combination with the lhs() and rhs() operators to extract the # bilinear and linear forms from an expression F = 0. -from ufl import (Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, div, dot, dx, grad, inner, lhs, rhs, - triangle) +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + div, + dot, + dx, + grad, + inner, + lhs, + rhs, + triangle, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 cell = triangle -P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) +P2 = FiniteElement("Lagrange", cell, 2, (2,), identity_pullback, H1) P1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) TH = MixedElement([P2, P1]) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) diff --git a/demo/SubDomain.py b/demo/SubDomain.py index 4205a7790..39dca0653 100644 --- a/demo/SubDomain.py +++ b/demo/SubDomain.py @@ -23,7 +23,7 @@ from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/demo/SubDomains.py b/demo/SubDomains.py index 55e9ddbe5..f4d76da7a 100644 --- a/demo/SubDomains.py +++ b/demo/SubDomains.py @@ -17,17 +17,23 @@ # # This simple example illustrates how forms can be defined on different sub domains. # It is supported for all three integral types. -from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, ds, dS, dx, tetrahedron +from ufl import FunctionSpace, Mesh, TestFunction, TrialFunction, dS, ds, dx, tetrahedron from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) -a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * ds(1)\ - + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) +a = ( + v * u * dx(0) + + 10.0 * v * u * dx(1) + + v * u * ds(0) + + 2.0 * v * u * ds(1) + + v("+") * u("+") * dS(0) + + 4.3 * v("+") * u("+") * dS(1) +) diff --git a/demo/TensorWeightedPoisson.py b/demo/TensorWeightedPoisson.py index 6ebc8e3c1..a46516dd4 100644 --- a/demo/TensorWeightedPoisson.py +++ b/demo/TensorWeightedPoisson.py @@ -17,14 +17,24 @@ # # The bilinear form a(v, u) and linear form L(v) for # tensor-weighted Poisson's equation. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dx, + grad, + inner, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1, L2 P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) -domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) diff --git a/demo/VectorLaplaceGradCurl.py b/demo/VectorLaplaceGradCurl.py index f5d9b863f..ca08db9ac 100644 --- a/demo/VectorLaplaceGradCurl.py +++ b/demo/VectorLaplaceGradCurl.py @@ -18,7 +18,18 @@ # The bilinear form a(v, u) and linear form L(v) for the Hodge Laplace # problem using 0- and 1-forms. Intended to demonstrate use of Nedelec # elements. -from ufl import Coefficient, FunctionSpace, Mesh, TestFunctions, TrialFunctions, curl, dx, grad, inner, tetrahedron +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunctions, + TrialFunctions, + curl, + dx, + grad, + inner, + tetrahedron, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import covariant_piola, identity_pullback from ufl.sobolevspace import H1, HCurl @@ -29,7 +40,9 @@ def HodgeLaplaceGradCurl(space, fspace): sigma, u = TrialFunctions(space) f = Coefficient(fspace) - a = (inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx + a = ( + inner(tau, sigma) - inner(grad(tau), u) + inner(v, grad(sigma)) + inner(curl(v), curl(u)) + ) * dx L = inner(v, f) * dx return a, L @@ -39,11 +52,11 @@ def HodgeLaplaceGradCurl(space, fspace): order = 1 GRAD = FiniteElement("Lagrange", cell, order, (), identity_pullback, H1) -CURL = FiniteElement("N1curl", cell, order, (3, ), covariant_piola, HCurl) +CURL = FiniteElement("N1curl", cell, order, (3,), covariant_piola, HCurl) -VectorLagrange = FiniteElement("Lagrange", cell, order + 1, (3, ), identity_pullback, H1) +VectorLagrange = FiniteElement("Lagrange", cell, order + 1, (3,), identity_pullback, H1) -domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) +domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, MixedElement([GRAD, CURL])) fspace = FunctionSpace(domain, VectorLagrange) diff --git a/demo/_TensorProductElement.py b/demo/_TensorProductElement.py index 9e6fb6ef0..c32b1fd09 100644 --- a/demo/_TensorProductElement.py +++ b/demo/_TensorProductElement.py @@ -17,8 +17,17 @@ # # First added: 2012-08-16 # Last changed: 2012-08-16 -from ufl import (FunctionSpace, Mesh, TensorProductElement, TestFunction, TrialFunction, dx, interval, tetrahedron, - triangle) +from ufl import ( + FunctionSpace, + Mesh, + TensorProductElement, + TestFunction, + TrialFunction, + dx, + interval, + tetrahedron, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1, L2 diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py index 60a0af8ee..5ad9345db 100644 --- a/doc/sphinx/source/conf.py +++ b/doc/sphinx/source/conf.py @@ -18,43 +18,43 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.coverage", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Unified Form Language (UFL)' +project = "Unified Form Language (UFL)" this_year = datetime.date.today().year -copyright = u'%s, FEniCS Project' % this_year -author = u'FEniCS Project' +copyright = "%s, FEniCS Project" % this_year +author = "FEniCS Project" version = importlib.metadata.version("fenics-ufl") release = version @@ -68,9 +68,9 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -78,27 +78,27 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -108,31 +108,31 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -#html_theme = 'alabaster' +# html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -142,109 +142,111 @@ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'UnifiedFormLanguageUFLdoc' +htmlhelp_basename = "UnifiedFormLanguageUFLdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'UnifiedFormLanguageUFL.tex', u'Unified Form Language (UFL) Documentation', - u'FEniCS Project', 'manual'), + ( + master_doc, + "UnifiedFormLanguageUFL.tex", + "Unified Form Language (UFL) Documentation", + "FEniCS Project", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- @@ -252,12 +254,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'unifiedformlanguageufl', u'Unified Form Language (UFL) Documentation', - [author], 1) + (master_doc, "unifiedformlanguageufl", "Unified Form Language (UFL) Documentation", [author], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -266,19 +267,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'UnifiedFormLanguageUFL', u'Unified Form Language (UFL) Documentation', - author, 'UnifiedFormLanguageUFL', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "UnifiedFormLanguageUFL", + "Unified Form Language (UFL) Documentation", + author, + "UnifiedFormLanguageUFL", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/pyproject.toml b/pyproject.toml index 468cd0a10..8fb43220d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,11 +5,14 @@ build-backend = "setuptools.build_meta" [project] name = "fenics-ufl" version = "2023.3.0.dev0" -authors = [{name="UFL contributors"}] -maintainers = [{email="fenics-steering-council@googlegroups.com"}, {name="FEniCS Steering Council"}] +authors = [{ name = "UFL contributors" }] +maintainers = [ + { email = "fenics-steering-council@googlegroups.com" }, + { name = "FEniCS Steering Council" }, +] description = "Unified Form Language" readme = "README.rst" -license = {file = "COPYING.lesser"} +license = { file = "COPYING.lesser" } requires-python = ">=3.8.0" dependencies = ["numpy"] @@ -21,7 +24,7 @@ issues = "https://github.com/FEniCS/ufl/issues" funding = "https://numfocus.org/donate" [project.optional-dependencies] -lint = ["flake8", "pydocstyle[toml]"] +lint = ["ruff"] docs = ["sphinx", "sphinx_rtd_theme"] test = ["pytest"] ci = [ @@ -44,8 +47,38 @@ packages = [ "ufl.utils", ] -[tool.pydocstyle] -convention = "google" +[tool.ruff] +line-length = 100 +indent-width = 4 -[tool.isort] -line_length = 120 +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = [ + # "N", # pep8-naming + "E", # pycodestyle + "W", # pycodestyle + "D", # pydocstyle + "F", # pyflakes + "I", # isort + "RUF", # Ruff-specific rules + # "UP", # pyupgrade + "ICN", # flake8-import-conventions + "NPY", # numpy-specific rules + "FLY", # use f-string not static joins + "LOG", # https://docs.astral.sh/ruff/rules/#flake8-logging-log + # "ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc + # "B", # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + # "A", # https://docs.astral.sh/ruff/rules/#flake8-builtins-a +] +ignore = ["RUF005", "RUF012"] +allowed-confusables = ["𝐚", "𝐀", "∕", "γ", "⨯", "∨"] + +[tool.ruff.lint.per-file-ignores] +"demo/*" = ["D"] +"doc/*" = ["D"] +"test/*" = ["D"] + +[tool.ruff.lint.pydocstyle] +convention = "google" diff --git a/test/conftest.py b/test/conftest.py index e2c610a6e..5c8030c74 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -8,7 +8,6 @@ class Tester: - def assertTrue(self, a): assert a diff --git a/test/mockobjects.py b/test/mockobjects.py index 4c68e0e21..caf8a88a8 100644 --- a/test/mockobjects.py +++ b/test/mockobjects.py @@ -2,7 +2,6 @@ class MockMesh: - def __init__(self, ufl_id): self._ufl_id = ufl_id @@ -12,9 +11,16 @@ def ufl_id(self): def ufl_domain(self): return Mesh(triangle, ufl_id=self.ufl_id(), cargo=self) - def ufl_measure(self, integral_type="dx", subdomain_id="everywhere", metadata=None, subdomain_data=None): - return Measure(integral_type, subdomain_id=subdomain_id, metadata=metadata, domain=self, - subdomain_data=subdomain_data) + def ufl_measure( + self, integral_type="dx", subdomain_id="everywhere", metadata=None, subdomain_data=None + ): + return Measure( + integral_type, + subdomain_id=subdomain_id, + metadata=metadata, + domain=self, + subdomain_data=subdomain_data, + ) class MockMeshFunction: @@ -32,5 +38,9 @@ def mesh(self): def ufl_measure(self, integral_type=None, subdomain_id="everywhere", metadata=None): return Measure( - integral_type, subdomain_id=subdomain_id, metadata=metadata, - domain=self.mesh(), subdomain_data=self) + integral_type, + subdomain_id=subdomain_id, + metadata=metadata, + domain=self.mesh(), + subdomain_data=self, + ) diff --git a/test/test_algorithms.py b/test/test_algorithms.py index 4b87c2851..f8543249a 100755 --- a/test/test_algorithms.py +++ b/test/test_algorithms.py @@ -6,11 +6,37 @@ import pytest -from ufl import (Argument, Coefficient, FacetNormal, FunctionSpace, Mesh, TestFunction, TrialFunction, adjoint, div, - dot, ds, dx, grad, inner, triangle) -from ufl.algorithms import (expand_derivatives, expand_indices, extract_arguments, extract_coefficients, - extract_elements, extract_unique_elements) -from ufl.corealg.traversal import post_traversal, pre_traversal, unique_post_traversal, unique_pre_traversal +from ufl import ( + Argument, + Coefficient, + FacetNormal, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + adjoint, + div, + dot, + ds, + dx, + grad, + inner, + triangle, +) +from ufl.algorithms import ( + expand_derivatives, + expand_indices, + extract_arguments, + extract_coefficients, + extract_elements, + extract_unique_elements, +) +from ufl.corealg.traversal import ( + post_traversal, + pre_traversal, + unique_post_traversal, + unique_pre_traversal, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -18,29 +44,29 @@ # TODO: add more tests, covering all utility algorithms -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def element(): return FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def domain(): - return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def space(element, domain): return FunctionSpace(domain, element) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def arguments(space): v = TestFunction(space) u = TrialFunction(space) return (v, u) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def coefficients(space): c = Coefficient(space) f = Coefficient(space) @@ -110,7 +136,9 @@ def test_expand_indices(domain): u = TrialFunction(space) def evaluate(form): - return form.cell_integral()[0].integrand()((), {v: 3, u: 5}) # TODO: How to define values of derivatives? + return form.cell_integral()[0].integrand()( + (), {v: 3, u: 5} + ) # TODO: How to define values of derivatives? a = div(grad(v)) * u * dx # a1 = evaluate(a) diff --git a/test/test_analyse_demos.py b/test/test_analyse_demos.py index e9f41451d..a22980136 100755 --- a/test/test_analyse_demos.py +++ b/test/test_analyse_demos.py @@ -13,9 +13,8 @@ def get_demo_filenames(): filenames = sorted( - set(glob(os.path.join(demodir, "*.py"))) - - set(glob(os.path.join(demodir, "_*.py"))) - ) + set(glob(os.path.join(demodir, "*.py"))) - set(glob(os.path.join(demodir, "_*.py"))) + ) return filenames diff --git a/test/test_apply_algebra_lowering.py b/test/test_apply_algebra_lowering.py index e1f496eb9..c0bdceb46 100755 --- a/test/test_apply_algebra_lowering.py +++ b/test/test_apply_algebra_lowering.py @@ -10,51 +10,72 @@ @pytest.fixture def A0(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)), - FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)), + FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1), + ) + ) @pytest.fixture def A1(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)), - FiniteElement("Lagrange", interval, 1, (1, 1), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)), + FiniteElement("Lagrange", interval, 1, (1, 1), identity_pullback, H1), + ) + ) @pytest.fixture def A2(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1), + ) + ) @pytest.fixture def A3(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (3, 3), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (3,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 3), identity_pullback, H1), + ) + ) @pytest.fixture def A21(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (2, 1), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2, 1), identity_pullback, H1), + ) + ) @pytest.fixture def A31(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (3, 1), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 1), identity_pullback, H1), + ) + ) @pytest.fixture def A32(request): - return Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (3, 2), identity_pullback, H1))) + return Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (3, 2), identity_pullback, H1), + ) + ) def test_determinant0(A0): @@ -66,38 +87,42 @@ def test_determinant1(A1): def test_determinant2(A2): - assert determinant_expr(A2) == A2[0, 0]*A2[1, 1] - A2[0, 1]*A2[1, 0] + assert determinant_expr(A2) == A2[0, 0] * A2[1, 1] - A2[0, 1] * A2[1, 0] def test_determinant3(A3): - assert determinant_expr(A3) == (A3[0, 0]*(A3[1, 1]*A3[2, 2] - A3[1, 2]*A3[2, 1]) - + (A3[1, 0]*A3[2, 2] - A3[1, 2]*A3[2, 0])*(-A3[0, 1]) - + A3[0, 2]*(A3[1, 0]*A3[2, 1] - A3[1, 1]*A3[2, 0])) + assert determinant_expr(A3) == ( + A3[0, 0] * (A3[1, 1] * A3[2, 2] - A3[1, 2] * A3[2, 1]) + + (A3[1, 0] * A3[2, 2] - A3[1, 2] * A3[2, 0]) * (-A3[0, 1]) + + A3[0, 2] * (A3[1, 0] * A3[2, 1] - A3[1, 1] * A3[2, 0]) + ) def test_pseudo_determinant21(A21): i = Index() - assert renumber_indices(determinant_expr(A21)) == renumber_indices(sqrt(A21[i, 0]*A21[i, 0])) + assert renumber_indices(determinant_expr(A21)) == renumber_indices(sqrt(A21[i, 0] * A21[i, 0])) def test_pseudo_determinant31(A31): i = Index() - assert renumber_indices(determinant_expr(A31)) == renumber_indices(sqrt((A31[i, 0]*A31[i, 0]))) + assert renumber_indices(determinant_expr(A31)) == renumber_indices( + sqrt((A31[i, 0] * A31[i, 0])) + ) def test_pseudo_determinant32(A32): i = Index() c = cross_expr(A32[:, 0], A32[:, 1]) - assert renumber_indices(determinant_expr(A32)) == renumber_indices(sqrt(c[i]*c[i])) + assert renumber_indices(determinant_expr(A32)) == renumber_indices(sqrt(c[i] * c[i])) def test_inverse0(A0): - expected = 1.0/A0 # stays scalar + expected = 1.0 / A0 # stays scalar assert inverse_expr(A0) == renumber_indices(expected) def test_inverse1(A1): - expected = as_tensor(((1.0/A1[0, 0],),)) # reshaped into 1x1 tensor + expected = as_tensor(((1.0 / A1[0, 0],),)) # reshaped into 1x1 tensor assert inverse_expr(A1) == renumber_indices(expected) diff --git a/test/test_apply_function_pullbacks.py b/test/test_apply_function_pullbacks.py index 872cfdd88..a3c009a77 100755 --- a/test/test_apply_function_pullbacks.py +++ b/test/test_apply_function_pullbacks.py @@ -1,11 +1,17 @@ -import numpy +import numpy as np from ufl import Cell, Coefficient, FunctionSpace, Mesh, as_tensor, as_vector, dx, indices, triangle from ufl.algorithms.renumbering import renumber_indices from ufl.classes import Jacobian, JacobianDeterminant, JacobianInverse, ReferenceValue from ufl.finiteelement import FiniteElement, MixedElement, SymmetricElement -from ufl.pullback import (contravariant_piola, covariant_piola, double_contravariant_piola, double_covariant_piola, - identity_pullback, l2_piola) +from ufl.pullback import ( + contravariant_piola, + covariant_piola, + double_contravariant_piola, + double_covariant_piola, + identity_pullback, + l2_piola, +) from ufl.sobolevspace import H1, L2, HCurl, HDiv, HDivDiv, HEin @@ -13,7 +19,7 @@ def check_single_function_pullback(g, mappings): expected = mappings[g] actual = g.ufl_element().pullback.apply(ReferenceValue(g)) assert expected.ufl_shape == actual.ufl_shape - for idx in numpy.ndindex(actual.ufl_shape): + for idx in np.ndindex(actual.ufl_shape): rexp = renumber_indices(expected[idx]) ract = renumber_indices(actual[idx]) if not rexp == ract: @@ -26,26 +32,37 @@ def check_single_function_pullback(g, mappings): print("actual:") print(str(ract)) print("signatures:") - print((expected**2*dx).signature()) - print((actual**2*dx).signature()) + print((expected**2 * dx).signature()) + print((actual**2 * dx).signature()) print() assert ract == rexp def test_apply_single_function_pullbacks_triangle3d(): cell = Cell("triangle") - domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) UL2 = FiniteElement("Discontinuous Lagrange", cell, 1, (), l2_piola, L2) U0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) U = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - V = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) - Vd = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) - Vc = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + V = FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1) + Vd = FiniteElement("Raviart-Thomas", cell, 1, (2,), contravariant_piola, HDiv) + Vc = FiniteElement("N1curl", cell, 1, (2,), covariant_piola, HCurl) T = FiniteElement("Lagrange", cell, 1, (3, 3), identity_pullback, H1) S = SymmetricElement( - {(0, 0): 0, (1, 0): 1, (2, 0): 2, (0, 1): 1, (1, 1): 3, (2, 1): 4, (0, 2): 2, (1, 2): 4, (2, 2): 5}, - [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)]) + { + (0, 0): 0, + (1, 0): 1, + (2, 0): 2, + (0, 1): 1, + (1, 1): 3, + (2, 1): 4, + (0, 2): 2, + (1, 2): 4, + (2, 2): 5, + }, + [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)], + ) # (0, 2)-symmetric tensors COV2T = FiniteElement("Regge", cell, 0, (2, 2), double_covariant_piola, HEin) # (2, 0)-symmetric tensors @@ -122,7 +139,7 @@ def test_apply_single_function_pullbacks_triangle3d(): i, j, k, l = indices(4) # noqa: E741 # Contravariant H(div) Piola mapping: - M_hdiv = ((1.0/detJ) * J) # Not applying cell orientation here + M_hdiv = (1.0 / detJ) * J # Not applying cell orientation here # Covariant H(curl) Piola mapping: Jinv.T mappings = { @@ -130,82 +147,136 @@ def test_apply_single_function_pullbacks_triangle3d(): ul2: rul2 / detJ, u: ru, v: rv, - vd: as_vector(M_hdiv[i, j]*rvd[j], i), - vc: as_vector(Jinv[j, i]*rvc[j], i), + vd: as_vector(M_hdiv[i, j] * rvd[j], i), + vc: as_vector(Jinv[j, i] * rvc[j], i), t: rt, - s: as_tensor([[rs[0], rs[1], rs[2]], - [rs[1], rs[3], rs[4]], - [rs[2], rs[4], rs[5]]]), + s: as_tensor([[rs[0], rs[1], rs[2]], [rs[1], rs[3], rs[4]], [rs[2], rs[4], rs[5]]]), cov2t: as_tensor(Jinv[k, i] * rcov2t[k, l] * Jinv[l, j], (i, j)), - contra2t: as_tensor((1.0 / detJ)**2 - * J[i, k] * rcontra2t[k, l] * J[j, l], (i, j)), + contra2t: as_tensor((1.0 / detJ) ** 2 * J[i, k] * rcontra2t[k, l] * J[j, l], (i, j)), # Mixed elements become a bit more complicated uml2: as_vector([ruml2[0] / detJ, ruml2[1] / detJ]), um: rum, vm: rvm, - vdm: as_vector([ - # V - rvdm[0], - rvdm[1], - rvdm[2], - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rvdm[3], rvdm[4]])[j], (i,))[n] - for n in range(3)) - ]), vcm: as_vector([ - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] - for n in range(3)), - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] - for n in range(3)) - ]), tm: as_vector([ - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rtm[0], rtm[1]])[i], (j,))[n] - for n in range(3)), - # T - rtm[2], rtm[3], rtm[4], - rtm[5], rtm[6], rtm[7], - rtm[8], rtm[9], rtm[10], - ]), sm: as_vector([ - # T - rsm[0], rsm[1], rsm[2], - rsm[3], rsm[4], rsm[5], - rsm[6], rsm[7], rsm[8], - # S - rsm[9], rsm[10], rsm[11], - rsm[10], rsm[12], rsm[13], - rsm[11], rsm[13], rsm[14], - ]), + vdm: as_vector( + [ + # V + rvdm[0], + rvdm[1], + rvdm[2], + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rvdm[3], rvdm[4]])[j], (i,))[n] + for n in range(3) + ), + ] + ), + vcm: as_vector( + [ + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] + for n in range(3) + ), + # Vc + *( + as_tensor(Jinv[i, j] * as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] + for n in range(3) + ), + ] + ), + tm: as_vector( + [ + # Vc + *( + as_tensor(Jinv[i, j] * as_vector([rtm[0], rtm[1]])[i], (j,))[n] + for n in range(3) + ), + # T + rtm[2], + rtm[3], + rtm[4], + rtm[5], + rtm[6], + rtm[7], + rtm[8], + rtm[9], + rtm[10], + ] + ), + sm: as_vector( + [ + # T + rsm[0], + rsm[1], + rsm[2], + rsm[3], + rsm[4], + rsm[5], + rsm[6], + rsm[7], + rsm[8], + # S + rsm[9], + rsm[10], + rsm[11], + rsm[10], + rsm[12], + rsm[13], + rsm[11], + rsm[13], + rsm[14], + ] + ), # Case from failing ffc demo: - vd0m: as_vector([ - M_hdiv[0, j]*as_vector([rvd0m[0], rvd0m[1]])[j], - M_hdiv[1, j]*as_vector([rvd0m[0], rvd0m[1]])[j], - M_hdiv[2, j]*as_vector([rvd0m[0], rvd0m[1]])[j], - rvd0m[2] - ]), + vd0m: as_vector( + [ + M_hdiv[0, j] * as_vector([rvd0m[0], rvd0m[1]])[j], + M_hdiv[1, j] * as_vector([rvd0m[0], rvd0m[1]])[j], + M_hdiv[2, j] * as_vector([rvd0m[0], rvd0m[1]])[j], + rvd0m[2], + ] + ), # This combines it all: - w: as_vector([ - # S - rw[0], rw[1], rw[2], - rw[1], rw[3], rw[4], - rw[2], rw[4], rw[5], - # T - rw[6], rw[7], rw[8], - rw[9], rw[10], rw[11], - rw[12], rw[13], rw[14], - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rw[15], rw[16]])[i], (j,))[n] - for n in range(3)), - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rw[17], rw[18]])[j], (i,))[n] - for n in range(3)), - # V - rw[19], - rw[20], - rw[21], - # U - rw[22], - ]), + w: as_vector( + [ + # S + rw[0], + rw[1], + rw[2], + rw[1], + rw[3], + rw[4], + rw[2], + rw[4], + rw[5], + # T + rw[6], + rw[7], + rw[8], + rw[9], + rw[10], + rw[11], + rw[12], + rw[13], + rw[14], + # Vc + *( + as_tensor(Jinv[i, j] * as_vector([rw[15], rw[16]])[i], (j,))[n] + for n in range(3) + ), + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rw[17], rw[18]])[j], (i,))[n] + for n in range(3) + ), + # V + rw[19], + rw[20], + rw[21], + # U + rw[22], + ] + ), } # Check functions of various elements outside a mixed context @@ -234,16 +305,18 @@ def test_apply_single_function_pullbacks_triangle3d(): def test_apply_single_function_pullbacks_triangle(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) Ul2 = FiniteElement("Discontinuous Lagrange", cell, 1, (), l2_piola, L2) U = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) - Vd = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) - Vc = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + V = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) + Vd = FiniteElement("Raviart-Thomas", cell, 1, (2,), contravariant_piola, HDiv) + Vc = FiniteElement("N1curl", cell, 1, (2,), covariant_piola, HCurl) T = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) - S = SymmetricElement({(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, [ - FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for i in range(3)]) + S = SymmetricElement( + {(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, + [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for i in range(3)], + ) Uml2 = MixedElement([Ul2, Ul2]) Um = MixedElement([U, U]) @@ -303,7 +376,7 @@ def test_apply_single_function_pullbacks_triangle(): i, j, k, l = indices(4) # noqa: E741 # Contravariant H(div) Piola mapping: - M_hdiv = (1.0/detJ) * J + M_hdiv = (1.0 / detJ) * J # Covariant H(curl) Piola mapping: Jinv.T mappings = { @@ -311,66 +384,95 @@ def test_apply_single_function_pullbacks_triangle(): ul2: rul2 / detJ, u: ru, v: rv, - vd: as_vector(M_hdiv[i, j]*rvd[j], i), - vc: as_vector(Jinv[j, i]*rvc[j], i), + vd: as_vector(M_hdiv[i, j] * rvd[j], i), + vc: as_vector(Jinv[j, i] * rvc[j], i), t: rt, s: as_tensor([[rs[0], rs[1]], [rs[1], rs[2]]]), # Mixed elements become a bit more complicated uml2: as_vector([ruml2[0] / detJ, ruml2[1] / detJ]), um: rum, vm: rvm, - vdm: as_vector([ - # V - rvdm[0], - rvdm[1], - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rvdm[2], rvdm[3]])[j], (i,))[n] - for n in range(2)), - ]), - vcm: as_vector([ - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] - for n in range(2)), - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] - for n in range(2)), - ]), - tm: as_vector([ - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rtm[0], rtm[1]])[i], (j,))[n] - for n in range(2)), - # T - rtm[2], rtm[3], - rtm[4], rtm[5], - ]), - sm: as_vector([ - # T - rsm[0], rsm[1], - rsm[2], rsm[3], - # S - rsm[4], rsm[5], - rsm[5], rsm[6], - ]), + vdm: as_vector( + [ + # V + rvdm[0], + rvdm[1], + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rvdm[2], rvdm[3]])[j], (i,))[n] + for n in range(2) + ), + ] + ), + vcm: as_vector( + [ + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rvcm[0], rvcm[1]])[j], (i,))[n] + for n in range(2) + ), + # Vc + *( + as_tensor(Jinv[i, j] * as_vector([rvcm[2], rvcm[3]])[i], (j,))[n] + for n in range(2) + ), + ] + ), + tm: as_vector( + [ + # Vc + *( + as_tensor(Jinv[i, j] * as_vector([rtm[0], rtm[1]])[i], (j,))[n] + for n in range(2) + ), + # T + rtm[2], + rtm[3], + rtm[4], + rtm[5], + ] + ), + sm: as_vector( + [ + # T + rsm[0], + rsm[1], + rsm[2], + rsm[3], + # S + rsm[4], + rsm[5], + rsm[5], + rsm[6], + ] + ), # This combines it all: - w: as_vector([ - # S - rw[0], rw[1], - rw[1], rw[2], - # T - rw[3], rw[4], - rw[5], rw[6], - # Vc - *(as_tensor(Jinv[i, j]*as_vector([rw[7], rw[8]])[i], (j,))[n] - for n in range(2)), - # Vd - *(as_tensor(M_hdiv[i, j]*as_vector([rw[9], rw[10]])[j], (i,))[n] - for n in range(2)), - # V - rw[11], - rw[12], - # U - rw[13], - ]), + w: as_vector( + [ + # S + rw[0], + rw[1], + rw[1], + rw[2], + # T + rw[3], + rw[4], + rw[5], + rw[6], + # Vc + *(as_tensor(Jinv[i, j] * as_vector([rw[7], rw[8]])[i], (j,))[n] for n in range(2)), + # Vd + *( + as_tensor(M_hdiv[i, j] * as_vector([rw[9], rw[10]])[j], (i,))[n] + for n in range(2) + ), + # V + rw[11], + rw[12], + # U + rw[13], + ] + ), } # Check functions of various elements outside a mixed context diff --git a/test/test_apply_restrictions.py b/test/test_apply_restrictions.py index efb9ad514..61b370e00 100755 --- a/test/test_apply_restrictions.py +++ b/test/test_apply_restrictions.py @@ -1,6 +1,16 @@ from pytest import raises -from ufl import Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, as_tensor, grad, i, triangle +from ufl import ( + Coefficient, + FacetNormal, + FunctionSpace, + Mesh, + SpatialCoordinate, + as_tensor, + grad, + i, + triangle, +) from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions from ufl.algorithms.renumbering import renumber_indices from ufl.finiteelement import FiniteElement @@ -14,7 +24,7 @@ def test_apply_restrictions(): V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V2 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) @@ -31,23 +41,26 @@ def test_apply_restrictions(): # Continuous function gets default restriction if none # provided otherwise the user choice is respected - assert apply_restrictions(f) == f('+') - assert apply_restrictions(f('-')) == f('-') - assert apply_restrictions(f('+')) == f('+') + assert apply_restrictions(f) == f("+") + assert apply_restrictions(f("-")) == f("-") + assert apply_restrictions(f("+")) == f("+") # Propagation to terminals - assert apply_restrictions((f + f0)('+')) == f('+') + f0('+') + assert apply_restrictions((f + f0)("+")) == f("+") + f0("+") # Propagation stops at grad - assert apply_restrictions(grad(f)('-')) == grad(f)('-') - assert apply_restrictions((grad(f)**2)('+')) == grad(f)('+')**2 - assert apply_restrictions((grad(f) + grad(g))('-')) == (grad(f)('-') + grad(g)('-')) + assert apply_restrictions(grad(f)("-")) == grad(f)("-") + assert apply_restrictions((grad(f) ** 2)("+")) == grad(f)("+") ** 2 + assert apply_restrictions((grad(f) + grad(g))("-")) == (grad(f)("-") + grad(g)("-")) # x is the same from both sides but computed from one of them - assert apply_default_restrictions(x) == x('+') + assert apply_default_restrictions(x) == x("+") # n on a linear mesh is opposite pointing from the other side - assert apply_restrictions(n('+')) == n('+') - assert renumber_indices(apply_restrictions(n('-'))) == renumber_indices(as_tensor(-1*n('+')[i], i)) - # This would be nicer, but -f is translated to -1*f which is translated to as_tensor(-1*f[i], i). - # assert apply_restrictions(n('-')) == -n('+') + assert apply_restrictions(n("+")) == n("+") + assert renumber_indices(apply_restrictions(n("-"))) == renumber_indices( + as_tensor(-1 * n("+")[i], i) + ) + # This would be nicer, but -f is translated to -1*f which is + # translated to as_tensor(-1*f[i], i). assert + # apply_restrictions(n('-')) == -n('+') diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index 858f9ddd6..554eca967 100755 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -1,5 +1,17 @@ -from ufl import (Identity, Mesh, SpatialCoordinate, as_matrix, as_ufl, as_vector, elem_div, elem_mult, elem_op, sin, - tetrahedron, triangle) +from ufl import ( + Identity, + Mesh, + SpatialCoordinate, + as_matrix, + as_ufl, + as_vector, + elem_div, + elem_mult, + elem_op, + sin, + tetrahedron, + triangle, +) from ufl.classes import ComplexValue, Division, FloatValue, IntValue from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -19,13 +31,13 @@ def test_scalar_casting(self): def test_ufl_float_division(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) d = SpatialCoordinate(domain)[0] / 10.0 # TODO: Use mock instead of x self.assertIsInstance(d, Division) def test_float_ufl_division(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) d = 3.14 / SpatialCoordinate(domain)[0] # TODO: Use mock instead of x self.assertIsInstance(d, Division) @@ -68,7 +80,7 @@ def test_elem_mult(self): def test_elem_mult_on_matrices(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) A = as_matrix(((1, 2), (3, 4))) B = as_matrix(((4, 5), (6, 7))) @@ -77,7 +89,7 @@ def test_elem_mult_on_matrices(self): x, y = SpatialCoordinate(domain) A = as_matrix(((x, y), (3, 4))) B = as_matrix(((4, 5), (y, x))) - self.assertEqual(elem_mult(A, B), as_matrix(((4*x, 5*y), (3*y, 4*x)))) + self.assertEqual(elem_mult(A, B), as_matrix(((4 * x, 5 * y), (3 * y, 4 * x)))) x, y = SpatialCoordinate(domain) A = as_matrix(((x, y), (3, 4))) @@ -86,17 +98,18 @@ def test_elem_mult_on_matrices(self): def test_elem_div(self): - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) B = as_matrix(((7, 8, 9), (z, x, y))) - self.assertEqual(elem_div(A, B), as_matrix(((x/7, y/8, z/9), (3/z, 4/x, 5/y)))) + self.assertEqual(elem_div(A, B), as_matrix(((x / 7, y / 8, z / 9), (3 / z, 4 / x, 5 / y)))) def test_elem_op(self): - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) A = as_matrix(((x, y, z), (3, 4, 5))) - self.assertEqual(elem_op(sin, A), as_matrix(((sin(x), sin(y), sin(z)), - (sin(3), sin(4), sin(5))))) + self.assertEqual( + elem_op(sin, A), as_matrix(((sin(x), sin(y), sin(z)), (sin(3), sin(4), sin(5)))) + ) self.assertEqual(elem_op(sin, A).dx(0).ufl_shape, (2, 3)) diff --git a/test/test_automatic_differentiation.py b/test/test_automatic_differentiation.py index a1d080c76..45549ae6b 100755 --- a/test/test_automatic_differentiation.py +++ b/test/test_automatic_differentiation.py @@ -7,13 +7,76 @@ import pytest -from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, - MaxFacetEdgeLength, Mesh, MinCellEdgeLength, MinFacetEdgeLength, Not, Or, PermutationSymbol, - SpatialCoordinate, acos, as_matrix, as_tensor, as_ufl, as_vector, asin, atan, bessel_I, bessel_J, - bessel_K, bessel_Y, cofac, conditional, cos, cross, derivative, det, dev, diff, dot, eq, erf, exp, ge, - grad, gt, indices, inner, interval, inv, le, ln, lt, ne, outer, replace, sin, skew, sqrt, sym, tan, - tetrahedron, tr, triangle, variable) +from ufl import ( + And, + Argument, + CellDiameter, + CellVolume, + Circumradius, + Coefficient, + Constant, + FacetArea, + FacetNormal, + FunctionSpace, + Identity, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxCellEdgeLength, + MaxFacetEdgeLength, + Mesh, + MinCellEdgeLength, + MinFacetEdgeLength, + Not, + Or, + PermutationSymbol, + SpatialCoordinate, + acos, + as_matrix, + as_tensor, + as_ufl, + as_vector, + asin, + atan, + bessel_I, + bessel_J, + bessel_K, + bessel_Y, + cofac, + conditional, + cos, + cross, + derivative, + det, + dev, + diff, + dot, + eq, + erf, + exp, + ge, + grad, + gt, + indices, + inner, + interval, + inv, + le, + ln, + lt, + ne, + outer, + replace, + sin, + skew, + sqrt, + sym, + tan, + tetrahedron, + tr, + triangle, + variable, +) from ufl.algorithms import expand_derivatives from ufl.conditional import Conditional from ufl.corealg.traversal import unique_post_traversal @@ -23,11 +86,10 @@ class ExpressionCollection(object): - def __init__(self, cell): self.cell = cell d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) x = SpatialCoordinate(domain) n = FacetNormal(domain) @@ -49,7 +111,7 @@ def __init__(self, cell): eps = PermutationSymbol(d) U = FiniteElement("Undefined", cell, None, (), identity_pullback, L2) - V = FiniteElement("Undefined", cell, None, (d, ), identity_pullback, L2) + V = FiniteElement("Undefined", cell, None, (d,), identity_pullback, L2) W = FiniteElement("Undefined", cell, None, (d, d), identity_pullback, L2) u_space = FunctionSpace(domain, U) @@ -65,6 +127,7 @@ def __init__(self, cell): class ObjectCollection(object): pass + self.shared_objects = ObjectCollection() for key, value in list(locals().items()): setattr(self.shared_objects, key, value) @@ -78,51 +141,84 @@ class ObjectCollection(object): self.terminals += self.geometry self.terminals += self.functions - self.algebra = ([ - u*2, v*2, w*2, - u+2*u, v+2*v, w+2*w, - 2/u, u/2, v/2, w/2, - u**3, 3**u, - ]) - self.mathfunctions = ([ - abs(u), sqrt(u), exp(u), ln(u), - cos(u), sin(u), tan(u), acos(u), asin(u), atan(u), - erf(u), bessel_I(1, u), bessel_J(1, u), bessel_K(1, u), bessel_Y(1, u), - ]) - self.variables = ([ - variable(u), variable(v), variable(w), - variable(w*u), 3*variable(w*u), - ]) + self.algebra = [ + u * 2, + v * 2, + w * 2, + u + 2 * u, + v + 2 * v, + w + 2 * w, + 2 / u, + u / 2, + v / 2, + w / 2, + u**3, + 3**u, + ] + self.mathfunctions = [ + abs(u), + sqrt(u), + exp(u), + ln(u), + cos(u), + sin(u), + tan(u), + acos(u), + asin(u), + atan(u), + erf(u), + bessel_I(1, u), + bessel_J(1, u), + bessel_K(1, u), + bessel_Y(1, u), + ] + self.variables = [ + variable(u), + variable(v), + variable(w), + variable(w * u), + 3 * variable(w * u), + ] if d == 1: w2 = as_matrix(((u**2,),)) if d == 2: - w2 = as_matrix(((u**2, u**3), - (u**4, u**5))) + w2 = as_matrix(((u**2, u**3), (u**4, u**5))) if d == 3: - w2 = as_matrix(((u**2, u**3, u**4), - (u**4, u**5, u**6), - (u**6, u**7, u**8))) + w2 = as_matrix(((u**2, u**3, u**4), (u**4, u**5, u**6), (u**6, u**7, u**8))) # Indexed, ListTensor, ComponentTensor, IndexSum i, j, k, l = indices(4) # noqa: E741 - self.indexing = ([ - v[0], w[d-1, 0], v[i], w[i, j], - v[:], w[0, :], w[:, 0], - v[...], w[0, ...], w[..., 0], - v[i]*v[j], w[i, 0]*v[j], w[d-1, j]*v[i], - v[i]*v[i], w[i, 0]*w[0, i], v[i]*w[0, i], - v[j]*w[d-1, j], w[i, i], w[i, j]*w[j, i], - as_tensor(v[i]*w[k, 0], (k, i)), - as_tensor(v[i]*w[k, 0], (k, i))[:, l], - as_tensor(w[i, j]*w[k, l], (k, j, l, i)), - as_tensor(w[i, j]*w[k, l], (k, j, l, i))[0, 0, 0, 0], - as_vector((u, 2, 3)), - as_matrix(((u**2, u**3), (u**4, u**5))), - as_vector((u, 2, 3))[i], - w2[i, j]*w[i, j], - ]) - self.conditionals = ([ + self.indexing = [ + v[0], + w[d - 1, 0], + v[i], + w[i, j], + v[:], + w[0, :], + w[:, 0], + v[...], + w[0, ...], + w[..., 0], + v[i] * v[j], + w[i, 0] * v[j], + w[d - 1, j] * v[i], + v[i] * v[i], + w[i, 0] * w[0, i], + v[i] * w[0, i], + v[j] * w[d - 1, j], + w[i, i], + w[i, j] * w[j, i], + as_tensor(v[i] * w[k, 0], (k, i)), + as_tensor(v[i] * w[k, 0], (k, i))[:, l], + as_tensor(w[i, j] * w[k, l], (k, j, l, i)), + as_tensor(w[i, j] * w[k, l], (k, j, l, i))[0, 0, 0, 0], + as_vector((u, 2, 3)), + as_matrix(((u**2, u**3), (u**4, u**5))), + as_vector((u, 2, 3))[i], + w2[i, j] * w[i, j], + ] + self.conditionals = [ conditional(le(u, 1.0), 1, 0), conditional(eq(3.0, u), 1, 0), conditional(ne(sin(u), cos(u)), 1, 0), @@ -136,16 +232,16 @@ class ObjectCollection(object): conditional(Not(ge(u, 0.0)), 1, 2), conditional(And(Not(ge(u, 0.0)), lt(u, 1.0)), 1, 2), conditional(le(u, 0.0), u**3, ln(u)), - ]) - self.restrictions = [u('+'), u('-'), v('+'), v('-'), w('+'), w('-')] + ] + self.restrictions = [u("+"), u("-"), v("+"), v("-"), w("+"), w("-")] if d > 1: i, j = indices(2) - self.restrictions += ([ - v('+')[i]*v('+')[i], - v[i]('+')*v[i]('+'), - (v[i]*v[i])('+'), - (v[i]*v[j])('+')*w[i, j]('+'), - ]) + self.restrictions += [ + v("+")[i] * v("+")[i], + v[i]("+") * v[i]("+"), + (v[i] * v[i])("+"), + (v[i] * v[j])("+") * w[i, j]("+"), + ] self.noncompounds = [] self.noncompounds += self.algebra @@ -158,7 +254,7 @@ class ObjectCollection(object): if d == 1: self.tensorproducts = [] else: - self.tensorproducts = ([ + self.tensorproducts = [ dot(v, v), dot(v, w), dot(w, w), @@ -168,26 +264,32 @@ class ObjectCollection(object): outer(w, v), outer(v, w), outer(w, w), - ]) + ] if d == 1: self.tensoralgebra = [] else: - self.tensoralgebra = ([ - w.T, sym(w), skew(w), dev(w), - det(w), tr(w), cofac(w), inv(w), - ]) + self.tensoralgebra = [ + w.T, + sym(w), + skew(w), + dev(w), + det(w), + tr(w), + cofac(w), + inv(w), + ] if d != 3: self.crossproducts = [] else: - self.crossproducts = ([ + self.crossproducts = [ cross(v, v), - cross(v, 2*v), + cross(v, 2 * v), cross(v, w[0, :]), cross(v, w[:, 1]), cross(w[:, 0], v), - ]) + ] self.compounds = [] self.compounds += self.tensorproducts @@ -220,31 +322,36 @@ def ad_algorithm(expr): expr, apply_expand_compounds_before=True, apply_expand_compounds_after=False, - use_alternative_wrapper_algorithm=True) + use_alternative_wrapper_algorithm=True, + ) elif alt == 2: return expand_derivatives( expr, apply_expand_compounds_before=False, apply_expand_compounds_after=True, - use_alternative_wrapper_algorithm=False) + use_alternative_wrapper_algorithm=False, + ) elif alt == 3: return expand_derivatives( expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, - use_alternative_wrapper_algorithm=False) + use_alternative_wrapper_algorithm=False, + ) elif alt == 4: return expand_derivatives( expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, - use_alternative_wrapper_algorithm=True) + use_alternative_wrapper_algorithm=True, + ) elif alt == 5: return expand_derivatives( expr, apply_expand_compounds_before=False, apply_expand_compounds_after=False, - use_alternative_wrapper_algorithm=False) + use_alternative_wrapper_algorithm=False, + ) def _test_no_derivatives_no_change(self, collection): @@ -277,7 +384,9 @@ def test_no_derivatives_no_change(self, d_expr): _test_no_derivatives_no_change(self, ex.noncompounds) -def xtest_compounds_no_derivatives_no_change(self, d_expr): # This test fails with expand_compounds enabled +def xtest_compounds_no_derivatives_no_change( + self, d_expr +): # This test fails with expand_compounds enabled d, ex = d_expr _test_no_derivatives_no_change(self, ex.compounds) @@ -298,13 +407,13 @@ def _test_zero_derivatives_of_terminals_produce_the_right_types_and_shapes(self, for var in (u, v, w): before = derivative(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) - expected = 0*t + expected = 0 * t # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected - before = derivative(c*t, var) # This will usually not get simplified to zero + before = derivative(c * t, var) # This will usually not get simplified to zero after = ad_algorithm(before) - expected = 0*t + expected = 0 * t # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected @@ -328,13 +437,13 @@ def _test_zero_diffs_of_terminals_produce_the_right_types_and_shapes(self, colle for var in (vu, vv, vw): before = diff(t, var) # This will often get preliminary simplified to zero after = ad_algorithm(before) - expected = 0*outer(t, var) + expected = 0 * outer(t, var) # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected - before = diff(c*t, var) # This will usually not get simplified to zero + before = diff(c * t, var) # This will usually not get simplified to zero after = ad_algorithm(before) - expected = 0*outer(t, var) + expected = 0 * outer(t, var) # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected @@ -356,22 +465,22 @@ def _test_zero_derivatives_of_noncompounds_produce_the_right_types_and_shapes(se for t in collection.noncompounds: for var in (u, v, w): if debug: - print('\n', 'shapes: ', t.ufl_shape, var.ufl_shape, '\n') + print("\n", "shapes: ", t.ufl_shape, var.ufl_shape, "\n") if debug: - print('\n', 't: ', str(t), '\n') + print("\n", "t: ", str(t), "\n") if debug: - print('\n', 't ind: ', str(t.ufl_free_indices), '\n') + print("\n", "t ind: ", str(t.ufl_free_indices), "\n") if debug: - print('\n', 'var: ', str(var), '\n') + print("\n", "var: ", str(var), "\n") before = derivative(t, var) if debug: - print('\n', 'before: ', str(before), '\n') + print("\n", "before: ", str(before), "\n") after = ad_algorithm(before) if debug: - print('\n', 'after: ', str(after), '\n') - expected = 0*t + print("\n", "after: ", str(after), "\n") + expected = 0 * t if debug: - print('\n', 'expected: ', str(expected), '\n') + print("\n", "expected: ", str(expected), "\n") assert after == expected @@ -396,13 +505,13 @@ def _test_zero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, co for var in (vu, vv, vw): before = diff(t, var) if debug: - print('\n', 'before: ', str(before), '\n') + print("\n", "before: ", str(before), "\n") after = ad_algorithm(before) if debug: - print('\n', 'after: ', str(after), '\n') - expected = 0*outer(t, var) + print("\n", "after: ", str(after), "\n") + expected = 0 * outer(t, var) if debug: - print('\n', 'expected: ', str(expected), '\n') + print("\n", "expected: ", str(expected), "\n") # print '\n', str(expected), '\n', str(after), '\n', str(before), '\n' assert after == expected @@ -429,16 +538,16 @@ def _test_nonzero_derivatives_of_noncompounds_produce_the_right_types_and_shapes continue if debug: - print(('\n', '...: ', t.ufl_shape, var.ufl_shape, '\n')) + print(("\n", "...: ", t.ufl_shape, var.ufl_shape, "\n")) before = derivative(t, var) if debug: - print(('\n', 'before: ', str(before), '\n')) + print(("\n", "before: ", str(before), "\n")) after = ad_algorithm(before) if debug: - print(('\n', 'after: ', str(after), '\n')) - expected_shape = 0*t + print(("\n", "after: ", str(after), "\n")) + expected_shape = 0 * t if debug: - print(('\n', 'expected_shape: ', str(expected_shape), '\n')) + print(("\n", "expected_shape: ", str(expected_shape), "\n")) # print '\n', str(expected_shape), '\n', str(after), '\n', str(before), '\n' if var in unique_post_traversal(t): @@ -475,13 +584,13 @@ def _test_nonzero_diffs_of_noncompounds_produce_the_right_types_and_shapes(self, before = diff(t, var) if debug: - print(('\n', 'before: ', str(before), '\n')) + print(("\n", "before: ", str(before), "\n")) after = ad_algorithm(before) if debug: - print(('\n', 'after: ', str(after), '\n')) - expected_shape = 0*outer(t, var) # expected shape, not necessarily value + print(("\n", "after: ", str(after), "\n")) + expected_shape = 0 * outer(t, var) # expected shape, not necessarily value if debug: - print(('\n', 'expected_shape: ', str(expected_shape), '\n')) + print(("\n", "expected_shape: ", str(expected_shape), "\n")) # print '\n', str(expected_shape), '\n', str(after), '\n', str(before), '\n' if var in unique_post_traversal(t): @@ -502,8 +611,8 @@ def test_grad_coeff(self, d_expr): after = ad_algorithm(before) if before.ufl_shape != after.ufl_shape: - print(('\n', 'shapes:', before.ufl_shape, after.ufl_shape)) - print(('\n', str(before), '\n', str(after), '\n')) + print(("\n", "shapes:", before.ufl_shape, after.ufl_shape)) + print(("\n", str(before), "\n", str(after), "\n")) self.assertEqualTotalShape(before, after) if f is u: # Differing by being wrapped in indexing types @@ -543,8 +652,8 @@ def test_derivative_grad_coeff(self, d_expr): # assert after == expected if 0: print() - print(('B', f, "::", before)) - print(('A', f, "::", after)) + print(("B", f, "::", before)) + print(("A", f, "::", after)) def xtest_derivative_grad_coeff_with_variation_components(self, d_expr): @@ -556,7 +665,7 @@ def xtest_derivative_grad_coeff_with_variation_components(self, d_expr): dw = collection.shared_objects.dw for g, dg in ((v, dv), (w, dw)): # Pick a single component - ii = (0,)*(len(g.ufl_shape)) + ii = (0,) * (len(g.ufl_shape)) f = g[ii] df = dg[ii] @@ -576,5 +685,5 @@ def xtest_derivative_grad_coeff_with_variation_components(self, d_expr): # assert after == expected if 0: print() - print(('B', f, "::", before)) - print(('A', f, "::", after)) + print(("B", f, "::", before)) + print(("A", f, "::", after)) diff --git a/test/test_change_to_local.py b/test/test_change_to_local.py index f0789fbba..973195bc7 100755 --- a/test/test_change_to_local.py +++ b/test/test_change_to_local.py @@ -11,15 +11,15 @@ def test_change_to_reference_grad(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) U = FunctionSpace(domain, FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1)) - V = FunctionSpace(domain, FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + V = FunctionSpace(domain, FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) u = Coefficient(U) v = Coefficient(V) Jinv = JacobianInverse(domain) i, j, k = indices(3) q, r, s = indices(3) - t, = indices(1) + (t,) = indices(1) # Single grad change on a scalar function expr = grad(u) @@ -36,27 +36,33 @@ def test_change_to_reference_grad(): # Multiple grads should work fine for affine domains: expr = grad(grad(u)) actual = change_to_reference_grad(expr) - expected = as_tensor( - Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(u))[r, s]), (i, j)) + expected = as_tensor(Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(u))[r, s]), (i, j)) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(u))) actual = change_to_reference_grad(expr) expected = as_tensor( - Jinv[s, k] * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(u)))[q, r, s])), (i, j, k)) + Jinv[s, k] + * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(u)))[q, r, s])), + (i, j, k), + ) assert renumber_indices(actual) == renumber_indices(expected) # Multiple grads on a vector valued function expr = grad(grad(v)) actual = change_to_reference_grad(expr) expected = as_tensor( - Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(v))[t, r, s]), (t, i, j)) + Jinv[s, j] * (Jinv[r, i] * ReferenceGrad(ReferenceGrad(v))[t, r, s]), (t, i, j) + ) assert renumber_indices(actual) == renumber_indices(expected) expr = grad(grad(grad(v))) actual = change_to_reference_grad(expr) - expected = as_tensor(Jinv[s, k] * (Jinv[r, j] * ( - Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), (t, i, j, k)) + expected = as_tensor( + Jinv[s, k] + * (Jinv[r, j] * (Jinv[q, i] * ReferenceGrad(ReferenceGrad(ReferenceGrad(v)))[t, q, r, s])), + (t, i, j, k), + ) assert renumber_indices(actual) == renumber_indices(expected) # print tree_format(expected) diff --git a/test/test_change_to_reference_frame.py b/test/test_change_to_reference_frame.py index 9b46b510c..63b0751b3 100755 --- a/test/test_change_to_reference_frame.py +++ b/test/test_change_to_reference_frame.py @@ -14,10 +14,10 @@ def change_to_reference_frame(expr): def test_change_unmapped_form_arguments_to_reference_frame(): U = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) T = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) u_space = FunctionSpace(domain, U) v_space = FunctionSpace(domain, V) t_space = FunctionSpace(domain, T) @@ -31,9 +31,9 @@ def test_change_unmapped_form_arguments_to_reference_frame(): def test_change_hdiv_form_arguments_to_reference_frame(): - V = FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv) + V = FiniteElement("Raviart-Thomas", triangle, 1, (2,), contravariant_piola, HDiv) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) expr = Coefficient(v_space) @@ -41,15 +41,15 @@ def test_change_hdiv_form_arguments_to_reference_frame(): def test_change_hcurl_form_arguments_to_reference_frame(): - V = FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv) + V = FiniteElement("Raviart-Thomas", triangle, 1, (2,), contravariant_piola, HDiv) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) expr = Coefficient(v_space) assert change_to_reference_frame(expr) == ReferenceValue(expr) - ''' + """ # user input grad(f + g)('+') # change to reference frame @@ -121,10 +121,10 @@ def test_change_hcurl_form_arguments_to_reference_frame(): e = v | cell_avg(v) | facet_avg(v) | at_cell_midpoint(v) | at_facet_midpoint(v) # evaluated at point or averaged over cell entity m = e | indexed(e) # scalar component of - ''' + """ -''' +""" New form preprocessing pipeline: Preferably introduce these changes: @@ -139,7 +139,8 @@ def test_change_hcurl_form_arguments_to_reference_frame(): b) lower_compound_operators # expand_compounds c) change_to_reference_frame # change f->rv(f), m->M*rv(m), grad(f)->K*rgrad(rv(f)), - grad(grad(f))->K*rgrad(K*rgrad(rv(f))), grad(expr)->K*rgrad(expr) + grad(grad(f))->K*rgrad(K*rgrad(rv(f))), + grad(expr)->K*rgrad(expr) # if grad(expr)->K*rgrad(expr) should be valid, then rgrad must be applicable to quite generic expressions d) apply_derivatives # one possibility is to add an apply_mapped_derivatives AD @@ -147,4 +148,4 @@ def test_change_hcurl_form_arguments_to_reference_frame(): e) apply_geometry_lowering f) apply_restrictions # requiring grad(f)('+') instead of grad(f('+')) would simplify a lot... iii) extract final metadata about elements and coefficient ordering -''' +""" diff --git a/test/test_check_arities.py b/test/test_check_arities.py index 37ce7a26d..e4ede949f 100755 --- a/test/test_check_arities.py +++ b/test/test_check_arities.py @@ -1,7 +1,23 @@ import pytest -from ufl import (Coefficient, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, TrialFunction, adjoint, - cofac, conj, derivative, ds, dx, grad, inner, tetrahedron) +from ufl import ( + Coefficient, + FacetNormal, + FunctionSpace, + Mesh, + SpatialCoordinate, + TestFunction, + TrialFunction, + adjoint, + cofac, + conj, + derivative, + ds, + dx, + grad, + inner, + tetrahedron, +) from ufl.algorithms.check_arities import ArityMismatch from ufl.algorithms.compute_form_data import compute_form_data from ufl.finiteelement import FiniteElement @@ -12,8 +28,8 @@ def test_check_arities(): # Code from bitbucket issue #49 cell = tetrahedron - D = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) - V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) + V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3,), identity_pullback, H1)) dv = TestFunction(V) du = TrialFunction(V) @@ -36,8 +52,8 @@ def test_check_arities(): def test_complex_arities(): cell = tetrahedron - D = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) - V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) + V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3,), identity_pullback, H1)) v = TestFunction(V) u = TrialFunction(V) diff --git a/test/test_classcoverage.py b/test/test_classcoverage.py index caeff1b91..522e2f999 100755 --- a/test/test_classcoverage.py +++ b/test/test_classcoverage.py @@ -2,19 +2,113 @@ __date__ = "2008-09-06 -- 2009-02-10" import ufl -from ufl import * # noqa: F403, F401 -from ufl import (And, Argument, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FunctionSpace, Identity, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, - MinFacetEdgeLength, Not, Or, PermutationSymbol, SpatialCoordinate, TensorConstant, VectorConstant, - acos, action, as_matrix, as_tensor, as_ufl, as_vector, asin, atan, cell_avg, cofac, conditional, cos, - cosh, cross, curl, derivative, det, dev, diff, div, dot, ds, dS, dx, eq, exp, facet_avg, ge, grad, gt, - i, inner, inv, j, k, l, le, ln, lt, nabla_div, nabla_grad, ne, outer, rot, sin, sinh, skew, sqrt, sym, - tan, tanh, tetrahedron, tr, transpose, triangle, variable) -from ufl.algorithms import * # noqa: F403, F401 -from ufl.classes import * # noqa: F403, F401 -from ufl.classes import (Acos, Asin, Atan, CellCoordinate, Cos, Cosh, Exp, Expr, FacetJacobian, - FacetJacobianDeterminant, FacetJacobianInverse, FloatValue, IntValue, Ln, Outer, Sin, Sinh, - Sqrt, Tan, Tanh, all_ufl_classes) +from ufl import * # noqa: F403 +from ufl import ( + And, + Argument, + CellDiameter, + CellVolume, + Circumradius, + Coefficient, + Constant, + FacetArea, + FacetNormal, + FunctionSpace, + Identity, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxFacetEdgeLength, + Mesh, + MinFacetEdgeLength, + Not, + Or, + PermutationSymbol, + SpatialCoordinate, + TensorConstant, + VectorConstant, + acos, + action, + as_matrix, + as_tensor, + as_ufl, + as_vector, + asin, + atan, + cell_avg, + cofac, + conditional, + cos, + cosh, + cross, + curl, + derivative, + det, + dev, + diff, + div, + dot, + dS, + ds, + dx, + eq, + exp, + facet_avg, + ge, + grad, + gt, + i, + inner, + inv, + j, + k, + l, + le, + ln, + lt, + nabla_div, + nabla_grad, + ne, + outer, + rot, + sin, + sinh, + skew, + sqrt, + sym, + tan, + tanh, + tetrahedron, + tr, + transpose, + triangle, + variable, +) +from ufl.algorithms import * # noqa: F403 +from ufl.classes import * # noqa: F403 +from ufl.classes import ( + Acos, + Asin, + Atan, + CellCoordinate, + Cos, + Cosh, + Exp, + Expr, + FacetJacobian, + FacetJacobianDeterminant, + FacetJacobianInverse, + FloatValue, + IntValue, + Ln, + Outer, + Sin, + Sinh, + Sqrt, + Tan, + Tanh, + all_ufl_classes, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -25,9 +119,9 @@ def _test_object(a, shape, free_indices): # Check if instances of this type has certain memory consuming members - if hasattr(a, '_repr'): + if hasattr(a, "_repr"): has_repr.add(a.__class__.__name__) - if hasattr(a, '__dict__'): + if hasattr(a, "__dict__"): has_dict.add(a.__class__.__name__) # Test reproduction via repr string @@ -60,9 +154,9 @@ def _test_object(a, shape, free_indices): def _test_object2(a): # Check if instances of this type has certain memory consuming members - if hasattr(a, '_repr'): + if hasattr(a, "_repr"): has_repr.add(a.__class__.__name__) - if hasattr(a, '__dict__'): + if hasattr(a, "__dict__"): has_dict.add(a.__class__.__name__) # Test reproduction via repr string @@ -92,8 +186,9 @@ def testExports(self): for c in list(vars(m).values()): if isinstance(c, type) and issubclass(c, Expr): all_expr_classes.append(c) - missing_classes = set(c.__name__ for c in all_expr_classes)\ - - set(c.__name__ for c in all_ufl_classes) + missing_classes = set(c.__name__ for c in all_expr_classes) - set( + c.__name__ for c in all_ufl_classes + ) if missing_classes: print("The following subclasses of Expr were not exported from ufl.classes:") print(("\n".join(sorted(missing_classes)))) @@ -101,7 +196,6 @@ def testExports(self): def testAll(self): - Expr.ufl_enable_profiling() # --- Elements: @@ -109,14 +203,14 @@ def testAll(self): dim = 2 e0 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - e1 = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + e1 = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) e2 = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) e3 = MixedElement([e0, e1, e2]) - e13D = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) + e13D = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (dim, ), identity_pullback, H1)) - domain3D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (dim,), identity_pullback, H1)) + domain3D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) e0_space = FunctionSpace(domain, e0) e1_space = FunctionSpace(domain, e1) e2_space = FunctionSpace(domain, e2) @@ -136,7 +230,7 @@ def testAll(self): _test_object(v0, (), ()) _test_object(v1, (dim,), ()) _test_object(v2, (dim, dim), ()) - _test_object(v3, (1 + dim + dim ** 2, ), ()) + _test_object(v3, (1 + dim + dim**2,), ()) f0 = Coefficient(e0_space) f1 = Coefficient(e1_space) @@ -146,7 +240,7 @@ def testAll(self): _test_object(f0, (), ()) _test_object(f1, (dim,), ()) _test_object(f2, (dim, dim), ()) - _test_object(f3, (1 + dim + dim ** 2, ), ()) + _test_object(f3, (1 + dim + dim**2,), ()) c = Constant(domain) _test_object(c, (), ()) @@ -193,11 +287,11 @@ def testAll(self): _test_object(g, (dim, dim), ()) g = FacetJacobian(domain) - _test_object(g, (dim, dim-1), ()) + _test_object(g, (dim, dim - 1), ()) g = FacetJacobianDeterminant(domain) _test_object(g, (), ()) g = FacetJacobianInverse(domain) - _test_object(g, (dim-1, dim), ()) + _test_object(g, (dim - 1, dim), ()) g = FacetNormal(domain) _test_object(g, (dim,), ()) @@ -229,7 +323,7 @@ def testAll(self): a = variable(v2) _test_object(a, (dim, dim), ()) a = variable(v3) - _test_object(a, (1 + dim + dim ** 2, ), ()) + _test_object(a, (1 + dim + dim**2,), ()) a = variable(f0) _test_object(a, (), ()) a = variable(f1) @@ -237,7 +331,7 @@ def testAll(self): a = variable(f2) _test_object(a, (dim, dim), ()) a = variable(f3) - _test_object(a, (1 + dim + dim ** 2, ), ()) + _test_object(a, (1 + dim + dim**2,), ()) # a = MultiIndex() @@ -290,7 +384,7 @@ def testAll(self): a = v2 + f2 + v2 _test_object(a, (dim, dim), ()) # a = Product() - a = 3*v0*(2.0*v0)*f0*(v0*3.0) + a = 3 * v0 * (2.0 * v0) * f0 * (v0 * 3.0) _test_object(a, (), ()) # a = Division() a = v0 / 2.0 @@ -302,78 +396,76 @@ def testAll(self): # a = Power() a = f0**3 _test_object(a, (), ()) - a = (f0*2)**1.23 + a = (f0 * 2) ** 1.23 _test_object(a, (), ()) # a = ListTensor() - a = as_vector([1.0, 2.0*f0, f0**2]) + a = as_vector([1.0, 2.0 * f0, f0**2]) _test_object(a, (3,), ()) - a = as_matrix([[1.0, 2.0*f0, f0**2], - [1.0, 2.0*f0, f0**2]]) + a = as_matrix([[1.0, 2.0 * f0, f0**2], [1.0, 2.0 * f0, f0**2]]) _test_object(a, (2, 3), ()) - a = as_tensor([[[0.00, 0.01, 0.02], - [0.10, 0.11, 0.12]], - [[1.00, 1.01, 1.02], - [1.10, 1.11, 1.12]]]) + a = as_tensor( + [[[0.00, 0.01, 0.02], [0.10, 0.11, 0.12]], [[1.00, 1.01, 1.02], [1.10, 1.11, 1.12]]] + ) _test_object(a, (2, 2, 3), ()) # a = ComponentTensor() - a = as_vector(v1[i]*f1[j], i) + a = as_vector(v1[i] * f1[j], i) _test_object(a, (dim,), (j,)) - a = as_matrix(v1[i]*f1[j], (j, i)) + a = as_matrix(v1[i] * f1[j], (j, i)) _test_object(a, (dim, dim), ()) - a = as_tensor(v1[i]*f1[j], (i, j)) + a = as_tensor(v1[i] * f1[j], (i, j)) _test_object(a, (dim, dim), ()) - a = as_tensor(v2[i, j]*f2[j, k], (i, k)) + a = as_tensor(v2[i, j] * f2[j, k], (i, k)) _test_object(a, (dim, dim), ()) a = dev(v2) _test_object(a, (dim, dim), ()) a = dev(f2) _test_object(a, (dim, dim), ()) - a = dev(f2*f0+v2*3) + a = dev(f2 * f0 + v2 * 3) _test_object(a, (dim, dim), ()) a = sym(v2) _test_object(a, (dim, dim), ()) a = sym(f2) _test_object(a, (dim, dim), ()) - a = sym(f2*f0+v2*3) + a = sym(f2 * f0 + v2 * 3) _test_object(a, (dim, dim), ()) a = skew(v2) _test_object(a, (dim, dim), ()) a = skew(f2) _test_object(a, (dim, dim), ()) - a = skew(f2*f0+v2*3) + a = skew(f2 * f0 + v2 * 3) _test_object(a, (dim, dim), ()) a = v2.T _test_object(a, (dim, dim), ()) a = f2.T _test_object(a, (dim, dim), ()) - a = transpose(f2*f0+v2*3) + a = transpose(f2 * f0 + v2 * 3) _test_object(a, (dim, dim), ()) a = det(v2) _test_object(a, (), ()) a = det(f2) _test_object(a, (), ()) - a = det(f2*f0+v2*3) + a = det(f2 * f0 + v2 * 3) _test_object(a, (), ()) a = tr(v2) _test_object(a, (), ()) a = tr(f2) _test_object(a, (), ()) - a = tr(f2*f0+v2*3) + a = tr(f2 * f0 + v2 * 3) _test_object(a, (), ()) a = cofac(v2) _test_object(a, (dim, dim), ()) a = cofac(f2) _test_object(a, (dim, dim), ()) - a = cofac(f2*f0+v2*3) + a = cofac(f2 * f0 + v2 * 3) _test_object(a, (dim, dim), ()) cond1 = le(f0, 1.0) @@ -468,7 +560,7 @@ def testAll(self): s0 = variable(f0) s1 = variable(f1) s2 = variable(f2) - f = dot(s0*s1, s2) + f = dot(s0 * s1, s2) _test_object(s0, (), ()) _test_object(s1, (dim,), ()) _test_object(s2, (dim, dim), ()) @@ -477,7 +569,14 @@ def testAll(self): a = diff(f, s0) _test_object(a, (dim,), ()) a = diff(f, s1) - _test_object(a, (dim, dim,), ()) + _test_object( + a, + ( + dim, + dim, + ), + (), + ) a = diff(f, s2) _test_object(a, (dim, dim, dim), ()) @@ -500,9 +599,9 @@ def testAll(self): _test_object(a, (dim, dim), ()) a = grad(f1) _test_object(a, (dim, dim), ()) - a = grad(f0*v0) + a = grad(f0 * v0) _test_object(a, (dim,), ()) - a = grad(f0*v1) + a = grad(f0 * v1) _test_object(a, (dim, dim), ()) a = nabla_div(v1) @@ -524,9 +623,9 @@ def testAll(self): _test_object(a, (dim, dim), ()) a = nabla_grad(f1) _test_object(a, (dim, dim), ()) - a = nabla_grad(f0*v0) + a = nabla_grad(f0 * v0) _test_object(a, (dim,), ()) - a = nabla_grad(f0*v1) + a = nabla_grad(f0 * v1) _test_object(a, (dim, dim), ()) a = curl(v13D) @@ -540,16 +639,16 @@ def testAll(self): # a = PositiveRestricted(v0) # _test_object(a, (), ()) - a = v0('+') + a = v0("+") _test_object(a, (), ()) - a = v0('+')*f0 + a = v0("+") * f0 _test_object(a, (), ()) # a = NegativeRestricted(v0) # _test_object(a, (), ()) - a = v0('-') + a = v0("-") _test_object(a, (), ()) - a = v0('-') + f0 + a = v0("-") + f0 _test_object(a, (), ()) a = cell_avg(v0) @@ -567,47 +666,47 @@ def testAll(self): # --- Integrals: - a = v0*dx + a = v0 * dx _test_form(a) - a = v0*dx(0) + a = v0 * dx(0) _test_form(a) - a = v0*dx(1) + a = v0 * dx(1) _test_form(a) - a = v0*ds + a = v0 * ds _test_form(a) - a = v0*ds(0) + a = v0 * ds(0) _test_form(a) - a = v0*ds(1) + a = v0 * ds(1) _test_form(a) - a = v0*dS + a = v0 * dS _test_form(a) - a = v0*dS(0) + a = v0 * dS(0) _test_form(a) - a = v0*dS(1) + a = v0 * dS(1) _test_form(a) - a = v0*dot(v1, f1)*dx + a = v0 * dot(v1, f1) * dx _test_form(a) - a = v0*dot(v1, f1)*dx(0) + a = v0 * dot(v1, f1) * dx(0) _test_form(a) - a = v0*dot(v1, f1)*dx(1) + a = v0 * dot(v1, f1) * dx(1) _test_form(a) - a = v0*dot(v1, f1)*ds + a = v0 * dot(v1, f1) * ds _test_form(a) - a = v0*dot(v1, f1)*ds(0) + a = v0 * dot(v1, f1) * ds(0) _test_form(a) - a = v0*dot(v1, f1)*ds(1) + a = v0 * dot(v1, f1) * ds(1) _test_form(a) - a = v0*dot(v1, f1)*dS + a = v0 * dot(v1, f1) * dS _test_form(a) - a = v0*dot(v1, f1)*dS(0) + a = v0 * dot(v1, f1) * dS(0) _test_form(a) - a = v0*dot(v1, f1)*dS(1) + a = v0 * dot(v1, f1) * dS(1) _test_form(a) # --- Form transformations: - a = f0*v0*dx + f0*v0*dot(f1, v1)*dx + a = f0 * v0 * dx + f0 * v0 * dot(f1, v1) * dx # b = lhs(a) # TODO # c = rhs(a) # TODO d = derivative(a, f1, v1) diff --git a/test/test_complex.py b/test/test_complex.py index 6f3eda840..d91d0911c 100755 --- a/test/test_complex.py +++ b/test/test_complex.py @@ -2,9 +2,38 @@ import pytest -from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, as_tensor, as_ufl, atan, conditional, - conj, cos, cosh, dot, dx, exp, ge, grad, gt, imag, inner, le, ln, lt, max_value, min_value, outer, - real, sin, sqrt, triangle) +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + as_tensor, + as_ufl, + atan, + conditional, + conj, + cos, + cosh, + dot, + dx, + exp, + ge, + grad, + gt, + imag, + inner, + le, + ln, + lt, + max_value, + min_value, + outer, + real, + sin, + sqrt, + triangle, +) from ufl.algebra import Conj, Imag, Real from ufl.algorithms import estimate_total_polynomial_degree from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering @@ -18,8 +47,8 @@ def test_conj(self): - z1 = ComplexValue(1+2j) - z2 = ComplexValue(1-2j) + z1 = ComplexValue(1 + 2j) + z2 = ComplexValue(1 - 2j) assert z1 == Conj(z2) assert z2 == Conj(z1) @@ -29,7 +58,7 @@ def test_real(self): z0 = Zero() z1 = as_ufl(1.0) z2 = ComplexValue(1j) - z3 = ComplexValue(1+1j) + z3 = ComplexValue(1 + 1j) assert Real(z1) == z1 assert Real(z3) == z1 assert Real(z2) == z0 @@ -39,7 +68,7 @@ def test_imag(self): z0 = Zero() z1 = as_ufl(1.0) z2 = as_ufl(1j) - z3 = ComplexValue(1+1j) + z3 = ComplexValue(1 + 1j) assert Imag(z2) == z1 assert Imag(z3) == z1 @@ -48,8 +77,8 @@ def test_imag(self): def test_compute_form_adjoint(self): cell = triangle - element = FiniteElement('Lagrange', cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -62,23 +91,28 @@ def test_compute_form_adjoint(self): def test_complex_algebra(self): z1 = ComplexValue(1j) - z2 = ComplexValue(1+1j) - - # Remember that ufl.algebra functions return ComplexValues, but ufl.mathfunctions return complex Python scalar - # Any operations with a ComplexValue and a complex Python scalar promote to ComplexValue - assert z1*z2 == ComplexValue(-1+1j) - assert z2/z1 == ComplexValue(1-1j) - assert pow(z2, z1) == ComplexValue((1+1j)**1j) - assert sqrt(z2) * as_ufl(1) == ComplexValue(cmath.sqrt(1+1j)) + z2 = ComplexValue(1 + 1j) + + # Remember that ufl.algebra functions return ComplexValues, but + # ufl.mathfunctions return complex Python scalar + # Any operations with a ComplexValue and a complex Python scalar + # promote to ComplexValue + assert z1 * z2 == ComplexValue(-1 + 1j) + assert z2 / z1 == ComplexValue(1 - 1j) + assert pow(z2, z1) == ComplexValue((1 + 1j) ** 1j) + assert sqrt(z2) * as_ufl(1) == ComplexValue(cmath.sqrt(1 + 1j)) assert (sin(z2) + cosh(z2) - atan(z2)) * z1 == ComplexValue( - (cmath.sin(1+1j) + cmath.cosh(1+1j) - cmath.atan(1+1j))*1j) - assert (abs(z2) - ln(z2))/exp(z1) == ComplexValue((abs(1+1j) - cmath.log(1+1j))/cmath.exp(1j)) + (cmath.sin(1 + 1j) + cmath.cosh(1 + 1j) - cmath.atan(1 + 1j)) * 1j + ) + assert (abs(z2) - ln(z2)) / exp(z1) == ComplexValue( + (abs(1 + 1j) - cmath.log(1 + 1j)) / cmath.exp(1j) + ) def test_automatic_simplification(self): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -92,7 +126,7 @@ def test_automatic_simplification(self): def test_apply_algebra_lowering_complex(self): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -115,13 +149,15 @@ def test_apply_algebra_lowering_complex(self): assert lowered_a == gu[lowered_a_index] * gv[lowered_a_index] assert lowered_b == gv[lowered_b_index] * conj(gu[lowered_b_index]) assert lowered_c == as_tensor( - conj(gu[lowered_c_indices[0]]) * gv[lowered_c_indices[1]], (lowered_c_indices[0],) + (lowered_c_indices[1],)) + conj(gu[lowered_c_indices[0]]) * gv[lowered_c_indices[1]], + (lowered_c_indices[0],) + (lowered_c_indices[1],), + ) def test_remove_complex_nodes(self): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -131,7 +167,7 @@ def test_remove_complex_nodes(self): a = conj(v) b = real(u) c = imag(f) - d = conj(real(v))*imag(conj(u)) + d = conj(real(v)) * imag(conj(u)) assert remove_complex_nodes(a) == v assert remove_complex_nodes(b) == u @@ -144,7 +180,7 @@ def test_remove_complex_nodes(self): def test_comparison_checker(self): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = TrialFunction(space) @@ -172,7 +208,7 @@ def test_comparison_checker(self): def test_complex_degree_handling(self): cell = triangle element = FiniteElement("Lagrange", cell, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/test/test_conditionals.py b/test/test_conditionals.py index 7b60054d5..8b7980d2d 100755 --- a/test/test_conditionals.py +++ b/test/test_conditionals.py @@ -13,7 +13,7 @@ @pytest.fixture def f(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) return Coefficient(space) @@ -21,7 +21,7 @@ def f(): @pytest.fixture def g(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) return Coefficient(space) diff --git a/test/test_degree_estimation.py b/test/test_degree_estimation.py index dbadffef6..6101db9ee 100755 --- a/test/test_degree_estimation.py +++ b/test/test_degree_estimation.py @@ -1,8 +1,26 @@ __authors__ = "Martin Sandve Alnæs" __date__ = "2008-03-12 -- 2009-01-28" -from ufl import (Argument, Coefficient, Coefficients, FacetNormal, FunctionSpace, Mesh, SpatialCoordinate, cos, div, - dot, grad, i, inner, nabla_div, nabla_grad, sin, tan, triangle) +from ufl import ( + Argument, + Coefficient, + Coefficients, + FacetNormal, + FunctionSpace, + Mesh, + SpatialCoordinate, + cos, + div, + dot, + grad, + i, + inner, + nabla_div, + nabla_grad, + sin, + tan, + triangle, +) from ufl.algorithms import estimate_total_polynomial_degree from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import identity_pullback @@ -12,10 +30,10 @@ def test_total_degree_estimation(): V1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V2 = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - VV = FiniteElement("Lagrange", triangle, 3, (2, ), identity_pullback, H1) + VV = FiniteElement("Lagrange", triangle, 3, (2,), identity_pullback, H1) VM = MixedElement([V1, V2]) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) @@ -31,8 +49,8 @@ def test_total_degree_estimation(): x, y = SpatialCoordinate(domain) assert estimate_total_polynomial_degree(x) == 1 assert estimate_total_polynomial_degree(x * y) == 2 - assert estimate_total_polynomial_degree(x ** 3) == 3 - assert estimate_total_polynomial_degree(x ** 3) == 3 + assert estimate_total_polynomial_degree(x**3) == 3 + assert estimate_total_polynomial_degree(x**3) == 3 assert estimate_total_polynomial_degree((x - 1) ** 4) == 4 assert estimate_total_polynomial_degree(vv[0]) == 3 @@ -59,7 +77,7 @@ def test_total_degree_estimation(): assert estimate_total_polynomial_degree(f2 + 3) == 2 assert estimate_total_polynomial_degree(f2 * 3) == 2 - assert estimate_total_polynomial_degree(f2 ** 3) == 6 + assert estimate_total_polynomial_degree(f2**3) == 6 assert estimate_total_polynomial_degree(f2 / 3) == 2 assert estimate_total_polynomial_degree(f2 / v2) == 4 assert estimate_total_polynomial_degree(f2 / (x - 1)) == 3 @@ -70,15 +88,15 @@ def test_total_degree_estimation(): assert estimate_total_polynomial_degree(f2 * v2.dx(0) * v1.dx(0)) == 2 + 1 assert estimate_total_polynomial_degree(f2) == 2 - assert estimate_total_polynomial_degree(f2 ** 2) == 4 - assert estimate_total_polynomial_degree(f2 ** 3) == 6 - assert estimate_total_polynomial_degree(f2 ** 3 * v1) == 7 - assert estimate_total_polynomial_degree(f2 ** 3 * v1 + f1 * v1) == 7 + assert estimate_total_polynomial_degree(f2**2) == 4 + assert estimate_total_polynomial_degree(f2**3) == 6 + assert estimate_total_polynomial_degree(f2**3 * v1) == 7 + assert estimate_total_polynomial_degree(f2**3 * v1 + f1 * v1) == 7 # Math functions of constant values are constant values nx, ny = FacetNormal(domain) - e = nx ** 2 - for f in [sin, cos, tan, abs, lambda z:z**7]: + e = nx**2 + for f in [sin, cos, tan, abs, lambda z: z**7]: assert estimate_total_polynomial_degree(f(e)) == 0 # Based on the arbitrary chosen math function heuristics... @@ -89,7 +107,6 @@ def test_total_degree_estimation(): def test_some_compound_types(): - # NB! Although some compound types are supported here, # some derivatives and compounds must be preprocessed # prior to degree estimation. In generic code, this algorithm @@ -98,8 +115,8 @@ def test_some_compound_types(): etpd = estimate_total_polynomial_degree P2 = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - V2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + V2 = FiniteElement("Lagrange", triangle, 2, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) u = Coefficient(FunctionSpace(domain, P2)) v = Coefficient(FunctionSpace(domain, V2)) diff --git a/test/test_derivative.py b/test/test_derivative.py index fc8bfdd76..cce955a12 100755 --- a/test/test_derivative.py +++ b/test/test_derivative.py @@ -3,11 +3,58 @@ from itertools import chain -from ufl import (CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, FunctionSpace, - Identity, Index, Jacobian, JacobianInverse, Mesh, SpatialCoordinate, TestFunction, TrialFunction, acos, - as_matrix, as_tensor, as_vector, asin, atan, conditional, cos, derivative, diff, dot, dx, exp, i, - indices, inner, interval, j, k, ln, lt, nabla_grad, outer, quadrilateral, replace, sign, sin, split, - sqrt, tan, tetrahedron, triangle, variable, zero) +from ufl import ( + CellDiameter, + CellVolume, + Circumradius, + Coefficient, + Constant, + FacetArea, + FacetNormal, + FunctionSpace, + Identity, + Index, + Jacobian, + JacobianInverse, + Mesh, + SpatialCoordinate, + TestFunction, + TrialFunction, + acos, + as_matrix, + as_tensor, + as_vector, + asin, + atan, + conditional, + cos, + derivative, + diff, + dot, + dx, + exp, + i, + indices, + inner, + interval, + j, + k, + ln, + lt, + nabla_grad, + outer, + quadrilateral, + replace, + sign, + sin, + split, + sqrt, + tan, + tetrahedron, + triangle, + variable, + zero, +) from ufl.algorithms import compute_form_data, expand_indices, strip_variables from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_derivatives @@ -21,13 +68,14 @@ def assertEqualBySampling(actual, expected): - ad = compute_form_data(actual*dx) + ad = compute_form_data(actual * dx) a = ad.preprocessed_form.integrals_by_type("cell")[0].integrand() - bd = compute_form_data(expected*dx) + bd = compute_form_data(expected * dx) b = bd.preprocessed_form.integrals_by_type("cell")[0].integrand() - assert ([ad.function_replace_map[ac] for ac in ad.reduced_coefficients] - == [bd.function_replace_map[bc] for bc in bd.reduced_coefficients]) + assert [ad.function_replace_map[ac] for ac in ad.reduced_coefficients] == [ + bd.function_replace_map[bc] for bc in bd.reduced_coefficients + ] n = ad.num_coefficients @@ -45,8 +93,14 @@ def make_value(c): else: raise NotImplementedError("Tensor valued expressions not supported here.") - amapping = dict((c, make_value(c)) for c in chain(ad.original_form.coefficients(), ad.original_form.arguments())) - bmapping = dict((c, make_value(c)) for c in chain(bd.original_form.coefficients(), bd.original_form.arguments())) + amapping = dict( + (c, make_value(c)) + for c in chain(ad.original_form.coefficients(), ad.original_form.arguments()) + ) + bmapping = dict( + (c, make_value(c)) + for c in chain(bd.original_form.coefficients(), bd.original_form.arguments()) + ) adomain = extract_unique_domain(actual) bdomain = extract_unique_domain(expected) acell = adomain.ufl_cell() @@ -77,7 +131,7 @@ def make_value(c): def _test(self, f, df): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -97,12 +151,13 @@ def _test(self, f, df): dfv2 = dfv2(x, mapping) assert dfv1 == dfv2 - dfv1 = derivative(f(7*w), w, v) - dfv2 = 7*df(7*w, v) + dfv1 = derivative(f(7 * w), w, v) + dfv2 = 7 * df(7 * w, v) dfv1 = dfv1(x, mapping) dfv2 = dfv2(x, mapping) assert dfv1 == dfv2 + # --- Literals @@ -125,6 +180,7 @@ def df(w, v): _test(self, f, df) + # --- Form arguments @@ -140,21 +196,27 @@ def df(w, v): def testArgument(self): def f(w): - return TestFunction(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) + return TestFunction( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1), + ) + ) def df(w, v): return zero() + _test(self, f, df) + # --- Geometry def testSpatialCoordinate(self): def f(w): return SpatialCoordinate( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)))[0] + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + )[0] def df(w, v): return zero() @@ -165,7 +227,8 @@ def df(w, v): def testFacetNormal(self): def f(w): return FacetNormal( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)))[0] + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + )[0] def df(w, v): return zero() @@ -175,8 +238,7 @@ def df(w, v): def testFacetArea(self): def f(w): - return FacetArea( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + return FacetArea(Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1))) def df(w, v): return zero() @@ -187,7 +249,8 @@ def df(w, v): def testCellDiameter(self): def f(w): return CellDiameter( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + ) def df(w, v): return zero() @@ -197,22 +260,26 @@ def df(w, v): def testCircumradius(self): def f(w): - return Circumradius(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + return Circumradius( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + ) def df(w, v): return zero() + _test(self, f, df) def testCellVolume(self): def f(w): - return CellVolume(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + return CellVolume(Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1))) def df(w, v): return zero() _test(self, f, df) + # --- Basic operators @@ -228,10 +295,10 @@ def df(w, v): def testProduct(self): def f(w): - return 3*w + return 3 * w def df(w, v): - return 3*v + return 3 * v _test(self, f, df) @@ -241,7 +308,7 @@ def f(w): return w**3 def df(w, v): - return 3*w**2*v + return 3 * w**2 * v _test(self, f, df) @@ -271,7 +338,7 @@ def f(w): return exp(w) def df(w, v): - return v*exp(w) + return v * exp(w) _test(self, f, df) @@ -291,7 +358,7 @@ def f(w): return cos(w) def df(w, v): - return -v*sin(w) + return -v * sin(w) _test(self, f, df) @@ -301,7 +368,7 @@ def f(w): return sin(w) def df(w, v): - return v*cos(w) + return v * cos(w) _test(self, f, df) @@ -311,27 +378,27 @@ def f(w): return tan(w) def df(w, v): - return v*2.0/(cos(2.0*w) + 1.0) + return v * 2.0 / (cos(2.0 * w) + 1.0) _test(self, f, df) def testAcos(self): def f(w): - return acos(w/1000) + return acos(w / 1000) def df(w, v): - return -(v/1000)/sqrt(1.0 - (w/1000)**2) + return -(v / 1000) / sqrt(1.0 - (w / 1000) ** 2) _test(self, f, df) def testAsin(self): def f(w): - return asin(w/1000) + return asin(w / 1000) def df(w, v): - return (v/1000)/sqrt(1.0 - (w/1000)**2) + return (v / 1000) / sqrt(1.0 - (w / 1000) ** 2) _test(self, f, df) @@ -341,10 +408,11 @@ def f(w): return atan(w) def df(w, v): - return v/(1.0 + w**2) + return v / (1.0 + w**2) _test(self, f, df) + # FIXME: Add the new erf and bessel_* # --- Abs and conditionals @@ -355,7 +423,7 @@ def f(w): return abs(w) def df(w, v): - return sign(w)*v + return sign(w) * v _test(self, f, df) @@ -365,14 +433,14 @@ def cond(w): return lt(w, 1.0) def f(w): - return conditional(cond(w), 2*w, 3*w) + return conditional(cond(w), 2 * w, 3 * w) def df(w, v): - return (conditional(cond(w), 1, 0) * 2*v + - conditional(cond(w), 0, 1) * 3*v) + return conditional(cond(w), 1, 0) * 2 * v + conditional(cond(w), 0, 1) * 3 * v _test(self, f, df) + # --- Tensor algebra basics @@ -381,28 +449,32 @@ def f(w): # 3*w + 4*w**2 + 5*w**3 a = as_vector((w, w**2, w**3)) b = as_vector((3, 4, 5)) - i, = indices(1) - return a[i]*b[i] + (i,) = indices(1) + return a[i] * b[i] def df(w, v): - return 3*v + 4*2*w*v + 5*3*w**2*v + return 3 * v + 4 * 2 * w * v + 5 * 3 * w**2 * v _test(self, f, df) def testListTensor(self): v = variable(as_ufl(42)) - f = as_tensor(( - ((0, 0), (0, 0)), - ((v, 2*v), (0, 0)), - ((v**2, 1), (2, v/2)), - )) + f = as_tensor( + ( + ((0, 0), (0, 0)), + ((v, 2 * v), (0, 0)), + ((v**2, 1), (2, v / 2)), + ) + ) assert f.ufl_shape == (3, 2, 2) - g = as_tensor(( - ((0, 0), (0, 0)), - ((1, 2), (0, 0)), - ((84, 0), (0, 0.5)), - )) + g = as_tensor( + ( + ((0, 0), (0, 0)), + ((1, 2), (0, 0)), + ((84, 0), (0, 0.5)), + ) + ) assert g.ufl_shape == (3, 2, 2) dfv = diff(f, v) x = None @@ -411,40 +483,41 @@ def testListTensor(self): for c in range(2): self.assertEqual(dfv[a, b, c](x), g[a, b, c](x)) + # --- Coefficient and argument input configurations def test_single_scalar_coefficient_derivative(self): cell = triangle V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) - a = 3*u**2 + a = 3 * u**2 b = derivative(a, u, v) - self.assertEqualAfterPreprocessing(b, 3*(u*(2*v))) + self.assertEqualAfterPreprocessing(b, 3 * (u * (2 * v))) def test_single_vector_coefficient_derivative(self): cell = triangle - V = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + V = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) - a = 3*dot(u, u) + a = 3 * dot(u, u) actual = derivative(a, u, v) - expected = 3*(2*(u[i]*v[i])) + expected = 3 * (2 * (u[i] * v[i])) assertEqualBySampling(actual, expected) def test_multiple_coefficient_derivative(self): cell = triangle V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - W = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) M = MixedElement([V, W]) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) m_space = FunctionSpace(domain, M) @@ -453,14 +526,14 @@ def test_multiple_coefficient_derivative(self): v = TestFunction(m_space) vv, vw = split(v) - a = sin(uv)*dot(uw, uw) + a = sin(uv) * dot(uw, uw) actual = derivative(a, (uv, uw), split(v)) - expected = cos(uv)*vv * (uw[i]*uw[i]) + (uw[j]*vw[j])*2 * sin(uv) + expected = cos(uv) * vv * (uw[i] * uw[i]) + (uw[j] * vw[j]) * 2 * sin(uv) assertEqualBySampling(actual, expected) actual = derivative(a, (uv, uw), v) - expected = cos(uv)*vv * (uw[i]*uw[i]) + (uw[j]*vw[j])*2 * sin(uv) + expected = cos(uv) * vv * (uw[i] * uw[i]) + (uw[j] * vw[j]) * 2 * sin(uv) assertEqualBySampling(actual, expected) @@ -468,8 +541,8 @@ def test_indexed_coefficient_derivative(self): cell = triangle ident = Identity(2) V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - W = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + W = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) u = Coefficient(w_space) @@ -477,11 +550,11 @@ def test_indexed_coefficient_derivative(self): w = dot(u, nabla_grad(u)) # a = dot(w, w) - a = (u[i]*u[k].dx(i)) * w[k] + a = (u[i] * u[k].dx(i)) * w[k] actual = derivative(a, u[0], v) - dw = v*u[k].dx(0) + u[i]*ident[0, k]*v.dx(i) + dw = v * u[k].dx(0) + u[i] * ident[0, k] * v.dx(i) expected = 2 * w[k] * dw assertEqualBySampling(actual, expected) @@ -491,8 +564,8 @@ def test_multiple_indexed_coefficient_derivative(self): cell = tetrahedron V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V2 = MixedElement([V, V]) - W = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + W = FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) v2_space = FunctionSpace(domain, V2) w_space = FunctionSpace(domain, W) u = Coefficient(w_space) @@ -500,8 +573,8 @@ def test_multiple_indexed_coefficient_derivative(self): v = TestFunction(v2_space) vu, vw = split(v) - actual = derivative(cos(u[i]*w[i]), (u[2], w[1]), (vu, vw)) - expected = -sin(u[i]*w[i])*(vu*w[2] + u[1]*vw) + actual = derivative(cos(u[i] * w[i]), (u[2], w[1]), (vu, vw)) + expected = -sin(u[i] * w[i]) * (vu * w[2] + u[1] * vw) assertEqualBySampling(actual, expected) @@ -509,9 +582,9 @@ def test_multiple_indexed_coefficient_derivative(self): def test_segregated_derivative_of_convection(self): cell = tetrahedron V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - W = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) @@ -531,7 +604,7 @@ def test_segregated_derivative_of_convection(self): for a in range(3): for b in range(3): - form = Lvu[a, b]*dx + form = Lvu[a, b] * dx fd = compute_form_data(form) pf = fd.preprocessed_form expand_indices(pf) @@ -540,16 +613,17 @@ def test_segregated_derivative_of_convection(self): for a in range(3): for b in range(3): actual = Lvu[a, b] - expected = du*u[a].dx(b)*dv + u[k]*du.dx(k)*dv + expected = du * u[a].dx(b) * dv + u[k] * du.dx(k) * dv assertEqualBySampling(actual, expected) + # --- User provided derivatives of coefficients def test_coefficient_derivatives(self): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) dv = TestFunction(space) @@ -562,21 +636,21 @@ def test_coefficient_derivatives(self): cd = {f: df, g: dg} integrand = inner(f, g) - expected = (df*dv)*g + f*(dg*dv) + expected = (df * dv) * g + f * (dg * dv) - F = integrand*dx + F = integrand * dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() - assert (actual*dx).signature() == (expected*dx).signature() + assert (actual * dx).signature() == (expected * dx).signature() self.assertEqual(replace(actual, fd.function_replace_map), expected) def test_vector_coefficient_scalar_derivatives(self): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - VV = FiniteElement("vector Lagrange", triangle, 1, (2, ), identity_pullback, H1) + VV = FiniteElement("vector Lagrange", triangle, 1, (2,), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -591,20 +665,20 @@ def test_vector_coefficient_scalar_derivatives(self): integrand = inner(f, g) i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] - expected = as_tensor(df[i1]*dv, (i1,))[i0]*g[i0] + expected = as_tensor(df[i1] * dv, (i1,))[i0] * g[i0] - F = integrand*dx + F = integrand * dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() - assert (actual*dx).signature() == (expected*dx).signature() + assert (actual * dx).signature() == (expected * dx).signature() def test_vector_coefficient_derivatives(self): - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) VV = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -619,21 +693,21 @@ def test_vector_coefficient_derivatives(self): integrand = inner(f, g) i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] - expected = as_tensor(df[i2, i1]*dv[i1], (i2,))[i0]*g[i0] + expected = as_tensor(df[i2, i1] * dv[i1], (i2,))[i0] * g[i0] - F = integrand*dx + F = integrand * dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() - assert (actual*dx).signature() == (expected*dx).signature() + assert (actual * dx).signature() == (expected * dx).signature() # self.assertEqual(replace(actual, fd.function_replace_map), expected) def test_vector_coefficient_derivatives_of_product(self): - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) VV = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) vv_space = FunctionSpace(domain, VV) @@ -646,13 +720,15 @@ def test_vector_coefficient_derivatives_of_product(self): u = Coefficient(v_space, count=4) cd = {f: df, g: dg} - integrand = f[i]*g[i] + integrand = f[i] * g[i] i0, i1, i2, i3, i4 = [Index(count=c) for c in range(5)] - expected = as_tensor(df[i2, i1]*dv[i1], (i2,))[i0]*g[i0] +\ - f[i0]*as_tensor(dg[i4, i3]*dv[i3], (i4,))[i0] + expected = ( + as_tensor(df[i2, i1] * dv[i1], (i2,))[i0] * g[i0] + + f[i0] * as_tensor(dg[i4, i3] * dv[i3], (i4,))[i0] + ) - F = integrand*dx + F = integrand * dx J = derivative(F, u, dv, cd) fd = compute_form_data(J) actual = fd.preprocessed_form.integrals()[0].integrand() @@ -666,13 +742,14 @@ def test_vector_coefficient_derivatives_of_product(self): # TODO: Add tests covering more cases, in particular mixed stuff + # --- Some actual forms def testHyperElasticity(self): cell = interval element = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (1, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (1,), identity_pullback, H1)) space = FunctionSpace(domain, element) w = Coefficient(space) v = TestFunction(space) @@ -686,10 +763,10 @@ def testHyperElasticity(self): E = dw + dw**2 / 2 E = variable(E) - Q = b*E**2 - psi = K*(exp(Q)-1) + Q = b * E**2 + psi = K * (exp(Q) - 1) - f = psi*dx + f = psi * dx F = derivative(f, w, v) J = derivative(F, w, u) @@ -707,18 +784,18 @@ def testHyperElasticity(self): # classes = set(c.__class__ for c in post_traversal(f_expression)) - Kv = .2 - bv = .3 - dw = .5 - dv = .7 - du = .11 - E = dw + dw**2 / 2. - Q = bv*E**2 + Kv = 0.2 + bv = 0.3 + dw = 0.5 + dv = 0.7 + du = 0.11 + E = dw + dw**2 / 2.0 + Q = bv * E**2 expQ = float(exp(Q)) - psi = Kv*(expQ-1) + psi = Kv * (expQ - 1) fv = psi - Fv = 2*Kv*bv*E*(1+dw)*expQ*dv - Jv = 2*Kv*bv*expQ*dv*du*(E + (1+dw)**2*(2*bv*E**2 + 1)) + Fv = 2 * Kv * bv * E * (1 + dw) * expQ * dv + Jv = 2 * Kv * bv * expQ * dv * du * (E + (1 + dw) ** 2 * (2 * bv * E**2 + 1)) def Nv(x, derivatives): assert derivatives == (0,) @@ -736,7 +813,7 @@ def Nw(x, derivatives): fv2 = f_expression((0,), mapping) self.assertAlmostEqual(fv, fv2) - v, = form_data_F.original_form.arguments() + (v,) = form_data_F.original_form.arguments() mapping = {K: Kv, b: bv, v: Nv, w: Nw} Fv2 = F_expression((0,), mapping) self.assertAlmostEqual(Fv, Fv2) @@ -751,21 +828,22 @@ def test_mass_derived_from_functional(self): cell = triangle V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) u = TrialFunction(space) w = Coefficient(space) - f = (w**2/2)*dx - L = w*v*dx - a = u*v*dx # noqa: F841 + f = (w**2 / 2) * dx + L = w * v * dx + # a = u*v*dx F = derivative(f, w, v) - J1 = derivative(L, w, u) # noqa: F841 - J2 = derivative(F, w, u) # noqa: F841 + derivative(L, w, u) + derivative(F, w, u) # TODO: assert something + # --- Interaction with replace @@ -773,7 +851,7 @@ def test_derivative_replace_works_together(self): cell = triangle V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) @@ -781,14 +859,14 @@ def test_derivative_replace_works_together(self): f = Coefficient(space) g = Coefficient(space) - M = cos(f)*sin(g) + M = cos(f) * sin(g) F = derivative(M, f, v) J = derivative(F, f, u) JR = replace(J, {f: g}) - F2 = -sin(f)*v*sin(g) - J2 = -cos(f)*u*v*sin(g) - JR2 = -cos(g)*u*v*sin(g) + F2 = -sin(f) * v * sin(g) + J2 = -cos(f) * u * v * sin(g) + JR2 = -cos(g) * u * v * sin(g) assertEqualBySampling(F, F2) assertEqualBySampling(J, J2) @@ -796,30 +874,29 @@ def test_derivative_replace_works_together(self): def test_index_simplification_handles_repeated_indices(self): - mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1)) + mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2,), identity_pullback, H1)) V = FunctionSpace(mesh, FiniteElement("DQ", quadrilateral, 0, (2, 2), identity_pullback, L2)) K = JacobianInverse(mesh) G = outer(Identity(2), Identity(2)) - i, j, k, l, m, n = indices(6) - A = as_tensor(K[m, i] * K[n, j] * G[i, j, k, l], (m, n, k, l)) + i, j, k, L, m, n = indices(6) + A = as_tensor(K[m, i] * K[n, j] * G[i, j, k, L], (m, n, k, L)) i, j = indices(2) # Can't use A[i, i, j, j] because UFL automagically index-sums # repeated indices in the __getitem__ call. Adiag = Indexed(A, MultiIndex((i, i, j, j))) A = as_tensor(Adiag, (i, j)) v = TestFunction(V) - f = inner(A, v)*dx + f = inner(A, v) * dx fd = compute_form_data(f, do_apply_geometry_lowering=True) - integral, = fd.preprocessed_form.integrals() + (integral,) = fd.preprocessed_form.integrals() assert integral.integrand().ufl_free_indices == () def test_index_simplification_reference_grad(self): - mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1)) - i, = indices(1) + mesh = Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2,), identity_pullback, H1)) + (i,) = indices(1) A = as_tensor(Indexed(Jacobian(mesh), MultiIndex((i, i))), (i,)) - expr = apply_derivatives(apply_geometry_lowering( - apply_algebra_lowering(A[0]))) + expr = apply_derivatives(apply_geometry_lowering(apply_algebra_lowering(A[0]))) assert expr == ReferenceGrad(SpatialCoordinate(mesh))[0, 0] assert expr.ufl_free_indices == () assert expr.ufl_shape == () @@ -827,27 +904,24 @@ def test_index_simplification_reference_grad(self): # --- Scratch space + def test_foobar(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) - du = TrialFunction(space) - U = Coefficient(space) def planarGrad(u): - return as_matrix([[u[0].dx(0), 0, u[0].dx(1)], - [0, 0, 0], - [u[1].dx(0), 0, u[1].dx(1)]]) + return as_matrix([[u[0].dx(0), 0, u[0].dx(1)], [0, 0, 0], [u[1].dx(0), 0, u[1].dx(1)]]) def epsilon(u): - return 0.5*(planarGrad(u)+planarGrad(u).T) + return 0.5 * (planarGrad(u) + planarGrad(u).T) def NS_a(u, v): return inner(epsilon(u), epsilon(v)) - L = NS_a(U, v)*dx - a = derivative(L, U, du) # noqa: F841 + L = NS_a(U, v) * dx + _ = derivative(L, U, du) # TODO: assert something diff --git a/test/test_diff.py b/test/test_diff.py index 5a53e5c8d..5388eab43 100755 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -3,8 +3,23 @@ import pytest -from ufl import (Coefficient, FunctionSpace, Mesh, SpatialCoordinate, as_vector, atan, cos, diff, exp, indices, ln, sin, - tan, triangle, variable) +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + SpatialCoordinate, + as_vector, + atan, + cos, + diff, + exp, + indices, + ln, + sin, + tan, + triangle, + variable, +) from ufl.algorithms import expand_derivatives from ufl.constantvalue import as_ufl from ufl.finiteelement import FiniteElement @@ -46,6 +61,7 @@ def f(v): def df(v): return as_ufl(1) + _test(f, df) @@ -55,6 +71,7 @@ def f(v): def df(v): return as_ufl(1) + _test(f, df) @@ -64,15 +81,17 @@ def f(v): def df(v): return as_ufl(3) + _test(f, df) def testPower(v): def f(v): - return v ** 3 + return v**3 def df(v): - return 3 * v ** 2 + return 3 * v**2 + _test(f, df) @@ -82,6 +101,7 @@ def f(v): def df(v): return as_ufl(1.0 / 3.0) + _test(f, df) @@ -90,7 +110,8 @@ def f(v): return 3.0 / v def df(v): - return -3.0 / v ** 2 + return -3.0 / v**2 + _test(f, df) @@ -100,6 +121,7 @@ def f(v): def df(v): return exp(v) + _test(f, df) @@ -109,6 +131,7 @@ def f(v): def df(v): return 1.0 / v + _test(f, df) @@ -118,6 +141,7 @@ def f(v): def df(v): return cos(v) + _test(f, df) @@ -127,6 +151,7 @@ def f(v): def df(v): return -sin(v) + _test(f, df) @@ -136,8 +161,10 @@ def f(v): def df(v): return 2.0 / (cos(2.0 * v) + 1.0) + _test(f, df) + # TODO: Check the following tests. They run into strange math domain errors. # def testAsin(v): # def f(v): return asin(v) @@ -155,37 +182,39 @@ def f(v): return atan(v) def df(v): - return 1 / (1.0 + v ** 2) + return 1 / (1.0 + v**2) + _test(f, df) def testIndexSum(v): def f(v): # 3*v + 4*v**2 + 5*v**3 - a = as_vector((v, v ** 2, v ** 3)) + a = as_vector((v, v**2, v**3)) b = as_vector((3, 4, 5)) - i, = indices(1) + (i,) = indices(1) return a[i] * b[i] def df(v): - return 3 + 4 * 2 * v + 5 * 3 * v ** 2 + return 3 + 4 * 2 * v + 5 * 3 * v**2 + _test(f, df) def testCoefficient(): - coord_elem = FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1) + coord_elem = FiniteElement("Lagrange", triangle, 1, (3,), identity_pullback, H1) mesh = Mesh(coord_elem) V = FunctionSpace(mesh, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) v = Coefficient(V) - assert round(expand_derivatives(diff(v, v))-1.0, 7) == 0 + assert round(expand_derivatives(diff(v, v)) - 1.0, 7) == 0 def testDiffX(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) f = x[0] ** 2 * x[1] ** 2 - i, = indices(1) + (i,) = indices(1) df1 = diff(f, x) df2 = as_vector(f.dx(i), i) @@ -199,4 +228,5 @@ def testDiffX(): assert round(df10 - 2 * 2 * 9, 7) == 0 assert round(df11 - 2 * 4 * 3, 7) == 0 + # TODO: More tests involving wrapper types and indices diff --git a/test/test_domains.py b/test/test_domains.py index 112a1613b..44477cc9c 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -1,16 +1,30 @@ - """Tests of domain language and attaching domains to forms.""" import pytest from mockobjects import MockMesh import ufl # noqa: F401 -from ufl import (Cell, Coefficient, Constant, FunctionSpace, Mesh, ds, dS, dx, hexahedron, interval, quadrilateral, - tetrahedron, triangle) +from ufl import ( + Cell, + Coefficient, + Constant, + FunctionSpace, + Mesh, + dS, + ds, + dx, + hexahedron, + interval, + quadrilateral, + tetrahedron, + triangle, +) from ufl.algorithms import compute_form_data from ufl.finiteelement import FiniteElement -from ufl.pullback import IdentityPullback # noqa: F401 -from ufl.pullback import identity_pullback +from ufl.pullback import ( + IdentityPullback, # noqa: F401 + identity_pullback, +) from ufl.sobolevspace import H1 all_cells = (interval, triangle, tetrahedron, quadrilateral, hexahedron) @@ -19,13 +33,13 @@ def test_construct_domains_from_cells(): for cell in all_cells: d = cell.topological_dimension() - Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) def test_construct_domains_with_names(): for cell in all_cells: d = cell.topological_dimension() - e = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) + e = FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1) D2 = Mesh(e, ufl_id=2) D3 = Mesh(e, ufl_id=3) D3b = Mesh(e, ufl_id=3) @@ -36,52 +50,60 @@ def test_construct_domains_with_names(): def test_domains_sort_by_name(): # This ordering is rather arbitrary, but at least this shows sorting is # working - domains1 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1), - ufl_id=hash(cell.cellname())) - for cell in all_cells] - domains2 = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1), - ufl_id=hash(cell.cellname())) - for cell in sorted(all_cells)] - sdomains = sorted(domains1, key=lambda D: (D.topological_dimension(), - D.ufl_cell(), - D.ufl_id())) + domains1 = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ), + ufl_id=hash(cell.cellname()), + ) + for cell in all_cells + ] + domains2 = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ), + ufl_id=hash(cell.cellname()), + ) + for cell in sorted(all_cells) + ] + sdomains = sorted(domains1, key=lambda D: (D.topological_dimension(), D.ufl_cell(), D.ufl_id())) assert sdomains != domains1 assert sdomains == domains2 def test_topdomain_creation(): - D = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) assert D.geometric_dimension() == 1 - D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert D.geometric_dimension() == 2 - D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) assert D.geometric_dimension() == 3 def test_cell_legacy_case(): # Passing cell like old code does - D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) f = Coefficient(FunctionSpace(D, V)) - assert f.ufl_domains() == (D, ) + assert f.ufl_domains() == (D,) M = f * dx - assert M.ufl_domains() == (D, ) + assert M.ufl_domains() == (D,) def test_simple_domain_case(): # Creating domain from just cell with label like new dolfin will do - D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=3) V = FunctionSpace(D, FiniteElement("Lagrange", D.ufl_cell(), 1, (), identity_pullback, "H1")) f = Coefficient(V) - assert f.ufl_domains() == (D, ) + assert f.ufl_domains() == (D,) M = f * dx - assert M.ufl_domains() == (D, ) + assert M.ufl_domains() == (D,) def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new approach @@ -89,7 +111,7 @@ def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new ap # Mesh with P2 representation of coordinates cell = triangle - P2 = FiniteElement("Lagrange", cell, 2, (2, ), identity_pullback, H1) + P2 = FiniteElement("Lagrange", cell, 2, (2,), identity_pullback, H1) domain = Mesh(P2) # Piecewise linear function space over quadratic mesh @@ -98,8 +120,8 @@ def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new ap f = Coefficient(V) M = f * dx - assert f.ufl_domains() == (domain, ) - assert M.ufl_domains() == (domain, ) + assert f.ufl_domains() == (domain,) + assert M.ufl_domains() == (domain,) # Test the gymnastics that dolfin will have to go through domain2 = Mesh(P2, ufl_id=domain.ufl_id()) @@ -112,86 +134,193 @@ def test_creating_domains_with_coordinate_fields(): # FIXME: Rewrite for new ap def test_join_domains(): from ufl.domain import join_domains + mesh7 = MockMesh(7) mesh8 = MockMesh(8) triangle = Cell("triangle") - xa = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - xb = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + xa = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + xb = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) # Equal domains are joined - assert 1 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7)])) - assert 1 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7)])) + assert 1 == len( + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=7), + ] + ) + ) + assert 1 == len( + join_domains( + [ + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh7, + ), + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh7, + ), + ] + ) + ) assert 1 == len(join_domains([Mesh(xa, ufl_id=3), Mesh(xa, ufl_id=3)])) # Different domains are not joined - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))])) - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=8)])) - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), - Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), ufl_id=8)])) + assert 2 == len( + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + ] + ) + ) + assert 2 == len( + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=7), + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=8), + ] + ) + ) + assert 2 == len( + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=7), + Mesh( + FiniteElement("Lagrange", quadrilateral, 1, (2,), identity_pullback, H1), + ufl_id=8, + ), + ] + ) + ) assert 2 == len(join_domains([Mesh(xa, ufl_id=7), Mesh(xa, ufl_id=8)])) assert 2 == len(join_domains([Mesh(xa), Mesh(xb)])) # Incompatible coordinates require labeling - xc = Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) - xd = Coefficient(FunctionSpace( - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + xc = Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ) + ) + xd = Coefficient( + FunctionSpace( + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ) + ) with pytest.raises(BaseException): join_domains([Mesh(xc), Mesh(xd)]) # Incompatible data is checked if and only if the domains are the same - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)])) - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), - Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), - ufl_id=8, cargo=mesh8)])) + assert 2 == len( + join_domains( + [ + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh7, + ), + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=8, + cargo=mesh8, + ), + ] + ) + ) + assert 2 == len( + join_domains( + [ + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh7, + ), + Mesh( + FiniteElement("Lagrange", quadrilateral, 1, (2,), identity_pullback, H1), + ufl_id=8, + cargo=mesh8, + ), + ] + ) + ) # Geometric dimensions must match with pytest.raises(BaseException): - join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)), - Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1))]) + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)), + Mesh(FiniteElement("Lagrange", triangle, 1, (3,), identity_pullback, H1)), + ] + ) with pytest.raises(BaseException): - join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh7), - Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1), ufl_id=8, cargo=mesh8)]) + join_domains( + [ + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh7, + ), + Mesh( + FiniteElement("Lagrange", triangle, 1, (3,), identity_pullback, H1), + ufl_id=8, + cargo=mesh8, + ), + ] + ) # Cargo and mesh ids must match with pytest.raises(BaseException): - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7, cargo=mesh8) + Mesh( + FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), + ufl_id=7, + cargo=mesh8, + ) # Nones are removed - assert 2 == len(join_domains([ - None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3), - None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=3), - None, Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=4)])) - assert 2 == len(join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1), ufl_id=7), None, - Mesh(FiniteElement("Lagrange", quadrilateral, 1, (2, ), identity_pullback, H1), ufl_id=8)])) - assert None not in join_domains([ - Mesh(FiniteElement("Lagrange", triangle, 1, (3, ), identity_pullback, H1), ufl_id=7), None, - Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1), ufl_id=8)]) + assert 2 == len( + join_domains( + [ + None, + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=3), + None, + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=3), + None, + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=4), + ] + ) + ) + assert 2 == len( + join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1), ufl_id=7), + None, + Mesh( + FiniteElement("Lagrange", quadrilateral, 1, (2,), identity_pullback, H1), + ufl_id=8, + ), + ] + ) + ) + assert None not in join_domains( + [ + Mesh(FiniteElement("Lagrange", triangle, 1, (3,), identity_pullback, H1), ufl_id=7), + None, + Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1), ufl_id=8), + ] + ) def test_everywhere_integrals_with_backwards_compatibility(): - D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) V = FunctionSpace(D, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) f = Coefficient(V) a = f * dx - ida, = compute_form_data(a).integral_data + (ida,) = compute_form_data(a).integral_data # Check some integral data assert ida.integral_type == "cell" @@ -207,7 +336,7 @@ def test_everywhere_integrals_with_backwards_compatibility(): def test_merge_sort_integral_data(): - D = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + D = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) V = FunctionSpace(D, FiniteElement("CG", triangle, 1, (), identity_pullback, H1)) diff --git a/test/test_duals.py b/test/test_duals.py index 53e230f3f..7786e7400 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -3,9 +3,29 @@ import pytest -from ufl import (Action, Adjoint, Argument, Coargument, Coefficient, Cofunction, FormSum, FunctionSpace, Matrix, Mesh, - MixedFunctionSpace, TestFunction, TrialFunction, action, adjoint, derivative, dx, inner, interval, - tetrahedron, triangle) +from ufl import ( + Action, + Adjoint, + Argument, + Coargument, + Coefficient, + Cofunction, + FormSum, + FunctionSpace, + Matrix, + Mesh, + MixedFunctionSpace, + TestFunction, + TrialFunction, + action, + adjoint, + derivative, + dx, + inner, + interval, + tetrahedron, + triangle, +) from ufl.algorithms.ad import expand_derivatives from ufl.constantvalue import Zero from ufl.duals import is_dual, is_primal @@ -17,9 +37,9 @@ def test_mixed_functionspace(self): # Domains - domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) # Finite elements f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) @@ -50,7 +70,7 @@ def test_mixed_functionspace(self): def test_dual_coefficients(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -73,7 +93,7 @@ def test_dual_coefficients(): def test_dual_arguments(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -96,7 +116,7 @@ def test_dual_arguments(): def test_addition(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -134,7 +154,7 @@ def test_addition(): def test_scalar_mult(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() @@ -152,7 +172,7 @@ def test_scalar_mult(): def test_adjoint(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) a = Matrix(V, V) @@ -171,10 +191,10 @@ def test_adjoint(): def test_action(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) U = FunctionSpace(domain_1d, f_1d) @@ -222,7 +242,8 @@ def test_action(): u2 = Coefficient(U) u3 = Coefficient(U) # Check Action left-distributivity with Sum - # Add 3 Coefficients to check composition of Sum works fine since u + u2 + u3 => Sum(u, Sum(u2, u3)) + # Add 3 Coefficients to check composition of Sum works fine since u + # + u2 + u3 => Sum(u, Sum(u2, u3)) res = action(a, u + u2 + u3) assert res == Action(a, u3) + Action(a, u) + Action(a, u2) # Check Action right-distributivity with Sum @@ -231,10 +252,10 @@ def test_action(): def test_differentiation(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) - domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) U = FunctionSpace(domain_1d, f_1d) @@ -246,7 +267,10 @@ def test_differentiation(): w = Cofunction(U.dual()) dwdu = expand_derivatives(derivative(w, u)) assert isinstance(dwdu, ZeroBaseForm) - assert dwdu.arguments() == (Argument(w.ufl_function_space().dual(), 0), Argument(u.ufl_function_space(), 1)) + assert dwdu.arguments() == ( + Argument(w.ufl_function_space().dual(), 0), + Argument(u.ufl_function_space(), 1), + ) # Check compatibility with int/float assert dwdu == 0 @@ -277,8 +301,10 @@ def test_differentiation(): # -- Action -- # Ac = Action(w, u) dAcdu = derivative(Ac, u) - assert dAcdu == (action(adjoint(derivative(w, u), derivatives_expanded=True), u, derivatives_expanded=True) - + action(w, derivative(u, u), derivatives_expanded=True)) + assert dAcdu == ( + action(adjoint(derivative(w, u), derivatives_expanded=True), u, derivatives_expanded=True) + + action(w, derivative(u, u), derivatives_expanded=True) + ) dAcdu = expand_derivatives(dAcdu) # Since dw/du = 0 @@ -294,7 +320,7 @@ def test_differentiation(): def test_zero_base_form_mult(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) v = Argument(V, 0) @@ -308,7 +334,7 @@ def test_zero_base_form_mult(): def test_base_form_call(): - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) V = FunctionSpace(domain_2d, f_2d) diff --git a/test/test_equals.py b/test/test_equals.py index ddfa5058c..3956463e8 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -11,7 +11,7 @@ def test_comparison_of_coefficients(): U = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) Ub = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) u_space = FunctionSpace(domain, U) ub_space = FunctionSpace(domain, Ub) @@ -43,7 +43,7 @@ def test_comparison_of_cofunctions(): U = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) Ub = FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) u_space = FunctionSpace(domain, U) ub_space = FunctionSpace(domain, Ub) @@ -72,7 +72,7 @@ def test_comparison_of_cofunctions(): def test_comparison_of_products(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space) u = Coefficient(v_space) @@ -86,7 +86,7 @@ def test_comparison_of_products(): def test_comparison_of_sums(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space) u = Coefficient(v_space) @@ -100,7 +100,7 @@ def test_comparison_of_sums(): def test_comparison_of_deeply_nested_expression(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) v_space = FunctionSpace(domain, V) v = Coefficient(v_space, count=1) u = Coefficient(v_space, count=1) @@ -113,8 +113,9 @@ def build_expr(a): elif i % 3 == 1: a = a * i elif i % 3 == 2: - a = a ** i + a = a**i return a + a = build_expr(u) b = build_expr(v) c = build_expr(w) diff --git a/test/test_evaluate.py b/test/test_evaluate.py index 55ca15aeb..da01d452b 100755 --- a/test/test_evaluate.py +++ b/test/test_evaluate.py @@ -3,9 +3,36 @@ import math -from ufl import (Argument, Coefficient, FunctionSpace, Identity, Mesh, SpatialCoordinate, as_matrix, as_vector, cos, - cross, det, dev, dot, exp, i, indices, inner, j, ln, outer, sin, skew, sqrt, sym, tan, tetrahedron, tr, - triangle) +from ufl import ( + Argument, + Coefficient, + FunctionSpace, + Identity, + Mesh, + SpatialCoordinate, + as_matrix, + as_vector, + cos, + cross, + det, + dev, + dot, + exp, + i, + indices, + inner, + j, + ln, + outer, + sin, + skew, + sqrt, + sym, + tan, + tetrahedron, + tr, + triangle, +) from ufl.constantvalue import as_ufl from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -43,7 +70,7 @@ def testIdentity(): def testCoords(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) s = x[0] + x[1] e = s((5, 7)) @@ -54,7 +81,7 @@ def testCoords(): def testFunction1(): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) s = 3 * f @@ -66,12 +93,13 @@ def testFunction1(): def testFunction2(): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) def g(x): return x[0] + s = 3 * f e = s((5, 7), {f: g}) v = 3 * 5 @@ -81,12 +109,13 @@ def g(x): def testArgument2(): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Argument(space, 2) def g(x): return x[0] + s = 3 * f e = s((5, 7), {f: g}) v = 3 * 5 @@ -95,40 +124,40 @@ def g(x): def testAlgebra(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) s = 3 * (x[0] + x[1]) - 7 + x[0] ** (x[1] / 2) e = s((5, 7)) - v = 3 * (5. + 7.) - 7 + 5. ** (7. / 2) + v = 3 * (5.0 + 7.0) - 7 + 5.0 ** (7.0 / 2) assert e == v def testIndexSum(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) - i, = indices(1) + (i,) = indices(1) s = x[i] * x[i] e = s((5, 7)) - v = 5 ** 2 + 7 ** 2 + v = 5**2 + 7**2 assert e == v def testIndexSum2(): cell = triangle - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) ident = Identity(cell.topological_dimension()) i, j = indices(2) s = (x[i] * x[j]) * ident[i, j] e = s((5, 7)) # v = sum_i sum_j x_i x_j delta_ij = x_0 x_0 + x_1 x_1 - v = 5 ** 2 + 7 ** 2 + v = 5**2 + 7**2 assert e == v def testMathFunctions(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain)[0] s = sin(x) @@ -163,7 +192,7 @@ def testMathFunctions(): def testListTensor(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x, y = SpatialCoordinate(domain) m = as_matrix([[x, y], [-y, -x]]) @@ -175,12 +204,12 @@ def testListTensor(): s = m[0, 0] * m[1, 0] * m[0, 1] * m[1, 1] e = s((5, 7)) - v = 5 ** 2 * 7 ** 2 + v = 5**2 * 7**2 assert e == v def testComponentTensor1(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) m = as_vector(x[i], i) @@ -191,7 +220,7 @@ def testComponentTensor1(): def testComponentTensor2(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(x, x) @@ -204,7 +233,7 @@ def testComponentTensor2(): def testComponentTensor3(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(x, x) @@ -218,19 +247,20 @@ def testComponentTensor3(): def testCoefficient(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) - e = f ** 2 + e = f**2 def eval_f(x): return x[0] * x[1] ** 2 - assert e((3, 7), {f: eval_f}) == (3 * 7 ** 2) ** 2 + + assert e((3, 7), {f: eval_f}) == (3 * 7**2) ** 2 def testCoefficientDerivative(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) e = f.dx(0) ** 2 + f.dx(1) ** 2 @@ -239,19 +269,20 @@ def eval_f(x, derivatives): if not derivatives: return eval_f.c * x[0] * x[1] ** 2 # assume only first order derivative - d, = derivatives + (d,) = derivatives if d == 0: return eval_f.c * x[1] ** 2 if d == 1: return eval_f.c * x[0] * 2 * x[1] + # shows how to attach data to eval_f eval_f.c = 5 - assert e((3, 7), {f: eval_f}) == (5 * 7 ** 2) ** 2 + (5 * 3 * 2 * 7) ** 2 + assert e((3, 7), {f: eval_f}) == (5 * 7**2) ** 2 + (5 * 3 * 2 * 7) ** 2 def test_dot(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) s = dot(x, 2 * x) e = s((5, 7)) @@ -260,7 +291,7 @@ def test_dot(): def test_inner(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = as_matrix(((2 * x[0], 3 * x[0]), (2 * x[1], 3 * x[1]))) s = inner(xx, 2 * xx) @@ -270,35 +301,27 @@ def test_inner(): def test_outer(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xx = outer(outer(x, x), as_vector((2, 3))) s = inner(xx, 2 * xx) e = s((5, 7)) - v = 2 * (5 ** 2 + 7 ** 2) ** 2 * (2 ** 2 + 3 ** 2) + v = 2 * (5**2 + 7**2) ** 2 * (2**2 + 3**2) assert e == v def test_cross(): - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (3, 5, 7) # Test cross product of triplets of orthogonal # vectors, where |a x b| = |a| |b| ts = [ - [as_vector((x[0], 0, 0)), - as_vector((0, x[1], 0)), - as_vector((0, 0, x[2]))], - [as_vector((x[0], x[1], 0)), - as_vector((x[1], -x[0], 0)), - as_vector((0, 0, x[2]))], - [as_vector((0, x[0], x[1])), - as_vector((0, x[1], -x[0])), - as_vector((x[2], 0, 0))], - [as_vector((x[0], 0, x[1])), - as_vector((x[1], 0, -x[0])), - as_vector((0, x[2], 0))], + [as_vector((x[0], 0, 0)), as_vector((0, x[1], 0)), as_vector((0, 0, x[2]))], + [as_vector((x[0], x[1], 0)), as_vector((x[1], -x[0], 0)), as_vector((0, 0, x[2]))], + [as_vector((0, x[0], x[1])), as_vector((0, x[1], -x[0])), as_vector((x[2], 0, 0))], + [as_vector((x[0], 0, x[1])), as_vector((x[1], 0, -x[0])), as_vector((0, x[2], 0))], ] for t in ts: for a in range(3): @@ -313,7 +336,7 @@ def test_cross(): def xtest_dev(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -325,31 +348,31 @@ def xtest_dev(): def test_skew(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s1 = skew(2 * xx) - s2 = (xx - xx.T) + s2 = xx - xx.T e = inner(s1, s1)(xv) v = inner(s2, s2)(xv) assert e == v def test_sym(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) s1 = sym(2 * xx) - s2 = (xx + xx.T) + s2 = xx + xx.T e = inner(s1, s1)(xv) v = inner(s2, s2)(xv) assert e == v def test_tr(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) xx = outer(x, x) @@ -360,14 +383,14 @@ def test_tr(): def test_det2D(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) xv = (5, 7) a, b = 6.5, -4 xx = as_matrix(((x[0], x[1]), (a, b))) s = det(2 * xx) e = s(xv) - v = 2 ** 2 * (5 * b - 7 * a) + v = 2**2 * (5 * b - 7 * a) assert e == v @@ -376,14 +399,10 @@ def xtest_det3D(): # FIXME xv = (5, 7, 9) a, b, c = 6.5, -4, 3 d, e, f = 2, 3, 4 - xx = as_matrix(((x[0], x[1], x[2]), - (a, b, c), - (d, e, f))) + xx = as_matrix(((x[0], x[1], x[2]), (a, b, c), (d, e, f))) s = det(2 * xx) e = s(xv) - v = 2 ** 3 * \ - (xv[0] * (b * f - e * c) - xv[1] * - (a * f - c * d) + xv[2] * (a * e - b * d)) + v = 2**3 * (xv[0] * (b * f - e * c) - xv[1] * (a * f - c * d) + xv[2] * (a * e - b * d)) assert e == v diff --git a/test/test_expand_indices.py b/test/test_expand_indices.py index f8314df2c..1cd9d8983 100755 --- a/test/test_expand_indices.py +++ b/test/test_expand_indices.py @@ -8,8 +8,31 @@ import pytest -from ufl import (Coefficient, FunctionSpace, Identity, Mesh, as_tensor, cos, det, div, dot, dx, exp, grad, i, inner, j, - k, l, ln, nabla_div, nabla_grad, outer, sin, triangle) +from ufl import ( + Coefficient, + FunctionSpace, + Identity, + Mesh, + as_tensor, + cos, + det, + div, + dot, + dx, + exp, + grad, + i, + inner, + j, + k, + l, + ln, + nabla_div, + nabla_grad, + outer, + sin, + triangle, +) from ufl.algorithms import compute_form_data, expand_derivatives, expand_indices from ufl.algorithms.renumbering import renumber_indices from ufl.finiteelement import FiniteElement @@ -21,13 +44,12 @@ class Fixture: - def __init__(self): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - velement = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + velement = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) telement = FiniteElement("Lagrange", cell, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) vspace = FunctionSpace(domain, velement) tspace = FunctionSpace(domain, telement) @@ -113,22 +135,22 @@ def TF(x, derivatives=()): def compare(self, f, value): debug = 0 if debug: - print(('f', f)) + print(("f", f)) g = expand_derivatives(f) if debug: - print(('g', g)) + print(("g", g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 g = expand_indices(g) if debug: - print(('g', g)) + print(("g", g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 g = renumber_indices(g) if debug: - print(('g', g)) + print(("g", g)) gv = g(self.x, self.mapping) assert abs(gv - value) < 1e-7 @@ -149,13 +171,13 @@ def test_basic_expand_indices(self, fixt): compare(sf, 3) compare(sf + 1, 4) compare(sf - 2.5, 0.5) - compare(sf/2, 1.5) - compare(sf/0.5, 6) + compare(sf / 2, 1.5) + compare(sf / 0.5, 6) compare(sf**2, 9) compare(sf**0.5, 3**0.5) compare(sf**3, 27) compare(0.5**sf, 0.5**3) - compare(sf * (sf/6), 1.5) + compare(sf * (sf / 6), 1.5) compare(sin(sf), math.sin(3)) compare(cos(sf), math.cos(3)) compare(exp(sf), math.exp(3)) @@ -165,13 +187,13 @@ def test_basic_expand_indices(self, fixt): compare(vf[0], 5) compare(vf[0] + 1, 6) compare(vf[0] - 2.5, 2.5) - compare(vf[0]/2, 2.5) - compare(vf[0]/0.5, 10) - compare(vf[0]**2, 25) - compare(vf[0]**0.5, 5**0.5) - compare(vf[0]**3, 125) - compare(0.5**vf[0], 0.5**5) - compare(vf[0] * (vf[0]/6), 5*(5./6)) + compare(vf[0] / 2, 2.5) + compare(vf[0] / 0.5, 10) + compare(vf[0] ** 2, 25) + compare(vf[0] ** 0.5, 5**0.5) + compare(vf[0] ** 3, 125) + compare(0.5 ** vf[0], 0.5**5) + compare(vf[0] * (vf[0] / 6), 5 * (5.0 / 6)) compare(sin(vf[0]), math.sin(5)) compare(cos(vf[0]), math.cos(5)) compare(exp(vf[0]), math.exp(5)) @@ -181,13 +203,13 @@ def test_basic_expand_indices(self, fixt): compare(tf[1, 1], 19) compare(tf[1, 1] + 1, 20) compare(tf[1, 1] - 2.5, 16.5) - compare(tf[1, 1]/2, 9.5) - compare(tf[1, 1]/0.5, 38) - compare(tf[1, 1]**2, 19**2) - compare(tf[1, 1]**0.5, 19**0.5) - compare(tf[1, 1]**3, 19**3) - compare(0.5**tf[1, 1], 0.5**19) - compare(tf[1, 1] * (tf[1, 1]/6), 19*(19./6)) + compare(tf[1, 1] / 2, 9.5) + compare(tf[1, 1] / 0.5, 38) + compare(tf[1, 1] ** 2, 19**2) + compare(tf[1, 1] ** 0.5, 19**0.5) + compare(tf[1, 1] ** 3, 19**3) + compare(0.5 ** tf[1, 1], 0.5**19) + compare(tf[1, 1] * (tf[1, 1] / 6), 19 * (19.0 / 6)) compare(sin(tf[1, 1]), math.sin(19)) compare(cos(tf[1, 1]), math.cos(19)) compare(exp(tf[1, 1]), math.exp(19)) @@ -200,11 +222,14 @@ def test_expand_indices_index_sum(self, fixt): compare = fixt.compare # Basic index sums - compare(vf[i]*vf[i], 5*5+7*7) - compare(vf[j]*tf[i, j]*vf[i], 5*5*11 + 5*7*13 + 5*7*17 + 7*7*19) - compare(vf[j]*tf.T[j, i]*vf[i], 5*5*11 + 5*7*13 + 5*7*17 + 7*7*19) + compare(vf[i] * vf[i], 5 * 5 + 7 * 7) + compare(vf[j] * tf[i, j] * vf[i], 5 * 5 * 11 + 5 * 7 * 13 + 5 * 7 * 17 + 7 * 7 * 19) + compare(vf[j] * tf.T[j, i] * vf[i], 5 * 5 * 11 + 5 * 7 * 13 + 5 * 7 * 17 + 7 * 7 * 19) compare(tf[i, i], 11 + 19) - compare(tf[i, j]*(tf[j, i]+outer(vf, vf)[i, j]), (5*5+11)*11 + (7*5+17)*13 + (7*5+13)*17 + (7*7+19)*19) + compare( + tf[i, j] * (tf[j, i] + outer(vf, vf)[i, j]), + (5 * 5 + 11) * 11 + (7 * 5 + 17) * 13 + (7 * 5 + 13) * 17 + (7 * 7 + 19) * 19, + ) compare(as_tensor(as_tensor(tf[i, j], (i, j))[k, l], (l, k))[i, i], 11 + 19) @@ -216,8 +241,8 @@ def test_expand_indices_derivatives(self, fixt): # Basic derivatives compare(sf.dx(0), 0.3) compare(sf.dx(1), 0.31) - compare(sf.dx(i)*vf[i], 0.30*5 + 0.31*7) - compare(vf[j].dx(i)*vf[i].dx(j), 0.50*0.50 + 0.51*0.70 + 0.70*0.51 + 0.71*0.71) + compare(sf.dx(i) * vf[i], 0.30 * 5 + 0.31 * 7) + compare(vf[j].dx(i) * vf[i].dx(j), 0.50 * 0.50 + 0.51 * 0.70 + 0.70 * 0.51 + 0.71 * 0.71) def test_expand_indices_hyperelasticity(self, fixt): @@ -240,25 +265,25 @@ def test_expand_indices_hyperelasticity(self, fixt): compare(F[1, 1], F11) J = det(F) - compare(J, (1 + 0.50)*(1 + 0.71) - 0.70*0.51) + compare(J, (1 + 0.50) * (1 + 0.71) - 0.70 * 0.51) # Strain tensors - C = F.T*F + C = F.T * F # Cij = sum_k Fki Fkj - C00 = F00*F00 + F10*F10 - C01 = F00*F01 + F10*F11 - C10 = F01*F00 + F11*F10 - C11 = F01*F01 + F11*F11 + C00 = F00 * F00 + F10 * F10 + C01 = F00 * F01 + F10 * F11 + C10 = F01 * F00 + F11 * F10 + C11 = F01 * F01 + F11 * F11 compare(C[0, 0], C00) compare(C[0, 1], C01) compare(C[1, 0], C10) compare(C[1, 1], C11) - E = (C-ident)/2 - E00 = (C00-1)/2 - E01 = (C01)/2 - E10 = (C10)/2 - E11 = (C11-1)/2 + E = (C - ident) / 2 + E00 = (C00 - 1) / 2 + E01 = (C01) / 2 + E10 = (C10) / 2 + E11 = (C11 - 1) / 2 compare(E[0, 0], E00) compare(E[0, 1], E01) compare(E[1, 0], E10) @@ -270,8 +295,8 @@ def test_expand_indices_hyperelasticity(self, fixt): compare(Q, Qvalue) K = 0.5 - psi = (K/2)*exp(Q) - compare(psi, 0.25*math.exp(Qvalue)) + psi = (K / 2) * exp(Q) + compare(psi, 0.25 * math.exp(Qvalue)) def test_expand_indices_div_grad(self, fixt): @@ -291,18 +316,21 @@ def test_expand_indices_div_grad(self, fixt): Dvf = grad(vf) Lvf = div(Dvf) Lvf2 = dot(Lvf, Lvf) - pp = compute_form_data(Lvf2*dx).preprocessed_form.integrals()[0].integrand() - print(('vf', vf.ufl_shape, str(vf))) - print(('Dvf', Dvf.ufl_shape, str(Dvf))) - print(('Lvf', Lvf.ufl_shape, str(Lvf))) - print(('Lvf2', Lvf2.ufl_shape, str(Lvf2))) - print(('pp', pp.ufl_shape, str(pp))) + pp = compute_form_data(Lvf2 * dx).preprocessed_form.integrals()[0].integrand() + print(("vf", vf.ufl_shape, str(vf))) + print(("Dvf", Dvf.ufl_shape, str(Dvf))) + print(("Lvf", Lvf.ufl_shape, str(Lvf))) + print(("Lvf2", Lvf2.ufl_shape, str(Lvf2))) + print(("pp", pp.ufl_shape, str(pp))) a = div(grad(vf)) - compare(dot(a, a), (0.20+0.40)**2 + (0.21+0.41)**2) + compare(dot(a, a), (0.20 + 0.40) ** 2 + (0.21 + 0.41) ** 2) a = div(grad(tf)) - compare(inner(a, a), (10.00+11.00)**2 + (10.01+11.01)**2 + (10.10+11.10)**2 + (10.11+11.11)**2) + compare( + inner(a, a), + (10.00 + 11.00) ** 2 + (10.01 + 11.01) ** 2 + (10.10 + 11.10) ** 2 + (10.11 + 11.11) ** 2, + ) def test_expand_indices_nabla_div_grad(self, fixt): @@ -319,7 +347,10 @@ def test_expand_indices_nabla_div_grad(self, fixt): compare(a, 3.300 + 3.311) a = nabla_div(nabla_grad(vf)) - compare(dot(a, a), (0.20+0.40)**2 + (0.21+0.41)**2) + compare(dot(a, a), (0.20 + 0.40) ** 2 + (0.21 + 0.41) ** 2) a = nabla_div(nabla_grad(tf)) - compare(inner(a, a), (10.00+11.00)**2 + (10.01+11.01)**2 + (10.10+11.10)**2 + (10.11+11.11)**2) + compare( + inner(a, a), + (10.00 + 11.00) ** 2 + (10.01 + 11.01) ** 2 + (10.10 + 11.10) ** 2 + (10.11 + 11.11) ** 2, + ) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 0cdebb422..27e5d102e 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -5,8 +5,25 @@ import pytest -from ufl import (Action, Argument, Coefficient, Constant, Form, FunctionSpace, Mesh, TestFunction, TrialFunction, - action, adjoint, cos, derivative, dx, inner, sin, triangle) +from ufl import ( + Action, + Argument, + Coefficient, + Constant, + Form, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + action, + adjoint, + cos, + derivative, + dx, + inner, + sin, + triangle, +) from ufl.algorithms import expand_derivatives from ufl.algorithms.apply_derivatives import apply_derivatives from ufl.core.external_operator import ExternalOperator @@ -18,7 +35,7 @@ @pytest.fixture def domain_2d(): - return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) @pytest.fixture @@ -62,13 +79,15 @@ def test_properties(V1): v1 = Argument(V1, 1) e = ExternalOperator(u, function_space=V1) - assert str(e) == 'e(w_0; v_0)' + assert str(e) == "e(w_0; v_0)" e = ExternalOperator(u, function_space=V1, derivatives=(1,)) - assert str(e) == '∂e(w_0; v_0)/∂o1' + assert str(e) == "∂e(w_0; v_0)/∂o1" - e = ExternalOperator(u, r, 2 * s, t, function_space=V1, derivatives=(1, 0, 1, 2), argument_slots=(v0, v1)) - assert str(e) == '∂e(w_0, w_1, 2 * w_2, w_3; v_1, v_0)/∂o1∂o3∂o4∂o4' + e = ExternalOperator( + u, r, 2 * s, t, function_space=V1, derivatives=(1, 0, 1, 2), argument_slots=(v0, v1) + ) + assert str(e) == "∂e(w_0, w_1, 2 * w_2, w_3; v_1, v_0)/∂o1∂o3∂o4∂o4" def test_form(V1, V2): @@ -82,7 +101,7 @@ def test_form(V1, V2): F = N * v * dx actual = derivative(F, u, u_hat) - vstar, = N.arguments() + (vstar,) = N.arguments() Nhat = TrialFunction(N.ufl_function_space()) dNdu = N._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=(vstar, u_hat)) @@ -96,7 +115,7 @@ def test_form(V1, V2): F = N * u * v * dx actual = derivative(F, u, u_hat) - vstar, = N.arguments() + (vstar,) = N.arguments() Nhat = TrialFunction(N.ufl_function_space()) dNdu = N._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=(vstar, u_hat)) @@ -126,8 +145,8 @@ def test_differentiation_procedure_action(V1, V2): assert N2.coefficients() == (s,) # Get v* - vstar_N1, = N1.arguments() - vstar_N2, = N2.arguments() + (vstar_N1,) = N1.arguments() + (vstar_N2,) = N2.arguments() assert vstar_N1.ufl_function_space().dual() == V1 assert vstar_N2.ufl_function_space().dual() == V1 @@ -177,18 +196,23 @@ def test_differentiation_procedure_action(V1, V2): # Check argument slots assert dN1du.argument_slots() == (vstar_dN1du, u_hat) - assert dN2du.argument_slots() == (vstar_dN2du, - sin(s) * s_hat) + assert dN2du.argument_slots() == (vstar_dN2du, -sin(s) * s_hat) def test_extractions(domain_2d, V1): - from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, - extract_base_form_operators, extract_coefficients, extract_constants) + from ufl.algorithms.analysis import ( + extract_arguments, + extract_arguments_and_coefficients, + extract_base_form_operators, + extract_coefficients, + extract_constants, + ) u = Coefficient(V1) c = Constant(domain_2d) e = ExternalOperator(u, c, function_space=V1) - vstar_e, = e.arguments() + (vstar_e,) = e.arguments() assert extract_coefficients(e) == [u] assert extract_arguments(e) == [vstar_e] @@ -221,7 +245,7 @@ def test_extractions(domain_2d, V1): w = Coefficient(V1) e2 = ExternalOperator(w, e, function_space=V1) - vstar_e2, = e2.arguments() + (vstar_e2,) = e2.arguments() assert extract_coefficients(e2) == [u, w] assert extract_arguments(e2) == [vstar_e2, u_hat] @@ -242,24 +266,23 @@ def get_external_operators(form_base): elif isinstance(form_base, BaseForm): return form_base.base_form_operators() else: - raise ValueError('Expecting FormBase argument!') + raise ValueError("Expecting FormBase argument!") def test_adjoint_action_jacobian(V1, V2, V3): - u = Coefficient(V1) m = Coefficient(V2) # N(u, m; v*) N = ExternalOperator(u, m, function_space=V3) - vstar_N, = N.arguments() + (vstar_N,) = N.arguments() # Arguments for the Gateaux-derivative def u_hat(number): - return Argument(V1, number) # V1: degree 1 # dFdu.arguments()[-1] + return Argument(V1, number) # V1: degree 1 # dFdu.arguments()[-1] def m_hat(number): - return Argument(V2, number) # V2: degree 2 # dFdm.arguments()[-1] + return Argument(V2, number) # V2: degree 2 # dFdm.arguments()[-1] def vstar_N(number): return Argument(V3.dual(), number) # V3: degree 3 @@ -273,7 +296,6 @@ def vstar_N(number): form_base_expressions = (N * dx, N * v2 * dx, N * v3 * dx) # , N) for F in form_base_expressions: - # Get test function v_F = F.arguments() if isinstance(F, Form) else () # If we have a 0-form with an ExternalOperator: e.g. F = N * dx @@ -331,7 +353,6 @@ def vstar_N(number): def test_multiple_external_operators(V1, V2): - u = Coefficient(V1) m = Coefficient(V1) w = Coefficient(V2) @@ -364,14 +385,18 @@ def test_multiple_external_operators(V1, V2): dFdN1 = inner(v_hat, v) * dx dFdN2 = inner(w_hat, v) * dx dFdN3 = inner(v_hat, v) * dx - dN1du = N1._ufl_expr_reconstruct_(u, m, derivatives=(1, 0), argument_slots=N1.arguments() + (v_hat,)) + dN1du = N1._ufl_expr_reconstruct_( + u, m, derivatives=(1, 0), argument_slots=N1.arguments() + (v_hat,) + ) dN3du = N3._ufl_expr_reconstruct_(u, derivatives=(1,), argument_slots=N3.arguments() + (v_hat,)) assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) # dFdm = Action(dFdN1, dN1dm) dFdm = expand_derivatives(derivative(F, m)) - dN1dm = N1._ufl_expr_reconstruct_(u, m, derivatives=(0, 1), argument_slots=N1.arguments() + (v_hat,)) + dN1dm = N1._ufl_expr_reconstruct_( + u, m, derivatives=(0, 1), argument_slots=N1.arguments() + (v_hat,) + ) assert dFdm == Action(dFdN1, dN1dm) @@ -397,23 +422,31 @@ def test_multiple_external_operators(V1, V2): # Action(∂F/∂N4, Action(∂N4/∂N1, dN1/du)) dFdu = expand_derivatives(derivative(F, u)) dFdN4_partial = inner(v_hat, v) * dx - dN4dN1_partial = N4._ufl_expr_reconstruct_(N1, u, derivatives=(1, 0), argument_slots=N4.arguments() + (v_hat,)) - dN4du_partial = N4._ufl_expr_reconstruct_(N1, u, derivatives=(0, 1), argument_slots=N4.arguments() + (v_hat,)) + dN4dN1_partial = N4._ufl_expr_reconstruct_( + N1, u, derivatives=(1, 0), argument_slots=N4.arguments() + (v_hat,) + ) + dN4du_partial = N4._ufl_expr_reconstruct_( + N1, u, derivatives=(0, 1), argument_slots=N4.arguments() + (v_hat,) + ) - assert dFdu == Action(dFdN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dFdN4_partial, dN4du_partial) + assert dFdu == Action(dFdN4_partial, Action(dN4dN1_partial, dN1du)) + Action( + dFdN4_partial, dN4du_partial + ) # dFdm = Action(∂F/∂N4, Action(∂N4/∂N1, dN1/dm)) dFdm = expand_derivatives(derivative(F, m)) assert dFdm == Action(dFdN4_partial, Action(dN4dN1_partial, dN1dm)) - # --- F = < N1(u, m; v*), v > + + + < N4(N1(u, m), u; v*), v > --- # + # --- F = < N1(u, m; v*), v > + + + < + # N4(N1(u, m), u; v*), v > --- # F = (inner(N1, v) + inner(N2, v) + inner(N3, v) + inner(N4, v)) * dx dFdu = expand_derivatives(derivative(F, u)) assert dFdu == Action(dFdN1, dN1du) + Action(dFdN3, dN3du) + Action( - dFdN4_partial, Action(dN4dN1_partial, dN1du)) + Action(dFdN4_partial, dN4du_partial) + dFdN4_partial, Action(dN4dN1_partial, dN1du) + ) + Action(dFdN4_partial, dN4du_partial) dFdm = expand_derivatives(derivative(F, m)) assert dFdm == Action(dFdN1, dN1dm) + Action(dFdN4_partial, Action(dN4dN1_partial, dN1dm)) @@ -421,7 +454,8 @@ def test_multiple_external_operators(V1, V2): dFdw = expand_derivatives(derivative(F, w)) assert dFdw == Action(dFdN2, dN2dw) - # --- F = < N5(N4(N1(u, m), u), u; v*), v > + < N1(u, m; v*), v > + < u * N5(N4(N1(u, m), u), u; v*), v >--- # + # --- F = < N5(N4(N1(u, m), u), u; v*), v > + < N1(u, m; v*), v > + + # < u * N5(N4(N1(u, m), u), u; v*), v >--- # F = (inner(N5, v) + inner(N1, v) + inner(u * N5, v)) * dx @@ -439,10 +473,17 @@ def test_multiple_external_operators(V1, V2): dFdu_partial = inner(w * N5, v) * dx dFdN1_partial = inner(w, v) * dx dFdN5_partial = (inner(w, v) + inner(u * w, v)) * dx - dN5dN4_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(1, 0), argument_slots=N4.arguments() + (w,)) - dN5du_partial = N5._ufl_expr_reconstruct_(N4, u, derivatives=(0, 1), argument_slots=N4.arguments() + (w,)) - dN5du = Action(dN5dN4_partial, Action(dN4dN1_partial, dN1du)) + Action( - dN5dN4_partial, dN4du_partial) + dN5du_partial + dN5dN4_partial = N5._ufl_expr_reconstruct_( + N4, u, derivatives=(1, 0), argument_slots=N4.arguments() + (w,) + ) + dN5du_partial = N5._ufl_expr_reconstruct_( + N4, u, derivatives=(0, 1), argument_slots=N4.arguments() + (w,) + ) + dN5du = ( + Action(dN5dN4_partial, Action(dN4dN1_partial, dN1du)) + + Action(dN5dN4_partial, dN4du_partial) + + dN5du_partial + ) dFdu = expand_derivatives(derivative(F, u)) assert dFdu == dFdu_partial + Action(dFdN1_partial, dN1du) + Action(dFdN5_partial, dN5du) diff --git a/test/test_ffcforms.py b/test/test_ffcforms.py index 5ab8778ab..9b913947b 100755 --- a/test/test_ffcforms.py +++ b/test/test_ffcforms.py @@ -13,9 +13,36 @@ # Examples copied from the FFC demo directory, examples contributed # by Johan Jansson, Kristian Oelgaard, Marie Rognes, and Garth Wells. -from ufl import (Coefficient, Constant, Dx, FacetNormal, FunctionSpace, Mesh, TestFunction, TestFunctions, - TrialFunction, TrialFunctions, VectorConstant, avg, curl, div, dot, ds, dS, dx, grad, i, inner, j, - jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import ( + Coefficient, + Constant, + Dx, + FacetNormal, + FunctionSpace, + Mesh, + TestFunction, + TestFunctions, + TrialFunction, + TrialFunctions, + VectorConstant, + avg, + curl, + div, + dot, + dS, + ds, + dx, + grad, + i, + inner, + j, + jump, + lhs, + rhs, + sqrt, + tetrahedron, + triangle, +) from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback from ufl.sobolevspace import H1, L2, HCurl, HDiv @@ -23,7 +50,7 @@ def testConstant(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -32,15 +59,15 @@ def testConstant(): c = Constant(domain) d = VectorConstant(domain) - a = c * dot(grad(v), grad(u)) * dx # noqa: F841 + _ = c * dot(grad(v), grad(u)) * dx # FFC notation: L = dot(d, grad(v))*dx - L = inner(d, grad(v)) * dx # noqa: F841 + _ = inner(d, grad(v)) * dx def testElasticity(): - element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -51,21 +78,21 @@ def eps(v): return grad(v) + (grad(v)).T # FFC notation: a = 0.25*dot(eps(v), eps(u))*dx - a = 0.25 * inner(eps(v), eps(u)) * dx # noqa: F841 + _ = 0.25 * inner(eps(v), eps(u)) * dx def testEnergyNorm(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) - a = (v * v + dot(grad(v), grad(v))) * dx # noqa: F841 + _ = (v * v + dot(grad(v), grad(v))) * dx def testEquation(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 @@ -76,13 +103,13 @@ def testEquation(): F = v * (u - u0) * dx + k * dot(grad(v), grad(0.5 * (u0 + u))) * dx - a = lhs(F) # noqa: F841 - L = rhs(F) # noqa: F841 + _ = lhs(F) + _ = rhs(F) def testFunctionOperators(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -92,12 +119,12 @@ def testFunctionOperators(): # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx - a = sqrt(1 / abs(1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx # noqa: F841 + _ = sqrt(1 / abs(1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx def testHeat(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -107,19 +134,17 @@ def testHeat(): f = Coefficient(space) k = Constant(domain) - a = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx # noqa: F841 - L = v * u0 * dx + k * v * f * dx # noqa: F841 + _ = v * u1 * dx + k * c * dot(grad(v), grad(u1)) * dx + _ = v * u0 * dx + k * v * f * dx def testMass(): element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) - v = TestFunction(space) u = TrialFunction(space) - - a = v * u * dx # noqa: F841 + _ = v * u * dx def testMixedMixedElement(): @@ -129,40 +154,36 @@ def testMixedMixedElement(): def testMixedPoisson(): q = 1 - - BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2, ), contravariant_piola, HDiv) + BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2,), contravariant_piola, HDiv) DG = FiniteElement("Discontinuous Lagrange", triangle, q - 1, (), identity_pullback, L2) mixed_element = MixedElement([BDM, DG]) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, mixed_element) (tau, w) = TestFunctions(space) (sigma, u) = TrialFunctions(space) - f = Coefficient(FunctionSpace(domain, DG)) - - a = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx # noqa: F841 - L = w * f * dx # noqa: F841 + _ = (dot(tau, sigma) - div(tau) * u + w * div(sigma)) * dx + _ = w * f * dx def testNavierStokes(): - element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - w = Coefficient(space) # FFC notation: a = v[i]*w[j]*D(u[i], j)*dx - a = v[i] * w[j] * Dx(u[i], j) * dx # noqa: F841 + _ = v[i] * w[j] * Dx(u[i], j) * dx def testNeumannProblem(): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -171,23 +192,21 @@ def testNeumannProblem(): g = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx - a = inner(grad(v), grad(u)) * dx # noqa: F841 + _ = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx + dot(v, g)*ds - L = inner(v, f) * dx + inner(v, g) * ds # noqa: F841 + _ = inner(v, f) * dx + inner(v, g) * ds def testOptimization(): element = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) - v = TestFunction(space) u = TrialFunction(space) f = Coefficient(space) - - a = dot(grad(v), grad(u)) * dx # noqa: F841 - L = v * f * dx # noqa: F841 + _ = dot(grad(v), grad(u)) * dx + _ = v * f * dx def testP5tet(): @@ -200,20 +219,18 @@ def testP5tri(): def testPoissonDG(): element = FiniteElement("Discontinuous Lagrange", triangle, 1, (), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) f = Coefficient(space) - n = FacetNormal(domain) # FFC notation: h = MeshSize(domain), not supported by UFL h = Constant(domain) gN = Coefficient(space) - alpha = 4.0 gamma = 8.0 @@ -229,17 +246,17 @@ def testPoissonDG(): a = inner(grad(v), grad(u)) * dx a -= inner(avg(grad(v)), jump(u, n)) * dS a -= inner(jump(v, n), avg(grad(u))) * dS - a += alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS + a += alpha / h("+") * dot(jump(v, n), jump(u, n)) * dS a -= inner(grad(v), u * n) * ds a -= inner(u * n, grad(u)) * ds a += gamma / h * v * u * ds - L = v * f * dx + v * gN * ds # noqa: F841 + _ = v * f * dx + v * gN * ds def testPoisson(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -247,13 +264,13 @@ def testPoisson(): f = Coefficient(space) # Note: inner() also works - a = dot(grad(v), grad(u)) * dx # noqa: F841 - L = v * f * dx # noqa: F841 + _ = dot(grad(v), grad(u)) * dx + _ = v * f * dx def testPoissonSystem(): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -261,10 +278,10 @@ def testPoissonSystem(): f = Coefficient(space) # FFC notation: a = dot(grad(v), grad(u))*dx - a = inner(grad(v), grad(u)) * dx # noqa: F841 + _ = inner(grad(v), grad(u)) * dx # FFC notation: L = dot(v, f)*dx - L = inner(v, f) * dx # noqa: F841 + _ = inner(v, f) * dx def testProjection(): @@ -273,11 +290,10 @@ def testProjection(): # projection can be extended to handle also local projections. P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, P1) - - v = TestFunction(space) # noqa: F841 - f = Coefficient(space) # noqa: F841 + _ = TestFunction(space) + _ = Coefficient(space) # pi0 = Projection(P0) # pi1 = Projection(P1) @@ -294,9 +310,9 @@ def testQuadratureElement(): # sig = VectorQuadratureElement(triangle, 3) QE = FiniteElement("Quadrature", triangle, 3, (), identity_pullback, L2) - sig = FiniteElement("Quadrature", triangle, 3, (2, ), identity_pullback, L2) + sig = FiniteElement("Quadrature", triangle, 3, (2,), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -306,53 +322,48 @@ def testQuadratureElement(): sig0 = Coefficient(FunctionSpace(domain, sig)) f = Coefficient(space) - a = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx # noqa: F841 - L = v * f * dx - dot(grad(v), sig0) * dx # noqa: F841 + _ = v.dx(i) * C * u.dx(i) * dx + v.dx(i) * 2 * u0 * u * u0.dx(i) * dx + _ = v * f * dx - dot(grad(v), sig0) * dx def testStokes(): # UFLException: Shape mismatch in sum. - P2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) + P2 = FiniteElement("Lagrange", triangle, 2, (2,), identity_pullback, H1) P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) TH = MixedElement([P2, P1]) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) th_space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) (v, q) = TestFunctions(th_space) (u, p) = TrialFunctions(th_space) - f = Coefficient(p2_space) # FFC notation: # a = (dot(grad(v), grad(u)) - div(v)*p + q*div(u))*dx - a = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx # noqa: F841 - - L = dot(v, f) * dx # noqa: F841 + _ = (inner(grad(v), grad(u)) - div(v) * p + q * div(u)) * dx + _ = dot(v, f) * dx def testSubDomain(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) - f = Coefficient(space) - - M = f * dx(2) + f * ds(5) # noqa: F841 + _ = f * dx(2) + f * ds(5) def testSubDomains(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * ds(1) - a += v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) + a += v("+") * u("+") * dS(0) + 4.3 * v("+") * u("+") * dS(1) def testTensorWeightedPoisson(): @@ -376,15 +387,14 @@ def testTensorWeightedPoisson(): P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) v = TestFunction(p1_space) u = TrialFunction(p1_space) C = Coefficient(p0_space) - - a = inner(grad(v), C * grad(u)) * dx # noqa: F841 + _ = inner(grad(v), C * grad(u)) * dx def testVectorLaplaceGradCurl(): @@ -395,8 +405,12 @@ def HodgeLaplaceGradCurl(space, fspace): # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx - a = (inner(tau, sigma) - inner(grad(tau), u) + - inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx + a = ( + inner(tau, sigma) + - inner(grad(tau), u) + + inner(v, grad(sigma)) + + inner(curl(v), curl(u)) + ) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx @@ -409,10 +423,11 @@ def HodgeLaplaceGradCurl(space, fspace): GRAD = FiniteElement("Lagrange", shape, order, (), identity_pullback, H1) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) - CURL = FiniteElement("N1curl", shape, order, (3, ), covariant_piola, HCurl) + CURL = FiniteElement("N1curl", shape, order, (3,), covariant_piola, HCurl) - VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", shape, 1, (3, ), identity_pullback, H1)) + VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", shape, 1, (3,), identity_pullback, H1)) - [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, MixedElement([GRAD, CURL])), - FunctionSpace(domain, VectorLagrange)) + [a, L] = HodgeLaplaceGradCurl( + FunctionSpace(domain, MixedElement([GRAD, CURL])), FunctionSpace(domain, VectorLagrange) + ) diff --git a/test/test_form.py b/test/test_form.py index 30ca5117a..e83abcfcf 100755 --- a/test/test_form.py +++ b/test/test_form.py @@ -1,7 +1,23 @@ import pytest -from ufl import (Coefficient, Cofunction, Form, FormSum, FunctionSpace, Mesh, SpatialCoordinate, TestFunction, - TrialFunction, dot, ds, dx, grad, inner, nabla_grad, triangle) +from ufl import ( + Coefficient, + Cofunction, + Form, + FormSum, + FunctionSpace, + Mesh, + SpatialCoordinate, + TestFunction, + TrialFunction, + dot, + ds, + dx, + grad, + inner, + nabla_grad, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.form import BaseForm from ufl.pullback import identity_pullback @@ -18,7 +34,7 @@ def element(): @pytest.fixture def domain(): cell = triangle - return Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) @pytest.fixture @@ -44,7 +60,7 @@ def stiffness(domain): @pytest.fixture def convection(domain): cell = triangle - element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) + element = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -74,7 +90,7 @@ def boundary_load(domain): def test_form_arguments(mass, stiffness, convection, load): v, u = mass.arguments() - f, = load.coefficients() + (f,) = load.coefficients() assert v.number() == 0 assert u.number() == 1 @@ -104,7 +120,7 @@ def test_form_coefficients(element, domain): def test_form_domains(): cell = triangle element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) V = FunctionSpace(domain, element) v = TestFunction(V) @@ -136,32 +152,33 @@ def test_form_integrals(mass, boundary_load): def test_form_call(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) V = FunctionSpace(domain, element) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) g = Coefficient(V) - a = g*inner(grad(v), grad(u))*dx + a = g * inner(grad(v), grad(u)) * dx M = a(f, f, coefficients={g: 1}) - assert M == grad(f)**2*dx + assert M == grad(f) ** 2 * dx import sys + if sys.version_info.major >= 3 and sys.version_info.minor >= 5: - a = u*v*dx + a = u * v * dx M = eval("(a @ f) @ g") - assert M == g*f*dx + assert M == g * f * dx def test_formsum(mass): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) V = FunctionSpace(domain, element) v = Cofunction(V.dual()) assert v + mass assert mass + v - assert isinstance((mass+v), FormSum) + assert isinstance((mass + v), FormSum) assert len((mass + v + v).components()) == 3 # Variational forms are summed appropriately @@ -169,7 +186,7 @@ def test_formsum(mass): assert v - mass assert mass - v - assert isinstance((mass+v), FormSum) + assert isinstance((mass + v), FormSum) assert -v assert isinstance(-v, BaseForm) diff --git a/test/test_illegal.py b/test/test_illegal.py index 9931946a4..acc2d33d5 100755 --- a/test/test_illegal.py +++ b/test/test_illegal.py @@ -15,12 +15,12 @@ def selement(): @pytest.fixture def velement(): - return FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) + return FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) @pytest.fixture def domain(): - return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) @pytest.fixture diff --git a/test/test_indexing.py b/test/test_indexing.py index 4ffdc7eb5..8b4d69d29 100755 --- a/test/test_indexing.py +++ b/test/test_indexing.py @@ -9,7 +9,7 @@ @pytest.fixture def domain(): - return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) @pytest.fixture diff --git a/test/test_indices.py b/test/test_indices.py index 52ef28cf1..ec85f7aa0 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -1,85 +1,106 @@ import pytest -from ufl import (Argument, Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, as_matrix, as_tensor, - as_vector, cos, dx, exp, i, indices, j, k, l, outer, sin, triangle) +from ufl import ( + Argument, + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + as_matrix, + as_tensor, + as_vector, + cos, + dx, + exp, + i, + indices, + j, + k, + l, + outer, + sin, + triangle, +) from ufl.classes import IndexSum from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -# TODO: add more expressions to test as many possible combinations of index notation as feasible... +# TODO: add more expressions to test as many possible combinations of +# index notation as feasible... def test_vector_indices(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) - u[i]*f[i]*dx - u[j]*f[j]*dx + u[i] * f[i] * dx + u[j] * f[j] * dx def test_tensor_indices(self): element = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) - u[i, j]*f[i, j]*dx - u[j, i]*f[i, j]*dx - u[j, i]*f[j, i]*dx + u[i, j] * f[i, j] * dx + u[j, i] * f[i, j] * dx + u[j, i] * f[j, i] * dx with pytest.raises(BaseException): - (u[i, i]+f[j, i])*dx + (u[i, i] + f[j, i]) * dx def test_indexed_sum1(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) - a = u[i]+f[i] + a = u[i] + f[i] with pytest.raises(BaseException): - a*dx + a * dx def test_indexed_sum2(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) f = Coefficient(space) - a = u[j]+f[j]+v[j]+2*v[j]+exp(u[i]*u[i])/2*f[j] + a = u[j] + f[j] + v[j] + 2 * v[j] + exp(u[i] * u[i]) / 2 * f[j] with pytest.raises(BaseException): - a*dx + a * dx def test_indexed_sum3(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) u = Argument(space, 2) f = Coefficient(space) with pytest.raises(BaseException): - u[i]+f[j] + u[i] + f[j] def test_indexed_function1(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) f = Coefficient(space) - aarg = (u[i]+f[i])*v[i] - exp(aarg)*dx + aarg = (u[i] + f[i]) * v[i] + exp(aarg) * dx def test_indexed_function2(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Argument(space, 2) u = Argument(space, 3) @@ -95,19 +116,19 @@ def test_indexed_function2(self): def test_indexed_function3(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) Argument(space, 2) u = Argument(space, 3) f = Coefficient(space) with pytest.raises(BaseException): - sin(u[i] + f[i])*dx + sin(u[i] + f[i]) * dx def test_vector_from_indices(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -124,14 +145,14 @@ def test_vector_from_indices(self): def test_matrix_from_indices(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - A = as_matrix(u[i]*v[j], (i, j)) - B = as_matrix(v[k]*v[k]*u[i]*v[j], (j, i)) + A = as_matrix(u[i] * v[j], (i, j)) + B = as_matrix(v[k] * v[k] * u[i] * v[j], (j, i)) C = A + A C = B + B D = A + B @@ -142,8 +163,8 @@ def test_matrix_from_indices(self): def test_vector_from_list(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -156,8 +177,8 @@ def test_vector_from_list(self): def test_matrix_from_list(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -165,7 +186,7 @@ def test_matrix_from_list(self): # create matrix from list A = as_matrix([[u[0], u[1]], [v[0], v[1]]]) # create matrix from indices - B = as_matrix((v[k]*v[k]) * u[i]*v[j], (j, i)) + B = as_matrix((v[k] * v[k]) * u[i] * v[j], (j, i)) # Test addition C = A + A C = B + B @@ -177,8 +198,8 @@ def test_matrix_from_list(self): def test_tensor(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -186,7 +207,7 @@ def test_tensor(self): g = Coefficient(space) # define the components of a fourth order tensor - Cijkl = u[i]*v[j]*f[k]*g[l] + Cijkl = u[i] * v[j] * f[k] * g[l] assert len(Cijkl.ufl_shape) == 0 assert set(Cijkl.ufl_free_indices) == {i.count(), j.count(), k.count(), l.count()} @@ -205,7 +226,7 @@ def test_tensor(self): # legal? vv = as_vector([u[i], v[i]]) - f[i]*vv # this is well defined: ww = sum_i + f[i] * vv # this is well defined: ww = sum_i # illegal with pytest.raises(BaseException): @@ -217,8 +238,8 @@ def test_tensor(self): def test_indexed(self): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -238,8 +259,8 @@ def test_indexed(self): def test_spatial_derivative(self): cell = triangle - element = FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) @@ -256,7 +277,7 @@ def test_spatial_derivative(self): self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () - a = (v[i]*u[j]).dx(i, j) + a = (v[i] * u[j]).dx(i, j) self.assertSameIndices(a, ()) self.assertIsInstance(a, IndexSum) assert a.ufl_shape == () @@ -272,7 +293,7 @@ def test_spatial_derivative(self): self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () - a = (v[i]*u[j]).dx(0, 1) + a = (v[i] * u[j]).dx(0, 1) assert set(a.ufl_free_indices) == {i.count(), j.count()} self.assertNotIsInstance(a, IndexSum) assert a.ufl_shape == () diff --git a/test/test_interpolate.py b/test/test_interpolate.py index 71f3cd145..877f55a35 100644 --- a/test/test_interpolate.py +++ b/test/test_interpolate.py @@ -5,11 +5,31 @@ import pytest -from ufl import (Action, Adjoint, Argument, Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, action, - adjoint, derivative, dx, grad, inner, replace, triangle) +from ufl import ( + Action, + Adjoint, + Argument, + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + action, + adjoint, + derivative, + dx, + grad, + inner, + replace, + triangle, +) from ufl.algorithms.ad import expand_derivatives -from ufl.algorithms.analysis import (extract_arguments, extract_arguments_and_coefficients, extract_base_form_operators, - extract_coefficients) +from ufl.algorithms.analysis import ( + extract_arguments, + extract_arguments_and_coefficients, + extract_base_form_operators, + extract_coefficients, +) from ufl.algorithms.expand_indices import expand_indices from ufl.core.interpolate import Interpolate from ufl.finiteelement import FiniteElement @@ -19,7 +39,7 @@ @pytest.fixture def domain_2d(): - return Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + return Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) @pytest.fixture @@ -35,7 +55,6 @@ def V2(domain_2d): def test_symbolic(V1, V2): - # Set dual of V2 V2_dual = V2.dual() @@ -51,7 +70,6 @@ def test_symbolic(V1, V2): def test_action_adjoint(V1, V2): - # Set dual of V2 V2_dual = V2.dual() vstar = Argument(V2_dual, 0) @@ -78,7 +96,6 @@ def test_action_adjoint(V1, V2): def test_differentiation(V1, V2): - u = Coefficient(V1) v = TestFunction(V1) @@ -133,7 +150,6 @@ def test_differentiation(V1, V2): def test_extract_base_form_operators(V1, V2): - u = Coefficient(V1) uhat = TrialFunction(V1) vstar = Argument(V2.dual(), 0) diff --git a/test/test_lhs_rhs.py b/test/test_lhs_rhs.py index 2be0e584c..bb99eab3a 100755 --- a/test/test_lhs_rhs.py +++ b/test/test_lhs_rhs.py @@ -3,8 +3,23 @@ # First added: 2011-11-09 # Last changed: 2011-11-09 -from ufl import (Argument, Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, action, derivative, - ds, dS, dx, exp, interval, system) +from ufl import ( + Argument, + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + action, + derivative, + dS, + ds, + dx, + exp, + interval, + system, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -12,7 +27,7 @@ def test_lhs_rhs_simple(): V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) u = TrialFunction(space) @@ -41,13 +56,13 @@ def test_lhs_rhs_simple(): def test_lhs_rhs_derivatives(): V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = TestFunction(space) u = TrialFunction(space) f = Coefficient(space) - F0 = exp(f) * u * v * dx + v * dx + f * v * ds + exp(f)('+') * v * dS + F0 = exp(f) * u * v * dx + v * dx + f * v * ds + exp(f)("+") * v * dS a, L = system(F0) assert len(a.integrals()) == 1 assert len(L.integrals()) == 3 @@ -58,7 +73,7 @@ def test_lhs_rhs_derivatives(): def test_lhs_rhs_slightly_obscure(): V = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) space = FunctionSpace(domain, V) u = TrialFunction(space) w = Argument(space, 2) diff --git a/test/test_literals.py b/test/test_literals.py index aed180b24..7e63c89bf 100755 --- a/test/test_literals.py +++ b/test/test_literals.py @@ -16,7 +16,8 @@ def test_zero(self): self.assertNotEqual(z1, 1.0) self.assertFalse(z1) - # If zero() == 0 is to be allowed, it must not have the same hash or it will collide with 0 as key in dicts... + # If zero() == 0 is to be allowed, it must not have the same hash or + # it will collide with 0 as key in dicts... self.assertNotEqual(hash(z1), hash(0.0)) self.assertNotEqual(hash(z1), hash(0)) @@ -77,7 +78,7 @@ def test_scalar_sums(self): s = [as_ufl(i) for i in range(n)] for i in range(n): - self.assertNotEqual(s[i], i+1) + self.assertNotEqual(s[i], i + 1) for i in range(n): assert s[i] == i @@ -98,9 +99,9 @@ def test_scalar_sums(self): assert s[1] + s[2] == 3 assert s[1] + s[2] + s[3] == s[6] assert s[5] - s[2] == 3 - assert 1*s[5] == 5 - assert 2*s[5] == 10 - assert s[6]/3 == 2 + assert 1 * s[5] == 5 + assert 2 * s[5] == 10 + assert s[6] / 3 == 2 def test_identity(self): @@ -114,7 +115,7 @@ def test_permutation_symbol_3(self): for i in range(3): for j in range(3): for k in range(3): - value = (j-i)*(k-i)*(k-j)/2 + value = (j - i) * (k - i) * (k - j) / 2 self.assertEqual(e[i, j, k], value) i, j, k = indices(3) self.assertIsInstance(e[i, j, k], Indexed) @@ -125,17 +126,18 @@ def test_permutation_symbol_3(self): def test_permutation_symbol_n(self): for n in range(2, 5): # tested with upper limit 7, but evaluation is a bit slow then e = PermutationSymbol(n) - assert e.ufl_shape == (n,)*n + assert e.ufl_shape == (n,) * n assert eval(repr(e)) == e ii = indices(n) - x = (0,)*n - nfac = product(m for m in range(1, n+1)) + x = (0,) * n + nfac = product(m for m in range(1, n + 1)) assert (e[ii] * e[ii])(x) == nfac def test_unit_dyads(self): from ufl.tensors import unit_matrices, unit_vectors + ei, ej = unit_vectors(2) self.assertEqual(as_vector((1, 0)), ei) self.assertEqual(as_vector((0, 1)), ej) diff --git a/test/test_measures.py b/test/test_measures.py index 539151672..eaaffd745 100755 --- a/test/test_measures.py +++ b/test/test_measures.py @@ -60,18 +60,18 @@ def test_construct_forms_from_default_measures(): # Check that we can create a basic form with default measure one = as_ufl(1) - one * dx(Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1))) + one * dx(Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1))) def test_foo(): - # Define a manifold domain, allows checking gdim/tdim mixup errors gdim = 3 tdim = 2 cell = Cell("triangle") mymesh = MockMesh(9) - mydomain = Mesh(FiniteElement("Lagrange", cell, 1, (gdim, ), identity_pullback, H1), - ufl_id=9, cargo=mymesh) + mydomain = Mesh( + FiniteElement("Lagrange", cell, 1, (gdim,), identity_pullback, H1), ufl_id=9, cargo=mymesh + ) assert cell.topological_dimension() == tdim assert cell.cellname() == "triangle" @@ -87,10 +87,7 @@ def test_foo(): # Test definition of a custom measure with explicit parameters metadata = {"opt": True} - mydx = Measure("dx", - domain=mydomain, - subdomain_id=3, - metadata=metadata) + mydx = Measure("dx", domain=mydomain, subdomain_id=3, metadata=metadata) assert mydx.ufl_domain().ufl_id() == mydomain.ufl_id() assert mydx.metadata() == metadata M = f * mydx @@ -120,7 +117,7 @@ def test_foo(): # Set subdomain_id to (2,3), still no domain set dx23 = dx((2, 3)) assert dx23.ufl_domain() is None - assert dx23.subdomain_id(), (2 == 3) + assert dx23.subdomain_id(), 2 == 3 # Map metadata to metadata, ffc interprets as before dxm = dx(metadata={"dummy": 123}) @@ -161,29 +158,29 @@ def test_foo(): # Create some forms with these measures (used in checks below): Mx = f * dxd - Ms = f ** 2 * dsd - MS = f('+') * dSd - M = f * dxd + f ** 2 * dsd + f('+') * dSd + Ms = f**2 * dsd + MS = f("+") * dSd + M = f * dxd + f**2 * dsd + f("+") * dSd # Test extracting domain data from a form for each measure: - domain, = Mx.ufl_domains() + (domain,) = Mx.ufl_domains() assert domain.ufl_id() == mydomain.ufl_id() assert domain.ufl_cargo() == mymesh assert len(Mx.subdomain_data()[mydomain]["cell"]) == 1 assert Mx.subdomain_data()[mydomain]["cell"][0] == cell_domains - domain, = Ms.ufl_domains() + (domain,) = Ms.ufl_domains() assert domain.ufl_cargo() == mymesh assert len(Ms.subdomain_data()[mydomain]["exterior_facet"]) == 1 assert Ms.subdomain_data()[mydomain]["exterior_facet"][0] == exterior_facet_domains - domain, = MS.ufl_domains() + (domain,) = MS.ufl_domains() assert domain.ufl_cargo() == mymesh assert len(MS.subdomain_data()[mydomain]["interior_facet"]) == 1 assert MS.subdomain_data()[mydomain]["interior_facet"][0] == interior_facet_domains # Test joining of these domains in a single form - domain, = M.ufl_domains() + (domain,) = M.ufl_domains() assert domain.ufl_cargo() == mymesh assert len(M.subdomain_data()[mydomain]["cell"]) == 1 assert M.subdomain_data()[mydomain]["cell"][0] == cell_domains diff --git a/test/test_mixed_function_space.py b/test/test_mixed_function_space.py index 6e3e68356..476be6271 100644 --- a/test/test_mixed_function_space.py +++ b/test/test_mixed_function_space.py @@ -1,8 +1,17 @@ __authors__ = "Cecile Daversin Catty" __date__ = "2019-03-26 -- 2019-03-26" -from ufl import (FunctionSpace, Measure, Mesh, MixedFunctionSpace, TestFunctions, TrialFunctions, interval, tetrahedron, - triangle) +from ufl import ( + FunctionSpace, + Measure, + Mesh, + MixedFunctionSpace, + TestFunctions, + TrialFunctions, + interval, + tetrahedron, + triangle, +) from ufl.algorithms.formsplitter import extract_blocks from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -11,9 +20,9 @@ def test_mixed_functionspace(self): # Domains - domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) - domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1, ), identity_pullback, H1)) + domain_3d = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) + domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + domain_1d = Mesh(FiniteElement("Lagrange", interval, 1, (1,), identity_pullback, H1)) # Finite elements f_1d = FiniteElement("Lagrange", interval, 1, (), identity_pullback, H1) f_2d = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) @@ -42,20 +51,20 @@ def test_mixed_functionspace(self): # Mixed variational form # LHS - a_11 = u_1d*v_1d*dx1 - a_22 = u_2d*v_2d*dx2 - a_33 = u_3d*v_3d*dx3 - a_21 = u_2d*v_1d*dx1 - a_12 = u_1d*v_2d*dx1 - a_32 = u_3d*v_2d*dx2 - a_23 = u_2d*v_3d*dx2 - a_31 = u_3d*v_1d*dx1 - a_13 = u_1d*v_3d*dx1 + a_11 = u_1d * v_1d * dx1 + a_22 = u_2d * v_2d * dx2 + a_33 = u_3d * v_3d * dx3 + a_21 = u_2d * v_1d * dx1 + a_12 = u_1d * v_2d * dx1 + a_32 = u_3d * v_2d * dx2 + a_23 = u_2d * v_3d * dx2 + a_31 = u_3d * v_1d * dx1 + a_13 = u_1d * v_3d * dx1 a = a_11 + a_22 + a_33 + a_21 + a_12 + a_32 + a_23 + a_31 + a_13 # RHS - f_1 = v_1d*dx1 - f_2 = v_2d*dx2 - f_3 = v_3d*dx3 + f_1 = v_1d * dx1 + f_2 = v_2d * dx2 + f_3 = v_3d * dx3 f = f_1 + f_2 + f_3 # Check extract_block algorithm diff --git a/test/test_new_ad.py b/test/test_new_ad.py index 7fc69e850..38ef9de44 100755 --- a/test/test_new_ad.py +++ b/test/test_new_ad.py @@ -1,7 +1,32 @@ -from ufl import (CellVolume, Coefficient, Constant, FacetNormal, FunctionSpace, Identity, Mesh, SpatialCoordinate, - TestFunction, VectorConstant, as_ufl, cos, derivative, diff, exp, grad, ln, sin, tan, triangle, - variable, zero) -from ufl.algorithms.apply_derivatives import GenericDerivativeRuleset, GradRuleset, apply_derivatives +from ufl import ( + CellVolume, + Coefficient, + Constant, + FacetNormal, + FunctionSpace, + Identity, + Mesh, + SpatialCoordinate, + TestFunction, + VectorConstant, + as_ufl, + cos, + derivative, + diff, + exp, + grad, + ln, + sin, + tan, + triangle, + variable, + zero, +) +from ufl.algorithms.apply_derivatives import ( + GenericDerivativeRuleset, + GradRuleset, + apply_derivatives, +) from ufl.algorithms.renumbering import renumber_indices from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -21,7 +46,7 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) @@ -50,7 +75,7 @@ def test_apply_derivatives_doesnt_change_expression_without_derivatives(): # Expressions e0 = f0 + f1 - e1 = v0 * (f1/3 - f0**2) + e1 = v0 * (f1 / 3 - f0**2) e2 = exp(sin(cos(tan(ln(x[0]))))) expressions = [e0, e1, e2] @@ -73,7 +98,7 @@ def test_literal_derivatives_are_zero(): # Generic ruleset handles literals directly: for lit in literals: - for sh in [(), (d,), (d, d+1)]: + for sh in [(), (d,), (d, d + 1)]: assert GenericDerivativeRuleset(sh)(lit) == zero(lit.ufl_shape + sh) # Variables @@ -89,7 +114,7 @@ def test_literal_derivatives_are_zero(): V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) u0 = Coefficient(v0_space) @@ -115,11 +140,11 @@ def test_grad_ruleset(): V0 = FiniteElement("Discontinuous Lagrange", cell, 0, (), identity_pullback, L2) V1 = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V2 = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) - W0 = FiniteElement("Discontinuous Lagrange", cell, 0, (2, ), identity_pullback, L2) - W1 = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) - W2 = FiniteElement("Lagrange", cell, 2, (d, ), identity_pullback, H1) + W0 = FiniteElement("Discontinuous Lagrange", cell, 0, (2,), identity_pullback, L2) + W1 = FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1) + W2 = FiniteElement("Lagrange", cell, 2, (d,), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) v0_space = FunctionSpace(domain, V0) v1_space = FunctionSpace(domain, V1) v2_space = FunctionSpace(domain, V2) @@ -197,10 +222,14 @@ def test_grad_ruleset(): assert renumber_indices(apply_derivatives(grad(vf2[1])[0])) == renumber_indices(grad(vf2)[1, 0]) # Grad of gradually more complex expressions - assert apply_derivatives(grad(2*f0)) == zero((d,)) - assert renumber_indices(apply_derivatives(grad(2*f1))) == renumber_indices(2*grad(f1)) - assert renumber_indices(apply_derivatives(grad(sin(f1)))) == renumber_indices(cos(f1) * grad(f1)) - assert renumber_indices(apply_derivatives(grad(cos(f1)))) == renumber_indices(-sin(f1) * grad(f1)) + assert apply_derivatives(grad(2 * f0)) == zero((d,)) + assert renumber_indices(apply_derivatives(grad(2 * f1))) == renumber_indices(2 * grad(f1)) + assert renumber_indices(apply_derivatives(grad(sin(f1)))) == renumber_indices( + cos(f1) * grad(f1) + ) + assert renumber_indices(apply_derivatives(grad(cos(f1)))) == renumber_indices( + -sin(f1) * grad(f1) + ) def test_variable_ruleset(): diff --git a/test/test_pickle.py b/test/test_pickle.py index 5ab4026f7..e2611c48a 100755 --- a/test/test_pickle.py +++ b/test/test_pickle.py @@ -10,9 +10,37 @@ import pickle -from ufl import (Coefficient, Constant, Dx, FacetNormal, FunctionSpace, Identity, Mesh, TestFunction, TestFunctions, - TrialFunction, TrialFunctions, VectorConstant, avg, curl, div, dot, dS, ds, dx, grad, i, inner, j, - jump, lhs, rhs, sqrt, tetrahedron, triangle) +from ufl import ( + Coefficient, + Constant, + Dx, + FacetNormal, + FunctionSpace, + Identity, + Mesh, + TestFunction, + TestFunctions, + TrialFunction, + TrialFunctions, + VectorConstant, + avg, + curl, + div, + dot, + dS, + ds, + dx, + grad, + i, + inner, + j, + jump, + lhs, + rhs, + sqrt, + tetrahedron, + triangle, +) from ufl.algorithms import compute_form_data from ufl.finiteelement import FiniteElement, MixedElement from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback @@ -23,7 +51,7 @@ def testConstant(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -47,8 +75,8 @@ def testConstant(): def testElasticity(): - element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -69,7 +97,7 @@ def eps(v): def testEnergyNorm(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = Coefficient(space) @@ -83,7 +111,7 @@ def testEnergyNorm(): def testEquation(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) k = 0.1 @@ -108,7 +136,7 @@ def testEquation(): def testFunctionOperators(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -118,8 +146,7 @@ def testFunctionOperators(): # FFC notation: a = sqrt(1/modulus(1/f))*sqrt(g)*dot(grad(v), grad(u))*dx # + v*u*sqrt(f*g)*g*dx - a = sqrt(1 / abs(1 / f)) * sqrt(g) * \ - dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx + a = sqrt(1 / abs(1 / f)) * sqrt(g) * dot(grad(v), grad(u)) * dx + v * u * sqrt(f * g) * g * dx a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) @@ -129,7 +156,7 @@ def testFunctionOperators(): def testHeat(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -153,7 +180,7 @@ def testHeat(): def testMass(): element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -181,11 +208,11 @@ def testMixedMixedElement(): def testMixedPoisson(): q = 1 - BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2, ), contravariant_piola, HDiv) + BDM = FiniteElement("Brezzi-Douglas-Marini", triangle, q, (2,), contravariant_piola, HDiv) DG = FiniteElement("Discontinuous Lagrange", triangle, q - 1, (), identity_pullback, L2) mixed_element = MixedElement([BDM, DG]) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) mixed_space = FunctionSpace(domain, mixed_element) dg_space = FunctionSpace(domain, DG) @@ -207,8 +234,8 @@ def testMixedPoisson(): def testNavierStokes(): - element = FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -226,8 +253,8 @@ def testNavierStokes(): def testNeumannProblem(): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -252,7 +279,7 @@ def testNeumannProblem(): def testOptimization(): element = FiniteElement("Lagrange", triangle, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -289,7 +316,7 @@ def testP5tri(): def testPoissonDG(): element = FiniteElement("Discontinuous Lagrange", triangle, 1, (), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -315,13 +342,15 @@ def testPoissonDG(): # - dot(mult(v,n), grad(u))*ds \ # + gamma/h*v*u*ds - a = inner(grad(v), grad(u)) * dx \ - - inner(avg(grad(v)), jump(u, n)) * dS \ - - inner(jump(v, n), avg(grad(u))) * dS \ - + alpha / h('+') * dot(jump(v, n), jump(u, n)) * dS \ - - inner(grad(v), u * n) * ds \ - - inner(u * n, grad(u)) * ds \ + a = ( + inner(grad(v), grad(u)) * dx + - inner(avg(grad(v)), jump(u, n)) * dS + - inner(jump(v, n), avg(grad(u))) * dS + + alpha / h("+") * dot(jump(v, n), jump(u, n)) * dS + - inner(grad(v), u * n) * ds + - inner(u * n, grad(u)) * ds + gamma / h * v * u * ds + ) L = v * f * dx + v * gN * ds @@ -336,7 +365,7 @@ def testPoissonDG(): def testPoisson(): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -357,8 +386,8 @@ def testPoisson(): def testPoissonSystem(): - element = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + element = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) @@ -388,9 +417,9 @@ def testQuadratureElement(): # sig = VectorQuadratureElement(triangle, 3) QE = FiniteElement("Quadrature", triangle, 3, (), identity_pullback, L2) - sig = FiniteElement("Quadrature", triangle, 3, (2, ), identity_pullback, L2) + sig = FiniteElement("Quadrature", triangle, 3, (2,), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) qe_space = FunctionSpace(domain, QE) sig_space = FunctionSpace(domain, sig) @@ -417,11 +446,11 @@ def testQuadratureElement(): def testStokes(): # UFLException: Shape mismatch in sum. - P2 = FiniteElement("Lagrange", triangle, 2, (2, ), identity_pullback, H1) + P2 = FiniteElement("Lagrange", triangle, 2, (2,), identity_pullback, H1) P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) TH = MixedElement([P2, P1]) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) th_space = FunctionSpace(domain, TH) p2_space = FunctionSpace(domain, P2) @@ -447,7 +476,7 @@ def testStokes(): def testSubDomain(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) @@ -462,14 +491,20 @@ def testSubDomain(): def testSubDomains(): element = FiniteElement("Lagrange", tetrahedron, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - a = v * u * dx(0) + 10.0 * v * u * dx(1) + v * u * ds(0) + 2.0 * v * u * \ - ds(1) + v('+') * u('+') * dS(0) + 4.3 * v('+') * u('+') * dS(1) + a = ( + v * u * dx(0) + + 10.0 * v * u * dx(1) + + v * u * ds(0) + + 2.0 * v * u * ds(1) + + v("+") * u("+") * dS(0) + + 4.3 * v("+") * u("+") * dS(1) + ) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) @@ -498,7 +533,7 @@ def testTensorWeightedPoisson(): P1 = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) P0 = FiniteElement("Discontinuous Lagrange", triangle, 0, (2, 2), identity_pullback, L2) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) p1_space = FunctionSpace(domain, P1) p0_space = FunctionSpace(domain, P0) @@ -522,8 +557,12 @@ def HodgeLaplaceGradCurl(space, fspace): # FFC notation: a = (dot(tau, sigma) - dot(grad(tau), u) + dot(v, # grad(sigma)) + dot(curl(v), curl(u)))*dx - a = (inner(tau, sigma) - inner(grad(tau), u) + - inner(v, grad(sigma)) + inner(curl(v), curl(u))) * dx + a = ( + inner(tau, sigma) + - inner(grad(tau), u) + + inner(v, grad(sigma)) + + inner(curl(v), curl(u)) + ) * dx # FFC notation: L = dot(v, f)*dx L = inner(v, f) * dx @@ -536,13 +575,14 @@ def HodgeLaplaceGradCurl(space, fspace): GRAD = FiniteElement("Lagrange", shape, order, (), identity_pullback, H1) # FFC notation: CURL = FiniteElement("Nedelec", shape, order-1) - CURL = FiniteElement("N1curl", shape, order, (3, ), covariant_piola, HCurl) + CURL = FiniteElement("N1curl", shape, order, (3,), covariant_piola, HCurl) - VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", shape, 1, (3, ), identity_pullback, H1)) + VectorLagrange = FiniteElement("Lagrange", shape, order + 1, (3,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", shape, 1, (3,), identity_pullback, H1)) - [a, L] = HodgeLaplaceGradCurl(FunctionSpace(domain, MixedElement([GRAD, CURL])), - FunctionSpace(domain, VectorLagrange)) + [a, L] = HodgeLaplaceGradCurl( + FunctionSpace(domain, MixedElement([GRAD, CURL])), FunctionSpace(domain, VectorLagrange) + ) a_pickle = pickle.dumps(a, p) a_restore = pickle.loads(a_pickle) @@ -562,7 +602,7 @@ def testIdentity(): def testFormData(): element = FiniteElement("Lagrange", tetrahedron, 3, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) diff --git a/test/test_piecewise_checks.py b/test/test_piecewise_checks.py index f0b1955f6..baeb13c59 100755 --- a/test/test_piecewise_checks.py +++ b/test/test_piecewise_checks.py @@ -2,12 +2,37 @@ import pytest -from ufl import (Cell, CellDiameter, CellVolume, Circumradius, Coefficient, Constant, FacetArea, FacetNormal, - FunctionSpace, Jacobian, JacobianDeterminant, JacobianInverse, MaxFacetEdgeLength, Mesh, - MinFacetEdgeLength, SpatialCoordinate, TestFunction, hexahedron, interval, quadrilateral, tetrahedron, - triangle) +from ufl import ( + Cell, + CellDiameter, + CellVolume, + Circumradius, + Coefficient, + Constant, + FacetArea, + FacetNormal, + FunctionSpace, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxFacetEdgeLength, + Mesh, + MinFacetEdgeLength, + SpatialCoordinate, + TestFunction, + hexahedron, + interval, + quadrilateral, + tetrahedron, + triangle, +) from ufl.checks import is_cellwise_constant -from ufl.classes import CellCoordinate, FacetJacobian, FacetJacobianDeterminant, FacetJacobianInverse +from ufl.classes import ( + CellCoordinate, + FacetJacobian, + FacetJacobianDeterminant, + FacetJacobianInverse, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1, L2, HInf @@ -22,15 +47,27 @@ def get_domains(): tetrahedron, hexahedron, ] - return [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1)) for cell in all_cells] + return [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ) + ) + for cell in all_cells + ] def get_nonlinear(): domains_with_quadratic_coordinates = [] for D in get_domains(): - V = FiniteElement("Lagrange", D.ufl_cell(), 2, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 2, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) domains_with_quadratic_coordinates.append(E) @@ -53,8 +90,14 @@ def domains(request): domains = get_domains() domains_with_linear_coordinates = [] for D in domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 1, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) domains_with_linear_coordinates.append(E) @@ -69,19 +112,29 @@ def affine_domains(request): triangle, tetrahedron, ] - affine_domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1)) - for cell in affine_cells] + affine_domains = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ) + ) + for cell in affine_cells + ] affine_domains_with_linear_coordinates = [] for D in affine_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 1, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) affine_domains_with_linear_coordinates.append(E) - all_affine_domains = affine_domains + \ - affine_domains_with_linear_coordinates + all_affine_domains = affine_domains + affine_domains_with_linear_coordinates return all_affine_domains[request.param] @@ -93,18 +146,28 @@ def affine_facet_domains(request): quadrilateral, tetrahedron, ] - affine_facet_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1)) for cell in affine_facet_cells] + affine_facet_domains = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ) + ) + for cell in affine_facet_cells + ] affine_facet_domains_with_linear_coordinates = [] for D in affine_facet_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 1, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) affine_facet_domains_with_linear_coordinates.append(E) - all_affine_facet_domains = affine_facet_domains + \ - affine_facet_domains_with_linear_coordinates + all_affine_facet_domains = affine_facet_domains + affine_facet_domains_with_linear_coordinates return all_affine_facet_domains[request.param] @@ -115,18 +178,28 @@ def nonaffine_domains(request): quadrilateral, hexahedron, ] - nonaffine_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1)) for cell in nonaffine_cells] + nonaffine_domains = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ) + ) + for cell in nonaffine_cells + ] nonaffine_domains_with_linear_coordinates = [] for D in nonaffine_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 1, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) nonaffine_domains_with_linear_coordinates.append(E) - all_nonaffine_domains = nonaffine_domains + \ - nonaffine_domains_with_linear_coordinates + all_nonaffine_domains = nonaffine_domains + nonaffine_domains_with_linear_coordinates return all_nonaffine_domains[request.param] @@ -136,18 +209,30 @@ def nonaffine_facet_domains(request): nonaffine_facet_cells = [ hexahedron, ] - nonaffine_facet_domains = [Mesh(FiniteElement( - "Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1)) for cell in nonaffine_facet_cells] + nonaffine_facet_domains = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ) + ) + for cell in nonaffine_facet_cells + ] nonaffine_facet_domains_with_linear_coordinates = [] for D in nonaffine_facet_domains: - V = FiniteElement("Lagrange", D.ufl_cell(), 1, (D.ufl_cell().topological_dimension(), ), - identity_pullback, H1) + V = FiniteElement( + "Lagrange", + D.ufl_cell(), + 1, + (D.ufl_cell().topological_dimension(),), + identity_pullback, + H1, + ) E = Mesh(V) nonaffine_facet_domains_with_linear_coordinates.append(E) - all_nonaffine_facet_domains = nonaffine_facet_domains + \ - nonaffine_facet_domains_with_linear_coordinates + all_nonaffine_facet_domains = ( + nonaffine_facet_domains + nonaffine_facet_domains_with_linear_coordinates + ) return all_nonaffine_facet_domains[request.param] @@ -177,7 +262,7 @@ def test_coordinates_never_cellwise_constant(domains): def test_coordinates_never_cellwise_constant_vertex(): # The only exception here: - domains = Mesh(FiniteElement("Lagrange", Cell("vertex"), 1, (3, ), identity_pullback, H1)) + domains = Mesh(FiniteElement("Lagrange", Cell("vertex"), 1, (3,), identity_pullback, H1)) assert domains.ufl_cell().cellname() == "vertex" e = SpatialCoordinate(domains) assert is_cellwise_constant(e) @@ -234,9 +319,13 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): e = Constant(domains_not_linear) assert is_cellwise_constant(e) - V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 0, (), identity_pullback, L2) + V = FiniteElement( + "Discontinuous Lagrange", domains_not_linear.ufl_cell(), 0, (), identity_pullback, L2 + ) d = domains_not_linear.ufl_cell().topological_dimension() - domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) + domain = Mesh( + FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d,), identity_pullback, H1) + ) space = FunctionSpace(domain, V) e = Coefficient(space) assert is_cellwise_constant(e) @@ -255,9 +344,13 @@ def test_coefficient_sometimes_cellwise_constant(domains_not_linear): def test_coefficient_mostly_not_cellwise_constant(domains_not_linear): - V = FiniteElement("Discontinuous Lagrange", domains_not_linear.ufl_cell(), 1, (), identity_pullback, L2) + V = FiniteElement( + "Discontinuous Lagrange", domains_not_linear.ufl_cell(), 1, (), identity_pullback, L2 + ) d = domains_not_linear.ufl_cell().topological_dimension() - domain = Mesh(FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d, ), identity_pullback, H1)) + domain = Mesh( + FiniteElement("Lagrange", domains_not_linear.ufl_cell(), 1, (d,), identity_pullback, H1) + ) space = FunctionSpace(domain, V) e = Coefficient(space) assert not is_cellwise_constant(e) diff --git a/test/test_reference_shapes.py b/test/test_reference_shapes.py index 69c0a2983..d5aec17a3 100755 --- a/test/test_reference_shapes.py +++ b/test/test_reference_shapes.py @@ -8,14 +8,14 @@ def test_reference_shapes(): # show_elements() cell = Cell("triangle") - domain = Mesh(FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) - V = FiniteElement("N1curl", cell, 1, (2, ), covariant_piola, HCurl) + V = FiniteElement("N1curl", cell, 1, (2,), covariant_piola, HCurl) Vspace = FunctionSpace(domain, V) assert Vspace.value_shape == (3,) assert V.reference_value_shape == (2,) - U = FiniteElement("Raviart-Thomas", cell, 1, (2, ), contravariant_piola, HDiv) + U = FiniteElement("Raviart-Thomas", cell, 1, (2,), contravariant_piola, HDiv) Uspace = FunctionSpace(domain, U) assert Uspace.value_shape == (3,) assert U.reference_value_shape == (2,) @@ -25,7 +25,7 @@ def test_reference_shapes(): assert Wspace.value_shape == () assert W.reference_value_shape == () - Q = FiniteElement("Lagrange", cell, 1, (3, ), identity_pullback, H1) + Q = FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1) Qspace = FunctionSpace(domain, Q) assert Qspace.value_shape == (3,) assert Q.reference_value_shape == (3,) @@ -36,8 +36,19 @@ def test_reference_shapes(): assert T.reference_value_shape == (3, 3) S = SymmetricElement( - {(0, 0): 0, (1, 0): 1, (2, 0): 2, (0, 1): 1, (1, 1): 3, (2, 1): 4, (0, 2): 2, (1, 2): 4, (2, 2): 5}, - [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)]) + { + (0, 0): 0, + (1, 0): 1, + (2, 0): 2, + (0, 1): 1, + (1, 1): 3, + (2, 1): 4, + (0, 2): 2, + (1, 2): 4, + (2, 2): 5, + }, + [FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) for _ in range(6)], + ) Sspace = FunctionSpace(domain, S) assert Sspace.value_shape == (3, 3) assert S.reference_value_shape == (6,) diff --git a/test/test_scratch.py b/test/test_scratch.py index 32ced3346..5b429e532 100755 --- a/test/test_scratch.py +++ b/test/test_scratch.py @@ -8,8 +8,22 @@ import warnings -from ufl import (Coefficient, FunctionSpace, Identity, Mesh, TestFunction, as_matrix, as_tensor, as_vector, dx, grad, - indices, inner, outer, triangle) +from ufl import ( + Coefficient, + FunctionSpace, + Identity, + Mesh, + TestFunction, + as_matrix, + as_tensor, + as_vector, + dx, + grad, + indices, + inner, + outer, + triangle, +) from ufl.classes import FixedIndex, FormArgument, Grad, Indexed, ListTensor, Zero from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -18,15 +32,14 @@ class MockForwardAD: - def __init__(self): self._w = () self._v = () class Obj: - def __init__(self): self._data = {} + self._cd = Obj() def grad(self, g): @@ -39,25 +52,25 @@ def grad(self, g): ngrads = 0 o = g while isinstance(o, Grad): - o, = o.ufl_operands + (o,) = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): raise ValueError("Expecting gradient of a FormArgument, not %s" % repr(o)) def apply_grads(f): if not isinstance(f, FormArgument): - print((','*60)) + print(("," * 60)) print(f) print(o) print(g) - print((','*60)) + print(("," * 60)) raise ValueError("What?") for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this easy - for (w, v) in zip(self._w, self._v): + for w, v in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return (g, apply_grads(v)) @@ -85,18 +98,17 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, # and get the right indexed scalar component(s) kk = indices(ngrads) - Dvkk = apply_grads(vval)[vcomp+kk] + Dvkk = apply_grads(vval)[vcomp + kk] # Place scalar component(s) Dvkk into the right tensor positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () - gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) + gprimeterm = as_tensor(Ejj * Dvkk, jj + kk) return gprimeterm # Accumulate contributions from variations in different components - for (w, v) in zip(self._w, self._v): - + for w, v in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: @@ -112,7 +124,9 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) - gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) + gprimesum = gprimesum + compute_gprimeterm( + ngrads, vval, vcomp, wshape, wcomp + ) else: if wshape != (): @@ -123,7 +137,9 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) - elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? + elif isinstance( + w, Indexed + ): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands @@ -154,20 +170,22 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): if not isinstance(oprimes, tuple): oprimes = (oprimes,) if len(oprimes) != len(self._v): - raise ValueError("Got a tuple of arguments, " - "expecting a matching tuple of coefficient derivatives.") + raise ValueError( + "Got a tuple of arguments, " + "expecting a matching tuple of coefficient derivatives." + ) # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. - for (oprime, v) in zip(oprimes, self._v): + for oprime, v in zip(oprimes, self._v): raise ValueError("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] - prod = so*v[oi2] + prod = so * v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: @@ -185,36 +203,41 @@ def test_unit_tensor(self): def test_unwrap_list_tensor(self): lt = as_tensor((1, 2)) - expected = [((0,), 1), - ((1,), 2), ] + expected = [ + ((0,), 1), + ((1,), 2), + ] comp = unwrap_list_tensor(lt) assert comp == expected lt = as_tensor(((1, 2), (3, 4))) - expected = [((0, 0), 1), - ((0, 1), 2), - ((1, 0), 3), - ((1, 1), 4), ] + expected = [ + ((0, 0), 1), + ((0, 1), 2), + ((1, 0), 3), + ((1, 1), 4), + ] comp = unwrap_list_tensor(lt) assert comp == expected - lt = as_tensor((((1, 2), (3, 4)), - ((11, 12), (13, 14)))) - expected = [((0, 0, 0), 1), - ((0, 0, 1), 2), - ((0, 1, 0), 3), - ((0, 1, 1), 4), - ((1, 0, 0), 11), - ((1, 0, 1), 12), - ((1, 1, 0), 13), - ((1, 1, 1), 14), ] + lt = as_tensor((((1, 2), (3, 4)), ((11, 12), (13, 14)))) + expected = [ + ((0, 0, 0), 1), + ((0, 0, 1), 2), + ((0, 1, 0), 3), + ((0, 1, 1), 4), + ((1, 0, 0), 11), + ((1, 0, 1), 12), + ((1, 1, 0), 13), + ((1, 1, 1), 14), + ] comp = unwrap_list_tensor(lt) assert comp == expected def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): U = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, U) u = Coefficient(space) du = TestFunction(space) @@ -239,8 +262,8 @@ def test__forward_coefficient_ad__grad_of_scalar_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient(self): - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -265,8 +288,8 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient(self): def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation(self): - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -280,19 +303,20 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var f = grad(v) df = grad(as_vector((dv[1], 0))) # Mathematically this would be the natural result j, k = indices(2) - df = as_tensor(Identity(2)[0, j]*grad(dv)[1, k], (j, k)) # Actual representation should have grad right next to dv + df = as_tensor( + Identity(2)[0, j] * grad(dv)[1, k], (j, k) + ) # Actual representation should have grad right next to dv g, dg = mad.grad(f) if 0: - print(('\nf ', f)) - print(('df ', df)) - print(('g ', g)) - print(('dg ', dg)) + print(("\nf ", f)) + print(("df ", df)) + print(("g ", g)) + print(("dg ", dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f - self.assertEqual((inner(dg, dg)*dx).signature(), - (inner(df, df)*dx).signature()) + self.assertEqual((inner(dg, dg) * dx).signature(), (inner(df, df) * dx).signature()) # assert dg == df # Expected to fail because of different index numbering # Multiple components of variation: @@ -305,25 +329,27 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var # Actual representation should have grad right next to dv: j0, k0 = indices(2) j1, k1 = indices(2) # Using j0,k0 for both terms gives different signature - df = (as_tensor(Identity(2)[0, j0]*grad(dv)[1, k0], (j0, k0)) - + as_tensor(Identity(2)[1, j1]*grad(dv)[0, k1], (j1, k1))) + df = as_tensor(Identity(2)[0, j0] * grad(dv)[1, k0], (j0, k0)) + as_tensor( + Identity(2)[1, j1] * grad(dv)[0, k1], (j1, k1) + ) g, dg = mad.grad(f) - print(('\nf ', f)) - print(('df ', df)) - print(('g ', g)) - print(('dg ', dg)) + print(("\nf ", f)) + print(("df ", df)) + print(("g ", g)) + print(("dg ", dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f - self.assertEqual((inner(dg, dg)*dx).signature(), - (inner(df, df)*dx).signature()) + self.assertEqual((inner(dg, dg) * dx).signature(), (inner(df, df) * dx).signature()) # assert dg == df # Expected to fail because of different index numbering -def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation_in_list(self): - V = FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) +def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_variation_in_list( + self, +): + V = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) v = Coefficient(space) dv = TestFunction(space) @@ -337,24 +363,25 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var f = grad(v) df = grad(as_vector((dv[1], 0))) # Mathematically this would be the natural result j, k = indices(2) - df = as_tensor(Identity(2)[0, j]*grad(dv)[1, k], (j, k)) # Actual representation should have grad right next to dv + df = as_tensor( + Identity(2)[0, j] * grad(dv)[1, k], (j, k) + ) # Actual representation should have grad right next to dv g, dg = mad.grad(f) if 0: - print(('\nf ', f)) - print(('df ', df)) - print(('g ', g)) - print(('dg ', dg)) + print(("\nf ", f)) + print(("df ", df)) + print(("g ", g)) + print(("dg ", dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f - self.assertEqual((inner(dg, dg)*dx).signature(), - (inner(df, df)*dx).signature()) + self.assertEqual((inner(dg, dg) * dx).signature(), (inner(df, df) * dx).signature()) # assert dg == df # Expected to fail because of different index numbering # Multiple components of variation: # grad(grad(c))[0,1,:,:] -> grad(grad(dc))[1,0,:,:] - mad._w = (v, ) + mad._w = (v,) mad._v = (as_vector((dv[1], dv[0])),) f = grad(v) # Mathematically this would be the natural result: @@ -362,25 +389,25 @@ def test__forward_coefficient_ad__grad_of_vector_coefficient__with_component_var # Actual representation should have grad right next to dv: j0, k0 = indices(2) j1, k1 = indices(2) # Using j0,k0 for both terms gives different signature - df = (as_tensor(Identity(2)[0, j0]*grad(dv)[1, k0], (j0, k0)) - + as_tensor(Identity(2)[1, j1]*grad(dv)[0, k1], (j1, k1))) + df = as_tensor(Identity(2)[0, j0] * grad(dv)[1, k0], (j0, k0)) + as_tensor( + Identity(2)[1, j1] * grad(dv)[0, k1], (j1, k1) + ) g, dg = mad.grad(f) - print(('\nf ', f)) - print(('df ', df)) - print(('g ', g)) - print(('dg ', dg)) + print(("\nf ", f)) + print(("df ", df)) + print(("g ", g)) + print(("dg ", dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f - self.assertEqual((inner(dg, dg)*dx).signature(), - (inner(df, df)*dx).signature()) + self.assertEqual((inner(dg, dg) * dx).signature(), (inner(df, df) * dx).signature()) # assert dg == df # Expected to fail because of different index numbering def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): W = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, W) w = Coefficient(space) dw = TestFunction(space) @@ -406,7 +433,7 @@ def test__forward_coefficient_ad__grad_of_tensor_coefficient(self): def test__forward_coefficient_ad__grad_of_tensor_coefficient__with_component_variation(self): W = FiniteElement("Lagrange", triangle, 1, (2, 2), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, W) w = Coefficient(space) dw = TestFunction(space) @@ -424,17 +451,16 @@ def test__forward_coefficient_ad__grad_of_tensor_coefficient__with_component_var i, j, k = indices(3) E = outer(Identity(2)[wc[0], i], Identity(2)[wc[1], j]) Ddw = grad(dw)[dwc + (k,)] - df = as_tensor(E*Ddw, (i, j, k)) # Actual representation should have grad next to dv + df = as_tensor(E * Ddw, (i, j, k)) # Actual representation should have grad next to dv g, dg = mad.grad(f) if 0: - print(('\nf ', f)) - print(('df ', df)) - print(('g ', g)) - print(('dg ', dg)) + print(("\nf ", f)) + print(("df ", df)) + print(("g ", g)) + print(("dg ", dg)) assert f.ufl_shape == df.ufl_shape assert g.ufl_shape == f.ufl_shape assert dg.ufl_shape == df.ufl_shape assert g == f - self.assertEqual((inner(dg, dg)*dx).signature(), - (inner(df, df)*dx).signature()) + self.assertEqual((inner(dg, dg) * dx).signature(), (inner(df, df) * dx).signature()) # assert dg == df # Expected to fail because of different index numbering diff --git a/test/test_signature.py b/test/test_signature.py index 660758fe7..aa8c57877 100755 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -1,8 +1,32 @@ """Test the computation of form signatures.""" -from ufl import (Argument, CellDiameter, CellVolume, Circumradius, Coefficient, FacetArea, FacetNormal, FunctionSpace, - Identity, Mesh, SpatialCoordinate, TestFunction, as_vector, diff, dot, ds, dx, hexahedron, indices, - inner, interval, quadrilateral, tetrahedron, triangle, variable) +from ufl import ( + Argument, + CellDiameter, + CellVolume, + Circumradius, + Coefficient, + FacetArea, + FacetNormal, + FunctionSpace, + Identity, + Mesh, + SpatialCoordinate, + TestFunction, + as_vector, + diff, + dot, + ds, + dx, + hexahedron, + indices, + inner, + interval, + quadrilateral, + tetrahedron, + triangle, + variable, +) from ufl.algorithms.signature import compute_multiindex_hashdata, compute_terminal_hashdata from ufl.classes import FixedIndex, MultiIndex from ufl.finiteelement import FiniteElement, SymmetricElement @@ -22,7 +46,7 @@ def domain_numbering(*cells): renumbering = {} for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i) renumbering[domain] = i return renumbering @@ -60,12 +84,14 @@ def test_terminal_hashdata_depends_on_literals(self): def forms(): i, j = indices(2) for d, cell in [(2, triangle), (3, tetrahedron)]: - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=d-2) + domain = Mesh( + FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=d - 2 + ) x = SpatialCoordinate(domain) ident = Identity(d) for fv in (1.1, 2.2): for iv in (5, 7): - expr = (ident[0, j]*(fv*x[j]))**iv + expr = (ident[0, j] * (fv * x[j])) ** iv reprs.add(repr(expr)) hashes.add(hash(expr)) @@ -89,7 +115,7 @@ def forms(): cells = (triangle, tetrahedron) for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i) x = SpatialCoordinate(domain) n = FacetNormal(domain) @@ -104,14 +130,14 @@ def forms(): qs = (h, r, a, v) # , s) for w in ws: for q in qs: - expr = (ident[0, j]*(q*w[j])) + expr = ident[0, j] * (q * w[j]) reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_terminal_hashdata(expr, domain_numbering(*cells)) c, d, r, h = compute_unique_terminal_hashdatas(forms()) - assert c == 2*4*2 # len(ws)*len(qs)*len(cells) + assert c == 2 * 4 * 2 # len(ws)*len(qs)*len(cells) assert d == c assert r == c assert h == c @@ -134,25 +160,48 @@ def forms(): for rep in range(nreps): for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh( + FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i + ) for degree in degrees: for family, sobolev in families: V = FiniteElement(family, cell, degree, (), identity_pullback, sobolev) - W = FiniteElement(family, cell, degree, (d, ), identity_pullback, sobolev) - W2 = FiniteElement(family, cell, degree, (d+1, ), identity_pullback, sobolev) + W = FiniteElement(family, cell, degree, (d,), identity_pullback, sobolev) + W2 = FiniteElement( + family, cell, degree, (d + 1,), identity_pullback, sobolev + ) T = FiniteElement(family, cell, degree, (d, d), identity_pullback, sobolev) if d == 2: S = SymmetricElement( {(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, - [FiniteElement(family, cell, degree, (), identity_pullback, sobolev) - for _ in range(3)]) + [ + FiniteElement( + family, cell, degree, (), identity_pullback, sobolev + ) + for _ in range(3) + ], + ) else: assert d == 3 S = SymmetricElement( - {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, (1, 1): 3, - (1, 2): 4, (2, 0): 2, (2, 1): 4, (2, 2): 5}, - [FiniteElement(family, cell, degree, (), identity_pullback, sobolev) - for _ in range(6)]) + { + (0, 0): 0, + (0, 1): 1, + (0, 2): 2, + (1, 0): 1, + (1, 1): 3, + (1, 2): 4, + (2, 0): 2, + (2, 1): 4, + (2, 2): 5, + }, + [ + FiniteElement( + family, cell, degree, (), identity_pullback, sobolev + ) + for _ in range(6) + ], + ) elements = [V, W, W2, T, S] assert len(elements) == nelm @@ -174,7 +223,7 @@ def forms(): c1 = nreps * len(cells) * len(degrees) * len(families) * nelm * 2 assert c == c1 - c0 = len(cells) * len(degrees) * (len(families)-1) * nelm * 2 + c0 = len(cells) * len(degrees) * (len(families) - 1) * nelm * 2 assert d == c0 assert r == c0 assert h == c0 @@ -194,12 +243,14 @@ def forms(): for rep in range(nreps): for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh( + FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i + ) for k in counts: V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) space = FunctionSpace(domain, V) f = Coefficient(space, count=k) - g = Coefficient(space, count=k+2) + g = Coefficient(space, count=k + 2) expr = inner(f, g) renumbering = domain_numbering(*cells) @@ -233,12 +284,14 @@ def forms(): for rep in range(nreps): for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh( + FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i + ) for k in counts: V = FiniteElement("Lagrange", cell, 2, (), identity_pullback, H1) space = FunctionSpace(domain, V) f = Argument(space, k) - g = Argument(space, k+2) + g = Argument(space, k + 2) expr = inner(f, g) reprs.add(repr(expr)) @@ -246,7 +299,9 @@ def forms(): yield compute_terminal_hashdata(expr, domain_numbering(*cells)) c, d, r, h = compute_unique_terminal_hashdatas(forms()) - c0 = len(cells) * len(counts) # Number of actually unique cases from a code generation perspective + c0 = len(cells) * len( + counts + ) # Number of actually unique cases from a code generation perspective c1 = 1 * c0 # Number of unique cases from a symbolic representation perspective assert len(reprs) == c1 assert len(hashes) == c1 @@ -263,7 +318,7 @@ def test_domain_signature_data_does_not_depend_on_domain_label_value(self): s2s = set() for i, cell in enumerate(cells): d = cell.topological_dimension() - domain = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) + domain = FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1) d0 = Mesh(domain) d1 = Mesh(domain, ufl_id=1) d2 = Mesh(domain, ufl_id=2) @@ -285,17 +340,26 @@ def test_terminal_hashdata_does_not_depend_on_domain_label_value(self): hashes = set() ufl_ids = [1, 2] cells = [triangle, quadrilateral] - domains = [Mesh(FiniteElement("Lagrange", cell, 1, (cell.topological_dimension(), ), - identity_pullback, H1), - ufl_id=ufl_id) for cell in cells for ufl_id in ufl_ids] + domains = [ + Mesh( + FiniteElement( + "Lagrange", cell, 1, (cell.topological_dimension(),), identity_pullback, H1 + ), + ufl_id=ufl_id, + ) + for cell in cells + for ufl_id in ufl_ids + ] nreps = 2 num_exprs = 2 def forms(): for rep in range(nreps): for domain in domains: - V = FunctionSpace(domain, FiniteElement("Lagrange", domain.ufl_cell(), 2, (), - identity_pullback, H1)) + V = FunctionSpace( + domain, + FiniteElement("Lagrange", domain.ufl_cell(), 2, (), identity_pullback, H1), + ) f = Coefficient(V, count=0) v = TestFunction(V) x = SpatialCoordinate(domain) @@ -312,8 +376,12 @@ def forms(): yield compute_terminal_hashdata(expr, renumbering) c, d, r, h = compute_unique_terminal_hashdatas(forms()) - c0 = num_exprs * len(cells) # Number of actually unique cases from a code generation perspective - c1 = num_exprs * len(domains) # Number of unique cases from a symbolic representation perspective + c0 = num_exprs * len( + cells + ) # Number of actually unique cases from a code generation perspective + c1 = num_exprs * len( + domains + ) # Number of unique cases from a symbolic representation perspective assert len(reprs) == c1 assert len(hashes) == c1 self.assertEqual(c, nreps * c1) # number of inner loop executions in forms() above @@ -350,9 +418,9 @@ def hashdatas(): c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) assert c == 9 - assert d == 9-1 # (1,0 is repeated, therefore -1) - assert len(reprs) == 9-1 - assert len(hashes) == 9-1 + assert d == 9 - 1 # (1,0 is repeated, therefore -1) + assert len(reprs) == 9 - 1 + assert len(hashes) == 9 - 1 def test_multiindex_hashdata_does_not_depend_on_counts(self): @@ -373,11 +441,12 @@ def hashdatas(): reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, {}) + c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) - assert c == 3+9+9 - assert d == 1+1 - assert len(reprs) == 3+9+9 - assert len(hashes) == 3+9+9 + assert c == 3 + 9 + 9 + assert d == 1 + 1 + assert len(reprs) == 3 + 9 + 9 + assert len(hashes) == 3 + 9 + 9 def test_multiindex_hashdata_depends_on_the_order_indices_are_observed(self): @@ -387,28 +456,31 @@ def test_multiindex_hashdata_depends_on_the_order_indices_are_observed(self): def hashdatas(): for rep in range(nrep): - # Resetting index_numbering for each repetition, - # resulting in hashdata staying the same for - # each repetition but repr and hashes changing - # because new indices are created each repetition. + # Resetting index_numbering for each repetition, resulting + # in hashdata staying the same for each repetition but repr + # and hashes changing because new indices are created each + # repetition. index_numbering = {} i, j, k, l = indices(4) # noqa: E741 - for expr in (MultiIndex((i,)), - MultiIndex((i,)), # r - MultiIndex((i, j)), - MultiIndex((j, i)), - MultiIndex((i, j)), # r - MultiIndex((i, j, k)), - MultiIndex((k, j, i)), - MultiIndex((j, i))): # r + for expr in ( + MultiIndex((i,)), + MultiIndex((i,)), # r + MultiIndex((i, j)), + MultiIndex((j, i)), + MultiIndex((i, j)), # r + MultiIndex((i, j, k)), + MultiIndex((k, j, i)), + MultiIndex((j, i)), + ): # r reprs.add(repr(expr)) hashes.add(hash(expr)) yield compute_multiindex_hashdata(expr, index_numbering) + c, d, r, h = compute_unique_multiindex_hashdatas(hashdatas()) - assert c == nrep*8 + assert c == nrep * 8 assert d == 5 - assert len(reprs) == nrep*5 - assert len(hashes) == nrep*5 + assert len(reprs) == nrep * 5 + assert len(hashes) == nrep * 5 def check_unique_signatures(forms): @@ -437,17 +509,18 @@ def forms(): for family, sobolev in (("Lagrange", H1), ("Discontinuous Lagrange", L2)): for cell in (triangle, tetrahedron, quadrilateral): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) for degree in (1, 2): V = FiniteElement(family, cell, degree, (), identity_pullback, sobolev) space = FunctionSpace(domain, V) u = Coefficient(space) v = TestFunction(space) x = SpatialCoordinate(domain) - w = as_vector([v]*x.ufl_shape[0]) - f = dot(w, u*x) - a = f*dx + w = as_vector([v] * x.ufl_shape[0]) + f = dot(w, u * x) + a = f * dx yield a + check_unique_signatures(forms()) @@ -455,15 +528,16 @@ def test_signature_is_affected_by_domains(self): def forms(): for cell in (triangle, tetrahedron): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) for di in (1, 2): for dj in (1, 2): for dk in (1, 2): V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) space = FunctionSpace(domain, V) u = Coefficient(space) - a = u*dx(di) + 2*u*dx(dj) + 3*u*ds(dk) + a = u * dx(di) + 2 * u * dx(dj) + 3 * u * ds(dk) yield a + check_unique_signatures(forms()) @@ -471,35 +545,36 @@ def test_signature_of_forms_with_diff(self): def forms(): for i, cell in enumerate([triangle, tetrahedron]): d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1), ufl_id=i) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1), ufl_id=i) for k in (1, 2, 3): d = cell.topological_dimension() V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - W = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1) + W = FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1) v_space = FunctionSpace(domain, V) w_space = FunctionSpace(domain, W) u = Coefficient(v_space) w = Coefficient(w_space) vu = variable(u) vw = variable(w) - f = vu*dot(vw, vu**k*vw) + f = vu * dot(vw, vu**k * vw) g = diff(f, vu) h = dot(diff(f, vw), FacetNormal(domain)) - a = f*dx(1) + g*dx(2) + h*ds(0) + a = f * dx(1) + g * dx(2) + h * ds(0) yield a + check_unique_signatures(forms()) def test_signature_of_form_depend_on_coefficient_numbering_across_integrals(self): cell = triangle V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, V) f = Coefficient(space) g = Coefficient(space) - M1 = f*dx(0) + g*dx(1) - M2 = g*dx(0) + f*dx(1) - M3 = g*dx(0) + g*dx(1) + M1 = f * dx(0) + g * dx(1) + M2 = g * dx(0) + f * dx(1) + M3 = g * dx(0) + g * dx(1) self.assertTrue(M1.signature() != M2.signature()) self.assertTrue(M1.signature() != M3.signature()) self.assertTrue(M2.signature() != M3.signature()) @@ -510,19 +585,21 @@ def forms(): for cell in (triangle, tetrahedron): d = cell.topological_dimension() V = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) space = FunctionSpace(domain, V) u = Coefficient(space) v = Coefficient(space) - fs = [(u*v)+(u/v), - (u+v)+(u/v), - (u+v)*(u/v), - (u*v)*(u*v), - (u+v)*(u*v), # H1 same - # (u*v)*(u+v), # H1 same - (u*v)+(u+v), - ] + fs = [ + (u * v) + (u / v), + (u + v) + (u / v), + (u + v) * (u / v), + (u * v) * (u * v), + (u + v) * (u * v), # H1 same + # (u*v)*(u+v), # H1 same + (u * v) + (u + v), + ] for f in fs: - a = f*dx + a = f * dx yield a + check_unique_signatures(forms()) diff --git a/test/test_simplify.py b/test/test_simplify.py index 66a176d9d..1bbef4ba5 100755 --- a/test/test_simplify.py +++ b/test/test_simplify.py @@ -1,7 +1,33 @@ import math -from ufl import (Coefficient, FunctionSpace, Mesh, TestFunction, TrialFunction, VectorConstant, acos, as_tensor, as_ufl, - asin, atan, cos, cosh, dx, exp, i, j, ln, max_value, min_value, outer, sin, sinh, tan, tanh, triangle) +from ufl import ( + Coefficient, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + VectorConstant, + acos, + as_tensor, + as_ufl, + asin, + atan, + cos, + cosh, + dx, + exp, + i, + j, + ln, + max_value, + min_value, + outer, + sin, + sinh, + tan, + tanh, + triangle, +) from ufl.algorithms import compute_form_data from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -11,13 +37,13 @@ def xtest_zero_times_argument(self): # FIXME: Allow zero forms element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) v = TestFunction(space) u = TrialFunction(space) - L = 0*v*dx - a = 0*(u*v)*dx - b = (0*u)*v*dx + L = 0 * v * dx + a = 0 * (u * v) * dx + b = (0 * u) * v * dx assert len(compute_form_data(L).arguments) == 1 assert len(compute_form_data(a).arguments) == 2 assert len(compute_form_data(b).arguments) == 2 @@ -25,23 +51,23 @@ def xtest_zero_times_argument(self): def test_divisions(self): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) # Test simplification of division by 1 a = f - b = f/1 + b = f / 1 assert a == b # Test simplification of division by 1.0 a = f - b = f/1.0 + b = f / 1.0 assert a == b # Test simplification of division by of zero by something - a = 0/f - b = 0*f + a = 0 / f + b = 0 * f assert a == b # Test simplification of division by self (this simplification has been disabled) @@ -52,21 +78,21 @@ def test_divisions(self): def test_products(self): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) g = Coefficient(space) # Test simplification of literal multiplication - assert f*0 == as_ufl(0) - assert 0*f == as_ufl(0) - assert 1*f == f - assert f*1 == f - assert as_ufl(2)*as_ufl(3) == as_ufl(6) - assert as_ufl(2.0)*as_ufl(3.0) == as_ufl(6.0) + assert f * 0 == as_ufl(0) + assert 0 * f == as_ufl(0) + assert 1 * f == f + assert f * 1 == f + assert as_ufl(2) * as_ufl(3) == as_ufl(6) + assert as_ufl(2.0) * as_ufl(3.0) == as_ufl(6.0) # Test reordering of operands - assert f*g == g*f + assert f * g == g * f # Test simplification of self-multiplication (this simplification has been disabled) # assert f*f == f**2 @@ -74,7 +100,7 @@ def test_products(self): def test_sums(self): element = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) space = FunctionSpace(domain, element) f = Coefficient(space) g = Coefficient(space) @@ -118,13 +144,13 @@ def test_mathfunctions(self): assert math.exp(a) == exp(a) assert math.log(a) == ln(a) # TODO: Implement automatic simplification of conditionals? - assert a == float(max_value(a, a-1)) + assert a == float(max_value(a, a - 1)) # TODO: Implement automatic simplification of conditionals? - assert a == float(min_value(a, a+1)) + assert a == float(min_value(a, a + 1)) def test_indexing(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) u = VectorConstant(domain) v = VectorConstant(domain) @@ -132,7 +158,7 @@ def test_indexing(self): A2 = as_tensor(A[i, j], (i, j)) assert A2 == A - Bij = u[i]*v[j] + Bij = u[i] * v[j] Bij2 = as_tensor(Bij, (i, j))[i, j] as_tensor(Bij, (i, j)) assert Bij2 == Bij diff --git a/test/test_sobolevspace.py b/test/test_sobolevspace.py index 28e42a24d..b21760f63 100755 --- a/test/test_sobolevspace.py +++ b/test/test_sobolevspace.py @@ -6,8 +6,10 @@ from ufl import H1, H2, L2, HCurl, HDiv, HInf, triangle from ufl.finiteelement import FiniteElement from ufl.pullback import contravariant_piola, covariant_piola, identity_pullback -from ufl.sobolevspace import SobolevSpace # noqa: F401 -from ufl.sobolevspace import DirectionalSobolevSpace +from ufl.sobolevspace import ( + DirectionalSobolevSpace, + SobolevSpace, # noqa: F401 +) # Construct directional Sobolev spaces, with varying smoothness in # spatial coordinates @@ -26,10 +28,10 @@ def test_inclusion(): - assert H2 < H1 # Inclusion - assert not H2 > H1 # Not included + assert H2 < H1 # Inclusion + assert not H2 > H1 # Not included assert HDiv <= HDiv # Reflexivity - assert H2 < L2 # Transitivity + assert H2 < L2 # Transitivity assert H1 > H2 assert L2 > H1 @@ -84,7 +86,7 @@ def test_contains_h1(): FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1), FiniteElement("Lagrange", triangle, 2, (), identity_pullback, H1), # Some special elements: - FiniteElement("MTW", triangle, 3, (2, ), contravariant_piola, H1), + FiniteElement("MTW", triangle, 3, (2,), contravariant_piola, H1), FiniteElement("Hermite", triangle, 3, (), "custom", H1), ] for h1_element in h1_elements: @@ -115,9 +117,7 @@ def test_contains_h2(): def test_contains_hinf(): - hinf_elements = [ - FiniteElement("Real", triangle, 0, (), identity_pullback, HInf) - ] + hinf_elements = [FiniteElement("Real", triangle, 0, (), identity_pullback, HInf)] for hinf_element in hinf_elements: assert hinf_element in HInf assert hinf_element in H2 @@ -132,9 +132,9 @@ def test_contains_hinf(): def test_contains_hdiv(): hdiv_elements = [ - FiniteElement("Raviart-Thomas", triangle, 1, (2, ), contravariant_piola, HDiv), - FiniteElement("BDM", triangle, 1, (2, ), contravariant_piola, HDiv), - FiniteElement("BDFM", triangle, 2, (2, ), contravariant_piola, HDiv), + FiniteElement("Raviart-Thomas", triangle, 1, (2,), contravariant_piola, HDiv), + FiniteElement("BDM", triangle, 1, (2,), contravariant_piola, HDiv), + FiniteElement("BDFM", triangle, 2, (2,), contravariant_piola, HDiv), ] for hdiv_element in hdiv_elements: assert hdiv_element in HDiv @@ -149,8 +149,8 @@ def test_contains_hdiv(): def test_contains_hcurl(): hcurl_elements = [ - FiniteElement("N1curl", triangle, 1, (2, ), covariant_piola, HCurl), - FiniteElement("N2curl", triangle, 1, (2, ), covariant_piola, HCurl), + FiniteElement("N1curl", triangle, 1, (2,), covariant_piola, HCurl), + FiniteElement("N2curl", triangle, 1, (2,), covariant_piola, HCurl), ] for hcurl_element in hcurl_elements: assert hcurl_element in HCurl diff --git a/test/test_split.py b/test/test_split.py index 33b0a5885..91eb4b0b6 100755 --- a/test/test_split.py +++ b/test/test_split.py @@ -10,14 +10,17 @@ def test_split(self): cell = triangle d = cell.topological_dimension() - domain = Mesh(FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", cell, 1, (d,), identity_pullback, H1)) f = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) - v = FiniteElement("Lagrange", cell, 1, (d, ), identity_pullback, H1, - sub_elements=[f for _ in range(d)]) - w = FiniteElement("Lagrange", cell, 1, (d+1, ), identity_pullback, H1, - sub_elements=[f for _ in range(d + 1)]) - t = FiniteElement("Lagrange", cell, 1, (d, d), identity_pullback, H1, - sub_elements=[f for _ in range(d ** 2)]) + v = FiniteElement( + "Lagrange", cell, 1, (d,), identity_pullback, H1, sub_elements=[f for _ in range(d)] + ) + w = FiniteElement( + "Lagrange", cell, 1, (d + 1,), identity_pullback, H1, sub_elements=[f for _ in range(d + 1)] + ) + t = FiniteElement( + "Lagrange", cell, 1, (d, d), identity_pullback, H1, sub_elements=[f for _ in range(d**2)] + ) s = SymmetricElement({(0, 0): 0, (0, 1): 1, (1, 0): 1, (1, 1): 2}, [f for _ in range(3)]) m = MixedElement([f, v, w, t, s, s]) @@ -31,15 +34,15 @@ def test_split(self): # Check that shapes of all these functions are correct: assert () == Coefficient(f_space).ufl_shape assert (d,) == Coefficient(v_space).ufl_shape - assert (d+1,) == Coefficient(w_space).ufl_shape + assert (d + 1,) == Coefficient(w_space).ufl_shape assert (d, d) == Coefficient(t_space).ufl_shape assert (d, d) == Coefficient(s_space).ufl_shape # sum of value sizes, not accounting for symmetries: - assert (3*d*d + 2*d + 2,) == Coefficient(m_space).ufl_shape + assert (3 * d * d + 2 * d + 2,) == Coefficient(m_space).ufl_shape # Shapes of subelements are reproduced: g = Coefficient(m_space) - s, = g.ufl_shape + (s,) = g.ufl_shape for g2 in split(g): s -= product(g2.ufl_shape) assert s == 0 @@ -51,8 +54,8 @@ def test_split(self): m2_space = FunctionSpace(domain, m2) # assert d == 2 # assert (2,2) == Coefficient(v2_space).ufl_shape - assert (d+d,) == Coefficient(v2_space).ufl_shape - assert (2*d*d,) == Coefficient(m2_space).ufl_shape + assert (d + d,) == Coefficient(v2_space).ufl_shape + assert (2 * d * d,) == Coefficient(m2_space).ufl_shape # Split simple element t = TestFunction(f_space) @@ -67,8 +70,7 @@ def test_split(self): assert split(t) == (t[0], as_vector((t[1], t[2], t[3]))) assert split(split(t)[1]) == (t[1], as_vector((t[2], t[3]))) t = TestFunction(FunctionSpace(domain, MixedElement([[v, f], [f, v]]))) - assert split(t) == (as_vector((t[0], t[1], t[2])), - as_vector((t[3], t[4], t[5]))) + assert split(t) == (as_vector((t[0], t[1], t[2])), as_vector((t[3], t[4], t[5]))) assert split(split(t)[0]) == (as_vector((t[0], t[1])), t[2]) assert split(split(t)[1]) == (t[3], as_vector((t[4], t[5]))) assert split(split(split(t)[0])[0]) == (t[0], t[1]) diff --git a/test/test_str.py b/test/test_str.py index df80d8215..085aee7b2 100755 --- a/test/test_str.py +++ b/test/test_str.py @@ -1,6 +1,22 @@ -from ufl import (CellDiameter, CellVolume, Circumradius, FacetArea, FacetNormal, FunctionSpace, Index, Mesh, - SpatialCoordinate, TestFunction, TrialFunction, as_matrix, as_ufl, as_vector, quadrilateral, - tetrahedron, triangle) +from ufl import ( + CellDiameter, + CellVolume, + Circumradius, + FacetArea, + FacetNormal, + FunctionSpace, + Index, + Mesh, + SpatialCoordinate, + TestFunction, + TrialFunction, + as_matrix, + as_ufl, + as_vector, + quadrilateral, + tetrahedron, + triangle, +) from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -15,11 +31,11 @@ def test_str_float_value(self): def test_str_zero(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x = SpatialCoordinate(domain) assert str(as_ufl(0)) == "0" - assert str(0*x) == "0 (shape (2,))" - assert str(0*x*x[Index(42)]) == "0 (shape (2,), index labels (42,))" + assert str(0 * x) == "0 (shape (2,))" + assert str(0 * x * x[Index(42)]) == "0 (shape (2,), index labels (42,))" def test_str_index(self): @@ -28,41 +44,45 @@ def test_str_index(self): def test_str_coordinate(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(SpatialCoordinate(domain)) == "x" assert str(SpatialCoordinate(domain)[0]) == "x[0]" def test_str_normal(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(FacetNormal(domain)) == "n" assert str(FacetNormal(domain)[0]) == "n[0]" def test_str_circumradius(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(Circumradius(domain)) == "circumradius" def test_str_diameter(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(CellDiameter(domain)) == "diameter" def test_str_facetarea(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(FacetArea(domain)) == "facetarea" def test_str_volume(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) assert str(CellVolume(domain)) == "volume" def test_str_scalar_argument(self): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) - v = TestFunction(FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) - u = TrialFunction(FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1))) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) + v = TestFunction( + FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) + ) + u = TrialFunction( + FunctionSpace(domain, FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1)) + ) assert str(v) == "v_0" assert str(u) == "v_1" @@ -75,38 +95,36 @@ def test_str_scalar_argument(self): def test_str_list_vector(): - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) v = as_vector((x, y, z)) assert str(v) == ("[%s, %s, %s]" % (x, y, z)) def test_str_list_vector_with_zero(): - domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", tetrahedron, 1, (3,), identity_pullback, H1)) x, y, z = SpatialCoordinate(domain) v = as_vector((x, 0, 0)) assert str(v) == ("[%s, 0, 0]" % (x,)) def test_str_list_matrix(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x, y = SpatialCoordinate(domain) - v = as_matrix(((2*x, 3*y), - (4*x, 5*y))) - a = str(2*x) - b = str(3*y) - c = str(4*x) - d = str(5*y) + v = as_matrix(((2 * x, 3 * y), (4 * x, 5 * y))) + a = str(2 * x) + b = str(3 * y) + c = str(4 * x) + d = str(5 * y) assert str(v) == ("[\n [%s, %s],\n [%s, %s]\n]" % (a, b, c, d)) def test_str_list_matrix_with_zero(): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) x, y = SpatialCoordinate(domain) - v = as_matrix(((2*x, 3*y), - (0, 0))) - a = str(2*x) - b = str(3*y) + v = as_matrix(((2 * x, 3 * y), (0, 0))) + a = str(2 * x) + b = str(3 * y) c = str(as_vector((0, 0))) assert str(v) == ("[\n [%s, %s],\n%s\n]" % (a, b, c)) @@ -117,5 +135,8 @@ def test_str_list_matrix_with_zero(): def test_str_element(): elem = FiniteElement("Q", quadrilateral, 1, (), identity_pullback, H1) - assert repr(elem) == "ufl.finiteelement.FiniteElement(\"Q\", quadrilateral, 1, (), IdentityPullback(), H1)" + assert ( + repr(elem) + == 'ufl.finiteelement.FiniteElement("Q", quadrilateral, 1, (), IdentityPullback(), H1)' + ) assert str(elem) == "" diff --git a/test/test_strip_forms.py b/test/test_strip_forms.py index b58eea904..9b5a0fff9 100644 --- a/test/test_strip_forms.py +++ b/test/test_strip_forms.py @@ -1,7 +1,18 @@ import gc import sys -from ufl import Coefficient, Constant, FunctionSpace, Mesh, TestFunction, TrialFunction, dx, grad, inner, triangle +from ufl import ( + Coefficient, + Constant, + FunctionSpace, + Mesh, + TestFunction, + TrialFunction, + dx, + grad, + inner, + triangle, +) from ufl.algorithms import replace_terminal_data, strip_terminal_data from ufl.core.ufl_id import attach_ufl_id from ufl.core.ufl_type import UFLObject @@ -51,8 +62,9 @@ def test_strip_form_arguments_strips_data_refs(): assert sys.getrefcount(const_data) == MIN_REF_COUNT cell = triangle - domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (2, ), - identity_pullback, H1), data=mesh_data) + domain = AugmentedMesh( + FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1), data=mesh_data + ) element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) @@ -61,7 +73,7 @@ def test_strip_form_arguments_strips_data_refs(): f = AugmentedCoefficient(V, data=coeff_data) k = AugmentedConstant(V, data=const_data) - form = k*f*inner(grad(v), grad(u))*dx + form = k * f * inner(grad(v), grad(u)) * dx # Remove extraneous references del cell, domain, element, V, v, u, f, k @@ -89,8 +101,9 @@ def test_strip_form_arguments_does_not_change_form(): const_data = object() cell = triangle - domain = AugmentedMesh(FiniteElement("Lagrange", cell, 1, (2, ), - identity_pullback, H1), data=mesh_data) + domain = AugmentedMesh( + FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1), data=mesh_data + ) element = FiniteElement("Lagrange", cell, 1, (), identity_pullback, H1) V = AugmentedFunctionSpace(domain, element, data=fs_data) @@ -99,7 +112,7 @@ def test_strip_form_arguments_does_not_change_form(): f = AugmentedCoefficient(V, data=coeff_data) k = AugmentedConstant(V, data=const_data) - form = k*f*inner(grad(v), grad(u))*dx + form = k * f * inner(grad(v), grad(u)) * dx stripped_form, mapping = strip_terminal_data(form) assert stripped_form.signature() == form.signature() diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py index bdb6ad406..4d80e671d 100755 --- a/test/test_tensoralgebra.py +++ b/test/test_tensoralgebra.py @@ -2,8 +2,30 @@ import pytest -from ufl import (FacetNormal, Mesh, as_matrix, as_tensor, as_vector, cofac, cross, det, dev, diag, diag_vector, dot, - inner, inv, outer, perp, skew, sym, tr, transpose, triangle, zero) +from ufl import ( + FacetNormal, + Mesh, + as_matrix, + as_tensor, + as_vector, + cofac, + cross, + det, + dev, + diag, + diag_vector, + dot, + inner, + inv, + outer, + perp, + skew, + sym, + tr, + transpose, + triangle, + zero, +) from ufl.algorithms.remove_complex_nodes import remove_complex_nodes from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback @@ -43,13 +65,13 @@ def test_repeated_as_tensor(self, A, B, u, v): def test_outer(self, A, B, u, v): C = outer(u, v) - D = as_matrix([[10*30, 10*40], [20*30, 20*40]]) + D = as_matrix([[10 * 30, 10 * 40], [20 * 30, 20 * 40]]) self.assertEqualValues(C, D) C = outer(A, v) A, v = A, v dims = (0, 1) - D = as_tensor([[[A[i, j]*v[k] for k in dims] for j in dims] for i in dims]) + D = as_tensor([[[A[i, j] * v[k] for k in dims] for j in dims] for i in dims]) self.assertEqualValues(C, D) # TODO: Test other ranks @@ -57,18 +79,18 @@ def test_outer(self, A, B, u, v): def test_inner(self, A, B, u, v): C = inner(A, B) - D = 2*6 + 3*7 + 4*8 + 5*9 + D = 2 * 6 + 3 * 7 + 4 * 8 + 5 * 9 self.assertEqualValues(C, D) C = inner(u, v) - D = 10*30 + 20*40 + D = 10 * 30 + 20 * 40 self.assertEqualValues(C, D) def test_pow2_inner(self, A, u): - domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2, ), identity_pullback, H1)) + domain = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) f = FacetNormal(domain)[0] - f2 = f*f + f2 = f * f assert f2 == remove_complex_nodes(inner(f, f)) u2 = u**2 @@ -83,13 +105,12 @@ def test_pow2_inner(self, A, u): def test_dot(self, A, B, u, v): C = dot(u, v) - D = 10*30 + 20*40 + D = 10 * 30 + 20 * 40 self.assertEqualValues(C, D) C = dot(A, B) dims = (0, 1) - D = as_matrix([[sum(A[i, k]*B[k, j] for k in dims) - for j in dims] for i in dims]) + D = as_matrix([[sum(A[i, k] * B[k, j] for k in dims) for j in dims] for i in dims]) self.assertEqualValues(C, D) @@ -131,21 +152,21 @@ def test_perp(self): def xtest_dev(self, A): C = dev(A) - D = 0*C # FIXME: Add expected value here + D = 0 * C # FIXME: Add expected value here self.assertEqualValues(C, D) def test_skew(self, A): C = skew(A) A, dims = A, (0, 1) - D = 0.5*as_matrix([[A[i, j] - A[j, i] for j in dims] for i in dims]) + D = 0.5 * as_matrix([[A[i, j] - A[j, i] for j in dims] for i in dims]) self.assertEqualValues(C, D) def test_sym(self, A): C = sym(A) A, dims = A, (0, 1) - D = 0.5*as_matrix([[A[i, j] + A[j, i] for j in dims] for i in dims]) + D = 0.5 * as_matrix([[A[i, j] + A[j, i] for j in dims] for i in dims]) self.assertEqualValues(C, D) @@ -185,7 +206,7 @@ def test_tr(self, A): def test_det(self, A): dims = (0, 1) C = det(A) - D = sum((-A[i, 0]*A[0, i] if i != 0 else A[i-1, -1]*A[i, 0]) for i in dims) + D = sum((-A[i, 0] * A[0, i] if i != 0 else A[i - 1, -1] * A[i, 0]) for i in dims) self.assertEqualValues(C, D) @@ -198,6 +219,6 @@ def test_cofac(self, A): def xtest_inv(self, A): # FIXME: Test fails probably due to integer division C = inv(A) - detA = sum((-A[i, 0]*A[0, i] if i != 0 else A[i-1, -1]*A[i, 0]) for i in (0, 1)) + detA = sum((-A[i, 0] * A[0, i] if i != 0 else A[i - 1, -1] * A[i, 0]) for i in (0, 1)) D = as_matrix([[(-A[i, j] if i != j else A[i, j]) for j in (-1, 0)] for i in (-1, 0)]) / detA self.assertEqualValues(C, D) diff --git a/test/test_utilities.py b/test/test_utilities.py index 4fdf660d6..a06252898 100755 --- a/test/test_utilities.py +++ b/test/test_utilities.py @@ -31,11 +31,14 @@ def test_indexing_to_component(): for i in range(5): for j in range(3): for k in range(2): - assert 15*k+5*j+i == flatten_multiindex((k, j, i), shape_to_strides((2, 3, 5))) + assert 15 * k + 5 * j + i == flatten_multiindex( + (k, j, i), shape_to_strides((2, 3, 5)) + ) def test_component_numbering(): from ufl.permutation import build_component_numbering + sh = (2, 2) sm = {(1, 0): (0, 1)} v, s = build_component_numbering(sh, sm) @@ -45,8 +48,17 @@ def test_component_numbering(): sh = (3, 3) sm = {(1, 0): (0, 1), (2, 0): (0, 2), (2, 1): (1, 2)} v, s = build_component_numbering(sh, sm) - assert v == {(0, 1): 1, (1, 2): 4, (0, 0): 0, (2, 1): 4, (1, 1): 3, - (2, 0): 2, (2, 2): 5, (1, 0): 1, (0, 2): 2} + assert v == { + (0, 1): 1, + (1, 2): 4, + (0, 0): 0, + (2, 1): 4, + (1, 1): 3, + (2, 0): 2, + (2, 2): 5, + (1, 0): 1, + (0, 2): 2, + } assert s == [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)] @@ -143,11 +155,12 @@ def test_index_flattening(): i -= 0 # map back to tensor component: c2 = unflatten_index(i, shape_to_strides(ts)) - assert (k//2, k % 2) == c2 + assert (k // 2, k % 2) == c2 def test_stackdict(): from ufl.utils.stacks import StackDict + d = StackDict(a=1) assert d["a"] == 1 d.push("a", 2) diff --git a/ufl/__init__.py b/ufl/__init__.py index 59c19e61f..e2656a76b 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -245,10 +245,17 @@ from math import e, pi -import ufl.exproperators as __exproperators from ufl.action import Action from ufl.adjoint import Adjoint -from ufl.argument import Argument, Arguments, Coargument, TestFunction, TestFunctions, TrialFunction, TrialFunctions +from ufl.argument import ( + Argument, + Arguments, + Coargument, + TestFunction, + TestFunctions, + TrialFunction, + TrialFunctions, +) from ufl.cell import AbstractCell, Cell, TensorProductCell, as_cell from ufl.coefficient import Coefficient, Coefficients, Cofunction from ufl.constant import Constant, TensorConstant, VectorConstant @@ -259,84 +266,372 @@ from ufl.domain import AbstractDomain, Mesh, MeshView from ufl.finiteelement import AbstractFiniteElement from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm -from ufl.formoperators import (action, adjoint, derivative, energy_norm, extract_blocks, functional, lhs, replace, rhs, - sensitivity_rhs, system) +from ufl.formoperators import ( + action, + adjoint, + derivative, + energy_norm, + extract_blocks, + functional, + lhs, + replace, + rhs, + sensitivity_rhs, + system, +) from ufl.functionspace import FunctionSpace, MixedFunctionSpace -from ufl.geometry import (CellDiameter, CellNormal, CellVolume, Circumradius, FacetArea, FacetNormal, Jacobian, - JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, MaxFacetEdgeLength, - MinCellEdgeLength, MinFacetEdgeLength, SpatialCoordinate) +from ufl.geometry import ( + CellDiameter, + CellNormal, + CellVolume, + Circumradius, + FacetArea, + FacetNormal, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxCellEdgeLength, + MaxFacetEdgeLength, + MinCellEdgeLength, + MinFacetEdgeLength, + SpatialCoordinate, +) from ufl.integral import Integral from ufl.matrix import Matrix from ufl.measure import Measure, custom_integral_types, integral_types, register_integral_type -from ufl.objects import (dc, dC, dI, dO, dP, ds, dS, ds_b, dS_h, ds_t, ds_tb, ds_v, dS_v, dx, dX, facet, hexahedron, i, - interval, j, k, l, p, pentatope, prism, pyramid, q, quadrilateral, r, s, tesseract, - tetrahedron, triangle, vertex) -from ufl.operators import (And, Dn, Dx, Not, Or, acos, asin, atan, atan2, avg, bessel_I, bessel_J, bessel_K, bessel_Y, - cell_avg, cofac, conditional, conj, cos, cosh, cross, curl, det, dev, diag, diag_vector, - diff, div, dot, elem_div, elem_mult, elem_op, elem_pow, eq, erf, exp, exterior_derivative, - facet_avg, ge, grad, gt, imag, inner, inv, jump, le, ln, lt, max_value, min_value, nabla_div, - nabla_grad, ne, outer, perp, rank, real, rot, shape, sign, sin, sinh, skew, sqrt, sym, tan, - tanh, tr, transpose, variable) -from ufl.pullback import (AbstractPullback, MixedPullback, SymmetricPullback, contravariant_piola, covariant_piola, - double_contravariant_piola, double_covariant_piola, identity_pullback, l2_piola) +from ufl.objects import ( + dC, + dc, + dI, + dO, + dP, + dS, + ds, + ds_b, + dS_h, + ds_t, + ds_tb, + dS_v, + ds_v, + dX, + dx, + facet, + hexahedron, + i, + interval, + j, + k, + l, + p, + pentatope, + prism, + pyramid, + q, + quadrilateral, + r, + s, + tesseract, + tetrahedron, + triangle, + vertex, +) +from ufl.operators import ( + And, + Dn, + Dx, + Not, + Or, + acos, + asin, + atan, + atan2, + avg, + bessel_I, + bessel_J, + bessel_K, + bessel_Y, + cell_avg, + cofac, + conditional, + conj, + cos, + cosh, + cross, + curl, + det, + dev, + diag, + diag_vector, + diff, + div, + dot, + elem_div, + elem_mult, + elem_op, + elem_pow, + eq, + erf, + exp, + exterior_derivative, + facet_avg, + ge, + grad, + gt, + imag, + inner, + inv, + jump, + le, + ln, + lt, + max_value, + min_value, + nabla_div, + nabla_grad, + ne, + outer, + perp, + rank, + real, + rot, + shape, + sign, + sin, + sinh, + skew, + sqrt, + sym, + tan, + tanh, + tr, + transpose, + variable, +) +from ufl.pullback import ( + AbstractPullback, + MixedPullback, + SymmetricPullback, + contravariant_piola, + covariant_piola, + double_contravariant_piola, + double_covariant_piola, + identity_pullback, + l2_piola, +) from ufl.sobolevspace import H1, H2, L2, HCurl, HDiv, HDivDiv, HEin, HInf from ufl.split_functions import split -from ufl.tensors import as_matrix, as_tensor, as_vector, unit_matrices, unit_matrix, unit_vector, unit_vectors +from ufl.tensors import ( + as_matrix, + as_tensor, + as_vector, + unit_matrices, + unit_matrix, + unit_vector, + unit_vectors, +) from ufl.utils.sequences import product __all__ = [ - 'product', - 'as_cell', 'AbstractCell', 'Cell', 'TensorProductCell', - 'AbstractDomain', 'Mesh', 'MeshView', - 'L2', 'H1', 'H2', 'HCurl', 'HDiv', 'HInf', 'HEin', 'HDivDiv', - 'identity_pullback', 'l2_piola', 'contravariant_piola', 'covariant_piola', - 'double_contravariant_piola', 'double_covariant_piola', - 'l2_piola', 'MixedPullback', 'SymmetricPullback', 'AbstractPullback', - 'SpatialCoordinate', - 'CellVolume', 'CellDiameter', 'Circumradius', - 'MinCellEdgeLength', 'MaxCellEdgeLength', - 'FacetArea', 'MinFacetEdgeLength', 'MaxFacetEdgeLength', - 'FacetNormal', 'CellNormal', - 'Jacobian', 'JacobianDeterminant', 'JacobianInverse', - 'AbstractFiniteElement', - 'FunctionSpace', 'MixedFunctionSpace', - 'Argument', 'Coargument', 'TestFunction', 'TrialFunction', - 'Arguments', 'TestFunctions', 'TrialFunctions', - 'Coefficient', 'Cofunction', 'Coefficients', - 'Matrix', 'Adjoint', 'Action', - 'Interpolate', 'interpolate', - 'ExternalOperator', - 'Constant', 'VectorConstant', 'TensorConstant', - 'split', - 'PermutationSymbol', 'Identity', 'zero', 'as_ufl', - 'Index', 'indices', - 'as_tensor', 'as_vector', 'as_matrix', - 'unit_vector', 'unit_vectors', 'unit_matrix', 'unit_matrices', - 'rank', 'shape', 'conj', 'real', 'imag', - 'outer', 'inner', 'dot', 'cross', 'perp', - 'det', 'inv', 'cofac', - 'transpose', 'tr', 'diag', 'diag_vector', 'dev', 'skew', 'sym', - 'sqrt', 'exp', 'ln', 'erf', - 'cos', 'sin', 'tan', - 'acos', 'asin', 'atan', 'atan2', - 'cosh', 'sinh', 'tanh', - 'bessel_J', 'bessel_Y', 'bessel_I', 'bessel_K', - 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', - 'conditional', 'sign', 'max_value', 'min_value', - 'variable', 'diff', - 'Dx', 'grad', 'div', 'curl', 'rot', 'nabla_grad', 'nabla_div', 'Dn', 'exterior_derivative', - 'jump', 'avg', 'cell_avg', 'facet_avg', - 'elem_mult', 'elem_div', 'elem_pow', 'elem_op', - 'Form', 'BaseForm', 'FormSum', 'ZeroBaseForm', - 'Integral', 'Measure', 'register_integral_type', 'integral_types', 'custom_integral_types', - 'replace', 'derivative', 'action', 'energy_norm', 'rhs', 'lhs', 'extract_blocks', - 'system', 'functional', 'adjoint', 'sensitivity_rhs', - 'dx', 'ds', 'dS', 'dP', - 'dc', 'dC', 'dO', 'dI', 'dX', - 'ds_b', 'ds_t', 'ds_tb', 'ds_v', 'dS_h', 'dS_v', - 'vertex', 'interval', 'triangle', 'tetrahedron', - 'prism', 'pyramid', 'pentatope', 'tesseract', - 'quadrilateral', 'hexahedron', 'facet', - 'i', 'j', 'k', 'l', 'p', 'q', 'r', 's', - 'e', 'pi', + "product", + "as_cell", + "AbstractCell", + "Cell", + "TensorProductCell", + "AbstractDomain", + "Mesh", + "MeshView", + "L2", + "H1", + "H2", + "HCurl", + "HDiv", + "HInf", + "HEin", + "HDivDiv", + "identity_pullback", + "l2_piola", + "contravariant_piola", + "covariant_piola", + "double_contravariant_piola", + "double_covariant_piola", + "l2_piola", + "MixedPullback", + "SymmetricPullback", + "AbstractPullback", + "SpatialCoordinate", + "CellVolume", + "CellDiameter", + "Circumradius", + "MinCellEdgeLength", + "MaxCellEdgeLength", + "FacetArea", + "MinFacetEdgeLength", + "MaxFacetEdgeLength", + "FacetNormal", + "CellNormal", + "Jacobian", + "JacobianDeterminant", + "JacobianInverse", + "AbstractFiniteElement", + "FunctionSpace", + "MixedFunctionSpace", + "Argument", + "Coargument", + "TestFunction", + "TrialFunction", + "Arguments", + "TestFunctions", + "TrialFunctions", + "Coefficient", + "Cofunction", + "Coefficients", + "Matrix", + "Adjoint", + "Action", + "Interpolate", + "interpolate", + "ExternalOperator", + "Constant", + "VectorConstant", + "TensorConstant", + "split", + "PermutationSymbol", + "Identity", + "zero", + "as_ufl", + "Index", + "indices", + "as_tensor", + "as_vector", + "as_matrix", + "unit_vector", + "unit_vectors", + "unit_matrix", + "unit_matrices", + "rank", + "shape", + "conj", + "real", + "imag", + "outer", + "inner", + "dot", + "cross", + "perp", + "det", + "inv", + "cofac", + "transpose", + "tr", + "diag", + "diag_vector", + "dev", + "skew", + "sym", + "sqrt", + "exp", + "ln", + "erf", + "cos", + "sin", + "tan", + "acos", + "asin", + "atan", + "atan2", + "cosh", + "sinh", + "tanh", + "bessel_J", + "bessel_Y", + "bessel_I", + "bessel_K", + "eq", + "ne", + "le", + "ge", + "lt", + "gt", + "And", + "Or", + "Not", + "conditional", + "sign", + "max_value", + "min_value", + "variable", + "diff", + "Dx", + "grad", + "div", + "curl", + "rot", + "nabla_grad", + "nabla_div", + "Dn", + "exterior_derivative", + "jump", + "avg", + "cell_avg", + "facet_avg", + "elem_mult", + "elem_div", + "elem_pow", + "elem_op", + "Form", + "BaseForm", + "FormSum", + "ZeroBaseForm", + "Integral", + "Measure", + "register_integral_type", + "integral_types", + "custom_integral_types", + "replace", + "derivative", + "action", + "energy_norm", + "rhs", + "lhs", + "extract_blocks", + "system", + "functional", + "adjoint", + "sensitivity_rhs", + "dx", + "ds", + "dS", + "dP", + "dc", + "dC", + "dO", + "dI", + "dX", + "ds_b", + "ds_t", + "ds_tb", + "ds_v", + "dS_h", + "dS_v", + "vertex", + "interval", + "triangle", + "tetrahedron", + "prism", + "pyramid", + "pentatope", + "tesseract", + "quadrilateral", + "hexahedron", + "facet", + "i", + "j", + "k", + "l", + "p", + "q", + "r", + "s", + "e", + "pi", ] diff --git a/ufl/action.py b/ufl/action.py index 2401e05ea..3bb9fd533 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -45,7 +45,8 @@ class Action(BaseForm): "_arguments", "_coefficients", "_domains", - "_hash") + "_hash", + ) def __new__(cls, *args, **kw): """Create a new Action.""" @@ -71,12 +72,10 @@ def __new__(cls, *args, **kw): if isinstance(left, (FormSum, Sum)): # Action distributes over sums on the LHS - return FormSum(*[(Action(component, right), 1) - for component in left.ufl_operands]) + return FormSum(*[(Action(component, right), 1) for component in left.ufl_operands]) if isinstance(right, (FormSum, Sum)): # Action also distributes over sums on the RHS - return FormSum(*[(Action(left, component), 1) - for component in right.ufl_operands]) + return FormSum(*[(Action(left, component), 1) for component in right.ufl_operands]) return super(Action, cls).__new__(cls) @@ -98,8 +97,7 @@ def __init__(self, left, right): def ufl_function_spaces(self): """Get the tuple of function spaces of the underlying form.""" if isinstance(self._right, Form): - return self._left.ufl_function_spaces()[:-1] \ - + self._right.ufl_function_spaces()[1:] + return self._left.ufl_function_spaces()[:-1] + self._right.ufl_function_spaces()[1:] elif isinstance(self._right, Coefficient): return self._left.ufl_function_spaces()[:-1] @@ -124,7 +122,9 @@ def _analyze_domains(self): from ufl.domain import join_domains # Collect domains - self._domains = join_domains(chain.from_iterable(e.ufl_domains() for e in self.ufl_operands)) + self._domains = join_domains( + chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + ) def equals(self, other): """Check if two Actions are equal.""" @@ -158,18 +158,19 @@ def _check_function_spaces(left, right): # right as a consequence of Leibniz formula. right, *_ = right.ufl_operands - # `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`. + # `left` can also be a Coefficient in V (= V**), e.g. + # `action(Coefficient(V), Cofunction(V.dual()))`. left_arg = left.arguments()[-1] if not isinstance(left, Coefficient) else left if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)): if left_arg.ufl_function_space().dual() != right.arguments()[0].ufl_function_space(): raise TypeError("Incompatible function spaces in Action") elif isinstance(right, (Coefficient, Cofunction, Argument, BaseFormOperator)): if left_arg.ufl_function_space() != right.ufl_function_space(): - raise TypeError("Incompatible function spaces in Action") # `Zero` doesn't contain any information about the function space. - # -> Not a problem since Action will get simplified with a `ZeroBaseForm` - # which won't take into account the arguments on the right because of argument contraction. + # -> Not a problem since Action will get simplified with a + # `ZeroBaseForm` which won't take into account the arguments on + # the right because of argument contraction. # This occurs for: # `derivative(Action(A, B), u)` with B is an `Expr` such that dB/du == 0 # -> `derivative(B, u)` becomes `Zero` when expanding derivatives since B is an Expr. @@ -180,7 +181,8 @@ def _check_function_spaces(left, right): def _get_action_form_arguments(left, right): """Perform argument contraction to work out the arguments of Action.""" coefficients = () - # `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`. + # `left` can also be a Coefficient in V (= V**), e.g. + # `action(Coefficient(V), Cofunction(V.dual()))`. left_args = left.arguments()[:-1] if not isinstance(left, Coefficient) else () if isinstance(right, BaseForm): arguments = left_args + right.arguments()[1:] @@ -189,6 +191,7 @@ def _get_action_form_arguments(left, right): # Action differentiation pushes differentiation through # right as a consequence of Leibniz formula. from ufl.algorithms.analysis import extract_arguments_and_coefficients + right_args, right_coeffs = extract_arguments_and_coefficients(right) arguments = left_args + tuple(right_args) coefficients += tuple(right_coeffs) diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 38f003870..92447b985 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -32,7 +32,8 @@ class Adjoint(BaseForm): "_coefficients", "_domains", "ufl_operands", - "_hash") + "_hash", + ) def __new__(cls, *args, **kw): """Create a new Adjoint.""" @@ -46,15 +47,17 @@ def __new__(cls, *args, **kw): return form._form elif isinstance(form, FormSum): # Adjoint distributes over sums - return FormSum(*[(Adjoint(component), 1) - for component in form.components()]) + return FormSum(*[(Adjoint(component), 1) for component in form.components()]) elif isinstance(form, Coargument): - # The adjoint of a coargument `c: V* -> V*` is the identity matrix mapping from V to V (i.e. V x V* -> R). - # Equivalently, the adjoint of `c` is its first argument, which is a ufl.Argument defined on the - # primal space of `c`. + # The adjoint of a coargument `c: V* -> V*` is the identity + # matrix mapping from V to V (i.e. V x V* -> R). + # Equivalently, the adjoint of `c` is its first argument, + # which is a ufl.Argument defined on the primal space of + # `c`. primal_arg, _ = form.arguments() - # Returning the primal argument avoids explicit argument reconstruction, making it - # a robust strategy for handling subclasses of `ufl.Coargument`. + # Returning the primal argument avoids explicit argument + # reconstruction, making it a robust strategy for handling + # subclasses of `ufl.Coargument`. return primal_arg return super(Adjoint, cls).__new__(cls) @@ -98,8 +101,9 @@ def equals(self, other): return False if self is other: return True - # Make sure we are returning a boolean as the equality can result in a `ufl.Equation` - # if the underlying objects are `ufl.BaseForm`. + # Make sure we are returning a boolean as the equality can + # result in a `ufl.Equation` if the underlying objects are + # `ufl.BaseForm`. return bool(self._form == other._form) def __str__(self): diff --git a/ufl/algebra.py b/ufl/algebra.py index 376c16d02..dea6fd021 100644 --- a/ufl/algebra.py +++ b/ufl/algebra.py @@ -19,9 +19,13 @@ # --- Algebraic operators --- -@ufl_type(num_ops=2, - inherit_shape_from_operand=0, inherit_indices_from_operand=0, - binop="__add__", rbinop="__radd__") +@ufl_type( + num_ops=2, + inherit_shape_from_operand=0, + inherit_indices_from_operand=0, + binop="__add__", + rbinop="__radd__", +) class Sum(Operator): """Sum.""" @@ -87,16 +91,14 @@ def __init__(self, a, b): def evaluate(self, x, mapping, component, index_values): """Evaluate.""" - return sum(o.evaluate(x, mapping, component, - index_values) for o in self.ufl_operands) + return sum(o.evaluate(x, mapping, component, index_values) for o in self.ufl_operands) def __str__(self): """Format as a string.""" return " + ".join([parstr(o, self) for o in self.ufl_operands]) -@ufl_type(num_ops=2, - binop="__mul__", rbinop="__rmul__") +@ufl_type(num_ops=2, binop="__mul__", rbinop="__rmul__") class Product(Operator): """The product of two or more UFL objects.""" @@ -111,16 +113,20 @@ def __new__(cls, a, b): # Type checking # Make sure everything is scalar if a.ufl_shape or b.ufl_shape: - raise ValueError("Product can only represent products of scalars, " - f"got\n {ufl_err_str(a)}\nand\n {ufl_err_str(b)}") + raise ValueError( + "Product can only represent products of scalars, " + f"got\n {ufl_err_str(a)}\nand\n {ufl_err_str(b)}" + ) # Simplification if isinstance(a, Zero) or isinstance(b, Zero): # Got any zeros? Return zero. - fi, fid = merge_unique_indices(a.ufl_free_indices, - a.ufl_index_dimensions, - b.ufl_free_indices, - b.ufl_index_dimensions) + fi, fid = merge_unique_indices( + a.ufl_free_indices, + a.ufl_index_dimensions, + b.ufl_free_indices, + b.ufl_index_dimensions, + ) return Zero((), fi, fid) sa = isinstance(a, ScalarValue) sb = isinstance(b, ScalarValue) @@ -154,10 +160,9 @@ def _init(self, a, b): self.ufl_operands = (a, b) # Extract indices - fi, fid = merge_unique_indices(a.ufl_free_indices, - a.ufl_index_dimensions, - b.ufl_free_indices, - b.ufl_index_dimensions) + fi, fid = merge_unique_indices( + a.ufl_free_indices, a.ufl_index_dimensions, b.ufl_free_indices, b.ufl_index_dimensions + ) self.ufl_free_indices = fi self.ufl_index_dimensions = fid @@ -173,7 +178,9 @@ def evaluate(self, x, mapping, component, index_values): sh = self.ufl_shape if sh: if sh != ops[-1].ufl_shape: - raise ValueError("Expecting nonscalar product operand to be the last by convention.") + raise ValueError( + "Expecting nonscalar product operand to be the last by convention." + ) tmp = ops[-1].evaluate(x, mapping, component, index_values) ops = ops[:-1] else: @@ -188,9 +195,7 @@ def __str__(self): return " * ".join((parstr(a, self), parstr(b, self))) -@ufl_type(num_ops=2, - inherit_indices_from_operand=0, - binop="__div__", rbinop="__rdiv__") +@ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__div__", rbinop="__rdiv__") class Division(Operator): """Division.""" @@ -260,9 +265,7 @@ def __str__(self): return f"{parstr(self.ufl_operands[0], self)} / {parstr(self.ufl_operands[1], self)}" -@ufl_type(num_ops=2, - inherit_indices_from_operand=0, - binop="__pow__", rbinop="__rpow__") +@ufl_type(num_ops=2, inherit_indices_from_operand=0, binop="__pow__", rbinop="__rpow__") class Power(Operator): """Power.""" @@ -282,7 +285,7 @@ def __new__(cls, a, b): # Simplification if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): - return as_ufl(a._value ** b._value) + return as_ufl(a._value**b._value) if isinstance(b, Zero): return IntValue(1) if isinstance(a, Zero) and isinstance(b, ScalarValue): @@ -324,9 +327,7 @@ def __str__(self): return f"{parstr(a, self)} ** {parstr(b, self)}" -@ufl_type(num_ops=1, - inherit_shape_from_operand=0, inherit_indices_from_operand=0, - unop="__abs__") +@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0, unop="__abs__") class Abs(Operator): """Absolute value.""" @@ -357,12 +358,11 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): """Format as a string.""" - a, = self.ufl_operands + (a,) = self.ufl_operands return f"|{parstr(a, self)}|" -@ufl_type(num_ops=1, - inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Conj(Operator): """Complex conjugate.""" @@ -393,12 +393,11 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): """Format as a string.""" - a, = self.ufl_operands + (a,) = self.ufl_operands return f"conj({parstr(a, self)})" -@ufl_type(num_ops=1, - inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Real(Operator): """Real part.""" @@ -431,12 +430,11 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): """Format as a string.""" - a, = self.ufl_operands + (a,) = self.ufl_operands return f"Re[{parstr(a, self)}]" -@ufl_type(num_ops=1, - inherit_shape_from_operand=0, inherit_indices_from_operand=0) +@ufl_type(num_ops=1, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class Imag(Operator): """Imaginary part.""" @@ -467,5 +465,5 @@ def evaluate(self, x, mapping, component, index_values): def __str__(self): """Format as a string.""" - a, = self.ufl_operands + (a,) = self.ufl_operands return f"Im[{parstr(a, self)}]" diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 8e3ce5a1a..43eac078f 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -57,9 +57,16 @@ ] from ufl.algorithms.ad import expand_derivatives -from ufl.algorithms.analysis import (extract_arguments, extract_base_form_operators, extract_coefficients, - extract_elements, extract_sub_elements, extract_type, extract_unique_elements, - sort_elements) +from ufl.algorithms.analysis import ( + extract_arguments, + extract_base_form_operators, + extract_coefficients, + extract_elements, + extract_sub_elements, + extract_type, + extract_unique_elements, + sort_elements, +) from ufl.algorithms.change_to_reference import change_to_reference_grad from ufl.algorithms.checks import validate_form from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form @@ -68,13 +75,24 @@ from ufl.algorithms.expand_indices import expand_indices from ufl.algorithms.formfiles import load_forms, load_ufl_file, read_ufl_file from ufl.algorithms.formsplitter import FormSplitter -from ufl.algorithms.formtransformations import (compute_energy_norm, compute_form_action, compute_form_adjoint, - compute_form_arities, compute_form_functional, compute_form_lhs, - compute_form_rhs) +from ufl.algorithms.formtransformations import ( + compute_energy_norm, + compute_form_action, + compute_form_adjoint, + compute_form_arities, + compute_form_functional, + compute_form_lhs, + compute_form_rhs, +) from ufl.algorithms.replace import replace from ufl.algorithms.signature import compute_form_signature from ufl.algorithms.strip_terminal_data import replace_terminal_data, strip_terminal_data -from ufl.algorithms.transformer import ReuseTransformer, Transformer, apply_transformer, strip_variables +from ufl.algorithms.transformer import ( + ReuseTransformer, + Transformer, + apply_transformer, + strip_variables, +) from ufl.corealg.multifunction import MultiFunction from ufl.corealg.traversal import post_traversal from ufl.utils.formatting import tree_format diff --git a/ufl/algorithms/analysis.py b/ufl/algorithms/analysis.py index ba3afe6c6..1d0464a95 100644 --- a/ufl/algorithms/analysis.py +++ b/ufl/algorithms/analysis.py @@ -1,4 +1,4 @@ -"""Utility algorithms for inspection of and information extraction from UFL objects in various ways.""" +"""Utility algorithms for inspection of and information extraction from UFL objects.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -43,6 +43,7 @@ def unique_tuple(objects): # --- Utilities to extract information from an expression --- + def extract_type(a, ufl_types): """Build a set of all objects found in a whose class is in ufl_types. @@ -76,15 +77,22 @@ def extract_type(a, ufl_types): if all(issubclass(t, Terminal) for t in ufl_types): # Optimization - objects = set(o for e in iter_expressions(a) - for o in traverse_unique_terminals(e) - if any(isinstance(o, t) for t in ufl_types)) + objects = set( + o + for e in iter_expressions(a) + for o in traverse_unique_terminals(e) + if any(isinstance(o, t) for t in ufl_types) + ) else: - objects = set(o for e in iter_expressions(a) - for o in unique_pre_traversal(e) - if any(isinstance(o, t) for t in ufl_types)) - - # Need to extract objects contained in base form operators whose type is in ufl_types + objects = set( + o + for e in iter_expressions(a) + for o in unique_pre_traversal(e) + if any(isinstance(o, t) for t in ufl_types) + ) + + # Need to extract objects contained in base form operators whose + # type is in ufl_types base_form_ops = set(e for e in objects if isinstance(e, BaseFormOperator)) ufl_types_no_args = tuple(t for t in ufl_types if not issubclass(t, BaseArgument)) base_form_objects = () @@ -93,16 +101,19 @@ def extract_type(a, ufl_types): # `N(u; v*) * v * dx` <=> `action(v1 * v * dx, N(...; v*))` # where `v`, `v1` are `Argument`s and `v*` a `Coargument`. for ai in tuple(arg for arg in o.argument_slots(isinstance(a, Form))): - # Extracting BaseArguments of an object of which a Coargument is an argument, - # then we just return the dual argument of the Coargument and not its primal argument. + # Extracting BaseArguments of an object of which a + # Coargument is an argument, then we just return the dual + # argument of the Coargument and not its primal argument. if isinstance(ai, Coargument): new_types = tuple(Coargument if t is BaseArgument else t for t in ufl_types) base_form_objects += tuple(extract_type(ai, new_types)) else: base_form_objects += tuple(extract_type(ai, ufl_types)) - # Look for BaseArguments in BaseFormOperator's argument slots only since that's where they are by definition. - # Don't look into operands, which is convenient for external operator composition, e.g. N1(N2; v*) - # where N2 is seen as an operator and not a form. + # Look for BaseArguments in BaseFormOperator's argument slots + # only since that's where they are by definition. Don't look + # into operands, which is convenient for external operator + # composition, e.g. N1(N2; v*) where N2 is seen as an operator + # and not a form. slots = o.ufl_operands for ai in slots: base_form_objects += tuple(extract_type(ai, ufl_types_no_args)) @@ -207,14 +218,16 @@ def extract_arguments_and_coefficients(a): raise ValueError( "Found different Arguments with same number and part.\n" "Did you combine test or trial functions from different spaces?\n" - "The Arguments found are:\n" + "\n".join(f" {a}" for a in arguments)) + "The Arguments found are:\n" + "\n".join(f" {a}" for a in arguments) + ) # Build count: instance mappings, should be one to one fcounts = dict((f, f.count()) for f in coefficients) if len(fcounts) != len(set(fcounts.values())): raise ValueError( "Found different coefficients with same counts.\n" - "The arguments found are:\n" + "\n".join(f" {c}" for c in coefficients)) + "The arguments found are:\n" + "\n".join(f" {c}" for c in coefficients) + ) # Passed checks, so we can safely sort the instances by count arguments = _sorted_by_number_and_part(arguments) diff --git a/ufl/algorithms/apply_algebra_lowering.py b/ufl/algorithms/apply_algebra_lowering.py index e7dc4cd46..ea5b3bbb2 100644 --- a/ufl/algorithms/apply_algebra_lowering.py +++ b/ufl/algorithms/apply_algebra_lowering.py @@ -1,4 +1,4 @@ -"""Algorithm for expanding compound expressions into equivalent representations using basic operators.""" +"""Algorithm for expanding compound expressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # @@ -53,8 +53,10 @@ def sym(self, o, A): def cross(self, o, a, b): """Lower a cross.""" + def c(i, j): return Product(a[i], b[j]) - Product(a[j], b[i]) + return as_vector((c(1, 2), c(2, 0), c(0, 1))) def perp(self, o, a): @@ -125,12 +127,14 @@ def nabla_grad(self, o, a): def curl(self, o, a): """Lower a curl.""" + # o = curl a = "[a.dx(1), -a.dx(0)]" if a.ufl_shape == () # o = curl a = "cross(nabla, (a0, a1, 0))[2]" if a.ufl_shape == (2,) # o = curl a = "cross(nabla, a)" if a.ufl_shape == (3,) def c(i, j): """A component of curl.""" return a[j].dx(i) - a[i].dx(j) + sh = a.ufl_shape if sh == (): return as_vector((a.dx(1), -a.dx(0))) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 7ef8a0a5f..6fcbb1b5f 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1,4 +1,4 @@ -"""This module contains the apply_derivatives algorithm which computes the derivatives of a form of expression.""" +"""Apply derivatives algorithm which computes the derivatives of a form of expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -16,9 +16,31 @@ from ufl.algorithms.replace_derivative_nodes import replace_derivative_nodes from ufl.argument import BaseArgument from ufl.checks import is_cellwise_constant -from ufl.classes import (Coefficient, ComponentTensor, Conj, ConstantValue, ExprList, ExprMapping, FloatValue, - FormArgument, Grad, Identity, Imag, Indexed, IndexSum, JacobianInverse, ListTensor, Product, - Real, ReferenceGrad, ReferenceValue, SpatialCoordinate, Sum, Variable, Zero) +from ufl.classes import ( + Coefficient, + ComponentTensor, + Conj, + ConstantValue, + ExprList, + ExprMapping, + FloatValue, + FormArgument, + Grad, + Identity, + Imag, + Indexed, + IndexSum, + JacobianInverse, + ListTensor, + Product, + Real, + ReferenceGrad, + ReferenceValue, + SpatialCoordinate, + Sum, + Variable, + Zero, +) from ufl.constantvalue import is_true_ufl_scalar, is_ufl_scalar from ufl.core.base_form_operator import BaseFormOperator from ufl.core.expr import ufl_err_str @@ -26,11 +48,30 @@ from ufl.core.terminal import Terminal from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction -from ufl.differentiation import BaseFormCoordinateDerivative, BaseFormOperatorDerivative, CoordinateDerivative +from ufl.differentiation import ( + BaseFormCoordinateDerivative, + BaseFormOperatorDerivative, + CoordinateDerivative, +) from ufl.domain import extract_unique_domain from ufl.form import Form, ZeroBaseForm -from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg, conditional, cos, cosh, exp, facet_avg, ln, - sign, sin, sinh, sqrt) +from ufl.operators import ( + bessel_I, + bessel_J, + bessel_K, + bessel_Y, + cell_avg, + conditional, + cos, + cosh, + exp, + facet_avg, + ln, + sign, + sin, + sinh, + sqrt, +) from ufl.pullback import CustomPullback, PhysicalPullback from ufl.tensors import as_scalar, as_scalars, as_tensor, unit_indexed_tensor, unwrap_list_tensor @@ -53,8 +94,10 @@ def __init__(self, var_shape): def expr(self, o): """Raise error.""" - raise ValueError(f"Missing differentiation handler for type {o._ufl_class_.__name__}. " - "Have you added a new type?") + raise ValueError( + f"Missing differentiation handler for type {o._ufl_class_.__name__}. " + "Have you added a new type?" + ) def unexpected(self, o): """Raise error about unexpected type.""" @@ -62,11 +105,16 @@ def unexpected(self, o): def override(self, o): """Raise error about overriding.""" - raise ValueError(f"Type {o._ufl_class_.__name__} must be overridden in specialized AD rule set.") + raise ValueError( + f"Type {o._ufl_class_.__name__} must be overridden in specialized AD rule set." + ) def derivative(self, o): """Raise error.""" - raise ValueError(f"Unhandled derivative type {o._ufl_class_.__name__}, nested differentiation has failed.") + raise ValueError( + f"Unhandled derivative type {o._ufl_class_.__name__}, " + "nested differentiation has failed." + ) # --- Some types just don't have any derivative, this is just to # --- make algorithm structure generic @@ -74,20 +122,22 @@ def derivative(self, o): def non_differentiable_terminal(self, o): """Return the non-differentiated object. - Labels and indices are not differentiable: it's convenient to return the non-differentiated object. + Labels and indices are not differentiable: it's convenient to + return the non-differentiated object. """ return o + label = non_differentiable_terminal multi_index = non_differentiable_terminal # --- Helper functions for creating zeros with the right shapes def independent_terminal(self, o): - """Return a zero with the right shape for terminals independent of differentiation variable.""" + """A zero with correct shape for terminals independent of diff. variable.""" return Zero(o.ufl_shape + self._var_shape) def independent_operator(self, o): - """Return a zero with the right shape and indices for operators independent of differentiation variable.""" + """A zero with correct shape and indices for operators independent of diff. variable.""" return Zero(o.ufl_shape + self._var_shape, o.ufl_free_indices, o.ufl_index_dimensions) # --- All derivatives need to define grad and averaging @@ -276,11 +326,13 @@ def power(self, o, fp, gp): if isinstance(gp, Zero): # This probably produces better results for the common # case of f**constant - op = fp * g * f**(g - 1) + op = fp * g * f ** (g - 1) else: # Note: This produces expressions like (1/w)*w**5 instead of w**4 # op = o * (fp * g / f + gp * ln(f)) # This reuses o - op = f**(g - 1) * (g * fp + f * ln(f) * gp) # This gives better accuracy in dolfin integration test + op = f ** (g - 1) * ( + g * fp + f * ln(f) * gp + ) # This gives better accuracy in dolfin integration test # Example: d/dx[x**(x**3)]: # f = x @@ -296,7 +348,7 @@ def power(self, o, fp, gp): def abs(self, o, df): """Differentiate an abs.""" - f, = o.ufl_operands + (f,) = o.ufl_operands # return conditional(eq(f, 0), 0, Product(sign(f), df)) abs is # not complex differentiable, so we workaround the case of a # real F in complex mode by defensively casting to real inside @@ -323,8 +375,8 @@ def math_function(self, o, df): """Differentiate a math_function.""" # FIXME: Introduce a UserOperator type instead of this hack # and define user derivative() function properly - if hasattr(o, 'derivative'): - f, = o.ufl_operands + if hasattr(o, "derivative"): + (f,) = o.ufl_operands return df * o.derivative() raise ValueError("Unknown math function.") @@ -338,57 +390,58 @@ def exp(self, o, fp): def ln(self, o, fp): """Differentiate a ln.""" - f, = o.ufl_operands + (f,) = o.ufl_operands if isinstance(f, Zero): raise ZeroDivisionError() return fp / f def cos(self, o, fp): """Differentiate a cos.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp * -sin(f) def sin(self, o, fp): """Differentiate a sin.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp * cos(f) def tan(self, o, fp): """Differentiate a tan.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return 2.0 * fp / (cos(2.0 * f) + 1.0) def cosh(self, o, fp): """Differentiate a cosh.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp * sinh(f) def sinh(self, o, fp): """Differentiate a sinh.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp * cosh(f) def tanh(self, o, fp): """Differentiate a tanh.""" - f, = o.ufl_operands + (f,) = o.ufl_operands def sech(y): return (2.0 * cosh(y)) / (cosh(2.0 * y) + 1.0) - return fp * sech(f)**2 + + return fp * sech(f) ** 2 def acos(self, o, fp): """Differentiate an acos.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return -fp / sqrt(1.0 - f**2) def asin(self, o, fp): """Differentiate an asin.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp / sqrt(1.0 - f**2) def atan(self, o, fp): """Differentiate an atan.""" - f, = o.ufl_operands + (f,) = o.ufl_operands return fp / (1.0 + f**2) def atan2(self, o, fp, gp): @@ -398,8 +451,8 @@ def atan2(self, o, fp, gp): def erf(self, o, fp): """Differentiate an erf.""" - f, = o.ufl_operands - return fp * (2.0 / sqrt(pi) * exp(-f**2)) + (f,) = o.ufl_operands + return fp * (2.0 / sqrt(pi) * exp(-(f**2))) # --- Bessel functions @@ -407,7 +460,9 @@ def bessel_j(self, o, nup, fp): """Differentiate a bessel_j.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError( + "Differentiation of bessel function w.r.t. nu is not supported." + ) if isinstance(nu, Zero): op = -bessel_J(1, f) @@ -419,7 +474,9 @@ def bessel_y(self, o, nup, fp): """Differentiate a bessel_y.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError( + "Differentiation of bessel function w.r.t. nu is not supported." + ) if isinstance(nu, Zero): op = -bessel_Y(1, f) @@ -431,7 +488,9 @@ def bessel_i(self, o, nup, fp): """Differentiate a bessel_i.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError( + "Differentiation of bessel function w.r.t. nu is not supported." + ) if isinstance(nu, Zero): op = bessel_I(1, f) @@ -443,7 +502,9 @@ def bessel_k(self, o, nup, fp): """Differentiate a bessel_k.""" nu, f = o.ufl_operands if not (nup is None or isinstance(nup, Zero)): - raise NotImplementedError("Differentiation of bessel function w.r.t. nu is not supported.") + raise NotImplementedError( + "Differentiation of bessel function w.r.t. nu is not supported." + ) if isinstance(nu, Zero): op = -bessel_K(1, f) @@ -568,7 +629,8 @@ def base_form_operator(self, o): """Differentiate a base_form_operator.""" # Push the grad through the operator is not legal in most cases: # -> Not enouth regularity for chain rule to hold! - # By the time we evaluate `grad(o)`, the operator `o` will have been assembled and substituted by its output. + # By the time we evaluate `grad(o)`, the operator `o` will have + # been assembled and substituted by its output. return Grad(o) def coefficient(self, o): @@ -609,7 +671,9 @@ def reference_grad(self, o): # grad(o) == grad(rgrad(rv(f))) -> K_ji*rgrad(rgrad(rv(f)))_rj f = o.ufl_operands[0] - valid_operand = f._ufl_is_in_reference_frame_ or isinstance(f, (JacobianInverse, SpatialCoordinate)) + valid_operand = f._ufl_is_in_reference_frame_ or isinstance( + f, (JacobianInverse, SpatialCoordinate) + ) if not valid_operand: raise ValueError("ReferenceGrad can only wrap a reference frame type!") domain = extract_unique_domain(f) @@ -664,10 +728,10 @@ def grad_to_reference_grad(o, K): class ReferenceGradRuleset(GenericDerivativeRuleset): """Apply the reference grad derivative.""" + def __init__(self, topological_dimension): """Initialise.""" - GenericDerivativeRuleset.__init__(self, - var_shape=(topological_dimension,)) + GenericDerivativeRuleset.__init__(self, var_shape=(topological_dimension,)) self._Id = Identity(topological_dimension) # --- Specialized rules for geometric quantities @@ -722,7 +786,9 @@ def argument(self, o): def grad(self, o): """Differentiate a grad.""" - raise ValueError(f"Grad should have been transformed by this point, but got {type(o).__name__}.") + raise ValueError( + f"Grad should have been transformed by this point, but got {type(o).__name__}." + ) def reference_grad(self, o): """Differentiate a reference_grad. @@ -730,8 +796,7 @@ def reference_grad(self, o): Represent ref_grad(ref_grad(f)) as RefGrad(RefGrad(f)). """ # Check that o is a "differential terminal" - if not isinstance(o.ufl_operands[0], - (ReferenceGrad, ReferenceValue, Terminal)): + if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue, Terminal)): raise ValueError("Expecting only grads applied to a terminal.") return ReferenceGrad(o) @@ -741,6 +806,7 @@ def reference_grad(self, o): class VariableRuleset(GenericDerivativeRuleset): """Differentiate with respect to a variable.""" + def __init__(self, var): """Initialise.""" GenericDerivativeRuleset.__init__(self, var_shape=var.ufl_shape) @@ -785,9 +851,6 @@ def _make_identity(self, sh): # Explicitly defining da/dw == 0 argument = GenericDerivativeRuleset.independent_terminal - # def _argument(self, o): - # return AnnotatedZero(o.ufl_shape + self._var_shape, arguments=(o,)) # TODO: Missing this type - def coefficient(self, o): """Differentiate a coefficient. @@ -838,7 +901,8 @@ def reference_value(self, o): # convert to reference frame in the first place raise ValueError( "Missing implementation: To handle derivatives of rv(f) w.r.t. f for " - "mapped elements, rewriting to reference frame should not happen first...") + "mapped elements, rewriting to reference frame should not happen first..." + ) # dv/dv = identity of rank 2*rank(v) return self._Id else: @@ -850,8 +914,7 @@ def reference_grad(self, o): Variable derivative of a gradient of a terminal must be 0. """ - if not isinstance(o.ufl_operands[0], - (ReferenceGrad, ReferenceValue)): + if not isinstance(o.ufl_operands[0], (ReferenceGrad, ReferenceValue)): raise ValueError("Unexpected argument to reference_grad.") return self.independent_terminal(o) @@ -942,7 +1005,10 @@ def coefficient(self, o): if not isinstance(dos, tuple): dos = (dos,) if len(dos) != len(self._v): - raise ValueError("Got a tuple of arguments, expecting a matching tuple of coefficient derivatives.") + raise ValueError( + "Got a tuple of arguments, expecting a " + "matching tuple of coefficient derivatives." + ) dosum = Zero(o.ufl_shape) for do, v in zip(dos, self._v): so, oi = as_scalar(do) @@ -958,7 +1024,9 @@ def coefficient(self, o): def reference_value(self, o): """Differentiate a reference_value.""" - raise NotImplementedError("Currently no support for ReferenceValue in CoefficientDerivative.") + raise NotImplementedError( + "Currently no support for ReferenceValue in CoefficientDerivative." + ) # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need @@ -976,7 +1044,9 @@ def reference_value(self, o): def reference_grad(self, o): """Differentiate a reference_grad.""" - raise NotImplementedError("Currently no support for ReferenceGrad in CoefficientDerivative.") + raise NotImplementedError( + "Currently no support for ReferenceGrad in CoefficientDerivative." + ) # TODO: This is implementable for regular derivative(M(f),f,v) # but too messy if customized coefficient derivative # relations are given by the user. We would only need @@ -995,7 +1065,7 @@ def grad(self, g): ngrads = 0 o = g while isinstance(o, Grad): - o, = o.ufl_operands + (o,) = o.ufl_operands ngrads += 1 # `grad(N)` where N is a BaseFormOperator is treated as if `N` was a Coefficient. if not isinstance(o, (FormArgument, BaseFormOperator)): @@ -1008,7 +1078,7 @@ def apply_grads(f): # Find o among all w without any indexing, which makes this # easy - for (w, v) in zip(self._w, self._v): + for w, v in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) @@ -1048,8 +1118,7 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Accumulate contributions from variations in different # components - for (w, v) in zip(self._w, self._v): - + for w, v in zip(self._w, self._v): # -- Analyse differentiation variable coefficient -- # # Can differentiate a Form wrt a BaseFormOperator @@ -1067,7 +1136,9 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) - gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) + gprimesum = gprimesum + compute_gprimeterm( + ngrads, vval, vcomp, wshape, wcomp + ) else: if wshape != (): @@ -1076,11 +1147,11 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) - gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, - vcomp, wshape, - wcomp) + gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) - elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? + elif isinstance( + w, Indexed + ): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands @@ -1115,14 +1186,15 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): if len(oprimes) != len(self._v): raise ValueError( "Got a tuple of arguments, expecting a" - " matching tuple of coefficient derivatives.") + " matching tuple of coefficient derivatives." + ) # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs # in a 'mixed' space, sum over them all to get the # complete inner product. Using indices to define a # non-compound inner product. - for (oprime, v) in zip(oprimes, self._v): + for oprime, v in zip(oprimes, self._v): raise NotImplementedError("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) @@ -1144,8 +1216,10 @@ def coordinate_derivative(self, o): def base_form_operator(self, o, *dfs): """Differentiate a base_form_operator. - If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a variable => we call the appropriate handler. - Otherwise => differentiation done wrt the BaseFormOperator (dF/dN[Nhat]) => we treat o as a Coefficient. + If d_coeff = 0 => BaseFormOperator's derivative is taken wrt a + variable => we call the appropriate handler. Otherwise => + differentiation done wrt the BaseFormOperator (dF/dN[Nhat]) => + we treat o as a Coefficient. """ d_coeff = self.coefficient(o) # It also handles the non-scalar case @@ -1178,7 +1252,8 @@ def coargument(self, o): def matrix(self, M): """Differentiate a matrix.""" # Matrix rule: D_w[v](M) = v if M == w else 0 - # We can't differentiate wrt a matrix so always return zero in the appropriate space + # We can't differentiate wrt a matrix so always return zero in + # the appropriate space return ZeroBaseForm(M.arguments() + self._v) @@ -1191,22 +1266,31 @@ class BaseFormOperatorDerivativeRuleset(GateauxDerivativeRuleset): def __init__(self, coefficients, arguments, coefficient_derivatives, pending_operations): """Initialise.""" - GateauxDerivativeRuleset.__init__(self, coefficients, arguments, coefficient_derivatives, pending_operations) + GateauxDerivativeRuleset.__init__( + self, coefficients, arguments, coefficient_derivatives, pending_operations + ) def pending_operations_recording(base_form_operator_handler): """Decorate a function to record pending operations.""" + def wrapper(self, base_form_op, *dfs): """Decorate.""" - # Get the outer `BaseFormOperator` expression, i.e. the operator that is being differentiated. + # Get the outer `BaseFormOperator` expression, i.e. the + # operator that is being differentiated. expression = self.pending_operations.expression - # If the base form operator we observe is different from the outer `BaseFormOperator`: - # -> Record that `BaseFormOperator` so that `d(expression)/d(base_form_op)` can then be computed later. + # If the base form operator we observe is different from the + # outer `BaseFormOperator`: + # -> Record that `BaseFormOperator` so that + # `d(expression)/d(base_form_op)` can then be computed + # later. # Else: - # -> Compute the Gateaux derivative of `base_form_ops` by calling the appropriate handler. + # -> Compute the Gateaux derivative of `base_form_ops` by + # calling the appropriate handler. if expression != base_form_op: self.pending_operations += (base_form_op,) return self.coefficient(base_form_op) return base_form_operator_handler(self, base_form_op, *dfs) + return wrapper @pending_operations_recording @@ -1232,16 +1316,22 @@ def external_operator(self, N, *dfs): # # `\sum_{i} dNdOi(..., Oi, ...; DOi(u)[v], ..., v*)` # - # where we differentate wrt u, Oi is the i-th operand, N(..., Oi, ...; ..., v*) an ExternalOperator - # and v the direction (Argument). dNdOi(..., Oi, ...; DOi(u)[v]) is an ExternalOperator - # representing the Gateaux-derivative of N. For example: + # where we differentate wrt u, Oi is the i-th operand, + # N(..., Oi, ...; ..., v*) an ExternalOperator and v the + # direction (Argument). dNdOi(..., Oi, ...; DOi(u)[v]) + # is an ExternalOperator representing the + # Gateaux-derivative of N. For example: # -> From N(u) = u**2, we get `dNdu(u; uhat, v*) = 2 * u * uhat`. new_args = N.argument_slots() + (df,) - extop = N._ufl_expr_reconstruct_(*N.ufl_operands, derivatives=derivatives, argument_slots=new_args) + extop = N._ufl_expr_reconstruct_( + *N.ufl_operands, derivatives=derivatives, argument_slots=new_args + ) elif df == 0: extop = Zero(N.ufl_shape) else: - raise NotImplementedError('Frechet derivative of external operators need to be provided!') + raise NotImplementedError( + "Frechet derivative of external operators need to be provided!" + ) result += (extop,) return sum(result) @@ -1274,49 +1364,47 @@ def grad(self, o, f): """Apply to a grad.""" rules = GradRuleset(o.ufl_shape[-1]) key = (GradRuleset, o.ufl_shape[-1]) - return map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) + return map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key]) def reference_grad(self, o, f): """Apply to a reference_grad.""" rules = ReferenceGradRuleset(o.ufl_shape[-1]) # FIXME: Look over this and test better. key = (ReferenceGradRuleset, o.ufl_shape[-1]) - return map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) + return map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key]) def variable_derivative(self, o, f, dummy_v): """Apply to a variable_derivative.""" op = o.ufl_operands[1] rules = VariableRuleset(op) key = (VariableRuleset, op) - return map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) + return map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key]) def coefficient_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): """Apply to a coefficient_derivative.""" dummy, w, v, cd = o.ufl_operands - pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) + pending_operations = BaseFormOperatorDerivativeRecorder( + f, w, arguments=v, coefficient_derivatives=cd + ) rules = GateauxDerivativeRuleset(w, v, cd, pending_operations) key = (GateauxDerivativeRuleset, w, v, cd) - # We need to go through the dag first to record the pending operations - mapped_expr = map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) - # Need to account for pending operations that have been stored in other integrands + # We need to go through the dag first to record the pending + # operations + mapped_expr = map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key]) + # Need to account for pending operations that have been stored + # in other integrands self.pending_operations += pending_operations return mapped_expr def base_form_operator_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): """Apply to a base_form_operator_derivative.""" dummy, w, v, cd = o.ufl_operands - pending_operations = BaseFormOperatorDerivativeRecorder(f, w, arguments=v, coefficient_derivatives=cd) + pending_operations = BaseFormOperatorDerivativeRecorder( + f, w, arguments=v, coefficient_derivatives=cd + ) rules = BaseFormOperatorDerivativeRuleset(w, v, cd, pending_operations=pending_operations) key = (BaseFormOperatorDerivativeRuleset, w, v, cd) if isinstance(f, ZeroBaseForm): - arg, = v.ufl_operands + (arg,) = v.ufl_operands arguments = f.arguments() # derivative(F, u, du) with `du` a Coefficient # is equivalent to taking the action of the derivative. @@ -1325,9 +1413,7 @@ def base_form_operator_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): arguments += (arg,) return ZeroBaseForm(arguments) # We need to go through the dag first to record the pending operations - mapped_expr = map_expr_dag(rules, f, - vcache=self.vcaches[key], - rcache=self.rcaches[key]) + mapped_expr = map_expr_dag(rules, f, vcache=self.vcaches[key], rcache=self.rcaches[key]) mapped_f = rules.coefficient(f) if mapped_f != 0: @@ -1342,19 +1428,23 @@ def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): """Apply to a coordinate_derivative.""" o_ = o.ufl_operands key = (CoordinateDerivative, o_[0]) - return CoordinateDerivative(map_expr_dag(self, o_[0], - vcache=self.vcaches[key], - rcache=self.rcaches[key]), - o_[1], o_[2], o_[3]) + return CoordinateDerivative( + map_expr_dag(self, o_[0], vcache=self.vcaches[key], rcache=self.rcaches[key]), + o_[1], + o_[2], + o_[3], + ) def base_form_coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd): """Apply to a base_form_coordinate_derivative.""" o_ = o.ufl_operands key = (BaseFormCoordinateDerivative, o_[0]) - return BaseFormCoordinateDerivative(map_expr_dag(self, o_[0], - vcache=self.vcaches[key], - rcache=self.rcaches[key]), - o_[1], o_[2], o_[3]) + return BaseFormCoordinateDerivative( + map_expr_dag(self, o_[0], vcache=self.vcaches[key], rcache=self.rcaches[key]), + o_[1], + o_[2], + o_[3], + ) def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules """Apply to an indexed.""" @@ -1389,15 +1479,18 @@ def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules return op -class BaseFormOperatorDerivativeRecorder(): +class BaseFormOperatorDerivativeRecorder: """A derivative recorded for a base form operator.""" def __init__(self, expression, var, **kwargs): """Initialise.""" base_form_ops = kwargs.pop("base_form_ops", ()) - if kwargs.keys() != {'arguments', 'coefficient_derivatives'}: - raise ValueError("Only `arguments` and `coefficient_derivatives` are allowed as derivative arguments.") + if kwargs.keys() != {"arguments", "coefficient_derivatives"}: + raise ValueError( + "Only `arguments` and `coefficient_derivatives` are " + "allowed as derivative arguments." + ) self.expression = expression self.var = var @@ -1418,18 +1511,23 @@ def __add__(self, other): base_form_ops = self.base_form_ops + other elif isinstance(other, BaseFormOperatorDerivativeRecorder): if self.der_kwargs != other.der_kwargs: - raise ValueError(f"Derivative arguments must match when summing {type(self).__name__} objects.") + raise ValueError( + f"Derivative arguments must match when summing {type(self).__name__} objects." + ) base_form_ops = self.base_form_ops + other.base_form_ops else: - raise NotImplementedError(f"Sum of {type(self)} and {type(other)} objects is not supported.") + raise NotImplementedError( + f"Sum of {type(self)} and {type(other)} objects is not supported." + ) - return BaseFormOperatorDerivativeRecorder(self.expression, self.var, - base_form_ops=base_form_ops, - **self.der_kwargs) + return BaseFormOperatorDerivativeRecorder( + self.expression, self.var, base_form_ops=base_form_ops, **self.der_kwargs + ) def __radd__(self, other): """Add.""" - # Recording order doesn't matter as collected `BaseFormOperator`s are sorted later on. + # Recording order doesn't matter as collected + # `BaseFormOperator`s are sorted later on. return self.__add__(other) def __iadd__(self, other): @@ -1457,8 +1555,10 @@ def apply_derivatives(expression): rules = DerivativeRuleDispatcher() # If we hit a base form operator (bfo), then if `var` is: - # - a BaseFormOperator → Return `d(expression)/dw` where `w` is the coefficient produced by the bfo `var`. - # - else → Record the bfo on the MultiFunction object and returns 0. + # - a BaseFormOperator → Return `d(expression)/dw` where `w` is + # the coefficient produced by the bfo `var`. + # - else → Record the bfo on the MultiFunction object and returns + # - 0. # Example: # → If derivative(F(u, N(u); v), u) was taken the following line would compute `∂F/∂u`. dexpression_dvar = map_integrand_dags(rules, expression) @@ -1474,29 +1574,40 @@ def apply_derivatives(expression): else: dexpression_dvar = () - # Retrieve the base form operators, var, and the argument and coefficient_derivatives for `derivative` + # Retrieve the base form operators, var, and the argument and + # coefficient_derivatives for `derivative` var = pending_operations.var base_form_ops = pending_operations.base_form_ops der_kwargs = pending_operations.der_kwargs for N in sorted(set(base_form_ops), key=lambda x: x.count()): # -- Replace dexpr/dvar by dexpr/dN -- # - # We don't use `apply_derivatives` since the differentiation is done via `\partial` and not `d`. - dexpr_dN = map_integrand_dags(rules, replace_derivative_nodes(expression, {var.ufl_operands[0]: N})) + # We don't use `apply_derivatives` since the differentiation is + # done via `\partial` and not `d`. + dexpr_dN = map_integrand_dags( + rules, replace_derivative_nodes(expression, {var.ufl_operands[0]: N}) + ) # -- Add the BaseFormOperatorDerivative node -- # - var_arg, = der_kwargs['arguments'].ufl_operands - cd = der_kwargs['coefficient_derivatives'] - # Not always the case since `derivative`'s syntax enables one to use a Coefficient as the Gateaux direction + (var_arg,) = der_kwargs["arguments"].ufl_operands + cd = der_kwargs["coefficient_derivatives"] + # Not always the case since `derivative`'s syntax enables one to + # use a Coefficient as the Gateaux direction if isinstance(var_arg, BaseArgument): - # Construct the argument number based on the BaseFormOperator arguments instead of naively - # using `var_arg`. This is critical when BaseFormOperators are used inside 0-forms. + # Construct the argument number based on the + # BaseFormOperator arguments instead of naively using + # `var_arg`. This is critical when BaseFormOperators are + # used inside 0-forms. # # Example: F = 0.5 * u** 2 * dx + 0.5 * N(u; v*)** 2 * dx # -> dFdu[vhat] = + Action(, dNdu(u; v1, v*)) - # with `vhat` a 0-numbered argument, and where `v1` and `vhat` have the same function space but - # a different number. Here, applying `vhat` (`var_arg`) naively would result in `dNdu(u; vhat, v*)`, - # i.e. the 2-forms `dNdu` would have two 0-numbered arguments. Instead we increment the argument number - # of `vhat` to form `v1`. - var_arg = type(var_arg)(var_arg.ufl_function_space(), number=len(N.arguments()), part=var_arg.part()) + # with `vhat` a 0-numbered argument, and where `v1` and + # `vhat` have the same function space but a different + # number. Here, applying `vhat` (`var_arg`) naively would + # result in `dNdu(u; vhat, v*)`, i.e. the 2-forms `dNdu` + # would have two 0-numbered arguments. Instead we increment + # the argument number of `vhat` to form `v1`. + var_arg = type(var_arg)( + var_arg.ufl_function_space(), number=len(N.arguments()), part=var_arg.part() + ) dN_dvar = apply_derivatives(BaseFormOperatorDerivative(N, var, ExprList(var_arg), cd)) # -- Sum the Action: dF/du = ∂F/∂u + \sum_{i=1,...} Action(∂F/∂Ni, dNi/du) -- # if not (isinstance(dexpr_dN, Form) and len(dexpr_dN.integrals()) == 0): @@ -1545,7 +1656,9 @@ def __init__(self, coefficients, arguments, coefficient_derivatives): def coefficient(self, o): """Differentiate a coefficient.""" - raise NotImplementedError("CoordinateDerivative of coefficient in physical space is not implemented.") + raise NotImplementedError( + "CoordinateDerivative of coefficient in physical space is not implemented." + ) def grad(self, o): """Differentiate a grad.""" @@ -1558,8 +1671,10 @@ def spatial_coordinate(self, o): if do is not None: return do else: - raise NotImplementedError("CoordinateDerivative found a SpatialCoordinate that is different " - "from the one being differentiated.") + raise NotImplementedError( + "CoordinateDerivative found a SpatialCoordinate that is different " + "from the one being differentiated." + ) def reference_value(self, o): """Differentiate a reference_value.""" @@ -1575,7 +1690,7 @@ def reference_grad(self, g): o = g ngrads = 0 while isinstance(o, ReferenceGrad): - o, = o.ufl_operands + (o,) = o.ufl_operands ngrads += 1 if not (isinstance(o, SpatialCoordinate) or isinstance(o.ufl_operands[0], FormArgument)): raise ValueError(f"Expecting gradient of a FormArgument, not {ufl_err_str(o)}") @@ -1587,8 +1702,12 @@ def apply_grads(f): # Find o among all w without any indexing, which makes this # easy - for (w, v) in zip(self._w, self._v): - if o == w and isinstance(v, ReferenceValue) and isinstance(v.ufl_operands[0], FormArgument): + for w, v in zip(self._w, self._v): + if ( + o == w + and isinstance(v, ReferenceValue) + and isinstance(v.ufl_operands[0], FormArgument) + ): # Case: d/dt [w + t v] return apply_grads(v) return self.independent_terminal(o) @@ -1596,8 +1715,10 @@ def apply_grads(f): def jacobian(self, o): """Differentiate a jacobian.""" # d (grad_X(x))/d x => grad_X(Argument(x.function_space()) - for (w, v) in zip(self._w, self._v): - if extract_unique_domain(o) == extract_unique_domain(w) and isinstance(v.ufl_operands[0], FormArgument): + for w, v in zip(self._w, self._v): + if extract_unique_domain(o) == extract_unique_domain(w) and isinstance( + v.ufl_operands[0], FormArgument + ): return ReferenceGrad(v) return self.independent_terminal(o) @@ -1636,10 +1757,12 @@ def coefficient_derivative(self, o): def coordinate_derivative(self, o, f, w, v, cd): """Apply to a coordinate_derivative.""" from ufl.algorithms import extract_unique_elements + for space in extract_unique_elements(o): if isinstance(space.pullback, CustomPullback): raise NotImplementedError( - "CoordinateDerivative is not supported for elements with custom pull back.") + "CoordinateDerivative is not supported for elements with custom pull back." + ) _, w, v, cd = o.ufl_operands rules = CoordinateDerivativeRuleset(w, v, cd) diff --git a/ufl/algorithms/apply_function_pullbacks.py b/ufl/algorithms/apply_function_pullbacks.py index 44b0e86ba..124c2a2dd 100644 --- a/ufl/algorithms/apply_function_pullbacks.py +++ b/ufl/algorithms/apply_function_pullbacks.py @@ -1,4 +1,4 @@ -"""Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" +"""Algorithm for replacing gradients in an expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -35,12 +35,15 @@ def form_argument(self, o): if r.ufl_shape != element.reference_value_shape: raise ValueError( - f"Expecting reference space expression with shape '{element.reference_value_shape}', " - f"got '{r.ufl_shape}'") + "Expecting reference space expression with shape " + f"'{element.reference_value_shape}', got '{r.ufl_shape}'" + ) f = element.pullback.apply(r) if f.ufl_shape != space.value_shape: - raise ValueError(f"Expecting pulled back expression with shape '{space.value_shape}', " - f"got '{f.ufl_shape}'") + raise ValueError( + f"Expecting pulled back expression with shape '{space.value_shape}', " + f"got '{f.ufl_shape}'" + ) assert f.ufl_shape == o.ufl_shape return f diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index c1a39d863..4ef00c1c1 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -14,10 +14,31 @@ from functools import reduce from itertools import combinations -from ufl.classes import (CellCoordinate, CellEdgeVectors, CellFacetJacobian, CellOrientation, CellOrigin, CellVertices, - CellVolume, Expr, FacetEdgeVectors, FacetJacobian, FacetJacobianDeterminant, FloatValue, Form, - Integral, Jacobian, JacobianDeterminant, JacobianInverse, MaxCellEdgeLength, - ReferenceCellVolume, ReferenceFacetVolume, ReferenceGrad, ReferenceNormal, SpatialCoordinate) +from ufl.classes import ( + CellCoordinate, + CellEdgeVectors, + CellFacetJacobian, + CellOrientation, + CellOrigin, + CellVertices, + CellVolume, + Expr, + FacetEdgeVectors, + FacetJacobian, + FacetJacobianDeterminant, + FloatValue, + Form, + Integral, + Jacobian, + JacobianDeterminant, + JacobianInverse, + MaxCellEdgeLength, + ReferenceCellVolume, + ReferenceFacetVolume, + ReferenceGrad, + ReferenceNormal, + SpatialCoordinate, +) from ufl.compound_expressions import cross_expr, determinant_expr, inverse_expr from ufl.core.multiindex import Index, indices from ufl.corealg.map_dag import map_expr_dag @@ -30,6 +51,7 @@ class GeometryLoweringApplier(MultiFunction): """Geometry lowering.""" + def __init__(self, preserve_types=()): """Initialise.""" MultiFunction.__init__(self) @@ -180,8 +202,10 @@ def facet_cell_coordinate(self, o): if self._preserve_types[o._ufl_typecode_]: return o - raise ValueError("Missing computation of facet reference coordinates " - "from physical coordinates via mappings.") + raise ValueError( + "Missing computation of facet reference coordinates " + "from physical coordinates via mappings." + ) @memoized_handler def cell_volume(self, o): @@ -256,7 +280,7 @@ def circumradius(self, o): lb = elen[4] * elen[1] lc = elen[5] * elen[0] # p = perimeter - p = (la + lb + lc) + p = la + lb + lc # s = semiperimeter s = p / 2 # area of intermediate triangle with Herons formula @@ -431,7 +455,9 @@ def facet_normal(self, o): r = n if r.ufl_shape != o.ufl_shape: - raise ValueError(f"Inconsistent dimensions (in={o.ufl_shape[0]}, out={r.ufl_shape[0]}).") + raise ValueError( + f"Inconsistent dimensions (in={o.ufl_shape[0]}, out={r.ufl_shape[0]})." + ) return r @@ -445,8 +471,9 @@ def apply_geometry_lowering(form, preserve_types=()): preserve_types: Preserved types """ if isinstance(form, Form): - newintegrals = [apply_geometry_lowering(integral, preserve_types) - for integral in form.integrals()] + newintegrals = [ + apply_geometry_lowering(integral, preserve_types) for integral in form.integrals() + ] return Form(newintegrals) elif isinstance(form, Integral): diff --git a/ufl/algorithms/apply_integral_scaling.py b/ufl/algorithms/apply_integral_scaling.py index 7e5e35ff2..81c6252a1 100644 --- a/ufl/algorithms/apply_integral_scaling.py +++ b/ufl/algorithms/apply_integral_scaling.py @@ -1,4 +1,4 @@ -"""Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" +"""Algorithm for replacing gradients in an expression.""" # Copyright (C) 2013-2016 Martin Sandve Alnæs # @@ -8,7 +8,13 @@ from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree -from ufl.classes import FacetJacobianDeterminant, Form, Integral, JacobianDeterminant, QuadratureWeight +from ufl.classes import ( + FacetJacobianDeterminant, + Form, + Integral, + JacobianDeterminant, + QuadratureWeight, +) from ufl.differentiation import CoordinateDerivative from ufl.measure import custom_integral_types, point_integral_types @@ -52,7 +58,7 @@ def compute_integrand_scaling_factor(integral): # side and quadrature weight detFJ = FacetJacobianDeterminant(domain) degree = estimate_total_polynomial_degree(apply_geometry_lowering(detFJ)) - scale = detFJ('+') * weight + scale = detFJ("+") * weight else: # No need to scale 'integral' over a vertex scale = 1 @@ -77,8 +83,7 @@ def apply_integral_scaling(form): # TODO: Consider adding an in_reference_frame property to Integral # and checking it here and setting it in the returned form if isinstance(form, Form): - newintegrals = [apply_integral_scaling(integral) - for integral in form.integrals()] + newintegrals = [apply_integral_scaling(integral) for integral in form.integrals()] return Form(newintegrals) elif isinstance(form, Integral): @@ -107,9 +112,12 @@ def scale_coordinate_derivative(o, scale): """Scale the coordinate derivative.""" o_ = o.ufl_operands if isinstance(o, CoordinateDerivative): - return CoordinateDerivative(scale_coordinate_derivative(o_[0], scale), o_[1], o_[2], o_[3]) + return CoordinateDerivative( + scale_coordinate_derivative(o_[0], scale), o_[1], o_[2], o_[3] + ) else: return scale * o + newintegrand = scale_coordinate_derivative(integrand, scale) return integral.reconstruct(integrand=newintegrand, metadata=md) diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index 8f788a009..b8c049ab7 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -1,7 +1,7 @@ """Apply restrictions. -This module contains the apply_restrictions algorithm which propagates restrictions in a form -towards the terminals. +This module contains the apply_restrictions algorithm which propagates +restrictions in a form towards the terminals. """ # Copyright (C) 2008-2016 Martin Sandve Alnæs @@ -32,8 +32,7 @@ def __init__(self, side=None): self.vcaches = {"+": {}, "-": {}} self.rcaches = {"+": {}, "-": {}} if self.current_restriction is None: - self._rp = {"+": RestrictionPropagator("+"), - "-": RestrictionPropagator("-")} + self._rp = {"+": RestrictionPropagator("+"), "-": RestrictionPropagator("-")} def restricted(self, o): """When hitting a restricted quantity, visit child with a separate restriction algorithm.""" @@ -43,14 +42,18 @@ def restricted(self, o): raise ValueError("Cannot restrict an expression twice.") # Configure a propagator for this side and apply to subtree side = o.side() - return map_expr_dag(self._rp[side], o.ufl_operands[0], - vcache=self.vcaches[side], - rcache=self.rcaches[side]) + return map_expr_dag( + self._rp[side], o.ufl_operands[0], vcache=self.vcaches[side], rcache=self.rcaches[side] + ) # --- Reusable rules def _ignore_restriction(self, o): - """Ignore current restriction, quantity is independent of side also from a computational point of view.""" + """Ignore current restriction. + + Quantity is independent of side also from a computational point + of view. + """ return o def _require_restriction(self, o): @@ -99,7 +102,7 @@ def variable(self, o, op, label): def reference_value(self, o): """Reference value of something follows same restriction rule as the underlying object.""" - f, = o.ufl_operands + (f,) = o.ufl_operands assert f._ufl_is_terminal_ g = self(f) if isinstance(g, Restricted): @@ -140,8 +143,8 @@ def reference_value(self, o): def coefficient(self, o): """Restrict a coefficient. - Allow coefficients to be unrestricted (apply default if so) if the values are fully continuous - across the facet. + Allow coefficients to be unrestricted (apply default if so) if + the values are fully continuous across the facet. """ if o.ufl_element() in H1: # If the coefficient _value_ is _fully_ continuous @@ -174,11 +177,11 @@ def facet_normal(self, o): def apply_restrictions(expression): """Propagate restriction nodes to wrap differential terminals directly.""" - integral_types = [k for k in integral_type_to_measure_name.keys() - if k.startswith("interior_facet")] + integral_types = [ + k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet") + ] rules = RestrictionPropagator() - return map_integrand_dags(rules, expression, - only_integral_type=integral_types) + return map_integrand_dags(rules, expression, only_integral_type=integral_types) class DefaultRestrictionApplier(MultiFunction): @@ -190,8 +193,7 @@ def __init__(self, side=None): self.current_restriction = side self.default_restriction = "+" if self.current_restriction is None: - self._rp = {"+": DefaultRestrictionApplier("+"), - "-": DefaultRestrictionApplier("-")} + self._rp = {"+": DefaultRestrictionApplier("+"), "-": DefaultRestrictionApplier("-")} def terminal(self, o): """Apply to terminal.""" @@ -241,8 +243,8 @@ def apply_default_restrictions(expression): This applies a default restriction to such terminals if unrestricted. """ - integral_types = [k for k in integral_type_to_measure_name.keys() - if k.startswith("interior_facet")] + integral_types = [ + k for k in integral_type_to_measure_name.keys() if k.startswith("interior_facet") + ] rules = DefaultRestrictionApplier() - return map_integrand_dags(rules, expression, - only_integral_type=integral_types) + return map_integrand_dags(rules, expression, only_integral_type=integral_types) diff --git a/ufl/algorithms/balancing.py b/ufl/algorithms/balancing.py index 477ec3f6f..e671d01d0 100644 --- a/ufl/algorithms/balancing.py +++ b/ufl/algorithms/balancing.py @@ -6,20 +6,31 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.classes import (CellAvg, FacetAvg, Grad, Indexed, NegativeRestricted, PositiveRestricted, ReferenceGrad, - ReferenceValue) +from ufl.classes import ( + CellAvg, + FacetAvg, + Grad, + Indexed, + NegativeRestricted, + PositiveRestricted, + ReferenceGrad, + ReferenceValue, +) from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction modifier_precedence = [ - ReferenceValue, ReferenceGrad, Grad, CellAvg, FacetAvg, PositiveRestricted, - NegativeRestricted, Indexed + ReferenceValue, + ReferenceGrad, + Grad, + CellAvg, + FacetAvg, + PositiveRestricted, + NegativeRestricted, + Indexed, ] -modifier_precedence = { - m._ufl_handler_name_: i - for i, m in enumerate(modifier_precedence) -} +modifier_precedence = {m._ufl_handler_name_: i for i, m in enumerate(modifier_precedence)} def balance_modified_terminal(expr): @@ -44,10 +55,9 @@ def balance_modified_terminal(expr): assert expr._ufl_is_terminal_ # Apply modifiers in order - layers = sorted( - layers[:-1], key=lambda e: modifier_precedence[e._ufl_handler_name_]) + layers = sorted(layers[:-1], key=lambda e: modifier_precedence[e._ufl_handler_name_]) for op in layers: - ops = (expr, ) + op.ufl_operands[1:] + ops = (expr,) + op.ufl_operands[1:] expr = op._ufl_expr_reconstruct_(*ops) # Preserve id if nothing has changed diff --git a/ufl/algorithms/change_to_reference.py b/ufl/algorithms/change_to_reference.py index ed5c22ca2..17b106ad7 100644 --- a/ufl/algorithms/change_to_reference.py +++ b/ufl/algorithms/change_to_reference.py @@ -1,4 +1,4 @@ -"""Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings.""" +"""Algorithm for replacing gradients in an expression.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -18,14 +18,17 @@ """ # Some notes: -# Below, let v_i mean physical coordinate of vertex i and V_i mean the reference cell coordinate of the same vertex. +# Below, let v_i mean physical coordinate of vertex i and V_i mean the +reference cell coordinate of the same vertex. -# Add a type for CellVertices? Note that vertices must be computed in linear cell cases! -triangle_vertices[i,j] = component j of vertex i, following ufc numbering conventions +# Add a type for CellVertices? Note that vertices must be computed in +linear cell cases! triangle_vertices[i,j] = component j of vertex i, +following ufc numbering conventions -# DONE Add a type for CellEdgeLengths? Note that these are only easy to define in the linear cell case! +# DONE Add a type for CellEdgeLengths? Note that these are only easy to +define in the linear cell case! triangle_edge_lengths = [v1v2, v0v2, v0v1] # shape (3,) tetrahedron_edge_lengths = [v0v1, v0v2, v0v3, v1v2, v1v3, v2v3] # shape (6,) @@ -33,7 +36,8 @@ # DONE Here's how to compute edge lengths from the Jacobian: J =[ [dx0/dX0, dx0/dX1], [dx1/dX0, dx1/dX1] ] -# First compute the edge vector, which is constant for each edge: the vector from one vertex to the other +# First compute the edge vector, which is constant for each edge: the +vector from one vertex to the other reference_edge_vector_0 = V2 - V1 # Example! Add a type ReferenceEdgeVectors? # Then apply J to it and take the length of the resulting vector, this is generic for affine cells edge_length_i = || dot(J, reference_edge_vector_i) || @@ -121,18 +125,18 @@ 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 = '' + restricted = "" rv = False while not o._ufl_is_terminal_: if isinstance(o, Grad): - o, = o.ufl_operands + (o,) = o.ufl_operands ngrads += 1 elif isinstance(o, Restricted): restricted = o.side() - o, = o.ufl_operands + (o,) = o.ufl_operands elif isinstance(o, ReferenceValue): rv = True - o, = o.ufl_operands + (o,) = o.ufl_operands else: raise ValueError(f"Invalid type {o._ufl_class_.__name__}") f = o @@ -150,7 +154,9 @@ def grad(self, o): # 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 + 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 @@ -182,7 +188,9 @@ def grad(self, o): 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 + 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) @@ -199,13 +207,16 @@ def reference_grad(self, o): def coefficient_derivative(self, o): """Apply to coefficient_derivative.""" - raise ValueError("Coefficient derivatives should be expanded before applying change to reference grad.") + raise ValueError( + "Coefficient derivatives should be expanded before applying change to reference grad." + ) def change_to_reference_grad(e): """Change Grad objects in expression to products of JacobianInverse and ReferenceGrad. - Assumes the expression is preprocessed or at least that derivatives have been expanded. + Assumes the expression is preprocessed or at least that derivatives + have been expanded. Args: e: An Expr or Form. diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index c93727ad8..12a7e53c1 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -9,6 +9,7 @@ class ArityMismatch(BaseException): """Arity mismatch exception.""" + pass @@ -41,8 +42,10 @@ def nonlinear_operator(self, o): # way we know of: for t in traverse_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: - raise ArityMismatch(f"Applying nonlinear operator {o._ufl_class_.__name__} to " - f"expression depending on form argument {t}.") + raise ArityMismatch( + f"Applying nonlinear operator {o._ufl_class_.__name__} to " + f"expression depending on form argument {t}." + ) return self._et expr = nonlinear_operator @@ -50,7 +53,9 @@ def nonlinear_operator(self, o): def sum(self, o, a, b): """Apply to sum.""" if a != b: - raise ArityMismatch(f"Adding expressions with non-matching form arguments {_afmt(a)} vs {_afmt(b)}.") + raise ArityMismatch( + f"Adding expressions with non-matching form arguments {_afmt(a)} vs {_afmt(b)}." + ) return a def division(self, o, a, b): @@ -67,15 +72,19 @@ def product(self, o, a, b): anumbers = set(x[0].number() for x in a) for x in b: if x[0].number() in anumbers: - raise ArityMismatch("Multiplying expressions with overlapping form argument number " - f"{x[0].number()}, argument is {_afmt(x)}.") + raise ArityMismatch( + "Multiplying expressions with overlapping form argument number " + f"{x[0].number()}, argument is {_afmt(x)}." + ) # Combine argument lists c = tuple(sorted(set(a + b), key=lambda x: (x[0].number(), x[0].part()))) # Check that we don't have any arguments shared between a # and b if len(c) != len(a) + len(b) or len(c) != len({x[0] for x in c}): - raise ArityMismatch("Multiplying expressions with overlapping form arguments " - f"{_afmt(a)} vs {_afmt(b)}.") + raise ArityMismatch( + "Multiplying expressions with overlapping form arguments " + f"{_afmt(a)} vs {_afmt(b)}." + ) # It's fine for argument parts to overlap return c elif a: @@ -139,8 +148,10 @@ def conditional(self, o, c, a, b): else: # Do not allow e.g. conditional(c, test, trial), # conditional(c, test, nonzeroconstant) - raise ArityMismatch("Conditional subexpressions with non-matching form arguments " - f"{_afmt(a)} vs {_afmt(b)}.") + raise ArityMismatch( + "Conditional subexpressions with non-matching form arguments " + f"{_afmt(a)} vs {_afmt(b)}." + ) def linear_indexed_type(self, o, a, i): """Apply to linear_indexed_type.""" @@ -161,8 +172,10 @@ def list_tensor(self, o, *ops): if () in numbers: # Allow e.g. but not numbers.remove(()) if len(numbers) > 1: - raise ArityMismatch("Listtensor components must depend on the same argument numbers, " - f"found {numbers}.") + raise ArityMismatch( + "Listtensor components must depend on the same argument numbers, " + f"found {numbers}." + ) # Allow different parts with the same number return tuple(sorted(args, key=lambda x: (x[0].number(), x[0].part()))) @@ -173,8 +186,7 @@ def list_tensor(self, o, *ops): def check_integrand_arity(expr, arguments, complex_mode=False): """Check the arity of an integrand.""" - arguments = tuple(sorted(set(arguments), - key=lambda x: (x.number(), x.part()))) + arguments = tuple(sorted(set(arguments), key=lambda x: (x.number(), x.part()))) rules = ArityChecker(arguments) arg_tuples = map_expr_dag(rules, expr, compress=False) args = tuple(a[0] for a in arg_tuples) diff --git a/ufl/algorithms/check_restrictions.py b/ufl/algorithms/check_restrictions.py index df75e279e..061935981 100644 --- a/ufl/algorithms/check_restrictions.py +++ b/ufl/algorithms/check_restrictions.py @@ -28,7 +28,7 @@ def restricted(self, o): if self.current_restriction is not None: raise ValueError("Not expecting twice restricted expression.") self.current_restriction = o._side - e, = o.ufl_operands + (e,) = o.ufl_operands self.visit(e) self.current_restriction = None diff --git a/ufl/algorithms/checks.py b/ufl/algorithms/checks.py index 9257b160d..a25ba9ec5 100644 --- a/ufl/algorithms/checks.py +++ b/ufl/algorithms/checks.py @@ -10,11 +10,13 @@ # Modified by Mehdi Nikbakht, 2010. from ufl.algorithms.check_restrictions import check_restrictions + # UFL algorithms from ufl.algorithms.traversal import iter_expressions from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import is_true_ufl_scalar + # UFL classes from ufl.core.expr import ufl_err_str from ufl.corealg.traversal import traverse_unique_terminals @@ -22,7 +24,9 @@ from ufl.form import Form -def validate_form(form): # TODO: Can we make this return a list of errors instead of raising exception? +def validate_form( + form, +): # TODO: Can we make this return a list of errors instead of raising exception? """Performs all implemented validations on a form. Raises exception if something fails.""" errors = [] @@ -37,9 +41,11 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste # errors.append("Form is not multilinear in arguments.") # FIXME DOMAIN: Add check for consistency between domains somehow - domains = set(extract_unique_domain(t) - for e in iter_expressions(form) - for t in traverse_unique_terminals(e)) - {None} + domains = set( + extract_unique_domain(t) + for e in iter_expressions(form) + for t in traverse_unique_terminals(e) + ) - {None} if not domains: errors.append("Missing domain definition in form.") @@ -61,8 +67,7 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste if c in coefficients: g = coefficients[c] if f is not g: - errors.append("Found different Coefficients with " - f"same count: {f} and {g}.") + errors.append(f"Found different Coefficients with same count: {f} and {g}.") else: coefficients[c] = f @@ -101,4 +106,4 @@ def validate_form(form): # TODO: Can we make this return a list of errors inste # TODO: Return errors list instead, need to collect messages from # all validations above first. if errors: - raise ValueError("Found errors in validation of form:\n" + '\n\n'.join(errors)) + raise ValueError("Found errors in validation of form:\n" + "\n\n".join(errors)) diff --git a/ufl/algorithms/comparison_checker.py b/ufl/algorithms/comparison_checker.py index e61aeecf3..509287e74 100644 --- a/ufl/algorithms/comparison_checker.py +++ b/ufl/algorithms/comparison_checker.py @@ -83,19 +83,19 @@ def min_value(self, o, *ops): def real(self, o, *ops): """Apply to real.""" o = self.reuse_if_untouched(o, *ops) - self.nodetype[o] = 'real' + self.nodetype[o] = "real" return o def imag(self, o, *ops): """Apply to imag.""" o = self.reuse_if_untouched(o, *ops) - self.nodetype[o] = 'real' + self.nodetype[o] = "real" return o def sqrt(self, o, *ops): """Apply to sqrt.""" o = self.reuse_if_untouched(o, *ops) - self.nodetype[o] = 'complex' + self.nodetype[o] = "complex" return o def power(self, o, base, exponent): @@ -104,28 +104,28 @@ def power(self, o, base, exponent): try: # Attempt to diagnose circumstances in which the result must be real. exponent = float(exponent) - if self.nodetype[base] == 'real' and int(exponent) == exponent: - self.nodetype[o] = 'real' + if self.nodetype[base] == "real" and int(exponent) == exponent: + self.nodetype[o] = "real" return o except TypeError: pass - self.nodetype[o] = 'complex' + self.nodetype[o] = "complex" return o def abs(self, o, *ops): """Apply to abs.""" o = self.reuse_if_untouched(o, *ops) - self.nodetype[o] = 'real' + self.nodetype[o] = "real" return o def terminal(self, term, *ops): """Apply to terminal.""" # default terminals to complex, except the ones we *know* are real if isinstance(term, (RealValue, Zero, Argument, GeometricQuantity)): - self.nodetype[term] = 'real' + self.nodetype[term] = "real" else: - self.nodetype[term] = 'complex' + self.nodetype[term] = "complex" return term def indexed(self, o, expr, multiindex): diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py index af0ddf68d..687e8edc4 100644 --- a/ufl/algorithms/compute_form_data.py +++ b/ufl/algorithms/compute_form_data.py @@ -1,7 +1,7 @@ """This module provides the compute_form_data function. -Form compilers will typically call compute_form_dataprior to code generation to preprocess/simplify a -raw input form given by a user. +Form compilers will typically call compute_form_dataprior to code +generation to preprocess/simplify a raw input form given by a user. """ # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -14,6 +14,7 @@ from ufl.algorithms.analysis import extract_coefficients, extract_sub_elements, unique_tuple from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering from ufl.algorithms.apply_derivatives import apply_coordinate_derivatives, apply_derivatives + # These are the main symbolic processing steps: from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks from ufl.algorithms.apply_geometry_lowering import apply_geometry_lowering @@ -21,9 +22,13 @@ from ufl.algorithms.apply_restrictions import apply_default_restrictions, apply_restrictions from ufl.algorithms.check_arities import check_form_arity from ufl.algorithms.comparison_checker import do_comparison_check + # See TODOs at the call sites of these below: -from ufl.algorithms.domain_analysis import (build_integral_data, group_form_integrals, - reconstruct_form_from_integral_data) +from ufl.algorithms.domain_analysis import ( + build_integral_data, + group_form_integrals, + reconstruct_form_from_integral_data, +) from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree from ufl.algorithms.formdata import FormData from ufl.algorithms.formtransformations import compute_form_arities @@ -56,8 +61,7 @@ def _compute_element_mapping(form): # worked around to drop this requirement # Extract all elements and include subelements of mixed elements - elements = [obj.ufl_element() for obj in chain(form.arguments(), - form.coefficients())] + elements = [obj.ufl_element() for obj in chain(form.arguments(), form.coefficients())] elements = extract_sub_elements(elements) # Try to find a common degree for elements @@ -66,7 +70,6 @@ def _compute_element_mapping(form): # Compute element map element_mapping = {} for element in elements: - # Flag for whether element needs to be reconstructed reconstruct = False @@ -74,9 +77,10 @@ def _compute_element_mapping(form): cell = element.cell if cell is None: domains = form.ufl_domains() - if not all(domains[0].ufl_cell() == d.ufl_cell() - for d in domains): - raise ValueError("Cannot replace unknown element cell without unique common cell in form.") + if not all(domains[0].ufl_cell() == d.ufl_cell() for d in domains): + raise ValueError( + "Cannot replace unknown element cell without unique common cell in form." + ) cell = domains[0].ufl_cell() reconstruct = True @@ -133,8 +137,7 @@ def _compute_form_data_elements(self, arguments, coefficients, domains): def _check_elements(form_data): """Check elements.""" - for element in chain(form_data.unique_elements, - form_data.unique_sub_elements): + for element in chain(form_data.unique_elements, form_data.unique_sub_elements): if element.cell is None: raise ValueError(f"Found element with undefined cell: {element}") @@ -244,10 +247,15 @@ def preprocess_form(form, complex_mode): def compute_form_data( - form, do_apply_function_pullbacks=False, do_apply_integral_scaling=False, - do_apply_geometry_lowering=False, preserve_geometry_types=(), - do_apply_default_restrictions=True, do_apply_restrictions=True, - do_estimate_degrees=True, do_append_everywhere_integrals=True, + form, + do_apply_function_pullbacks=False, + do_apply_integral_scaling=False, + do_apply_geometry_lowering=False, + preserve_geometry_types=(), + do_apply_default_restrictions=True, + do_apply_restrictions=True, + do_estimate_degrees=True, + do_append_everywhere_integrals=True, complex_mode=False, ): """Compute form data. @@ -273,8 +281,11 @@ def compute_form_data( # TODO: Refactor this, it's rather opaque what this does # TODO: Is self.original_form.ufl_domains() right here? # It will matter when we start including 'num_domains' in ufc form. - form = group_form_integrals(form, self.original_form.ufl_domains(), - do_append_everywhere_integrals=do_append_everywhere_integrals) + form = group_form_integrals( + form, + self.original_form.ufl_domains(), + do_append_everywhere_integrals=do_append_everywhere_integrals, + ) # Estimate polynomial degree of integrands now, before applying # any pullbacks and geometric lowering. Otherwise quad degrees @@ -350,17 +361,18 @@ def compute_form_data( reduced_coefficients_set = set() for itg_data in self.integral_data: reduced_coefficients_set.update(itg_data.integral_coefficients) - self.reduced_coefficients = sorted(reduced_coefficients_set, - key=lambda c: c.count()) + self.reduced_coefficients = sorted(reduced_coefficients_set, key=lambda c: c.count()) self.num_coefficients = len(self.reduced_coefficients) - self.original_coefficient_positions = [i for i, c in enumerate(self.original_form.coefficients()) - if c in self.reduced_coefficients] + self.original_coefficient_positions = [ + i for i, c in enumerate(self.original_form.coefficients()) if c in self.reduced_coefficients + ] # Store back into integral data which form coefficients are used # by each integral for itg_data in self.integral_data: - itg_data.enabled_coefficients = [bool(coeff in itg_data.integral_coefficients) - for coeff in self.reduced_coefficients] + itg_data.enabled_coefficients = [ + bool(coeff in itg_data.integral_coefficients) for coeff in self.reduced_coefficients + ] # --- Collect some trivial data @@ -384,17 +396,19 @@ def compute_form_data( # Mappings from elements and coefficients that reside in form to # objects with canonical numbering as well as completed cells and # elements - renumbered_coefficients, function_replace_map = \ - _build_coefficient_replace_map(self.reduced_coefficients, - self.element_replace_map) + renumbered_coefficients, function_replace_map = _build_coefficient_replace_map( + self.reduced_coefficients, self.element_replace_map + ) self.function_replace_map = function_replace_map # --- Store various lists of elements and sub elements (adds # members to self) - _compute_form_data_elements(self, - self.original_form.arguments(), - renumbered_coefficients, - self.original_form.ufl_domains()) + _compute_form_data_elements( + self, + self.original_form.arguments(), + renumbered_coefficients, + self.original_form.ufl_domains(), + ) # --- Store number of domains for integral types # TODO: Group this by domain first. For now keep a backwards diff --git a/ufl/algorithms/coordinate_derivative_helpers.py b/ufl/algorithms/coordinate_derivative_helpers.py index 77b1c19bf..852b655c2 100644 --- a/ufl/algorithms/coordinate_derivative_helpers.py +++ b/ufl/algorithms/coordinate_derivative_helpers.py @@ -1,4 +1,4 @@ -"""This module provides the necessary tools to strip away and reattach coordinate derivatives. +"""Tools to strip away and reattach coordinate derivatives. This is used in compute_form_data. """ @@ -64,7 +64,10 @@ def strip_coordinate_derivatives(integrals): coordinate_derivatives = [] def take_top_coordinate_derivatives(o): - """Grab all coordinate derivatives and store them, so that we can apply them later again.""" + """Get all coordinate derivatives and store them. + + So we can apply them later again. + """ o_ = o.ufl_operands if isinstance(o, CoordinateDerivative): coordinate_derivatives.append((o_[1], o_[2], o_[3])) diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 3a11b123a..7d7c34042 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -11,7 +11,10 @@ from collections import defaultdict import ufl -from ufl.algorithms.coordinate_derivative_helpers import attach_coordinate_derivatives, strip_coordinate_derivatives +from ufl.algorithms.coordinate_derivative_helpers import ( + attach_coordinate_derivatives, + strip_coordinate_derivatives, +) from ufl.form import Form from ufl.integral import Integral from ufl.protocols import id_or_none @@ -22,17 +25,22 @@ class IntegralData(object): """Utility class. - This class has members (domain, integral_type, subdomain_id, integrals, metadata), - where metadata is an empty dictionary that may be used for - associating metadata with each object. + This class has members (domain, integral_type, subdomain_id, + integrals, metadata), where metadata is an empty dictionary that may + be used for associating metadata with each object. """ - __slots__ = ('domain', 'integral_type', 'subdomain_id', - 'integrals', 'metadata', - 'integral_coefficients', - 'enabled_coefficients') - def __init__(self, domain, integral_type, subdomain_id, integrals, - metadata): + __slots__ = ( + "domain", + "integral_type", + "subdomain_id", + "integrals", + "metadata", + "integral_coefficients", + "enabled_coefficients", + ) + + def __init__(self, domain, integral_type, subdomain_id, integrals, metadata): """Initialise.""" if 1 != len(set(itg.ufl_domain() for itg in integrals)): raise ValueError("Multiple domains mismatch in integral data.") @@ -59,21 +67,27 @@ def __init__(self, domain, integral_type, subdomain_id, integrals, def __lt__(self, other): """Check if self is less than other.""" # To preserve behaviour of extract_integral_data: - return ( - self.integral_type, self.subdomain_id, self.integrals, self.metadata - ) < ( - other.integral_type, other.subdomain_id, other.integrals, other.metadata + return (self.integral_type, self.subdomain_id, self.integrals, self.metadata) < ( + other.integral_type, + other.subdomain_id, + other.integrals, + other.metadata, ) def __eq__(self, other): """Check for equality.""" # Currently only used for tests: - return (self.integral_type == other.integral_type and self.subdomain_id == other.subdomain_id and # noqa: W504 - self.integrals == other.integrals and self.metadata == other.metadata) + return ( + self.integral_type == other.integral_type + and self.subdomain_id == other.subdomain_id + and self.integrals == other.integrals + and self.metadata == other.metadata + ) def __str__(self): """Format as a string.""" - s = f"IntegralData over domain({self.integral_type}, {self.subdomain_id}), with integrals:\n" + s = f"IntegralData over domain({self.integral_type}, {self.subdomain_id})" + s += " with integrals:\n" s += "\n\n".join(map(str, self.integrals)) s += "\nand metadata:\n{metadata}" return s @@ -81,7 +95,8 @@ def __str__(self): class ExprTupleKey(object): """Tuple comparison helper.""" - __slots__ = ('x',) + + __slots__ = ("x",) def __init__(self, x): """Initialise.""" @@ -141,14 +156,14 @@ def integral_subdomain_ids(integral): def rearrange_integrals_by_single_subdomains( - integrals: typing.List[Integral], - do_append_everywhere_integrals: bool + integrals: typing.List[Integral], do_append_everywhere_integrals: bool ) -> typing.Dict[int, typing.List[Integral]]: """Rearrange integrals over multiple subdomains to single subdomain integrals. Args: integrals: List of integrals - do_append_everywhere_integrals: Boolean indicating if integrals defined on the whole domain should + do_append_everywhere_integrals: Boolean indicating if integrals + defined on the whole domain should just be restricted to the set of input subdomain ids. Returns: @@ -188,7 +203,8 @@ def rearrange_integrals_by_single_subdomains( if do_append_everywhere_integrals: for subdomain_id in sorted(single_subdomain_integrals.keys()): single_subdomain_integrals[subdomain_id].append( - ev_itg.reconstruct(subdomain_id=subdomain_id)) + ev_itg.reconstruct(subdomain_id=subdomain_id) + ) if otherwise_integrals: single_subdomain_integrals["otherwise"] = otherwise_integrals @@ -203,8 +219,9 @@ def accumulate_integrands_with_same_metadata(integrals): integrals: a list of integrals Returns: - A list of the form [(integrand0, metadata0), (integrand1, metadata1), ...] - where integrand0 < integrand1 by the canonical ufl expression ordering criteria. + A list of the form [(integrand0, metadata0), (integrand1, + metadata1), ...] where integrand0 < integrand1 by the canonical + ufl expression ordering criteria. """ # Group integrals by compiler data hash by_cdid = {} @@ -256,15 +273,26 @@ def build_integral_data(integrals): subdomain_id = integral.subdomain_id() subdomain_data = id_or_none(integral.subdomain_data()) if subdomain_id == "everywhere": - raise ValueError("'everywhere' not a valid subdomain id. Did you forget to call group_form_integrals?") - unique_integrals[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] += (subdomain_id,) + raise ValueError( + "'everywhere' not a valid subdomain id. " + "Did you forget to call group_form_integrals?" + ) + unique_integrals[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] += ( + subdomain_id, + ) metadata_table[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] = metadata for integral_data, subdomain_ids in unique_integrals.items(): (integral_type, ufl_domain, metadata, integrand, subdomain_data) = integral_data - integral = Integral(integrand, integral_type, ufl_domain, subdomain_ids, - metadata_table[integral_data], subdomain_data) + integral = Integral( + integrand, + integral_type, + ufl_domain, + subdomain_ids, + metadata_table[integral_data], + subdomain_data, + ) # Group for integral data (One integral data object for all # integrals with same domain, itype, (but possibly different metadata). itgs[(ufl_domain, integral_type, subdomain_ids)].append(integral) @@ -274,7 +302,8 @@ def build_integral_data(integrals): def keyfunc(item): (d, itype, sid), integrals = item sid_int = tuple(-1 if i == "otherwise" else i for i in sid) - return (d._ufl_sort_key_(), itype, (type(sid).__name__, ), sid_int) + return (d._ufl_sort_key_(), itype, (type(sid).__name__,), sid_int) + integral_datas = [] for (d, itype, sid), integrals in sorted(itgs.items(), key=keyfunc): integral_datas.append(IntegralData(d, itype, sid, integrals, {})) @@ -287,7 +316,8 @@ def group_form_integrals(form, domains, do_append_everywhere_integrals=True): Args: form: the Form to group the integrals of. domains: an iterable of Domains. - do_append_everywhere_integrals: Boolean indicating if integrals defined on the whole domain should + do_append_everywhere_integrals: Boolean indicating if integrals + defined on the whole domain should just be restricted to the set of input subdomain ids. Returns: @@ -309,18 +339,21 @@ def group_form_integrals(form, domains, do_append_everywhere_integrals=True): # (note: before this call, 'everywhere' is a valid subdomain_id, # and after this call, 'otherwise' is a valid subdomain_id) single_subdomain_integrals = rearrange_integrals_by_single_subdomains( - ddt_integrals, do_append_everywhere_integrals) + ddt_integrals, do_append_everywhere_integrals + ) for subdomain_id, ss_integrals in sorted_by_key(single_subdomain_integrals): - # strip the coordinate derivatives from all integrals # this yields a list of the form [(coordinate derivative, integral), ...] stripped_integrals_and_coordderivs = strip_coordinate_derivatives(ss_integrals) # now group the integrals by the coordinate derivative def calc_hash(cd): - return sum(sum(tuple_elem._ufl_compute_hash_() - for tuple_elem in tuple_) for tuple_ in cd) + return sum( + sum(tuple_elem._ufl_compute_hash_() for tuple_elem in tuple_) + for tuple_ in cd + ) + coordderiv_integrals_dict = {} for integral, coordderiv in stripped_integrals_and_coordderivs: coordderivhash = calc_hash(coordderiv) @@ -335,14 +368,16 @@ def calc_hash(cd): # apply the CoordinateDerivative again for cdhash, samecd_integrals in sorted_by_key(coordderiv_integrals_dict): - # Accumulate integrands of integrals that share the # same compiler data - integrands_and_cds = accumulate_integrands_with_same_metadata(samecd_integrals[1]) + integrands_and_cds = accumulate_integrands_with_same_metadata( + samecd_integrals[1] + ) for integrand, metadata in integrands_and_cds: - integral = Integral(integrand, integral_type, domain, - subdomain_id, metadata, None) + integral = Integral( + integrand, integral_type, domain, subdomain_id, metadata, None + ) integral = attach_coordinate_derivatives(integral, samecd_integrals[0]) integrals.append(integral) return Form(integrals) diff --git a/ufl/algorithms/estimate_degrees.py b/ufl/algorithms/estimate_degrees.py index 334041d3f..da550116c 100644 --- a/ufl/algorithms/estimate_degrees.py +++ b/ufl/algorithms/estimate_degrees.py @@ -46,7 +46,8 @@ def constant(self, v): def geometric_quantity(self, v): """Apply to geometric_quantity. - Some geometric quantities are cellwise constant. Others are nonpolynomial and thus hard to estimate. + Some geometric quantities are cellwise constant. Others are + nonpolynomial and thus hard to estimate. """ if is_cellwise_constant(v): return 0 @@ -74,7 +75,9 @@ def argument(self, v): A form argument provides a degree depending on the element, or the default degree if the element has no degree. """ - return v.ufl_element().embedded_superdegree # FIXME: Use component to improve accuracy for mixed elements + return ( + v.ufl_element().embedded_superdegree + ) # FIXME: Use component to improve accuracy for mixed elements def coefficient(self, v): """Apply to coefficient. @@ -95,7 +98,10 @@ def _reduce_degree(self, v, f): This is used when derivatives are taken. It does not reduce the degree when TensorProduct elements or quadrilateral elements are involved. """ - if isinstance(f, int) and extract_unique_domain(v).ufl_cell().cellname() not in ["quadrilateral", "hexahedron"]: + if isinstance(f, int) and extract_unique_domain(v).ufl_cell().cellname() not in [ + "quadrilateral", + "hexahedron", + ]: return max(f - 1, 0) else: return f @@ -284,7 +290,8 @@ def atan2(self, v, a, b): Using the heuristic: degree(atan2(const,const)) == 0 degree(atan2(a,b)) == max(degree(a),degree(b))+2 - which can be wildly inaccurate but at least gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat + high integration degree. """ if a or b: return self._add_degrees(v, self._max_degrees(v, a, b), 2) @@ -297,7 +304,8 @@ def math_function(self, v, a): Using the heuristic: degree(sin(const)) == 0 degree(sin(a)) == degree(a)+2 - which can be wildly inaccurate but at least gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat + high integration degree. """ if a: return self._add_degrees(v, a, 2) @@ -310,7 +318,8 @@ def bessel_function(self, v, nu, x): Using the heuristic degree(bessel_*(const)) == 0 degree(bessel_*(x)) == degree(x)+2 - which can be wildly inaccurate but at least gives a somewhat high integration degree. + which can be wildly inaccurate but at least gives a somewhat + high integration degree. """ if x: return self._add_degrees(v, x, 2) @@ -337,6 +346,7 @@ def min_value(self, v, a, r): Same as conditional. """ return self._max_degrees(v, a, r) + max_value = min_value def coordinate_derivative(self, v, integrand_degree, b, direction_degree, d): @@ -357,8 +367,7 @@ def expr_mapping(self, v, *o): return self._max_degrees(v, *o) -def estimate_total_polynomial_degree(e, default_degree=1, - element_replace_map={}): +def estimate_total_polynomial_degree(e, default_degree=1, element_replace_map={}): """Estimate total polynomial degree of integrand. NB: Although some compound types are supported here, @@ -366,8 +375,8 @@ def estimate_total_polynomial_degree(e, default_degree=1, prior to degree estimation. In generic code, this algorithm should only be applied after preprocessing. - For coefficients defined on an element with unspecified degree (None), - the degree is set to the given default degree. + For coefficients defined on an element with unspecified degree + (None), the degree is set to the given default degree. """ de = SumDegreeEstimator(default_degree, element_replace_map) if isinstance(e, Form): diff --git a/ufl/algorithms/expand_compounds.py b/ufl/algorithms/expand_compounds.py index 97eecc499..0f2bc9d14 100644 --- a/ufl/algorithms/expand_compounds.py +++ b/ufl/algorithms/expand_compounds.py @@ -1,4 +1,4 @@ -"""Algorithm for expanding compound expressions into equivalent representations using basic operators.""" +"""Algorithm for expanding compound expressions into equivalent representations.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # @@ -15,7 +15,10 @@ def expand_compounds(e): """Expand compounds.""" - warnings.warn("The use of expand_compounds is deprecated and will be removed after December 2023. " - "Please, use apply_algebra_lowering directly instead", FutureWarning) + warnings.warn( + "The use of expand_compounds is deprecated and will be removed after December 2023. " + "Please, use apply_algebra_lowering directly instead", + FutureWarning, + ) return apply_algebra_lowering(e) diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index 1523bd4c4..1ae1cbfc3 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -133,7 +133,7 @@ def index_sum(self, x): """Apply to index_sum.""" ops = [] summand, multiindex = x.ufl_operands - index, = multiindex + (index,) = multiindex # TODO: For the list tensor purging algorithm, do something like: # if index not in self._to_expand: @@ -222,7 +222,7 @@ def list_tensor(self, x): def grad(self, x): """Apply to grad.""" - f, = x.ufl_operands + (f,) = x.ufl_operands if not isinstance(f, (Terminal, Grad)): raise ValueError("Expecting expand_derivatives to have been applied.") # No need to visit child as long as it is on the form [Grad]([Grad](terminal)) diff --git a/ufl/algorithms/formdata.py b/ufl/algorithms/formdata.py index 6f1048aec..f6bdf4c14 100644 --- a/ufl/algorithms/formdata.py +++ b/ufl/algorithms/formdata.py @@ -20,9 +20,11 @@ def __init__(self): def __str__(self): """Return formatted summary of form data.""" types = sorted(self.max_subdomain_ids.keys()) - geometry = (("Geometric dimension", self.geometric_dimension), ) - subdomains = tuple((f"Number of {integral_type} subdomains", self.max_subdomain_ids[integral_type]) - for integral_type in types) + geometry = (("Geometric dimension", self.geometric_dimension),) + subdomains = tuple( + (f"Number of {integral_type} subdomains", self.max_subdomain_ids[integral_type]) + for integral_type in types + ) functions = ( # Arguments ("Rank", self.rank), diff --git a/ufl/algorithms/formfiles.py b/ufl/algorithms/formfiles.py index 7927f3a11..a93383275 100644 --- a/ufl/algorithms/formfiles.py +++ b/ufl/algorithms/formfiles.py @@ -37,8 +37,15 @@ def __init__(self): def __bool__(self): """Convert to a bool.""" - return bool(self.elements or self.coefficients or self.forms or self.expressions or # noqa: W504 - self.object_names or self.object_by_name or self.reserved_objects) + return bool( + self.elements + or self.coefficients + or self.forms + or self.expressions + or self.object_names + or self.object_by_name + or self.reserved_objects + ) __nonzero__ = __bool__ @@ -59,9 +66,9 @@ def match(line): for i in range(min(2, len(lines))): m = match(lines[i]) if m: - encoding, = m.groups() + (encoding,) = m.groups() # Drop encoding line - lines = lines[:i] + lines[i + 1:] + lines = lines[:i] + lines[i + 1 :] break else: # Default to utf-8 (works for ascii files @@ -107,7 +114,9 @@ def interpret_ufl_namespace(namespace): # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value - elif isinstance(value, (AbstractFiniteElement, Coefficient, Constant, Argument, Form, Expr)): + elif isinstance( + value, (AbstractFiniteElement, Coefficient, Constant, Argument, Form, Expr) + ): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name @@ -121,6 +130,7 @@ def interpret_ufl_namespace(namespace): def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None + a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") @@ -149,7 +159,9 @@ def get_form(name): # Validate types if not isinstance(ufd.elements, (list, tuple)): - raise ValueError(f"Expecting 'elements' to be a list or tuple, not '{type(ufd.elements)}''.") + raise ValueError( + f"Expecting 'elements' to be a list or tuple, not '{type(ufd.elements)}''." + ) if not all(isinstance(e, AbstractFiniteElement) for e in ufd.elements): raise ValueError("Expecting 'elements' to be a list of AbstractFiniteElement instances.") @@ -159,7 +171,9 @@ def get_form(name): # Validate types if not isinstance(ufd.coefficients, (list, tuple)): - raise ValueError(f"Expecting 'coefficients' to be a list or tuple, not '{type(ufd.coefficients)}'.") + raise ValueError( + f"Expecting 'coefficients' to be a list or tuple, not '{type(ufd.coefficients)}'." + ) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): raise ValueError("Expecting 'coefficients' to be a list of Coefficient instances.") @@ -168,7 +182,9 @@ def get_form(name): # Validate types if not isinstance(ufd.expressions, (list, tuple)): - raise ValueError(f"Expecting 'expressions' to be a list or tuple, not '{type(ufd.expressions)}'.") + raise ValueError( + f"Expecting 'expressions' to be a list or tuple, not '{type(ufd.expressions)}'." + ) if not all(isinstance(e[0], Expr) for e in ufd.expressions): raise ValueError("Expecting 'expressions' to be a list of Expr instances.") diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index ef9b2bbef..c96a63e99 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -27,11 +27,11 @@ def split(self, form, ix, iy=0): def argument(self, obj): """Apply to argument.""" - if (obj.part() is not None): + if obj.part() is not None: # Mixed element built from MixedFunctionSpace, # whose sub-function spaces are indexed by obj.part() if len(obj.ufl_shape) == 0: - if (obj.part() == self.idx[obj.number()]): + if obj.part() == self.idx[obj.number()]: return obj else: return Zero() @@ -40,7 +40,7 @@ def argument(self, obj): for m in obj.ufl_shape: indices = [(k + (j,)) for k in indices for j in range(m)] - if (obj.part() == self.idx[obj.number()]): + if obj.part() == self.idx[obj.number()]: return as_vector([obj[j] for j in indices]) else: return as_vector([Zero() for j in indices]) @@ -52,7 +52,7 @@ def argument(self, obj): sub_elements = obj.ufl_element().sub_elements() # If not a mixed element, do nothing - if (len(sub_elements) == 0): + if len(sub_elements) == 0: return obj args = [] @@ -64,7 +64,7 @@ def argument(self, obj): for m in a.ufl_shape: indices = [(k + (j,)) for k in indices for j in range(m)] - if (i == self.idx[obj.number()]): + if i == self.idx[obj.number()]: args += [a[j] for j in indices] else: args += [Zero() for j in indices] @@ -90,7 +90,7 @@ def extract_blocks(form, i=None, j=None): assert arity <= 2 if arity == 0: - return (form, ) + return (form,) for pi in parts: if arity > 1: @@ -111,7 +111,7 @@ def extract_blocks(form, i=None, j=None): forms_tuple = tuple(forms) except TypeError: # Only one form returned - forms_tuple = (forms, ) + forms_tuple = (forms,) if i is not None: if arity > 1 and j is not None: diff --git a/ufl/algorithms/formtransformations.py b/ufl/algorithms/formtransformations.py index 58d168c14..20a6a1743 100644 --- a/ufl/algorithms/formtransformations.py +++ b/ufl/algorithms/formtransformations.py @@ -1,4 +1,4 @@ -"""This module defines utilities for transforming complete Forms into new related Forms.""" +"""Utilities for transforming complete Forms into new related Forms.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -14,6 +14,7 @@ from logging import debug from ufl.algebra import Conj + # Other algorithms: from ufl.algorithms.map_integrands import map_integrands from ufl.algorithms.replace import replace @@ -21,6 +22,7 @@ from ufl.argument import Argument from ufl.coefficient import Coefficient from ufl.constantvalue import Zero + # All classes: from ufl.core.expr import ufl_err_str @@ -123,7 +125,6 @@ def sum(self, x): original_terms = x.ufl_operands assert len(original_terms) == 2 for term in original_terms: - # Visit this term in the sum part, term_provides = self.visit(term) @@ -145,8 +146,7 @@ def sum(self, x): # 3. Return the terms that provide the biggest set most_provided = frozenset() - for (provideds, parts) in parts_that_provide.items(): # TODO: Just sort instead? - + for provideds, parts in parts_that_provide.items(): # TODO: Just sort instead? # Throw error if size of sets are equal (and not zero) if len(provideds) == len(most_provided) and len(most_provided): raise ValueError("Don't know what to do with sums with different Arguments.") @@ -158,7 +158,7 @@ def sum(self, x): if len(terms) == 2: x = self.reuse_if_possible(x, *terms) else: - x, = terms + (x,) = terms return (x, most_provided) @@ -172,7 +172,6 @@ def product(self, x, *ops): factors = [] for factor, factor_provides in ops: - # If any factor is zero, return if isinstance(factor, Zero): return (zero_expr(x), set()) @@ -202,7 +201,9 @@ def division(self, x): # Check for Arguments in the denominator if _expr_has_terminal_types(denominator, Argument): - raise ValueError(f"Found Argument in denominator of {ufl_err_str(x)}, this is an invalid expression.") + raise ValueError( + f"Found Argument in denominator of {ufl_err_str(x)}, this is an invalid expression." + ) # Visit numerator numerator_parts, provides = self.visit(numerator) @@ -281,18 +282,20 @@ def list_tensor(self, x, *ops): # Extract the most arguments provided by any of the components most_provides = ops[0][1] - for (component, provides) in ops: - if (provides - most_provides): + for component, provides in ops: + if provides - most_provides: most_provides = provides # Check that all components either provide the same arguments # or vanish. (This check is here b/c it is not obvious what to # return if the components provide different arguments, at # least with the current transformer design.) - for (component, provides) in ops: - if (provides != most_provides and not isinstance(component, Zero)): - raise ValueError("PartExtracter does not know how to handle list_tensors with " - "non-zero components providing fewer arguments") + for component, provides in ops: + if provides != most_provides and not isinstance(component, Zero): + raise ValueError( + "PartExtracter does not know how to handle list_tensors with " + "non-zero components providing fewer arguments" + ) # Return components components = [op[0] for op in ops] @@ -327,6 +330,7 @@ def _transform(e): if provides == sub_arguments: return e return Zero() + return map_integrands(_transform, form) @@ -341,7 +345,6 @@ def compute_form_arities(form): arities = set() for arity in range(len(arguments) + 1): - # Compute parts with arity "arity" parts = compute_form_with_arity(form, arity, arguments) @@ -432,13 +435,17 @@ def compute_energy_norm(form, coefficient): U = u.ufl_function_space() V = v.ufl_function_space() if U != V: - raise ValueError(f"Expecting equal finite elements for test and trial functions, got '{U}' and '{V}'.") + raise ValueError( + f"Expecting equal finite elements for test and trial functions, got '{U}' and '{V}'." + ) if coefficient is None: coefficient = Coefficient(V) else: if coefficient.ufl_function_space() != U: - raise ValueError("Trying to compute action of form on a " - "coefficient in an incompatible element space.") + raise ValueError( + "Trying to compute action of form on a " + "coefficient in an incompatible element space." + ) return action(action(form, coefficient), coefficient) @@ -462,10 +469,8 @@ def compute_form_adjoint(form, reordered_arguments=None): raise ValueError("Mistaken assumption in code!") if reordered_arguments is None: - reordered_u = Argument(u.ufl_function_space(), number=v.number(), - part=v.part()) - reordered_v = Argument(v.ufl_function_space(), number=u.number(), - part=u.part()) + reordered_u = Argument(u.ufl_function_space(), number=v.number(), part=v.part()) + reordered_v = Argument(v.ufl_function_space(), number=u.number(), part=u.part()) else: reordered_u, reordered_v = reordered_arguments diff --git a/ufl/algorithms/map_integrands.py b/ufl/algorithms/map_integrands.py index 55266dfb9..0a6da1817 100644 --- a/ufl/algorithms/map_integrands.py +++ b/ufl/algorithms/map_integrands.py @@ -1,4 +1,4 @@ -"""Basic algorithms for applying functions to subexpressions.""" +"""Basic algorithms for applying functions to sub-expressions.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # @@ -20,12 +20,18 @@ def map_integrands(function, form, only_integral_type=None): - """Apply transform(expression) to each integrand expression in form, or to form if it is an Expr.""" + """Map integrands. + + Apply transform(expression) to each integrand expression in form, or + to form if it is an Expr. + """ if isinstance(form, Form): - mapped_integrals = [map_integrands(function, itg, only_integral_type) - for itg in form.integrals()] - nonzero_integrals = [itg for itg in mapped_integrals - if not isinstance(itg.integrand(), Zero)] + mapped_integrals = [ + map_integrands(function, itg, only_integral_type) for itg in form.integrals() + ] + nonzero_integrals = [ + itg for itg in mapped_integrals if not isinstance(itg.integrand(), Zero) + ] return Form(nonzero_integrals) elif isinstance(form, Integral): itg = form @@ -34,11 +40,16 @@ def map_integrands(function, form, only_integral_type=None): else: return itg elif isinstance(form, FormSum): - mapped_components = [map_integrands(function, component, only_integral_type) - for component in form.components()] - nonzero_components = [(component, w) for component, w in zip(mapped_components, form.weights()) - # Catch ufl.Zero and ZeroBaseForm - if component != 0] + mapped_components = [ + map_integrands(function, component, only_integral_type) + for component in form.components() + ] + nonzero_components = [ + (component, w) + for component, w in zip(mapped_components, form.weights()) + # Catch ufl.Zero and ZeroBaseForm + if component != 0 + ] # Simplify case with one nonzero component and the corresponding weight is 1 if len(nonzero_components) == 1 and nonzero_components[0][1] == 1: @@ -59,7 +70,9 @@ def map_integrands(function, form, only_integral_type=None): # Zeros are caught inside `Action.__new__` return Action(left, right) elif isinstance(form, ZeroBaseForm): - arguments = tuple(map_integrands(function, arg, only_integral_type) for arg in form._arguments) + arguments = tuple( + map_integrands(function, arg, only_integral_type) for arg in form._arguments + ) return ZeroBaseForm(arguments) elif isinstance(form, (Expr, BaseForm)): integrand = form @@ -70,5 +83,6 @@ def map_integrands(function, form, only_integral_type=None): def map_integrand_dags(function, form, only_integral_type=None, compress=True): """Map integrand dags.""" - return map_integrands(lambda expr: map_expr_dag(function, expr, compress), - form, only_integral_type) + return map_integrands( + lambda expr: map_expr_dag(function, expr, compress), form, only_integral_type + ) diff --git a/ufl/algorithms/remove_complex_nodes.py b/ufl/algorithms/remove_complex_nodes.py index 7d97a5731..99b646205 100644 --- a/ufl/algorithms/remove_complex_nodes.py +++ b/ufl/algorithms/remove_complex_nodes.py @@ -1,4 +1,4 @@ -"""Algorithm for removing conj, real, and imag nodes from a form for when the user is in 'real mode'.""" +"""Remove conj, real, and imag nodes from a form.""" from ufl.algorithms.map_integrands import map_integrand_dags from ufl.constantvalue import ComplexValue @@ -7,6 +7,7 @@ class ComplexNodeRemoval(MultiFunction): """Replaces complex operator nodes with their children.""" + expr = MultiFunction.reuse_if_untouched def conj(self, o, a): @@ -24,7 +25,7 @@ def imag(self, o, a): def terminal(self, t, *ops): """Apply to terminal.""" if isinstance(t, ComplexValue): - raise ValueError('Unexpected complex value in real expression.') + raise ValueError("Unexpected complex value in real expression.") else: return t @@ -32,8 +33,8 @@ def terminal(self, t, *ops): def remove_complex_nodes(expr): """Replaces complex operator nodes with their children. - This is called during compute_form_data if the compiler wishes to compile - real-valued forms. In essence this strips all trace of complex - support from the preprocessed form. + This is called during compute_form_data if the compiler wishes to + compile real-valued forms. In essence this strips all trace of + complex support from the preprocessed form. """ return map_integrand_dags(ComplexNodeRemoval(), expr) diff --git a/ufl/algorithms/renumbering.py b/ufl/algorithms/renumbering.py index 303457dd6..87e08203d 100644 --- a/ufl/algorithms/renumbering.py +++ b/ufl/algorithms/renumbering.py @@ -35,7 +35,8 @@ def variable(self, o): class IndexRenumberingTransformer(VariableRenumberingTransformer): """Index renumbering transformer. - This is a poorly designed algorithm. It is used in some tests, please do not use for anything else. + This is a poorly designed algorithm. It is used in some tests, + please do not use for anything else. """ def __init__(self): @@ -79,5 +80,8 @@ def renumber_indices(expr): if isinstance(expr, Expr): if num_free_indices != len(result.ufl_free_indices): - raise ValueError("The number of free indices left in expression should be invariant w.r.t. renumbering.") + raise ValueError( + "The number of free indices left in expression " + "should be invariant w.r.t. renumbering." + ) return result diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index e96ecc577..5f3616949 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -33,7 +33,9 @@ def get_shape(x): return x.ufl_shape if not all(get_shape(k) == get_shape(v) for k, v in mapping.items()): - raise ValueError("Replacement expressions must have the same shape as what they replace.") + raise ValueError( + "Replacement expressions must have the same shape as what they replace." + ) def ufl_type(self, o, *args): """Replace a ufl_type.""" @@ -88,6 +90,7 @@ def replace(e, mapping): if has_exact_type(e, CoefficientDerivative): # Hack to avoid circular dependencies from ufl.algorithms.ad import expand_derivatives + e = expand_derivatives(e) return map_integrand_dags(Replacer(mapping2), e) diff --git a/ufl/algorithms/replace_derivative_nodes.py b/ufl/algorithms/replace_derivative_nodes.py index 9c822fafb..9d373407e 100644 --- a/ufl/algorithms/replace_derivative_nodes.py +++ b/ufl/algorithms/replace_derivative_nodes.py @@ -22,18 +22,20 @@ def __init__(self, mapping, **derivative_kwargs): def coefficient_derivative(self, cd, o, coefficients, arguments, coefficient_derivatives): """Apply to coefficient_derivative.""" der_kwargs = self.der_kwargs - new_coefficients = tuple(self.mapping[c] if c in self.mapping.keys() else c for c in coefficients.ufl_operands) + new_coefficients = tuple( + self.mapping[c] if c in self.mapping.keys() else c for c in coefficients.ufl_operands + ) # Ensure type compatibility for arguments! - if 'argument' not in der_kwargs.keys(): + if "argument" not in der_kwargs.keys(): # Argument's number/part can be retrieved from the former coefficient derivative. arguments = arguments.ufl_operands new_arguments = () for c, a in zip(new_coefficients, arguments): if isinstance(a, ListTensor): - a, = extract_arguments(a) + (a,) = extract_arguments(a) new_arguments += (type(a)(c.ufl_function_space(), a.number(), a.part()),) - der_kwargs.update({'argument': new_arguments}) + der_kwargs.update({"argument": new_arguments}) return ufl.derivative(o, new_coefficients, **der_kwargs) @@ -44,8 +46,9 @@ def replace_derivative_nodes(expr, mapping, **derivative_kwargs): Replaces the variable with respect to which the derivative is taken. This is called during apply_derivatives to treat delayed derivatives. - Example: Let u be a Coefficient, N an ExternalOperator independent of u (i.e. N's operands don't depend on u), - and let uhat and Nhat be Arguments. + Example: Let u be a Coefficient, N an ExternalOperator independent + of u (i.e. N's operands don't depend on u), + and let uhat and Nhat be Arguments. F = u ** 2 * N * dx dFdu = derivative(F, u, uhat) @@ -59,8 +62,8 @@ def replace_derivative_nodes(expr, mapping, **derivative_kwargs): Args: expr: An Expr or BaseForm. mapping: A dict with from:to replacements to perform. - derivative_kwargs: A dict containing the keyword arguments for derivative - (i.e. `argument` and `coefficient_derivatives`). + derivative_kwargs: A dict containing the keyword arguments for + derivative (i.e. `argument` and `coefficient_derivatives`). """ mapping2 = dict((k, as_ufl(v)) for (k, v) in mapping.items()) return map_integrand_dags(DerivativeNodeReplacer(mapping2, **derivative_kwargs), expr) diff --git a/ufl/algorithms/signature.py b/ufl/algorithms/signature.py index 7f9dca8b8..807fa5c00 100644 --- a/ufl/algorithms/signature.py +++ b/ufl/algorithms/signature.py @@ -8,8 +8,18 @@ import hashlib from ufl.algorithms.domain_analysis import canonicalize_metadata -from ufl.classes import (Argument, Coefficient, Constant, ConstantValue, ExprList, ExprMapping, GeometricQuantity, - Index, Label, MultiIndex) +from ufl.classes import ( + Argument, + Coefficient, + Constant, + ConstantValue, + ExprList, + ExprMapping, + GeometricQuantity, + Index, + Label, + MultiIndex, +) from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal @@ -43,7 +53,6 @@ def compute_terminal_hashdata(expressions, renumbering): index_numbering = {} for expression in expressions: for expr in traverse_unique_terminals(expression): - if isinstance(expr, MultiIndex): # Indices need a canonical numbering for a stable # signature, thus this algorithm @@ -131,8 +140,7 @@ def compute_form_signature(form, renumbering): # FIXME: Fix callers hashdata = [] for integral in integrals: # Compute hash data for expression, this is the expensive part - integrand_hashdata = compute_expression_hashdata(integral.integrand(), - terminal_hashdata) + integrand_hashdata = compute_expression_hashdata(integral.integrand(), terminal_hashdata) domain_hashdata = integral.ufl_domain()._ufl_signature_data_(renumbering) diff --git a/ufl/algorithms/strip_terminal_data.py b/ufl/algorithms/strip_terminal_data.py index 4909febe4..a270d3273 100644 --- a/ufl/algorithms/strip_terminal_data.py +++ b/ufl/algorithms/strip_terminal_data.py @@ -4,8 +4,18 @@ """ from ufl.algorithms.replace import replace -from ufl.classes import (Argument, Coefficient, Constant, Form, FunctionSpace, Integral, Mesh, MeshView, - MixedFunctionSpace, TensorProductFunctionSpace) +from ufl.classes import ( + Argument, + Coefficient, + Constant, + Form, + FunctionSpace, + Integral, + Mesh, + MeshView, + MixedFunctionSpace, + TensorProductFunctionSpace, +) from ufl.corealg.map_dag import map_expr_dag from ufl.corealg.multifunction import MultiFunction @@ -20,20 +30,17 @@ def __init__(self): def argument(self, o): """Apply to argument.""" - o_new = Argument(strip_function_space(o.ufl_function_space()), - o.number(), o.part()) + o_new = Argument(strip_function_space(o.ufl_function_space()), o.number(), o.part()) return self.mapping.setdefault(o, o_new) def coefficient(self, o): """Apply to coefficient.""" - o_new = Coefficient(strip_function_space(o.ufl_function_space()), - o.count()) + o_new = Coefficient(strip_function_space(o.ufl_function_space()), o.count()) return self.mapping.setdefault(o, o_new) def constant(self, o): """Apply to constant.""" - o_new = Constant(strip_domain(o.ufl_domain()), o.ufl_shape, - o.count()) + o_new = Constant(strip_domain(o.ufl_domain()), o.ufl_shape, o.count()) return self.mapping.setdefault(o, o_new) expr = MultiFunction.reuse_if_untouched @@ -102,8 +109,9 @@ def replace_terminal_data(o, mapping): def strip_function_space(function_space): """Return a new function space with all non-UFL information removed.""" if isinstance(function_space, FunctionSpace): - return FunctionSpace(strip_domain(function_space.ufl_domain()), - function_space.ufl_element()) + return FunctionSpace( + strip_domain(function_space.ufl_domain()), function_space.ufl_element() + ) elif isinstance(function_space, TensorProductFunctionSpace): subspaces = [strip_function_space(sub) for sub in function_space.ufl_sub_spaces()] return TensorProductFunctionSpace(*subspaces) @@ -119,7 +127,8 @@ def strip_domain(domain): 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()) + 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/algorithms/transformer.py b/ufl/algorithms/transformer.py index e6eed85c3..6a3328c8a 100644 --- a/ufl/algorithms/transformer.py +++ b/ufl/algorithms/transformer.py @@ -34,6 +34,7 @@ class Transformer(object): Base class for a visitor-like algorithm design pattern used to transform expression trees from one representation to another. """ + _handlers_cache = {} def __init__(self, variable_cache=None): @@ -64,17 +65,16 @@ def __init__(self, variable_cache=None): handler_name = UFLType._ufl_handler_name_ function = getattr(self, handler_name, None) if function: - cache_data[ - classobject. - _ufl_typecode_] = handler_name, is_post_handler( - function) + cache_data[classobject._ufl_typecode_] = ( + handler_name, + is_post_handler(function), + ) break Transformer._handlers_cache[type(self)] = cache_data # Build handler list for this particular class (get functions # bound to self) - self._handlers = [(getattr(self, name), post) - for (name, post) in cache_data] + self._handlers = [(getattr(self, name), post) for (name, post) in cache_data] # Keep a stack of objects visit is called on, to ease # backtracking self._visit_stack = [] @@ -236,9 +236,12 @@ def variable(self, o): def apply_transformer(e, transformer, integral_type=None): - """Apply transformer.visit(expression) to each integrand expression in form, or to form if it is an Expr.""" - return map_integrands(lambda expr: transformer.visit(expr), e, - integral_type) + """Apply transforms. + + Apply transformer.visit(expression) to each integrand expression in + form, or to form if it is an Expr. + """ + return map_integrands(lambda expr: transformer.visit(expr), e, integral_type) def strip_variables(e): diff --git a/ufl/algorithms/traversal.py b/ufl/algorithms/traversal.py index 78a0f1b2f..5f6688086 100644 --- a/ufl/algorithms/traversal.py +++ b/ufl/algorithms/traversal.py @@ -16,7 +16,7 @@ def iter_expressions(a): - """Utility function to handle Form, Integral and any Expr the same way when inspecting expressions. + """Handle Form, Integral and any Expr the same way when inspecting expressions. Returns an iterable over Expr instances: - a is an Expr: (a,) diff --git a/ufl/argument.py b/ufl/argument.py index 804615b07..82a23f570 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -29,6 +29,7 @@ # --- Class representing an argument (basis function) in a form --- + class BaseArgument(object): """UFL value: Representation of an argument to a form.""" @@ -94,7 +95,11 @@ def ufl_domains(self): return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): - """Signature data for form arguments depend on the global numbering of the form arguments and domains.""" + """Signature data. + + Signature data for form arguments depend on the global numbering + of the form arguments and domains. + """ fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Argument", self._number, self._part, fsdata) @@ -132,8 +137,10 @@ def __eq__(self, other): are the same ufl element but different dolfin function spaces. """ return ( - type(self) is type(other) and self._number == other._number and # noqa: W504 - self._part == other._part and self._ufl_function_space == other._ufl_function_space + type(self) is type(other) + and self._number == other._number + and self._part == other._part + and self._ufl_function_space == other._ufl_function_space ) @@ -169,7 +176,10 @@ def __init__(self, function_space, number, part=None): BaseArgument.__init__(self, function_space, number, part) self._repr = "Argument(%s, %s, %s)" % ( - repr(self._ufl_function_space), repr(self._number), repr(self._part)) + repr(self._ufl_function_space), + repr(self._number), + repr(self._part), + ) def ufl_domains(self): """Return UFL domains.""" @@ -193,7 +203,7 @@ class Coargument(BaseForm, BaseArgument): "_number", "_part", "_repr", - "_hash" + "_hash", ) _primal = False @@ -202,8 +212,10 @@ class Coargument(BaseForm, BaseArgument): def __new__(cls, *args, **kw): """Create a new Coargument.""" if args[0] and is_primal(args[0]): - raise ValueError("ufl.Coargument takes in a dual space! If you want to define an argument " - "in the primal space you should use ufl.Argument.") + raise ValueError( + "ufl.Coargument takes in a dual space! If you want to define an argument " + "in the primal space you should use ufl.Argument." + ) return super().__new__(cls) def __init__(self, function_space, number, part=None): @@ -214,7 +226,10 @@ def __init__(self, function_space, number, part=None): self.ufl_operands = () self._hash = None self._repr = "Coargument(%s, %s, %s)" % ( - repr(self._ufl_function_space), repr(self._number), repr(self._part)) + repr(self._ufl_function_space), + repr(self._number), + repr(self._part), + ) def arguments(self, outer_form=None): """Return all Argument objects found in form.""" @@ -228,8 +243,9 @@ def _analyze_form_arguments(self, outer_form=None): self._coefficients = () # Coarguments map from V* to V*, i.e. V* -> V*, or equivalently V* x V -> R. # So they have one argument in the primal space and one in the dual space. - # However, when they are composed with linear forms with dual arguments, such as BaseFormOperators, - # the primal argument is discarded when analysing the argument as Coarguments. + # However, when they are composed with linear forms with dual + # arguments, such as BaseFormOperators, the primal argument is + # discarded when analysing the argument as Coarguments. if not outer_form: self._arguments = (Argument(self.ufl_function_space().dual(), 0), self) else: @@ -245,15 +261,16 @@ def equals(self, other): return False if self is other: return True - return (self._ufl_function_space == other._ufl_function_space and # noqa: W504 - self._number == other._number and self._part == other._part) + return ( + self._ufl_function_space == other._ufl_function_space + and self._number == other._number + and self._part == other._part + ) def __hash__(self): """Hash.""" - return hash(("Coargument", - hash(self._ufl_function_space), - self._number, - self._part)) + return hash(("Coargument", hash(self._ufl_function_space), self._number, self._part)) + # --- Helper functions for pretty syntax --- @@ -270,14 +287,17 @@ def TrialFunction(function_space, part=None): # --- Helper functions for creating subfunctions on mixed elements --- + def Arguments(function_space, number): """Create an Argument in a mixed space. Returns a tuple with the function components corresponding to the subelements. """ if isinstance(function_space, MixedFunctionSpace): - return [Argument(function_space.ufl_sub_space(i), number, i) - for i in range(function_space.num_sub_spaces())] + return [ + Argument(function_space.ufl_sub_space(i), number, i) + for i in range(function_space.num_sub_spaces()) + ] else: return split(Argument(function_space, number)) @@ -285,7 +305,8 @@ def Arguments(function_space, number): def TestFunctions(function_space): """Create a TestFunction in a mixed space. - Returns a tuple with the function components corresponding to the subelements. + Returns a tuple with the function components corresponding to the + subelements. """ return Arguments(function_space, 0) @@ -293,6 +314,7 @@ def TestFunctions(function_space): def TrialFunctions(function_space): """Create a TrialFunction in a mixed space. - Returns a tuple with the function components corresponding to the subelements. + Returns a tuple with the function components corresponding to the + subelements. """ return Arguments(function_space, 1) diff --git a/ufl/averaging.py b/ufl/averaging.py index 899cc3ca8..5c04c8912 100644 --- a/ufl/averaging.py +++ b/ufl/averaging.py @@ -11,10 +11,9 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(inherit_shape_from_operand=0, - inherit_indices_from_operand=0, - num_ops=1, - is_evaluation=True) +@ufl_type( + inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1, is_evaluation=True +) class CellAvg(Operator): """Cell average.""" @@ -37,18 +36,16 @@ def ufl_shape(self): def evaluate(self, x, mapping, component, index_values): """Performs an approximate symbolic evaluation, since we don't have a cell.""" - return self.ufl_operands[0].evaluate(x, mapping, component, - index_values) + return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): """Format as a string.""" return f"cell_avg({self.ufl_operands[0]})" -@ufl_type(inherit_shape_from_operand=0, - inherit_indices_from_operand=0, - num_ops=1, - is_evaluation=True) +@ufl_type( + inherit_shape_from_operand=0, inherit_indices_from_operand=0, num_ops=1, is_evaluation=True +) class FacetAvg(Operator): """Facet average.""" diff --git a/ufl/cell.py b/ufl/cell.py index 3eebad7dd..d85c305e5 100644 --- a/ufl/cell.py +++ b/ufl/cell.py @@ -36,7 +36,11 @@ def has_simplex_facets(self) -> bool: @abstractmethod def _lt(self, other) -> bool: - """Define an arbitrarily chosen but fixed sort order for all instances of this type with the same dimensions.""" + """Less than operator. + + Define an arbitrarily chosen but fixed sort order for all + instances of this type with the same dimensions. + """ @abstractmethod def num_sub_entities(self, dim: int) -> int: @@ -181,29 +185,70 @@ def peak_types(self) -> typing.Tuple[AbstractCell, ...]: _sub_entity_celltypes = { - "vertex": [("vertex", )], - "interval": [tuple("vertex" for i in range(2)), ("interval", )], - "triangle": [tuple("vertex" for i in range(3)), tuple("interval" for i in range(3)), ("triangle", )], - "quadrilateral": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(4)), ("quadrilateral", )], - "tetrahedron": [tuple("vertex" for i in range(4)), tuple("interval" for i in range(6)), - tuple("triangle" for i in range(4)), ("tetrahedron", )], - "hexahedron": [tuple("vertex" for i in range(8)), tuple("interval" for i in range(12)), - tuple("quadrilateral" for i in range(6)), ("hexahedron", )], - "prism": [tuple("vertex" for i in range(6)), tuple("interval" for i in range(9)), - ("triangle", "quadrilateral", "quadrilateral", "quadrilateral", "triangle"), ("prism", )], - "pyramid": [tuple("vertex" for i in range(5)), tuple("interval" for i in range(8)), - ("quadrilateral", "triangle", "triangle", "triangle", "triangle"), ("pyramid", )], - "pentatope": [tuple("vertex" for i in range(5)), tuple("interval" for i in range(10)), - tuple("triangle" for i in range(10)), tuple("tetrahedron" for i in range(5)), ("pentatope", )], - "tesseract": [tuple("vertex" for i in range(16)), tuple("interval" for i in range(32)), - tuple("quadrilateral" for i in range(24)), tuple("hexahedron" for i in range(8)), ("tesseract", )], + "vertex": [("vertex",)], + "interval": [tuple("vertex" for i in range(2)), ("interval",)], + "triangle": [ + tuple("vertex" for i in range(3)), + tuple("interval" for i in range(3)), + ("triangle",), + ], + "quadrilateral": [ + tuple("vertex" for i in range(4)), + tuple("interval" for i in range(4)), + ("quadrilateral",), + ], + "tetrahedron": [ + tuple("vertex" for i in range(4)), + tuple("interval" for i in range(6)), + tuple("triangle" for i in range(4)), + ("tetrahedron",), + ], + "hexahedron": [ + tuple("vertex" for i in range(8)), + tuple("interval" for i in range(12)), + tuple("quadrilateral" for i in range(6)), + ("hexahedron",), + ], + "prism": [ + tuple("vertex" for i in range(6)), + tuple("interval" for i in range(9)), + ("triangle", "quadrilateral", "quadrilateral", "quadrilateral", "triangle"), + ("prism",), + ], + "pyramid": [ + tuple("vertex" for i in range(5)), + tuple("interval" for i in range(8)), + ("quadrilateral", "triangle", "triangle", "triangle", "triangle"), + ("pyramid",), + ], + "pentatope": [ + tuple("vertex" for i in range(5)), + tuple("interval" for i in range(10)), + tuple("triangle" for i in range(10)), + tuple("tetrahedron" for i in range(5)), + ("pentatope",), + ], + "tesseract": [ + tuple("vertex" for i in range(16)), + tuple("interval" for i in range(32)), + tuple("quadrilateral" for i in range(24)), + tuple("hexahedron" for i in range(8)), + ("tesseract",), + ], } class Cell(AbstractCell): """Representation of a named finite element cell with known structure.""" - __slots__ = ("_cellname", "_tdim", "_num_cell_entities", "_sub_entity_types", - "_sub_entities", "_sub_entity_types") + + __slots__ = ( + "_cellname", + "_tdim", + "_num_cell_entities", + "_sub_entity_types", + "_sub_entities", + "_sub_entity_types", + ) def __init__(self, cellname: str): """Initialise. @@ -220,11 +265,12 @@ def __init__(self, cellname: str): self._tdim = len(self._sub_entity_celltypes) - 1 self._num_cell_entities = [len(i) for i in self._sub_entity_celltypes] - self._sub_entities = [tuple(Cell(t) for t in se_types) - for se_types in self._sub_entity_celltypes[:-1]] + self._sub_entities = [ + tuple(Cell(t) for t in se_types) for se_types in self._sub_entity_celltypes[:-1] + ] self._sub_entity_types = [tuple(set(i)) for i in self._sub_entities] - self._sub_entities.append((weakref.proxy(self), )) - self._sub_entity_types.append((weakref.proxy(self), )) + self._sub_entities.append((weakref.proxy(self),)) + self._sub_entity_types.append((weakref.proxy(self),)) if not isinstance(self._tdim, numbers.Integral): raise ValueError("Expecting integer topological_dimension.") @@ -285,7 +331,7 @@ def __repr__(self) -> str: def _ufl_hash_data_(self) -> typing.Hashable: """UFL hash data.""" - return (self._cellname, ) + return (self._cellname,) def reconstruct(self, **kwargs: typing.Any) -> Cell: """Reconstruct this cell, overwriting properties by those in kwargs.""" @@ -341,8 +387,9 @@ def num_sub_entities(self, dim: int) -> int: if dim == 0: return functools.reduce(lambda x, y: x * y, [c.num_vertices() for c in self._cells]) if dim == self._tdim - 1: - # Note: This is not the number of facets that the cell has, but I'm leaving it here for now - # to not change past behaviour + # Note: This is not the number of facets that the cell has, + # but I'm leaving it here for now to not change past + # behaviour return sum(c.num_facets() for c in self._cells if c.topological_dimension() > 0) if dim == self._tdim: return 1 diff --git a/ufl/checks.py b/ufl/checks.py index 5b4dc8ce1..09ae4453b 100644 --- a/ufl/checks.py +++ b/ufl/checks.py @@ -27,7 +27,9 @@ def is_ufl_scalar(expression): def is_true_ufl_scalar(expression): """Return True iff expression is scalar-valued, with no free indices.""" - return isinstance(expression, Expr) and not (expression.ufl_shape or expression.ufl_free_indices) + return isinstance(expression, Expr) and not ( + expression.ufl_shape or expression.ufl_free_indices + ) def is_cellwise_constant(expr): diff --git a/ufl/classes.py b/ufl/classes.py index b65c802d4..b8f48516c 100644 --- a/ufl/classes.py +++ b/ufl/classes.py @@ -88,6 +88,7 @@ def populate_namespace_with_expr_classes(namespace): # Semi-automated imports of non-expr classes: + def populate_namespace_with_module_classes(mod, loc): """Export the classes that submodules list in __all_classes__.""" names = mod.__all_classes__ diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 956cd5159..8dbcfdb5a 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -47,8 +47,7 @@ def __init__(self, function_space, count=None): self._ufl_function_space = function_space self._ufl_shape = function_space.value_shape - self._repr = "BaseCoefficient(%s, %s)" % ( - repr(self._ufl_function_space), repr(self._count)) + self._repr = "BaseCoefficient(%s, %s)" % (repr(self._ufl_function_space), repr(self._count)) @property def ufl_shape(self): @@ -76,7 +75,11 @@ def ufl_domains(self): return self._ufl_function_space.ufl_domains() def _ufl_signature_data_(self, renumbering): - """Signature data for form arguments depend on the global numbering of the form arguments and domains.""" + """Signature data. + + Signature data for form arguments depend on the global numbering + of the form arguments and domains. + """ count = renumbering[self] fsdata = self._ufl_function_space._ufl_signature_data_(renumbering) return ("Coefficient", count, fsdata) @@ -111,7 +114,7 @@ class Cofunction(BaseCoefficient, BaseForm): "ufl_operands", "_repr", "_ufl_shape", - "_hash" + "_hash", ) _primal = False _dual = True @@ -121,8 +124,10 @@ class Cofunction(BaseCoefficient, BaseForm): def __new__(cls, *args, **kw): """Create a new Cofunction.""" if args[0] and is_primal(args[0]): - raise ValueError("ufl.Cofunction takes in a dual space. If you want to define a coefficient " - "in the primal space you should use ufl.Coefficient.") + raise ValueError( + "ufl.Cofunction takes in a dual space. If you want to define a coefficient " + "in the primal space you should use ufl.Coefficient." + ) return super().__new__(cls) def __init__(self, function_space, count=None): @@ -132,8 +137,7 @@ def __init__(self, function_space, count=None): self.ufl_operands = () self._hash = None - self._repr = "Cofunction(%s, %s)" % ( - repr(self._ufl_function_space), repr(self._count)) + self._repr = "Cofunction(%s, %s)" % (repr(self._ufl_function_space), repr(self._count)) def equals(self, other): """Check equality.""" @@ -145,9 +149,7 @@ def equals(self, other): def __hash__(self): """Hash.""" - return hash(("Cofunction", - hash(self._ufl_function_space), - self._count)) + return hash(("Cofunction", hash(self._ufl_function_space), self._count)) def _analyze_form_arguments(self): """Analyze which Argument and Coefficient objects can be found in the form.""" @@ -180,8 +182,7 @@ def __init__(self, function_space, count=None): FormArgument.__init__(self) BaseCoefficient.__init__(self, function_space, count) - self._repr = "Coefficient(%s, %s)" % ( - repr(self._ufl_function_space), repr(self._count)) + self._repr = "Coefficient(%s, %s)" % (repr(self._ufl_function_space), repr(self._count)) def ufl_domains(self): """Get the UFL domains.""" @@ -202,13 +203,16 @@ def __repr__(self): # --- Helper functions for subfunctions on mixed elements --- + def Coefficients(function_space): """Create a Coefficient in a mixed space. Returns a tuple with the function components corresponding to the subelements. """ if isinstance(function_space, MixedFunctionSpace): - return [Coefficient(fs) if is_primal(fs) else Cofunction(fs) - for fs in function_space.num_sub_spaces()] + return [ + Coefficient(fs) if is_primal(fs) else Cofunction(fs) + for fs in function_space.num_sub_spaces() + ] else: return split(Coefficient(function_space)) diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 6b9c63402..5eedfdedf 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -1,4 +1,4 @@ -"""Functions implementing compound expressions as equivalent representations using basic operators.""" +"""Support for compound expressions as equivalent representations using basic operators.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg # # This file is part of UFL (https://www.fenicsproject.org) @@ -31,6 +31,7 @@ def cross_expr(a, b): def c(i, j): return a[i] * b[j] - a[j] * b[i] + return as_vector((c(1, 2), c(2, 0), c(0, 1))) @@ -114,9 +115,16 @@ def determinant_expr_2x2(B): def old_determinant_expr_3x3(A): """Determinant of a 3 by 3 matrix.""" - warnings.warn("The use of old_determinant_expr_3x3 is deprecated and will be removed after December 2023. " - "Please, use determinant_expr_3x3 instead", FutureWarning) - return A[0, 0] * _det_2x2(A, 1, 2, 1, 2) + A[0, 1] * _det_2x2(A, 1, 2, 2, 0) + A[0, 2] * _det_2x2(A, 1, 2, 0, 1) + warnings.warn( + "The use of old_determinant_expr_3x3 is deprecated and will be removed " + "after December 2023. Please, use determinant_expr_3x3 instead", + FutureWarning, + ) + return ( + A[0, 0] * _det_2x2(A, 1, 2, 1, 2) + + A[0, 1] * _det_2x2(A, 1, 2, 2, 0) + + A[0, 2] * _det_2x2(A, 1, 2, 0, 1) + ) def determinant_expr_3x3(A): @@ -139,8 +147,8 @@ def codeterminant_expr_nxn(A, rows, cols): r = rows[0] subrows = rows[1:] for i, c in enumerate(cols): - subcols = cols[:i] + cols[i + 1:] - codet += (-1)**i * A[r, c] * codeterminant_expr_nxn(A, subrows, subcols) + subcols = cols[:i] + cols[i + 1 :] + codet += (-1) ** i * A[r, c] * codeterminant_expr_nxn(A, subrows, subcols) return codet @@ -176,50 +184,142 @@ def adj_expr(A): def adj_expr_2x2(A): """Adjoint of a 2 by 2 matrix.""" - return as_matrix([[A[1, 1], -A[0, 1]], - [-A[1, 0], A[0, 0]]]) + return as_matrix([[A[1, 1], -A[0, 1]], [-A[1, 0], A[0, 0]]]) def adj_expr_3x3(A): """Adjoint of a 3 by 3 matrix.""" - return as_matrix([[ - A[2, 2] * A[1, 1] - A[1, 2] * A[2, 1], - -A[0, 1] * A[2, 2] + A[0, 2] * A[2, 1], - A[0, 1] * A[1, 2] - A[0, 2] * A[1, 1], - ], [ - -A[2, 2] * A[1, 0] + A[1, 2] * A[2, 0], - -A[0, 2] * A[2, 0] + A[2, 2] * A[0, 0], - A[0, 2] * A[1, 0] - A[1, 2] * A[0, 0], - ], [ - A[1, 0] * A[2, 1] - A[2, 0] * A[1, 1], - A[0, 1] * A[2, 0] - A[0, 0] * A[2, 1], - A[0, 0] * A[1, 1] - A[0, 1] * A[1, 0], - ]]) + return as_matrix( + [ + [ + A[2, 2] * A[1, 1] - A[1, 2] * A[2, 1], + -A[0, 1] * A[2, 2] + A[0, 2] * A[2, 1], + A[0, 1] * A[1, 2] - A[0, 2] * A[1, 1], + ], + [ + -A[2, 2] * A[1, 0] + A[1, 2] * A[2, 0], + -A[0, 2] * A[2, 0] + A[2, 2] * A[0, 0], + A[0, 2] * A[1, 0] - A[1, 2] * A[0, 0], + ], + [ + A[1, 0] * A[2, 1] - A[2, 0] * A[1, 1], + A[0, 1] * A[2, 0] - A[0, 0] * A[2, 1], + A[0, 0] * A[1, 1] - A[0, 1] * A[1, 0], + ], + ] + ) def adj_expr_4x4(A): """Adjoint of a 4 by 4 matrix.""" - return as_matrix([[ - -A[3, 3] * A[2, 1] * A[1, 2] + A[1, 2] * A[3, 1] * A[2, 3] + A[1, 1] * A[3, 3] * A[2, 2] - A[3, 1] * A[2, 2] * A[1, 3] + A[2, 1] * A[1, 3] * A[3, 2] - A[1, 1] * A[3, 2] * A[2, 3], # noqa: E501 - -A[3, 1] * A[0, 2] * A[2, 3] + A[0, 1] * A[3, 2] * A[2, 3] - A[0, 3] * A[2, 1] * A[3, 2] + A[3, 3] * A[2, 1] * A[0, 2] - A[3, 3] * A[0, 1] * A[2, 2] + A[0, 3] * A[3, 1] * A[2, 2], # noqa: E501 - A[3, 1] * A[1, 3] * A[0, 2] + A[1, 1] * A[0, 3] * A[3, 2] - A[0, 3] * A[1, 2] * A[3, 1] - A[0, 1] * A[1, 3] * A[3, 2] + A[3, 3] * A[1, 2] * A[0, 1] - A[1, 1] * A[3, 3] * A[0, 2], # noqa: E501 - A[1, 1] * A[0, 2] * A[2, 3] - A[2, 1] * A[1, 3] * A[0, 2] + A[0, 3] * A[2, 1] * A[1, 2] - A[1, 2] * A[0, 1] * A[2, 3] - A[1, 1] * A[0, 3] * A[2, 2] + A[0, 1] * A[2, 2] * A[1, 3], # noqa: E501 - ], [ - A[3, 3] * A[1, 2] * A[2, 0] - A[3, 0] * A[1, 2] * A[2, 3] + A[1, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[1, 0] * A[2, 2] - A[1, 3] * A[3, 2] * A[2, 0] + A[3, 0] * A[2, 2] * A[1, 3], # noqa: E501 - A[0, 3] * A[3, 2] * A[2, 0] - A[0, 3] * A[3, 0] * A[2, 2] + A[3, 3] * A[0, 0] * A[2, 2] + A[3, 0] * A[0, 2] * A[2, 3] - A[0, 0] * A[3, 2] * A[2, 3] - A[3, 3] * A[0, 2] * A[2, 0], # noqa: E501 - -A[3, 3] * A[0, 0] * A[1, 2] + A[0, 0] * A[1, 3] * A[3, 2] - A[3, 0] * A[1, 3] * A[0, 2] + A[3, 3] * A[1, 0] * A[0, 2] + A[0, 3] * A[3, 0] * A[1, 2] - A[0, 3] * A[1, 0] * A[3, 2], # noqa: E501 - A[0, 3] * A[1, 0] * A[2, 2] + A[1, 3] * A[0, 2] * A[2, 0] - A[0, 0] * A[2, 2] * A[1, 3] - A[0, 3] * A[1, 2] * A[2, 0] + A[0, 0] * A[1, 2] * A[2, 3] - A[1, 0] * A[0, 2] * A[2, 3], # noqa: E501 - ], [ - A[3, 1] * A[1, 3] * A[2, 0] + A[3, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[3, 0] * A[2, 3] - A[1, 0] * A[3, 1] * A[2, 3] - A[3, 0] * A[2, 1] * A[1, 3] - A[1, 1] * A[3, 3] * A[2, 0], # noqa: E501 - A[3, 3] * A[0, 1] * A[2, 0] - A[3, 3] * A[0, 0] * A[2, 1] - A[0, 3] * A[3, 1] * A[2, 0] - A[3, 0] * A[0, 1] * A[2, 3] + A[0, 0] * A[3, 1] * A[2, 3] + A[0, 3] * A[3, 0] * A[2, 1], # noqa: E501 - -A[0, 0] * A[3, 1] * A[1, 3] + A[0, 3] * A[1, 0] * A[3, 1] - A[3, 3] * A[1, 0] * A[0, 1] + A[1, 1] * A[3, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[3, 0] * A[0, 1] * A[1, 3], # noqa: E501 - A[0, 0] * A[2, 1] * A[1, 3] + A[1, 0] * A[0, 1] * A[2, 3] - A[0, 3] * A[2, 1] * A[1, 0] + A[1, 1] * A[0, 3] * A[2, 0] - A[1, 1] * A[0, 0] * A[2, 3] - A[0, 1] * A[1, 3] * A[2, 0], # noqa: E501 - ], [ - -A[1, 2] * A[3, 1] * A[2, 0] - A[2, 1] * A[1, 0] * A[3, 2] + A[3, 0] * A[2, 1] * A[1, 2] - A[1, 1] * A[3, 0] * A[2, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[1, 1] * A[3, 2] * A[2, 0], # noqa: E501 - -A[3, 0] * A[2, 1] * A[0, 2] - A[0, 1] * A[3, 2] * A[2, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 0] * A[3, 1] * A[2, 2] + A[3, 0] * A[0, 1] * A[2, 2] + A[0, 0] * A[2, 1] * A[3, 2], # noqa: E501 - A[0, 0] * A[1, 2] * A[3, 1] - A[1, 0] * A[3, 1] * A[0, 2] + A[1, 1] * A[3, 0] * A[0, 2] + A[1, 0] * A[0, 1] * A[3, 2] - A[3, 0] * A[1, 2] * A[0, 1] - A[1, 1] * A[0, 0] * A[3, 2], # noqa: E501 - -A[1, 1] * A[0, 2] * A[2, 0] + A[2, 1] * A[1, 0] * A[0, 2] + A[1, 2] * A[0, 1] * A[2, 0] + A[1, 1] * A[0, 0] * A[2, 2] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2], # noqa: E501 - ]]) + return as_matrix( + [ + [ + -A[3, 3] * A[2, 1] * A[1, 2] + + A[1, 2] * A[3, 1] * A[2, 3] + + A[1, 1] * A[3, 3] * A[2, 2] + - A[3, 1] * A[2, 2] * A[1, 3] + + A[2, 1] * A[1, 3] * A[3, 2] + - A[1, 1] * A[3, 2] * A[2, 3], + -A[3, 1] * A[0, 2] * A[2, 3] + + A[0, 1] * A[3, 2] * A[2, 3] + - A[0, 3] * A[2, 1] * A[3, 2] + + A[3, 3] * A[2, 1] * A[0, 2] + - A[3, 3] * A[0, 1] * A[2, 2] + + A[0, 3] * A[3, 1] * A[2, 2], + A[3, 1] * A[1, 3] * A[0, 2] + + A[1, 1] * A[0, 3] * A[3, 2] + - A[0, 3] * A[1, 2] * A[3, 1] + - A[0, 1] * A[1, 3] * A[3, 2] + + A[3, 3] * A[1, 2] * A[0, 1] + - A[1, 1] * A[3, 3] * A[0, 2], + A[1, 1] * A[0, 2] * A[2, 3] + - A[2, 1] * A[1, 3] * A[0, 2] + + A[0, 3] * A[2, 1] * A[1, 2] + - A[1, 2] * A[0, 1] * A[2, 3] + - A[1, 1] * A[0, 3] * A[2, 2] + + A[0, 1] * A[2, 2] * A[1, 3], + ], + [ + A[3, 3] * A[1, 2] * A[2, 0] + - A[3, 0] * A[1, 2] * A[2, 3] + + A[1, 0] * A[3, 2] * A[2, 3] + - A[3, 3] * A[1, 0] * A[2, 2] + - A[1, 3] * A[3, 2] * A[2, 0] + + A[3, 0] * A[2, 2] * A[1, 3], + A[0, 3] * A[3, 2] * A[2, 0] + - A[0, 3] * A[3, 0] * A[2, 2] + + A[3, 3] * A[0, 0] * A[2, 2] + + A[3, 0] * A[0, 2] * A[2, 3] + - A[0, 0] * A[3, 2] * A[2, 3] + - A[3, 3] * A[0, 2] * A[2, 0], + -A[3, 3] * A[0, 0] * A[1, 2] + + A[0, 0] * A[1, 3] * A[3, 2] + - A[3, 0] * A[1, 3] * A[0, 2] + + A[3, 3] * A[1, 0] * A[0, 2] + + A[0, 3] * A[3, 0] * A[1, 2] + - A[0, 3] * A[1, 0] * A[3, 2], + A[0, 3] * A[1, 0] * A[2, 2] + + A[1, 3] * A[0, 2] * A[2, 0] + - A[0, 0] * A[2, 2] * A[1, 3] + - A[0, 3] * A[1, 2] * A[2, 0] + + A[0, 0] * A[1, 2] * A[2, 3] + - A[1, 0] * A[0, 2] * A[2, 3], + ], + [ + A[3, 1] * A[1, 3] * A[2, 0] + + A[3, 3] * A[2, 1] * A[1, 0] + + A[1, 1] * A[3, 0] * A[2, 3] + - A[1, 0] * A[3, 1] * A[2, 3] + - A[3, 0] * A[2, 1] * A[1, 3] + - A[1, 1] * A[3, 3] * A[2, 0], + A[3, 3] * A[0, 1] * A[2, 0] + - A[3, 3] * A[0, 0] * A[2, 1] + - A[0, 3] * A[3, 1] * A[2, 0] + - A[3, 0] * A[0, 1] * A[2, 3] + + A[0, 0] * A[3, 1] * A[2, 3] + + A[0, 3] * A[3, 0] * A[2, 1], + -A[0, 0] * A[3, 1] * A[1, 3] + + A[0, 3] * A[1, 0] * A[3, 1] + - A[3, 3] * A[1, 0] * A[0, 1] + + A[1, 1] * A[3, 3] * A[0, 0] + - A[1, 1] * A[0, 3] * A[3, 0] + + A[3, 0] * A[0, 1] * A[1, 3], + A[0, 0] * A[2, 1] * A[1, 3] + + A[1, 0] * A[0, 1] * A[2, 3] + - A[0, 3] * A[2, 1] * A[1, 0] + + A[1, 1] * A[0, 3] * A[2, 0] + - A[1, 1] * A[0, 0] * A[2, 3] + - A[0, 1] * A[1, 3] * A[2, 0], + ], + [ + -A[1, 2] * A[3, 1] * A[2, 0] + - A[2, 1] * A[1, 0] * A[3, 2] + + A[3, 0] * A[2, 1] * A[1, 2] + - A[1, 1] * A[3, 0] * A[2, 2] + + A[1, 0] * A[3, 1] * A[2, 2] + + A[1, 1] * A[3, 2] * A[2, 0], + -A[3, 0] * A[2, 1] * A[0, 2] + - A[0, 1] * A[3, 2] * A[2, 0] + + A[3, 1] * A[0, 2] * A[2, 0] + - A[0, 0] * A[3, 1] * A[2, 2] + + A[3, 0] * A[0, 1] * A[2, 2] + + A[0, 0] * A[2, 1] * A[3, 2], + A[0, 0] * A[1, 2] * A[3, 1] + - A[1, 0] * A[3, 1] * A[0, 2] + + A[1, 1] * A[3, 0] * A[0, 2] + + A[1, 0] * A[0, 1] * A[3, 2] + - A[3, 0] * A[1, 2] * A[0, 1] + - A[1, 1] * A[0, 0] * A[3, 2], + -A[1, 1] * A[0, 2] * A[2, 0] + + A[2, 1] * A[1, 0] * A[0, 2] + + A[1, 2] * A[0, 1] * A[2, 0] + + A[1, 1] * A[0, 0] * A[2, 2] + - A[1, 0] * A[0, 1] * A[2, 2] + - A[0, 0] * A[2, 1] * A[1, 2], + ], + ] + ) def cofactor_expr(A): @@ -240,50 +340,142 @@ def cofactor_expr(A): def cofactor_expr_2x2(A): """Cofactor of a 2 by 2 matrix.""" - return as_matrix([[A[1, 1], -A[1, 0]], - [-A[0, 1], A[0, 0]]]) + return as_matrix([[A[1, 1], -A[1, 0]], [-A[0, 1], A[0, 0]]]) def cofactor_expr_3x3(A): """Cofactor of a 3 by 3 matrix.""" - return as_matrix([[ - A[1, 1] * A[2, 2] - A[2, 1] * A[1, 2], - A[2, 0] * A[1, 2] - A[1, 0] * A[2, 2], - -A[2, 0] * A[1, 1] + A[1, 0] * A[2, 1], - ], [ - A[2, 1] * A[0, 2] - A[0, 1] * A[2, 2], - A[0, 0] * A[2, 2] - A[2, 0] * A[0, 2], - -A[0, 0] * A[2, 1] + A[2, 0] * A[0, 1], - ], [ - A[0, 1] * A[1, 2] - A[1, 1] * A[0, 2], - A[1, 0] * A[0, 2] - A[0, 0] * A[1, 2], - -A[1, 0] * A[0, 1] + A[0, 0] * A[1, 1], - ]]) + return as_matrix( + [ + [ + A[1, 1] * A[2, 2] - A[2, 1] * A[1, 2], + A[2, 0] * A[1, 2] - A[1, 0] * A[2, 2], + -A[2, 0] * A[1, 1] + A[1, 0] * A[2, 1], + ], + [ + A[2, 1] * A[0, 2] - A[0, 1] * A[2, 2], + A[0, 0] * A[2, 2] - A[2, 0] * A[0, 2], + -A[0, 0] * A[2, 1] + A[2, 0] * A[0, 1], + ], + [ + A[0, 1] * A[1, 2] - A[1, 1] * A[0, 2], + A[1, 0] * A[0, 2] - A[0, 0] * A[1, 2], + -A[1, 0] * A[0, 1] + A[0, 0] * A[1, 1], + ], + ] + ) def cofactor_expr_4x4(A): """Cofactor of a 4 by 4 matrix.""" - return as_matrix([[ - -A[3, 1] * A[2, 2] * A[1, 3] - A[3, 2] * A[2, 3] * A[1, 1] + A[1, 3] * A[3, 2] * A[2, 1] + A[3, 1] * A[2, 3] * A[1, 2] + A[2, 2] * A[1, 1] * A[3, 3] - A[3, 3] * A[2, 1] * A[1, 2], # noqa: E501 - -A[1, 0] * A[2, 2] * A[3, 3] + A[2, 0] * A[3, 3] * A[1, 2] + A[2, 2] * A[1, 3] * A[3, 0] - A[2, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[3, 2] * A[2, 3] - A[1, 3] * A[3, 2] * A[2, 0], # noqa: E501 - A[1, 0] * A[3, 3] * A[2, 1] + A[2, 3] * A[1, 1] * A[3, 0] - A[2, 0] * A[1, 1] * A[3, 3] - A[1, 3] * A[3, 0] * A[2, 1] - A[1, 0] * A[3, 1] * A[2, 3] + A[3, 1] * A[1, 3] * A[2, 0], # noqa: E501 - A[3, 0] * A[2, 1] * A[1, 2] + A[1, 0] * A[3, 1] * A[2, 2] + A[3, 2] * A[2, 0] * A[1, 1] - A[2, 2] * A[1, 1] * A[3, 0] - A[3, 1] * A[2, 0] * A[1, 2] - A[1, 0] * A[3, 2] * A[2, 1], # noqa: E501 - ], [ - A[3, 1] * A[2, 2] * A[0, 3] + A[0, 2] * A[3, 3] * A[2, 1] + A[0, 1] * A[3, 2] * A[2, 3] - A[3, 1] * A[0, 2] * A[2, 3] - A[0, 1] * A[2, 2] * A[3, 3] - A[3, 2] * A[0, 3] * A[2, 1], # noqa: E501 - -A[2, 2] * A[0, 3] * A[3, 0] - A[0, 2] * A[2, 0] * A[3, 3] - A[3, 2] * A[2, 3] * A[0, 0] + A[2, 2] * A[3, 3] * A[0, 0] + A[0, 2] * A[2, 3] * A[3, 0] + A[3, 2] * A[2, 0] * A[0, 3], # noqa: E501 - A[3, 1] * A[2, 3] * A[0, 0] - A[0, 1] * A[2, 3] * A[3, 0] - A[3, 1] * A[2, 0] * A[0, 3] - A[3, 3] * A[0, 0] * A[2, 1] + A[0, 3] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 0] * A[3, 3], # noqa: E501 - A[3, 2] * A[0, 0] * A[2, 1] - A[0, 2] * A[3, 0] * A[2, 1] + A[0, 1] * A[2, 2] * A[3, 0] + A[3, 1] * A[0, 2] * A[2, 0] - A[0, 1] * A[3, 2] * A[2, 0] - A[3, 1] * A[2, 2] * A[0, 0], # noqa: E501 - ], [ - A[3, 1] * A[1, 3] * A[0, 2] - A[0, 2] * A[1, 1] * A[3, 3] - A[3, 1] * A[0, 3] * A[1, 2] + A[3, 2] * A[1, 1] * A[0, 3] + A[0, 1] * A[3, 3] * A[1, 2] - A[0, 1] * A[1, 3] * A[3, 2], # noqa: E501 - A[1, 3] * A[3, 2] * A[0, 0] - A[1, 0] * A[3, 2] * A[0, 3] - A[1, 3] * A[0, 2] * A[3, 0] + A[0, 3] * A[3, 0] * A[1, 2] + A[1, 0] * A[0, 2] * A[3, 3] - A[3, 3] * A[0, 0] * A[1, 2], # noqa: E501 - -A[1, 0] * A[0, 1] * A[3, 3] + A[0, 1] * A[1, 3] * A[3, 0] - A[3, 1] * A[1, 3] * A[0, 0] - A[1, 1] * A[0, 3] * A[3, 0] + A[1, 0] * A[3, 1] * A[0, 3] + A[1, 1] * A[3, 3] * A[0, 0], # noqa: E501 - A[0, 2] * A[1, 1] * A[3, 0] - A[3, 2] * A[1, 1] * A[0, 0] - A[0, 1] * A[3, 0] * A[1, 2] - A[1, 0] * A[3, 1] * A[0, 2] + A[3, 1] * A[0, 0] * A[1, 2] + A[1, 0] * A[0, 1] * A[3, 2], # noqa: E501 - ], [ - A[0, 3] * A[2, 1] * A[1, 2] + A[0, 2] * A[2, 3] * A[1, 1] + A[0, 1] * A[2, 2] * A[1, 3] - A[2, 2] * A[1, 1] * A[0, 3] - A[1, 3] * A[0, 2] * A[2, 1] - A[0, 1] * A[2, 3] * A[1, 2], # noqa: E501 - A[1, 0] * A[2, 2] * A[0, 3] + A[1, 3] * A[0, 2] * A[2, 0] - A[1, 0] * A[0, 2] * A[2, 3] - A[2, 0] * A[0, 3] * A[1, 2] - A[2, 2] * A[1, 3] * A[0, 0] + A[2, 3] * A[0, 0] * A[1, 2], # noqa: E501 - -A[0, 1] * A[1, 3] * A[2, 0] + A[2, 0] * A[1, 1] * A[0, 3] + A[1, 3] * A[0, 0] * A[2, 1] - A[1, 0] * A[0, 3] * A[2, 1] + A[1, 0] * A[0, 1] * A[2, 3] - A[2, 3] * A[1, 1] * A[0, 0], # noqa: E501 - A[1, 0] * A[0, 2] * A[2, 1] - A[0, 2] * A[2, 0] * A[1, 1] + A[0, 1] * A[2, 0] * A[1, 2] + A[2, 2] * A[1, 1] * A[0, 0] - A[1, 0] * A[0, 1] * A[2, 2] - A[0, 0] * A[2, 1] * A[1, 2], # noqa: E501 - ]]) + return as_matrix( + [ + [ + -A[3, 1] * A[2, 2] * A[1, 3] + - A[3, 2] * A[2, 3] * A[1, 1] + + A[1, 3] * A[3, 2] * A[2, 1] + + A[3, 1] * A[2, 3] * A[1, 2] + + A[2, 2] * A[1, 1] * A[3, 3] + - A[3, 3] * A[2, 1] * A[1, 2], + -A[1, 0] * A[2, 2] * A[3, 3] + + A[2, 0] * A[3, 3] * A[1, 2] + + A[2, 2] * A[1, 3] * A[3, 0] + - A[2, 3] * A[3, 0] * A[1, 2] + + A[1, 0] * A[3, 2] * A[2, 3] + - A[1, 3] * A[3, 2] * A[2, 0], + A[1, 0] * A[3, 3] * A[2, 1] + + A[2, 3] * A[1, 1] * A[3, 0] + - A[2, 0] * A[1, 1] * A[3, 3] + - A[1, 3] * A[3, 0] * A[2, 1] + - A[1, 0] * A[3, 1] * A[2, 3] + + A[3, 1] * A[1, 3] * A[2, 0], + A[3, 0] * A[2, 1] * A[1, 2] + + A[1, 0] * A[3, 1] * A[2, 2] + + A[3, 2] * A[2, 0] * A[1, 1] + - A[2, 2] * A[1, 1] * A[3, 0] + - A[3, 1] * A[2, 0] * A[1, 2] + - A[1, 0] * A[3, 2] * A[2, 1], + ], + [ + A[3, 1] * A[2, 2] * A[0, 3] + + A[0, 2] * A[3, 3] * A[2, 1] + + A[0, 1] * A[3, 2] * A[2, 3] + - A[3, 1] * A[0, 2] * A[2, 3] + - A[0, 1] * A[2, 2] * A[3, 3] + - A[3, 2] * A[0, 3] * A[2, 1], + -A[2, 2] * A[0, 3] * A[3, 0] + - A[0, 2] * A[2, 0] * A[3, 3] + - A[3, 2] * A[2, 3] * A[0, 0] + + A[2, 2] * A[3, 3] * A[0, 0] + + A[0, 2] * A[2, 3] * A[3, 0] + + A[3, 2] * A[2, 0] * A[0, 3], + A[3, 1] * A[2, 3] * A[0, 0] + - A[0, 1] * A[2, 3] * A[3, 0] + - A[3, 1] * A[2, 0] * A[0, 3] + - A[3, 3] * A[0, 0] * A[2, 1] + + A[0, 3] * A[3, 0] * A[2, 1] + + A[0, 1] * A[2, 0] * A[3, 3], + A[3, 2] * A[0, 0] * A[2, 1] + - A[0, 2] * A[3, 0] * A[2, 1] + + A[0, 1] * A[2, 2] * A[3, 0] + + A[3, 1] * A[0, 2] * A[2, 0] + - A[0, 1] * A[3, 2] * A[2, 0] + - A[3, 1] * A[2, 2] * A[0, 0], + ], + [ + A[3, 1] * A[1, 3] * A[0, 2] + - A[0, 2] * A[1, 1] * A[3, 3] + - A[3, 1] * A[0, 3] * A[1, 2] + + A[3, 2] * A[1, 1] * A[0, 3] + + A[0, 1] * A[3, 3] * A[1, 2] + - A[0, 1] * A[1, 3] * A[3, 2], + A[1, 3] * A[3, 2] * A[0, 0] + - A[1, 0] * A[3, 2] * A[0, 3] + - A[1, 3] * A[0, 2] * A[3, 0] + + A[0, 3] * A[3, 0] * A[1, 2] + + A[1, 0] * A[0, 2] * A[3, 3] + - A[3, 3] * A[0, 0] * A[1, 2], + -A[1, 0] * A[0, 1] * A[3, 3] + + A[0, 1] * A[1, 3] * A[3, 0] + - A[3, 1] * A[1, 3] * A[0, 0] + - A[1, 1] * A[0, 3] * A[3, 0] + + A[1, 0] * A[3, 1] * A[0, 3] + + A[1, 1] * A[3, 3] * A[0, 0], + A[0, 2] * A[1, 1] * A[3, 0] + - A[3, 2] * A[1, 1] * A[0, 0] + - A[0, 1] * A[3, 0] * A[1, 2] + - A[1, 0] * A[3, 1] * A[0, 2] + + A[3, 1] * A[0, 0] * A[1, 2] + + A[1, 0] * A[0, 1] * A[3, 2], + ], + [ + A[0, 3] * A[2, 1] * A[1, 2] + + A[0, 2] * A[2, 3] * A[1, 1] + + A[0, 1] * A[2, 2] * A[1, 3] + - A[2, 2] * A[1, 1] * A[0, 3] + - A[1, 3] * A[0, 2] * A[2, 1] + - A[0, 1] * A[2, 3] * A[1, 2], + A[1, 0] * A[2, 2] * A[0, 3] + + A[1, 3] * A[0, 2] * A[2, 0] + - A[1, 0] * A[0, 2] * A[2, 3] + - A[2, 0] * A[0, 3] * A[1, 2] + - A[2, 2] * A[1, 3] * A[0, 0] + + A[2, 3] * A[0, 0] * A[1, 2], + -A[0, 1] * A[1, 3] * A[2, 0] + + A[2, 0] * A[1, 1] * A[0, 3] + + A[1, 3] * A[0, 0] * A[2, 1] + - A[1, 0] * A[0, 3] * A[2, 1] + + A[1, 0] * A[0, 1] * A[2, 3] + - A[2, 3] * A[1, 1] * A[0, 0], + A[1, 0] * A[0, 2] * A[2, 1] + - A[0, 2] * A[2, 0] * A[1, 1] + + A[0, 1] * A[2, 0] * A[1, 2] + + A[2, 2] * A[1, 1] * A[0, 0] + - A[1, 0] * A[0, 1] * A[2, 2] + - A[0, 0] * A[2, 1] * A[1, 2], + ], + ] + ) def deviatoric_expr(A): @@ -302,12 +494,20 @@ def deviatoric_expr(A): def deviatoric_expr_2x2(A): """Deviatoric of a 2 by 2 matrix.""" - return as_matrix([[-1. / 2 * A[1, 1] + 1. / 2 * A[0, 0], A[0, 1]], - [A[1, 0], 1. / 2 * A[1, 1] - 1. / 2 * A[0, 0]]]) + return as_matrix( + [ + [-1.0 / 2 * A[1, 1] + 1.0 / 2 * A[0, 0], A[0, 1]], + [A[1, 0], 1.0 / 2 * A[1, 1] - 1.0 / 2 * A[0, 0]], + ] + ) def deviatoric_expr_3x3(A): """Deviatoric of a 3 by 3 matrix.""" - return as_matrix([[-1. / 3 * A[1, 1] - 1. / 3 * A[2, 2] + 2. / 3 * A[0, 0], A[0, 1], A[0, 2]], - [A[1, 0], 2. / 3 * A[1, 1] - 1. / 3 * A[2, 2] - 1. / 3 * A[0, 0], A[1, 2]], - [A[2, 0], A[2, 1], -1. / 3 * A[1, 1] + 2. / 3 * A[2, 2] - 1. / 3 * A[0, 0]]]) + return as_matrix( + [ + [-1.0 / 3 * A[1, 1] - 1.0 / 3 * A[2, 2] + 2.0 / 3 * A[0, 0], A[0, 1], A[0, 2]], + [A[1, 0], 2.0 / 3 * A[1, 1] - 1.0 / 3 * A[2, 2] - 1.0 / 3 * A[0, 0], A[1, 2]], + [A[2, 0], A[2, 1], -1.0 / 3 * A[1, 1] + 2.0 / 3 * A[2, 2] - 1.0 / 3 * A[0, 0]], + ] + ) diff --git a/ufl/conditional.py b/ufl/conditional.py index 5d0fb910d..117808c2b 100644 --- a/ufl/conditional.py +++ b/ufl/conditional.py @@ -43,7 +43,7 @@ def __bool__(self): class BinaryCondition(Condition): """Binary condition.""" - __slots__ = ('_name',) + __slots__ = ("_name",) def __init__(self, name, left, right): """Initialise.""" @@ -54,13 +54,13 @@ def __init__(self, name, left, right): self._name = name - if name in ('!=', '=='): + if name in ("!=", "=="): # Since equals and not-equals are used for comparing # representations, we have to allow any shape here. The # scalar properties must be checked when used in # conditional instead! pass - elif name in ('&&', '||'): + elif name in ("&&", "||"): # Binary operators acting on boolean expressions allow # only conditions for arg in (left, right): @@ -76,8 +76,11 @@ def __init__(self, name, left, right): def __str__(self): """Format as a string.""" - return "%s %s %s" % (parstr(self.ufl_operands[0], self), - self._name, parstr(self.ufl_operands[1], self)) + return "%s %s %s" % ( + parstr(self.ufl_operands[0], self), + self._name, + parstr(self.ufl_operands[1], self), + ) # Not associating with __eq__, the concept of equality with == is @@ -254,8 +257,7 @@ def __str__(self): return "!(%s)" % (str(self.ufl_operands[0]),) -@ufl_type(num_ops=3, inherit_shape_from_operand=1, - inherit_indices_from_operand=1) +@ufl_type(num_ops=3, inherit_shape_from_operand=1, inherit_indices_from_operand=1) class Conditional(Operator): """Conditional expression. @@ -279,10 +281,14 @@ def __init__(self, condition, true_value, false_value): if tfi != ffi: raise ValueError("Free index mismatch between conditional branches.") if isinstance(condition, (EQ, NE)): - if not all((condition.ufl_operands[0].ufl_shape == (), - condition.ufl_operands[0].ufl_free_indices == (), - condition.ufl_operands[1].ufl_shape == (), - condition.ufl_operands[1].ufl_free_indices == ())): + if not all( + ( + condition.ufl_operands[0].ufl_shape == (), + condition.ufl_operands[0].ufl_free_indices == (), + condition.ufl_operands[1].ufl_shape == (), + condition.ufl_operands[1].ufl_free_indices == (), + ) + ): raise ValueError("Non-scalar == or != is not allowed.") Operator.__init__(self, (condition, true_value, false_value)) @@ -303,6 +309,7 @@ def __str__(self): # --- Specific functions higher level than a conditional --- + @ufl_type(is_scalar=True, num_ops=1) class MinValue(Operator): """Take the minimum of two values.""" @@ -323,7 +330,7 @@ def evaluate(self, x, mapping, component, index_values): try: res = min(a, b) except ValueError: - warnings.warn('Value error in evaluation of min() of %s and %s.' % self.ufl_operands) + warnings.warn("Value error in evaluation of min() of %s and %s." % self.ufl_operands) raise return res @@ -352,7 +359,7 @@ def evaluate(self, x, mapping, component, index_values): try: res = max(a, b) except ValueError: - warnings.warn('Value error in evaluation of max() of %s and %s.' % self.ufl_operands) + warnings.warn("Value error in evaluation of max() of %s and %s." % self.ufl_operands) raise return res diff --git a/ufl/constant.py b/ufl/constant.py index 74eb81932..e9d658fcc 100644 --- a/ufl/constant.py +++ b/ufl/constant.py @@ -1,4 +1,4 @@ -"""This module defines classes representing non-literal values which are constant with respect to a domain.""" +"""Support fpr non-literal values which are constant with respect to a domain.""" # Copyright (C) 2019 Michal Habera # @@ -29,7 +29,8 @@ def __init__(self, domain, shape=(), count=None): # Repr string is build in such way, that reconstruction # with eval() is possible self._repr = "Constant({}, {}, {})".format( - repr(self._ufl_domain), repr(self._ufl_shape), repr(self._count)) + repr(self._ufl_domain), repr(self._ufl_shape), repr(self._count) + ) @property def ufl_shape(self): @@ -42,7 +43,7 @@ def ufl_domain(self): def ufl_domains(self): """Get the UFL domains.""" - return (self.ufl_domain(), ) + return (self.ufl_domain(),) def is_cellwise_constant(self): """Return True if the function is cellwise constant.""" @@ -62,23 +63,30 @@ def __eq__(self, other): return False if self is other: return True - return (self._count == other._count and self._ufl_domain == other._ufl_domain and # noqa: W504 - self._ufl_shape == self._ufl_shape) + return ( + self._count == other._count + and 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])) + self._ufl_domain._ufl_signature_data_(renumbering), + repr(self._ufl_shape), + repr(renumbering[self]), + ) 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) + return Constant( + domain, shape=(domain.geometric_dimension(), domain.geometric_dimension()), count=count + ) diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 82d9ed84e..0aa320d8f 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -12,6 +12,7 @@ from math import atan2 import ufl + # --- Helper functions imported here for compatibility--- from ufl.checks import is_python_scalar, is_true_ufl_scalar, is_ufl_scalar # noqa: F401 from ufl.core.expr import Expr @@ -33,6 +34,7 @@ def format_float(x): # --- Base classes for constant types --- + @ufl_type(is_abstract=True) class ConstantValue(Terminal): """Constant value.""" @@ -99,16 +101,23 @@ def _init(self, shape=(), free_indices=(), index_dimensions=None): self.ufl_free_indices = () self.ufl_index_dimensions = () elif all(isinstance(i, Index) for i in free_indices): # Handle old input format - if not isinstance(index_dimensions, dict) and all(isinstance(i, Index) for i in index_dimensions.keys()): + if not isinstance(index_dimensions, dict) and all( + isinstance(i, Index) for i in index_dimensions.keys() + ): raise ValueError(f"Expecting tuple of index dimensions, not {index_dimensions}") self.ufl_free_indices = tuple(sorted(i.count() for i in free_indices)) self.ufl_index_dimensions = tuple( - d for i, d in sorted(index_dimensions.items(), key=lambda x: x[0].count())) + d for i, d in sorted(index_dimensions.items(), key=lambda x: x[0].count()) + ) else: # Handle new input format if not all(isinstance(i, int) for i in free_indices): raise ValueError(f"Expecting tuple of integer free index ids, not {free_indices}") - if not isinstance(index_dimensions, tuple) and all(isinstance(i, int) for i in index_dimensions): - raise ValueError(f"Expecting tuple of integer index dimensions, not {index_dimensions}") + if not isinstance(index_dimensions, tuple) and all( + isinstance(i, int) for i in index_dimensions + ): + raise ValueError( + f"Expecting tuple of integer index dimensions, not {index_dimensions}" + ) # Assuming sorted now to avoid this cost, enable for debugging: # if sorted(free_indices) != list(free_indices): @@ -136,7 +145,8 @@ def __repr__(self): r = "Zero(%s, %s, %s)" % ( repr(self.ufl_shape), repr(self.ufl_free_indices), - repr(self.ufl_index_dimensions)) + repr(self.ufl_index_dimensions), + ) return r def __eq__(self, other): @@ -144,9 +154,11 @@ def __eq__(self, other): if isinstance(other, Zero): if self is other: return True - return (self.ufl_shape == other.ufl_shape and # noqa: W504 - self.ufl_free_indices == other.ufl_free_indices and # noqa: W504 - self.ufl_index_dimensions == other.ufl_index_dimensions) + return ( + self.ufl_shape == other.ufl_shape + and self.ufl_free_indices == other.ufl_free_indices + and self.ufl_index_dimensions == other.ufl_index_dimensions + ) elif isinstance(other, (int, float)): return other == 0 else: @@ -189,6 +201,7 @@ def zero(*shape): # --- Scalar value types --- + @ufl_type(is_abstract=True, is_scalar=True) class ScalarValue(ConstantValue): """A constant scalar value.""" @@ -345,6 +358,7 @@ def __repr__(self): @ufl_type(wraps_type=int, is_literal=True) class IntValue(RealValue): """Representation of a constant scalar integer value.""" + __slots__ = () _cache = {} @@ -387,9 +401,11 @@ def __repr__(self): # --- Identity matrix --- + @ufl_type() class Identity(ConstantValue): """Representation of an identity matrix.""" + __slots__ = ("_dim", "ufl_shape") def __init__(self, dim): @@ -427,6 +443,7 @@ def __eq__(self, other): # --- Permutation symbol --- + @ufl_type() class PermutationSymbol(ConstantValue): """Representation of a permutation symbol. @@ -497,4 +514,5 @@ def as_ufl(expression): return IntValue(expression) else: raise ValueError( - f"Invalid type conversion: {expression} can not be converted to any UFL type.") + f"Invalid type conversion: {expression} can not be converted to any UFL type." + ) diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index a136244cb..90bc23dd0 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -1,7 +1,8 @@ """Base form operator. -This module defines the BaseFormOperator class, which is the base class for objects that can be seen as forms -and as operators such as ExternalOperator or Interpolate. +This module defines the BaseFormOperator class, which is the base class +for objects that can be seen as forms and as operators such as +ExternalOperator or Interpolate. """ # Copyright (C) 2019 Nacime Bouziani @@ -36,9 +37,11 @@ def __init__(self, *operands, function_space, derivatives=None, argument_slots=( Args: operands: operands on which acts the operator. - function_space: the FunctionSpace or MixedFunctionSpace on which to build this Function. - derivatives: tuple specifiying the derivative multiindex. - argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + function_space: the FunctionSpace or MixedFunctionSpace on + which to build this Function. + derivatives: tuple specifying the derivative multiindex. + argument_slots: tuple composed containing expressions with + ufl.Argument or ufl.Coefficient objects. """ BaseForm.__init__(self) ufl_operands = tuple(map(as_ufl, operands)) @@ -73,12 +76,14 @@ def __init__(self, *operands, function_space, derivatives=None, argument_slots=( def argument_slots(self, outer_form=False): """Returns a tuple of expressions containing argument and coefficient based expressions. - We get an argument uhat when we take the Gateaux derivative in the direction uhat: - d/du N(u; v*) = dNdu(u; uhat, v*) where uhat is a ufl.Argument and v* a ufl.Coargument - Applying the action replace the last argument by coefficient: - action(dNdu(u; uhat, v*), w) = dNdu(u; w, v*) where du is a ufl.Coefficient. + We get an argument uhat when we take the Gateaux derivative in + the direction uhat: d/du N(u; v*) = dNdu(u; uhat, v*) where uhat + is a ufl.Argument and v* a ufl.Coargument Applying the action + replace the last argument by coefficient: action(dNdu(u; uhat, + v*), w) = dNdu(u; w, v*) where du is a ufl.Coefficient. """ from ufl.algorithms.analysis import extract_arguments + if not outer_form: return self._argument_slots # Takes into account argument contraction when a base form operator is in an outer form: @@ -96,14 +101,19 @@ def coefficients(self): def _analyze_form_arguments(self): """Analyze which Argument and Coefficient objects can be found in the base form.""" from ufl.algorithms.analysis import extract_arguments, extract_coefficients, extract_type + dual_arg, *arguments = self.argument_slots() - # When coarguments are treated as BaseForms, they have two arguments (one primal and one dual) - # as they map from V* to V* => V* x V -> R. However, when they are treated as mere "arguments", - # the primal space argument is discarded and we only have the dual space argument (Coargument). - # This is the exact same situation than BaseFormOperator's arguments which are different depending on - # whether the BaseFormOperator is used in an outer form or not. - arguments = (tuple(extract_type(dual_arg, Coargument)) - + tuple(a for arg in arguments for a in extract_arguments(arg))) + # When coarguments are treated as BaseForms, they have two + # arguments (one primal and one dual) as they map from V* to V* + # => V* x V -> R. However, when they are treated as mere + # "arguments", the primal space argument is discarded and we + # only have the dual space argument (Coargument). This is the + # exact same situation than BaseFormOperator's arguments which + # are different depending on whether the BaseFormOperator is + # used in an outer form or not. + arguments = tuple(extract_type(dual_arg, Coargument)) + tuple( + a for arg in arguments for a in extract_arguments(arg) + ) coefficients = tuple(c for op in self.ufl_operands for c in extract_coefficients(op)) # Define canonical numbering of arguments and coefficients # 1) Need concept of order since we may have arguments with the same number @@ -132,11 +142,16 @@ def ufl_function_space(self): """ return self.arguments()[0]._ufl_function_space.dual() - def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, argument_slots=None): + def _ufl_expr_reconstruct_( + self, *operands, function_space=None, derivatives=None, argument_slots=None + ): """Return a new object of the same type with new operands.""" - return type(self)(*operands, function_space=function_space or self.ufl_function_space(), - derivatives=derivatives or self.derivatives, - argument_slots=argument_slots or self.argument_slots()) + return type(self)( + *operands, + function_space=function_space or self.ufl_function_space(), + derivatives=derivatives or self.derivatives, + argument_slots=argument_slots or self.argument_slots(), + ) def __repr__(self): """Default repr string construction for base form operators.""" @@ -149,11 +164,13 @@ def __repr__(self): def __hash__(self): """Hash code for use in dicts.""" - hashdata = (type(self), - tuple(hash(op) for op in self.ufl_operands), - tuple(hash(arg) for arg in self._argument_slots), - self.derivatives, - hash(self.ufl_function_space())) + hashdata = ( + type(self), + tuple(hash(op) for op in self.ufl_operands), + tuple(hash(arg) for arg in self._argument_slots), + self.derivatives, + hash(self.ufl_function_space()), + ) return hash(hashdata) def __eq__(self, other): diff --git a/ufl/core/expr.py b/ufl/core/expr.py index e6b2ce3d8..cef955f0e 100644 --- a/ufl/core/expr.py +++ b/ufl/core/expr.py @@ -169,13 +169,10 @@ def __init__(self): _ufl_required_properties_ = ( # A tuple of operands, all of them Expr instances. "ufl_operands", - # A tuple of ints, the value shape of the expression. "ufl_shape", - # A tuple of free index counts. "ufl_free_indices", - # A tuple providing the int dimension for each free index. "ufl_index_dimensions", ) @@ -186,24 +183,19 @@ def __init__(self): _ufl_required_methods_ = ( # To compute the hash on demand, this method is called. "_ufl_compute_hash_", - # The data returned from this method is used to compute the # signature of a form "_ufl_signature_data_", - # The == operator must be implemented to compare for identical # representation, used by set() and dict(). The __hash__ # operator is added by ufl_type. "__eq__", - # To reconstruct an object of the same type with operands or # properties changed. "_ufl_expr_reconstruct_", # Implemented in Operator and Terminal so this should never fail - "ufl_domains", # "ufl_cell", # "ufl_domain", - # "__str__", # "__repr__", ) @@ -259,16 +251,22 @@ def _ufl_expr_reconstruct_(self, *operands): def ufl_domains(self): """Return all domains this expression is defined on.""" - warnings.warn("Expr.ufl_domains() is deprecated, please " - "use extract_domains(expr) instead.", DeprecationWarning) + warnings.warn( + "Expr.ufl_domains() is deprecated, please use extract_domains(expr) instead.", + DeprecationWarning, + ) from ufl.domain import extract_domains + return extract_domains(self) def ufl_domain(self): """Return the single unique domain this expression is defined on, or throw an error.""" - warnings.warn("Expr.ufl_domain() is deprecated, please " - "use extract_unique_domain(expr) instead.", DeprecationWarning) + warnings.warn( + "Expr.ufl_domain() is deprecated, please use extract_unique_domain(expr) instead.", + DeprecationWarning, + ) from ufl.domain import extract_unique_domain + return extract_unique_domain(self) # --- Functions for float evaluation --- diff --git a/ufl/core/external_operator.py b/ufl/core/external_operator.py index ecd736391..3a9304854 100644 --- a/ufl/core/external_operator.py +++ b/ufl/core/external_operator.py @@ -1,8 +1,9 @@ """External operator. -This module defines the ``ExternalOperator`` class, which symbolically represents operators that are not -straightforwardly expressible in UFL. Subclasses of ``ExternalOperator`` must define -how this operator should be evaluated as well as its derivatives from a given set of operands. +This module defines the ``ExternalOperator`` class, which symbolically +represents operators that are not straightforwardly expressible in UFL. +Subclasses of ``ExternalOperator`` must define how this operator should +be evaluated as well as its derivatives from a given set of operands. """ # Copyright (C) 2019 Nacime Bouziani # @@ -29,26 +30,34 @@ def __init__(self, *operands, function_space, derivatives=None, argument_slots=( Args: operands: operands on which acts the ExternalOperator. - function_space: the FunctionSpace, or MixedFunctionSpace on which to build this Function. - derivatives: tuple specifiying the derivative multiindex. - argument_slots: tuple composed containing expressions with ufl.Argument or ufl.Coefficient objects. + function_space: the FunctionSpace, or MixedFunctionSpace on + which to build this Function. + derivatives: tuple specifying the derivative multiindex. + argument_slots: tuple composed containing expressions with + ufl.Argument or ufl.Coefficient objects. + argument_slots: TODO """ # -- Derivatives -- # if derivatives is not None: if not isinstance(derivatives, tuple): - raise TypeError(f"Expecting a tuple for derivatives and not {derivatives}") + raise TypeError(f"Expecting a tuple for derivatives and not {derivatives}.") if not len(derivatives) == len(operands): - raise ValueError(f"Expecting a size of {len(operands)} for {derivatives}") + raise ValueError(f"Expecting a size of {len(operands)} for {derivatives}.") if not all(isinstance(d, int) for d in derivatives) or any(d < 0 for d in derivatives): raise ValueError( - f"Expecting a derivative multi-index with nonnegative indices and not {str(derivatives)}") + "Expecting a derivative multi-index with nonnegative indices, " + f"not {derivatives}." + ) else: derivatives = (0,) * len(operands) - BaseFormOperator.__init__(self, *operands, - function_space=function_space, - derivatives=derivatives, - argument_slots=argument_slots) + BaseFormOperator.__init__( + self, + *operands, + function_space=function_space, + derivatives=derivatives, + argument_slots=argument_slots, + ) def ufl_element(self): """Shortcut to get the finite element of the function space of the external operator.""" @@ -57,28 +66,38 @@ def ufl_element(self): def grad(self): """Returns the symbolic grad of the external operator.""" - # By default, differential rules produce `grad(assembled_o)` `where assembled_o` - # is the `Coefficient` resulting from assembling the external operator since - # the external operator may not be smooth enough for chain rule to hold. - # Symbolic gradient (`grad(ExternalOperator)`) depends on the operator considered - # and its implementation may be needed in some cases (e.g. convolution operator). - raise NotImplementedError('Symbolic gradient not defined for the external operator considered!') + # By default, differential rules produce `grad(assembled_o)` + # `where assembled_o` is the `Coefficient` resulting from + # assembling the external operator since the external operator + # may not be smooth enough for chain rule to hold. Symbolic + # gradient (`grad(ExternalOperator)`) depends on the operator + # considered and its implementation may be needed in some cases + # (e.g. convolution operator). + raise NotImplementedError( + "Symbolic gradient not defined for the external operator considered." + ) def assemble(self, *args, **kwargs): """Assemble the external operator.""" - raise NotImplementedError(f"Symbolic evaluation of {self._ufl_class_.__name__} not available.") + raise NotImplementedError( + f"Symbolic evaluation of {self._ufl_class_.__name__} not available." + ) - def _ufl_expr_reconstruct_(self, *operands, function_space=None, derivatives=None, - argument_slots=None, add_kwargs={}): + def _ufl_expr_reconstruct_( + self, *operands, function_space=None, derivatives=None, argument_slots=None, add_kwargs={} + ): """Return a new object of the same type with new operands.""" - return type(self)(*operands, function_space=function_space or self.ufl_function_space(), - derivatives=derivatives or self.derivatives, - argument_slots=argument_slots or self.argument_slots(), - **add_kwargs) + return type(self)( + *operands, + function_space=function_space or self.ufl_function_space(), + derivatives=derivatives or self.derivatives, + argument_slots=argument_slots or self.argument_slots(), + **add_kwargs, + ) def __str__(self): """Default str string for ExternalOperator operators.""" - d = '\N{PARTIAL DIFFERENTIAL}' + d = "\N{PARTIAL DIFFERENTIAL}" derivatives = self.derivatives d_ops = "".join(d + "o" + str(i + 1) for i, di in enumerate(derivatives) for j in range(di)) e = "e(" @@ -92,8 +111,10 @@ def __eq__(self, other): """Check for equality.""" if self is other: return True - return (type(self) is type(other) and - all(a == b for a, b in zip(self.ufl_operands, other.ufl_operands)) and - all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) and - self.derivatives == other.derivatives and - self.ufl_function_space() == other.ufl_function_space()) + return ( + type(self) is type(other) + and all(a == b for a, b in zip(self.ufl_operands, other.ufl_operands)) + and all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) + and self.derivatives == other.derivatives + and self.ufl_function_space() == other.ufl_function_space() + ) diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 86198c680..57e1506fd 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -39,10 +39,12 @@ def __init__(self, expr, v): if isinstance(v, AbstractFunctionSpace): if is_dual(v): - raise ValueError('Expecting a primal function space.') + raise ValueError("Expecting a primal function space.") v = Argument(v.dual(), 0) elif not isinstance(v, dual_args): - raise ValueError("Expecting the second argument to be FunctionSpace, FiniteElement or dual.") + raise ValueError( + "Expecting the second argument to be FunctionSpace, FiniteElement or dual." + ) expr = as_ufl(expr) if isinstance(expr, dual_args): @@ -55,8 +57,9 @@ def __init__(self, expr, v): function_space = vv.ufl_function_space().dual() # Set the operand as `expr` for DAG traversal purpose. operand = expr - BaseFormOperator.__init__(self, operand, function_space=function_space, - argument_slots=argument_slots) + BaseFormOperator.__init__( + self, operand, function_space=function_space, argument_slots=argument_slots + ) def _ufl_expr_reconstruct_(self, expr, v=None, **add_kwargs): """Return a new object of the same type with new operands.""" @@ -81,9 +84,11 @@ def __eq__(self, other): """Check for equality.""" if self is other: return True - return (type(self) is type(other) and - all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) and - self.ufl_function_space() == other.ufl_function_space()) + return ( + type(self) is type(other) + and all(a == b for a, b in zip(self._argument_slots, other._argument_slots)) + and self.ufl_function_space() == other.ufl_function_space() + ) # Helper function diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 81409ce7d..523d21d20 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -19,6 +19,7 @@ class IndexBase(object): """Base class for all indices.""" + __slots__ = () def __init__(self): @@ -27,6 +28,7 @@ def __init__(self): class FixedIndex(IndexBase): """UFL value: An index with a specific value assigned.""" + __slots__ = ("_value", "_hash") _cache = {} @@ -112,6 +114,7 @@ def __repr__(self): @ufl_type() class MultiIndex(Terminal): """Represents a sequence of indices, either fixed or free.""" + __slots__ = ("_indices",) _cache = {} @@ -164,8 +167,7 @@ def _ufl_compute_hash_(self): def __eq__(self, other): """Check equality.""" - return isinstance(other, MultiIndex) and \ - self._indices == other._indices + return isinstance(other, MultiIndex) and self._indices == other._indices def evaluate(self, x, mapping, component, index_values): """Evaluate index.""" diff --git a/ufl/core/terminal.py b/ufl/core/terminal.py index f258a1c04..47c6c16d9 100644 --- a/ufl/core/terminal.py +++ b/ufl/core/terminal.py @@ -54,10 +54,12 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): except Exception: pass # If it has an ufl_evaluate function, call it - if hasattr(self, 'ufl_evaluate'): + if hasattr(self, "ufl_evaluate"): return self.ufl_evaluate(x, component, derivatives) # Take component if any - warnings.warn(f"Couldn't map '{self}' to a float, returning ufl object without evaluation.") + warnings.warn( + f"Couldn't map '{self}' to a float, returning ufl object without evaluation." + ) f = self if component: f = f[component] @@ -93,9 +95,11 @@ def __eq__(self, other): # --- Subgroups of terminals --- + @ufl_type(is_abstract=True) class FormArgument(Terminal): """An abstract class for a form argument (a thing in a primal finite element space).""" + __slots__ = () def __init__(self): diff --git a/ufl/core/ufl_id.py b/ufl/core/ufl_id.py index bc1206761..34cbda859 100644 --- a/ufl/core/ufl_id.py +++ b/ufl/core/ufl_id.py @@ -51,6 +51,7 @@ def init_ufl_id(self, ufl_id): ufl_id = cls._ufl_global_id cls._ufl_global_id = max(ufl_id, cls._ufl_global_id) + 1 return ufl_id + return init_ufl_id # Modify class: diff --git a/ufl/core/ufl_type.py b/ufl/core/ufl_type.py index e19a9c340..c409d05fb 100644 --- a/ufl/core/ufl_type.py +++ b/ufl/core/ufl_type.py @@ -59,7 +59,8 @@ def get_base_attr(cls, name): def set_trait(cls, basename, value, inherit=False): """Assign a trait to class with namespacing ``_ufl_basename_`` applied. - If trait value is ``None``, optionally inherit it from the closest base class that has it. + If trait value is ``None``, optionally inherit it from the closest + base class that has it. """ name = "_ufl_" + basename + "_" if value is None and inherit: @@ -88,14 +89,18 @@ def determine_num_ops(cls, num_ops, unop, binop, rbinop): def check_is_terminal_consistency(cls): """Check for consistency in ``is_terminal`` trait among superclasses.""" if cls._ufl_is_terminal_ is None: - msg = (f"Class {cls.__name__} has not specified the is_terminal trait." - " Did you forget to inherit from Terminal or Operator?") + msg = ( + f"Class {cls.__name__} has not specified the is_terminal trait." + " Did you forget to inherit from Terminal or Operator?" + ) raise TypeError(msg) base_is_terminal = get_base_attr(cls, "_ufl_is_terminal_") if base_is_terminal is not None and cls._ufl_is_terminal_ != base_is_terminal: - msg = (f"Conflicting given and automatic 'is_terminal' trait for class {cls.__name__}." - " Check if you meant to inherit from Terminal or Operator.") + msg = ( + f"Conflicting given and automatic 'is_terminal' trait for class {cls.__name__}." + " Check if you meant to inherit from Terminal or Operator." + ) raise TypeError(msg) @@ -105,8 +110,10 @@ def check_abstract_trait_consistency(cls): if base is core.expr.Expr: break if not issubclass(base, core.expr.Expr) and base._ufl_is_abstract_: - msg = ("Base class {0.__name__} of class {1.__name__} " - "is not an abstract subclass of {2.__name__}.") + msg = ( + "Base class {0.__name__} of class {1.__name__} " + "is not an abstract subclass of {2.__name__}." + ) raise TypeError(msg.format(base, cls, core.expr.Expr)) @@ -116,15 +123,16 @@ def check_has_slots(cls): return if "__slots__" not in cls.__dict__: - msg = ("Class {0.__name__} is missing the __slots__ " - "attribute and is not marked with _ufl_noslots_.") + msg = ( + "Class {0.__name__} is missing the __slots__ " + "attribute and is not marked with _ufl_noslots_." + ) raise TypeError(msg.format(cls)) # Check base classes for __slots__ as well, skipping object which is the last one for base in cls.mro()[1:-1]: if "__slots__" not in base.__dict__: - msg = ("Class {0.__name__} is has a base class " - "{1.__name__} with __slots__ missing.") + msg = "Class {0.__name__} is has a base class {1.__name__} with __slots__ missing." raise TypeError(msg.format(cls, base)) @@ -202,22 +210,30 @@ def attach_implementations_of_indexing_interface( # operands. This simplifies refactoring because a lot of types do # this. if inherit_shape_from_operand is not None: + def _inherited_ufl_shape(self): return self.ufl_operands[inherit_shape_from_operand].ufl_shape + cls.ufl_shape = property(_inherited_ufl_shape) if inherit_indices_from_operand is not None: + def _inherited_ufl_free_indices(self): return self.ufl_operands[inherit_indices_from_operand].ufl_free_indices def _inherited_ufl_index_dimensions(self): return self.ufl_operands[inherit_indices_from_operand].ufl_index_dimensions + cls.ufl_free_indices = property(_inherited_ufl_free_indices) cls.ufl_index_dimensions = property(_inherited_ufl_index_dimensions) def update_global_expr_attributes(cls): - """Update global ``Expr`` attributes, mainly by adding *cls* to global collections of ufl types.""" + """Update global attributres. + + Update global ``Expr`` attributes, mainly by adding *cls* to global + collections of ufl types. + """ if cls._ufl_is_terminal_modifier_: core.expr.Expr._ufl_terminal_modifiers_.append(cls) @@ -249,20 +265,34 @@ def update_ufl_type_attributes(cls): def ufl_type( - is_abstract=False, is_terminal=None, is_scalar=False, is_index_free=False, is_shaping=False, - is_literal=False, is_terminal_modifier=False, is_in_reference_frame=False, is_restriction=False, - is_evaluation=False, is_differential=None, use_default_hash=True, num_ops=None, - inherit_shape_from_operand=None, inherit_indices_from_operand=None, wraps_type=None, unop=None, - binop=None, rbinop=None + is_abstract=False, + is_terminal=None, + is_scalar=False, + is_index_free=False, + is_shaping=False, + is_literal=False, + is_terminal_modifier=False, + is_in_reference_frame=False, + is_restriction=False, + is_evaluation=False, + is_differential=None, + use_default_hash=True, + num_ops=None, + inherit_shape_from_operand=None, + inherit_indices_from_operand=None, + wraps_type=None, + unop=None, + binop=None, + rbinop=None, ): - """This decorator is to be applied to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. + """Decorator to apply to every subclass in the UFL ``Expr`` and ``BaseForm`` hierarchy. - This decorator contains a number of checks that are - intended to enforce uniform behaviour across UFL types. + This decorator contains a number of checks that are intended to + enforce uniform behaviour across UFL types. - The rationale behind the checks and the meaning of the - optional arguments should be sufficiently documented - in the source code below. + The rationale behind the checks and the meaning of the optional + arguments should be sufficiently documented in the source code + below. """ def _ufl_type_decorator_(cls): @@ -285,11 +315,9 @@ def _ufl_type_decorator_(cls): set_trait(cls, "is_terminal", is_terminal, inherit=True) set_trait(cls, "is_literal", is_literal, inherit=True) - set_trait(cls, "is_terminal_modifier", is_terminal_modifier, - inherit=True) + set_trait(cls, "is_terminal_modifier", is_terminal_modifier, inherit=True) set_trait(cls, "is_shaping", is_shaping, inherit=True) - set_trait(cls, "is_in_reference_frame", is_in_reference_frame, - inherit=True) + set_trait(cls, "is_in_reference_frame", is_in_reference_frame, inherit=True) set_trait(cls, "is_restriction", is_restriction, inherit=True) set_trait(cls, "is_evaluation", is_evaluation, inherit=True) set_trait(cls, "is_differential", is_differential, inherit=True) @@ -305,7 +333,8 @@ def _ufl_type_decorator_(cls): """# These are currently handled in the as_ufl implementation in constantvalue.py if wraps_type is not None: if not isinstance(wraps_type, type): - msg = "Expecting a type, not a {0.__name__} for the wraps_type argument in definition of {1.__name__}." + msg = "Expecting a type, not a {0.__name__} for the + wraps_type argument in definition of {1.__name__}." raise TypeError(msg.format(type(wraps_type), cls)) def _ufl_from_type_(value): @@ -352,9 +381,9 @@ def _ufl_expr_rbinop_(self, other): # class! This approach significantly reduces the amount of # small functions to implement across all the types but of # course it's a bit more opaque. - attach_implementations_of_indexing_interface(cls, - inherit_shape_from_operand, - inherit_indices_from_operand) + attach_implementations_of_indexing_interface( + cls, inherit_shape_from_operand, inherit_indices_from_operand + ) # Update Expr update_global_expr_attributes(cls) diff --git a/ufl/corealg/map_dag.py b/ufl/corealg/map_dag.py index f299130c1..9b9196f17 100644 --- a/ufl/corealg/map_dag.py +++ b/ufl/corealg/map_dag.py @@ -12,10 +12,10 @@ from ufl.corealg.traversal import cutoff_unique_post_traversal, unique_post_traversal -def map_expr_dag(function, expression, compress=True, vcache=None, rcache=None): +def map_expr_dag(function, expression, compress=True, vcache=None, rcache=None): """Apply a function to each subexpression node in an expression DAG. - If the same funtion is called multiple times in a transformation + If the same function is called multiple times in a transformation (as for example in apply_derivatives), then to reuse caches across the call, use the arguments vcache and rcache. @@ -25,32 +25,33 @@ def map_expr_dag(function, expression, compress=True, vcache=None, rcache=None) compress: If True (default), the output object from the function is cached in a dict and reused such that the resulting expression DAG does not contain duplicate objects - vcache: Optional dict for caching results of intermediate transformations + vcache: Optional dict for caching results of intermediate + transformations rcache: Optional dict for caching results for compression Returns: The result of the final function call """ - result, = map_expr_dags(function, [expression], compress=compress, - vcache=vcache, - rcache=rcache) + (result,) = map_expr_dags( + function, [expression], compress=compress, vcache=vcache, rcache=rcache + ) return result def map_expr_dags(function, expressions, compress=True, vcache=None, rcache=None): - """Apply a function to each subexpression node in an expression DAG. + """Apply a function to each sub-expression node in an expression DAG. If *compress* is ``True`` (default) the output object from the function is cached in a ``dict`` and reused such that the resulting expression DAG does not contain duplicate objects. - If the same funtion is called multiple times in a transformation + If the same function is called multiple times in a transformation (as for example in apply_derivatives), then to reuse caches across the call, use the arguments vcache and rcache. Args: function: The function - expression: An expression + expressions: An expression compress: If True (default), the output object from the function is cached in a dict and reused such that the resulting expression DAG does not contain duplicate objects @@ -80,9 +81,11 @@ def map_expr_dags(function, expressions, compress=True, vcache=None, rcache=None # Pick faster traversal algorithm if we have no cutoffs if any(cutoff_types): + def traversal(expression): return cutoff_unique_post_traversal(expression, cutoff_types, visited) else: + def traversal(expression): return unique_post_traversal(expression, visited) diff --git a/ufl/corealg/multifunction.py b/ufl/corealg/multifunction.py index 1c57c2928..fc6e45136 100644 --- a/ufl/corealg/multifunction.py +++ b/ufl/corealg/multifunction.py @@ -29,6 +29,7 @@ def _memoized_handler(self, o): r = handler(self, o) c[o] = r return r + return _memoized_handler @@ -76,8 +77,7 @@ def __init__(self): if hasattr(self, handler_name): handler_names[classobject._ufl_typecode_] = handler_name break - is_cutoff_type = [get_num_args(getattr(self, name)) == 2 - for name in handler_names] + is_cutoff_type = [get_num_args(getattr(self, name)) == 2 for name in handler_names] cache_data = (handler_names, is_cutoff_type) MultiFunction._handlers_cache[algorithm_class] = cache_data diff --git a/ufl/corealg/traversal.py b/ufl/corealg/traversal.py index 782e16999..64c81deaa 100644 --- a/ufl/corealg/traversal.py +++ b/ufl/corealg/traversal.py @@ -39,7 +39,11 @@ def post_traversal(expr): def cutoff_post_traversal(expr, cutofftypes): - """Yield ``o`` for each node ``o`` in *expr*, child before parent, but skipping subtrees of the cutofftypes.""" + """Cut-off post-tranversal. + + Yield ``o`` for each node ``o`` in *expr*, child before parent, but + skipping subtrees of the cutofftypes. + """ lifo = [(expr, list(reversed(expr.ufl_operands)))] while lifo: expr, deps = lifo[-1] diff --git a/ufl/differentiation.py b/ufl/differentiation.py index 34c003b85..74e33617d 100644 --- a/ufl/differentiation.py +++ b/ufl/differentiation.py @@ -24,8 +24,7 @@ # --- Basic differentiation objects --- -@ufl_type(is_abstract=True, - is_differential=True) +@ufl_type(is_abstract=True, is_differential=True) class Derivative(Operator): """Base class for all derivative types.""" @@ -36,15 +35,13 @@ def __init__(self, operands): Operator.__init__(self, operands) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoefficientDerivative(Derivative): - """Derivative of the integrand of a form w.r.t. the degrees of freedom in a discrete Coefficient.""" + """Derivative of form integrand w.r.t. the degrees of freedom in a discrete Coefficient.""" __slots__ = () - def __new__(cls, integrand, coefficients, arguments, - coefficient_derivatives): + def __new__(cls, integrand, coefficients, arguments, coefficient_derivatives): """Create a new CoefficientDerivative.""" if not isinstance(coefficients, ExprList): raise ValueError("Expecting ExprList instance with Coefficients.") @@ -56,23 +53,23 @@ def __new__(cls, integrand, coefficients, arguments, return integrand return Derivative.__new__(cls) - def __init__(self, integrand, coefficients, arguments, - coefficient_derivatives): + def __init__(self, integrand, coefficients, arguments, coefficient_derivatives): """Initalise.""" if not isinstance(coefficient_derivatives, ExprMapping): coefficient_derivatives = ExprMapping(coefficient_derivatives) - Derivative.__init__(self, (integrand, coefficients, arguments, - coefficient_derivatives)) + Derivative.__init__(self, (integrand, coefficients, arguments, coefficient_derivatives)) def __str__(self): """Format as a string.""" - return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coefficient derivatives %s"\ - % (self.ufl_operands[0], self.ufl_operands[1], - self.ufl_operands[2], self.ufl_operands[3]) + return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coefficient derivatives %s" % ( + self.ufl_operands[0], + self.ufl_operands[1], + self.ufl_operands[2], + self.ufl_operands[3], + ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class CoordinateDerivative(CoefficientDerivative): """Derivative of the integrand of a form w.r.t. the SpatialCoordinates.""" @@ -80,82 +77,92 @@ class CoordinateDerivative(CoefficientDerivative): def __str__(self): """Format as a string.""" - return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coordinate derivatives %s"\ - % (self.ufl_operands[0], self.ufl_operands[1], - self.ufl_operands[2], self.ufl_operands[3]) + return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coordinate derivatives %s" % ( + self.ufl_operands[0], + self.ufl_operands[1], + self.ufl_operands[2], + self.ufl_operands[3], + ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormDerivative(CoefficientDerivative, BaseForm): """Derivative of a base form w.r.t the degrees of freedom in a discrete Coefficient.""" _ufl_noslots_ = True - def __init__(self, base_form, coefficients, arguments, - coefficient_derivatives): + def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): """Initalise.""" - CoefficientDerivative.__init__(self, base_form, coefficients, arguments, - coefficient_derivatives) + CoefficientDerivative.__init__( + self, base_form, coefficients, arguments, coefficient_derivatives + ) BaseForm.__init__(self) def _analyze_form_arguments(self): """Collect the arguments of the corresponding BaseForm.""" from ufl.algorithms.analysis import extract_coefficients, extract_type + base_form, _, arguments, _ = self.ufl_operands def arg_type(x): if isinstance(x, BaseForm): return Coargument return Argument + # Each derivative arguments can either be a: # - `ufl.BaseForm`: if it contains a `ufl.Coargument` # - or a `ufl.Expr`: if it contains a `ufl.Argument` - # When a `Coargument` is encountered, it is treated as an argument (i.e. as V* -> V* and not V* x V -> R) - # and should result in one single argument (in the dual space). - base_form_args = base_form.arguments() + tuple(arg for a in arguments.ufl_operands - for arg in extract_type(a, arg_type(a))) + # When a `Coargument` is encountered, it is treated as an + # argument (i.e. as V* -> V* and not V* x V -> R) and should + # result in one single argument (in the dual space). + base_form_args = base_form.arguments() + tuple( + arg for a in arguments.ufl_operands for arg in extract_type(a, arg_type(a)) + ) # BaseFormDerivative's arguments don't necessarily contain BaseArgument objects only # -> e.g. `derivative(u ** 2, u, u)` with `u` a Coefficient. - base_form_coeffs = base_form.coefficients() + tuple(arg for a in arguments.ufl_operands - for arg in extract_coefficients(a)) + base_form_coeffs = base_form.coefficients() + tuple( + arg for a in arguments.ufl_operands for arg in extract_coefficients(a) + ) # Reconstruct arguments for correct numbering - self._arguments = tuple(type(arg)(arg.ufl_function_space(), arg.number(), arg.part()) for arg in base_form_args) + self._arguments = tuple( + type(arg)(arg.ufl_function_space(), arg.number(), arg.part()) for arg in base_form_args + ) self._coefficients = base_form_coeffs -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormCoordinateDerivative(BaseFormDerivative, CoordinateDerivative): """Derivative of a base form w.r.t. the SpatialCoordinates.""" _ufl_noslots_ = True - def __init__(self, base_form, coefficients, arguments, - coefficient_derivatives): + def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): """Initalise.""" - BaseFormDerivative.__init__(self, base_form, coefficients, arguments, - coefficient_derivatives) + BaseFormDerivative.__init__( + self, base_form, coefficients, arguments, coefficient_derivatives + ) -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormOperatorDerivative(BaseFormDerivative, BaseFormOperator): """Derivative of a base form operator w.r.t the degrees of freedom in a discrete Coefficient.""" + _ufl_noslots_ = True # BaseFormOperatorDerivative is only needed because of a different # differentiation procedure for BaseformOperator objects. - def __init__(self, base_form, coefficients, arguments, - coefficient_derivatives): + def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): """Initalise.""" - BaseFormDerivative.__init__(self, base_form, coefficients, arguments, - coefficient_derivatives) + BaseFormDerivative.__init__( + self, base_form, coefficients, arguments, coefficient_derivatives + ) self._argument_slots = base_form._argument_slots - # Enforce Operator reconstruction as Operator is a parent class of both: BaseFormDerivative and BaseFormOperator. - # Therfore the latter overwrites Operator reconstruction and we would have: - # -> BaseFormOperatorDerivative._ufl_expr_reconstruct_ = BaseFormOperator._ufl_expr_reconstruct_ + # Enforce Operator reconstruction as Operator is a parent class of + # both: BaseFormDerivative and BaseFormOperator. + # Therefore the latter overwrites Operator reconstruction and we would have: + # -> BaseFormOperatorDerivative._ufl_expr_reconstruct_ = + # BaseFormOperator._ufl_expr_reconstruct_ _ufl_expr_reconstruct_ = Operator._ufl_expr_reconstruct_ # Set __repr__ __repr__ = Operator.__repr__ @@ -163,23 +170,25 @@ def __init__(self, base_form, coefficients, arguments, def argument_slots(self, outer_form=False): """Return a tuple of expressions containing argument and coefficient based expressions.""" from ufl.algorithms.analysis import extract_arguments + base_form, _, arguments, _ = self.ufl_operands - argument_slots = (base_form.argument_slots(outer_form) - + tuple(arg for a in arguments for arg in extract_arguments(a))) + argument_slots = base_form.argument_slots(outer_form) + tuple( + arg for a in arguments for arg in extract_arguments(a) + ) return argument_slots -@ufl_type(num_ops=4, inherit_shape_from_operand=0, - inherit_indices_from_operand=0) +@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0) class BaseFormOperatorCoordinateDerivative(BaseFormOperatorDerivative, CoordinateDerivative): """Derivative of a base form operator w.r.t. the SpatialCoordinates.""" + _ufl_noslots_ = True - def __init__(self, base_form, coefficients, arguments, - coefficient_derivatives): + def __init__(self, base_form, coefficients, arguments, coefficient_derivatives): """Initalise.""" - BaseFormOperatorDerivative.__init__(self, base_form, coefficients, arguments, - coefficient_derivatives) + BaseFormOperatorDerivative.__init__( + self, base_form, coefficients, arguments, coefficient_derivatives + ) @ufl_type(num_ops=2) @@ -205,8 +214,7 @@ def __new__(cls, f, v): # Simplification # Return zero if expression is trivially independent of variable if f._ufl_is_terminal_ and f != v: - return Zero(f.ufl_shape + v.ufl_shape, f.ufl_free_indices, - f.ufl_index_dimensions) + return Zero(f.ufl_shape + v.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) # Construction return Derivative.__new__(cls) @@ -222,12 +230,12 @@ def __str__(self): """Format as a string.""" if isinstance(self.ufl_operands[0], Terminal): return "d%s/d[%s]" % (self.ufl_operands[0], self.ufl_operands[1]) - return "d/d[%s] %s" % (self.ufl_operands[1], - parstr(self.ufl_operands[0], self)) + return "d/d[%s] %s" % (self.ufl_operands[1], parstr(self.ufl_operands[0], self)) # --- Compound differentiation objects --- + @ufl_type(is_abstract=True) class CompoundDerivative(Derivative): """Base class for all compound derivative types.""" @@ -250,8 +258,7 @@ def __new__(cls, f): # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = find_geometric_dimension(f) - return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, - f.ufl_index_dimensions) + return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): @@ -266,17 +273,16 @@ def _ufl_expr_reconstruct_(self, op): raise ValueError("Operand shape mismatch in Grad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in Grad reconstruct.") - return Zero(self.ufl_shape, self.ufl_free_indices, - self.ufl_index_dimensions) + return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): """Get child from mapping and return the component asked for.""" component, i = component[:-1], component[-1] derivatives = derivatives + (i,) - result = self.ufl_operands[0].evaluate(x, mapping, component, - index_values, - derivatives=derivatives) + result = self.ufl_operands[0].evaluate( + x, mapping, component, index_values, derivatives=derivatives + ) return result @property @@ -289,20 +295,20 @@ def __str__(self): return "grad(%s)" % self.ufl_operands[0] -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, - is_in_reference_frame=True) +@ufl_type( + num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True +) class ReferenceGrad(CompoundDerivative): """Reference grad.""" - __slots__ = ("_dim", ) + __slots__ = ("_dim",) def __new__(cls, f): """Create a new ReferenceGrad.""" # Return zero if expression is trivially constant if is_cellwise_constant(f): dim = extract_unique_domain(f).topological_dimension() - return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, - f.ufl_index_dimensions) + return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): @@ -317,17 +323,16 @@ def _ufl_expr_reconstruct_(self, op): raise ValueError("Operand shape mismatch in ReferenceGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in ReferenceGrad reconstruct.") - return Zero(self.ufl_shape, self.ufl_free_indices, - self.ufl_index_dimensions) + return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) def evaluate(self, x, mapping, component, index_values, derivatives=()): """Get child from mapping and return the component asked for.""" component, i = component[:-1], component[-1] derivatives = derivatives + (i,) - result = self.ufl_operands[0].evaluate(x, mapping, component, - index_values, - derivatives=derivatives) + result = self.ufl_operands[0].evaluate( + x, mapping, component, index_values, derivatives=derivatives + ) return result @property @@ -371,8 +376,9 @@ def __str__(self): return "div(%s)" % self.ufl_operands[0] -@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, - is_in_reference_frame=True) +@ufl_type( + num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True +) class ReferenceDiv(CompoundDerivative): """Reference divergence.""" @@ -414,8 +420,7 @@ 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 Zero((dim,) + f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions) return CompoundDerivative.__new__(cls) def __init__(self, f): @@ -430,8 +435,7 @@ def _ufl_expr_reconstruct_(self, op): raise ValueError("Operand shape mismatch in NablaGrad reconstruct.") if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices: raise ValueError("Free index mismatch in NablaGrad reconstruct.") - return Zero(self.ufl_shape, self.ufl_free_indices, - self.ufl_index_dimensions) + return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions) return self._ufl_class_(op) @property @@ -509,8 +513,9 @@ def __str__(self): return "curl(%s)" % self.ufl_operands[0] -@ufl_type(num_ops=1, inherit_indices_from_operand=0, - is_terminal_modifier=True, is_in_reference_frame=True) +@ufl_type( + num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True +) class ReferenceCurl(CompoundDerivative): """Reference curl.""" diff --git a/ufl/domain.py b/ufl/domain.py index 175507b39..50ee64d61 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -20,15 +20,22 @@ class AbstractDomain(object): - """Symbolic representation of a geometric domain with only a geometric and topological dimension.""" + """Symbolic representation of a geometric domain. + + Domain has only a geometric and a topological dimension. + """ def __init__(self, topological_dimension, geometric_dimension): """Initialise.""" # Validate dimensions if not isinstance(geometric_dimension, numbers.Integral): - raise ValueError(f"Expecting integer geometric dimension, not {geometric_dimension.__class__}") + 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__}") + 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.") @@ -67,6 +74,7 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): # 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.") @@ -74,7 +82,7 @@ def __init__(self, coordinate_element, ufl_id=None, cargo=None): self._ufl_coordinate_element = coordinate_element # Derive dimensions from element - gdim, = coordinate_element.reference_value_shape + (gdim,) = coordinate_element.reference_value_shape tdim = coordinate_element.cell.topological_dimension() AbstractDomain.__init__(self, tdim, gdim) @@ -117,8 +125,7 @@ def _ufl_signature_data_(self, renumbering): 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) + return (self.geometric_dimension(), self.topological_dimension(), "Mesh", typespecific) @attach_ufl_id @@ -134,7 +141,7 @@ def __init__(self, mesh, topological_dimension, ufl_id=None): # Derive dimensions from element coordinate_element = mesh.ufl_coordinate_element() - gdim, = coordinate_element.value_shape + (gdim,) = coordinate_element.value_shape tdim = coordinate_element.cell.topological_dimension() AbstractDomain.__init__(self, tdim, gdim) @@ -159,7 +166,10 @@ def __repr__(self): def __str__(self): """Format as a string.""" return "" % ( - self._ufl_id, self.topological_dimension(), self._ufl_mesh) + self._ufl_id, + self.topological_dimension(), + self._ufl_mesh, + ) def _ufl_hash_data_(self): """UFL hash data.""" @@ -167,16 +177,14 @@ def _ufl_hash_data_(self): def _ufl_signature_data_(self, renumbering): """UFL signature data.""" - return ("MeshView", renumbering[self], - self._ufl_mesh._ufl_signature_data_(renumbering)) + 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) + return (self.geometric_dimension(), self.topological_dimension(), "MeshView", typespecific) def as_domain(domain): @@ -212,7 +220,7 @@ def join_domains(domains): gdims.add(domain.geometric_dimension()) if len(gdims) != 1: raise ValueError("Found domains with different geometric dimensions.") - gdim, = gdims + (gdim,) = gdims # Split into legacy and modern style domains legacy_domains = [] @@ -226,14 +234,18 @@ def join_domains(domains): # Handle legacy domains checking if legacy_domains: - warnings.warn("The use of Legacy domains will be deprecated by December 2023. " - "Please, use FunctionSpace instead", DeprecationWarning) + warnings.warn( + "The use of Legacy domains will be deprecated by December 2023. " + "Please, use FunctionSpace instead", + DeprecationWarning, + ) if modern_domains: raise ValueError( "Found both a new-style domain and a legacy default domain. " "These should not be used interchangeably. To find the legacy " "domain, note that it is automatically created from a cell so " - "look for constructors taking a cell.") + "look for constructors taking a cell." + ) return tuple(legacy_domains) # Handle modern domains checking (assuming correct by construction) @@ -242,6 +254,7 @@ def join_domains(domains): # TODO: Move these to an analysis module? + def extract_domains(expr): """Return all domains expression is defined on.""" domainlist = [] @@ -271,5 +284,5 @@ def find_geometric_dimension(expr): if len(gdims) != 1: raise ValueError("Cannot determine geometric dimension from expression.") - gdim, = gdims + (gdim,) = gdims return gdim diff --git a/ufl/duals.py b/ufl/duals.py index 1e017a547..22681440b 100644 --- a/ufl/duals.py +++ b/ufl/duals.py @@ -14,7 +14,7 @@ def is_primal(object): because a mixed function space containing both primal and dual components is neither primal nor dual. """ - return hasattr(object, '_primal') and object._primal + return hasattr(object, "_primal") and object._primal def is_dual(object): @@ -24,4 +24,4 @@ def is_dual(object): because a mixed function space containing both primal and dual components is neither primal nor dual. """ - return hasattr(object, '_dual') and object._dual + return hasattr(object, "_dual") and object._dual diff --git a/ufl/equation.py b/ufl/equation.py index f36089abb..956ae80a1 100644 --- a/ufl/equation.py +++ b/ufl/equation.py @@ -41,6 +41,7 @@ def __bool__(self): return self.rhs.equals(self.lhs) else: raise ValueError("Either lhs or rhs of Equation must implement self.equals(other).") + __nonzero__ = __bool__ def __eq__(self, other): diff --git a/ufl/exproperators.py b/ufl/exproperators.py index 60eb87566..ff532e843 100644 --- a/ufl/exproperators.py +++ b/ufl/exproperators.py @@ -85,9 +85,13 @@ def _ne(self, other): def _as_tensor(self, indices): """A^indices := as_tensor(A, indices).""" if not isinstance(indices, tuple): - raise ValueError("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") + raise ValueError( + "Expecting a tuple of Index objects to A^indices := as_tensor(A, indices)." + ) if not all(isinstance(i, Index) for i in indices): - raise ValueError("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).") + raise ValueError( + "Expecting a tuple of Index objects to A^indices := as_tensor(A, indices)." + ) return as_tensor(self, indices) @@ -96,6 +100,7 @@ def _as_tensor(self, indices): # --- Helper functions for product handling --- + def _mult(a, b): """Multiply.""" # Discover repeated indices, which results in index sums @@ -307,6 +312,7 @@ def _abs(self): # --- Extend Expr with restiction operators a("+"), a("-") --- + def _restrict(self, side): """Restrict.""" if side == "+": @@ -324,6 +330,7 @@ def _eval(self, coord, mapping=None, component=()): """ # Evaluate derivatives first from ufl.algorithms import expand_derivatives + f = expand_derivatives(self) # Evaluate recursively @@ -348,10 +355,12 @@ def _call(self, arg, mapping=None, component=()): # --- Extend Expr with the transpose operation A.T --- + def _transpose(self): """Transpose a rank-2 tensor expression. - For more general transpose operations of higher order tensor expressions, use indexing and Tensor. + For more general transpose operations of higher order tensor + expressions, use indexing and Tensor. """ return Transposed(self) @@ -361,6 +370,7 @@ def _transpose(self): # --- Extend Expr with indexing operator a[i] --- + def _getitem(self, component): """Get an item.""" # Treat component consistently as tuple below @@ -370,12 +380,16 @@ def _getitem(self, component): shape = self.ufl_shape # Analyse slices (:) and Ellipsis (...) - all_indices, slice_indices, repeated_indices = create_slice_indices(component, shape, self.ufl_free_indices) + all_indices, slice_indices, repeated_indices = create_slice_indices( + component, shape, self.ufl_free_indices + ) # Check that we have the right number of indices for a tensor with # this shape if len(shape) != len(all_indices): - raise ValueError(f"Invalid number of indices {len(all_indices)} for expression of rank {len(shape)}.") + raise ValueError( + f"Invalid number of indices {len(all_indices)} for expression of rank {len(shape)}." + ) # Special case for simplifying foo[...] => foo, foo[:] => foo or # similar @@ -422,6 +436,7 @@ def _getitem(self, component): # --- Extend Expr with spatial differentiation operator a.dx(i) --- + def _dx(self, *ii): """Return the partial derivative with respect to spatial variable number *ii*.""" d = self diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py index d306002f1..a5ffff6a6 100644 --- a/ufl/finiteelement.py +++ b/ufl/finiteelement.py @@ -31,12 +31,14 @@ class AbstractFiniteElement(_abc.ABC): """Base class for all finite elements. - To make your element library compatible with UFL, you should make a subclass of AbstractFiniteElement - and provide implementions of all the abstract methods and properties. All methods and properties - that are not marked as abstract are implemented here and should not need to be overwritten in your - subclass. - - An example of how the methods in your subclass could be implemented can be found in Basix; see + To make your element library compatible with UFL, you should make a + subclass of AbstractFiniteElement and provide implementions of all + the abstract methods and properties. All methods and properties that + are not marked as abstract are implemented here and should not need + to be overwritten in your subclass. + + An example of how the methods in your subclass could be implemented + can be found in Basix; see https://github.com/FEniCS/basix/blob/main/python/basix/ufl.py """ @@ -66,29 +68,33 @@ def pullback(self) -> _AbstractPullback: @_abc.abstractproperty def embedded_superdegree(self) -> _typing.Union[int, None]: - """Return the degree of the minimum degree Lagrange space that spans this element. + """Degree of the minimum degree Lagrange space that spans this element. - This returns the degree of the lowest degree Lagrange space such that the polynomial - space of the Lagrange space is a superspace of this element's polynomial space. If this - element contains basis functions that are not in any Lagrange space, this function should - return None. + This returns the degree of the lowest degree Lagrange space such + that the polynomial space of the Lagrange space is a superspace + of this element's polynomial space. If this element contains + basis functions that are not in any Lagrange space, this + function should return None. - Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial - space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Note that on a simplex cells, the polynomial space of Lagrange + space is a complete polynomial space, but on other cells this is + not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ @_abc.abstractproperty def embedded_subdegree(self) -> int: - """Return the degree of the maximum degree Lagrange space that is spanned by this element. + """Degree of the maximum degree Lagrange space that is spanned by this element. - This returns the degree of the highest degree Lagrange space such that the polynomial - space of the Lagrange space is a subspace of this element's polynomial space. If this - element's polynomial space does not include the constant function, this function should - return -1. + This returns the degree of the highest degree Lagrange space + such that the polynomial space of the Lagrange space is a + subspace of this element's polynomial space. If this element's + polynomial space does not include the constant function, this + function should return -1. - Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial - space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Note that on a simplex cells, the polynomial space of Lagrange + space is a complete polynomial space, but on other cells this is + not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ @@ -143,7 +149,7 @@ def components(self) -> _typing.Dict[_typing.Tuple[int, ...], int]: c_offset = 0 for e in self.sub_elements: for i, j in enumerate(np.ndindex(e.value_shape)): - components[(offset + i, )] = c_offset + e.components[j] + components[(offset + i,)] = c_offset + e.components[j] c_offset += max(e.components.values()) + 1 offset += e.value_size return components @@ -165,15 +171,31 @@ def num_sub_elements(self) -> int: class FiniteElement(AbstractFiniteElement): """A directly defined finite element.""" - __slots__ = ("_repr", "_str", "_family", "_cell", "_degree", - "_reference_value_shape", "_pullback", "_sobolev_space", - "_sub_elements", "_subdegree") + + __slots__ = ( + "_repr", + "_str", + "_family", + "_cell", + "_degree", + "_reference_value_shape", + "_pullback", + "_sobolev_space", + "_sub_elements", + "_subdegree", + ) def __init__( - self, family: str, cell: _Cell, degree: int, - reference_value_shape: _typing.Tuple[int, ...], pullback: _AbstractPullback, - sobolev_space: _SobolevSpace, sub_elements=[], - _repr: _typing.Optional[str] = None, _str: _typing.Optional[str] = None, + self, + family: str, + cell: _Cell, + degree: int, + reference_value_shape: _typing.Tuple[int, ...], + pullback: _AbstractPullback, + sobolev_space: _SobolevSpace, + sub_elements=[], + _repr: _typing.Optional[str] = None, + _str: _typing.Optional[str] = None, subdegree: _typing.Optional[int] = None, ): """Initialise a finite element. @@ -199,12 +221,14 @@ def __init__( if _repr is None: if len(sub_elements) > 0: self._repr = ( - f"ufl.finiteelement.FiniteElement(\"{family}\", {cell}, {degree}, " - f"{reference_value_shape}, {pullback}, {sobolev_space}, {sub_elements!r})") + f'ufl.finiteelement.FiniteElement("{family}", {cell}, {degree}, ' + f"{reference_value_shape}, {pullback}, {sobolev_space}, {sub_elements!r})" + ) else: self._repr = ( - f"ufl.finiteelement.FiniteElement(\"{family}\", {cell}, {degree}, " - f"{reference_value_shape}, {pullback}, {sobolev_space})") + f'ufl.finiteelement.FiniteElement("{family}", {cell}, {degree}, ' + f"{reference_value_shape}, {pullback}, {sobolev_space})" + ) else: self._repr = _repr if _str is None: @@ -247,30 +271,34 @@ def pullback(self) -> _AbstractPullback: @property def embedded_superdegree(self) -> _typing.Union[int, None]: - """Return the degree of the minimum degree Lagrange space that spans this element. + """Degree of the minimum degree Lagrange space that spans this element. - This returns the degree of the lowest degree Lagrange space such that the polynomial - space of the Lagrange space is a superspace of this element's polynomial space. If this - element contains basis functions that are not in any Lagrange space, this function should - return None. + This returns the degree of the lowest degree Lagrange space such + that the polynomial space of the Lagrange space is a superspace + of this element's polynomial space. If this element contains + basis functions that are not in any Lagrange space, this + function should return None. - Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial - space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Note that on a simplex cells, the polynomial space of Lagrange + space is a complete polynomial space, but on other cells this is + not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ return self._degree @property def embedded_subdegree(self) -> int: - """Return the degree of the maximum degree Lagrange space that is spanned by this element. + """Degree of the maximum degree Lagrange space that is spanned by this element. - This returns the degree of the highest degree Lagrange space such that the polynomial - space of the Lagrange space is a subspace of this element's polynomial space. If this - element's polynomial space does not include the constant function, this function should - return -1. + This returns the degree of the highest degree Lagrange space + such that the polynomial space of the Lagrange space is a + subspace of this element's polynomial space. If this element's + polynomial space does not include the constant function, this + function should return -1. - Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial - space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Note that on a simplex cells, the polynomial space of Lagrange + space is a complete polynomial space, but on other cells this is + not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ return self._subdegree @@ -289,8 +317,8 @@ def reference_value_shape(self) -> _typing.Tuple[int, ...]: def sub_elements(self) -> _typing.List: """Return list of sub-elements. - This function does not recurse: ie it does not extract the sub-elements - of sub-elements. + This function does not recurse: ie it does not extract the + sub-elements of sub-elements. """ return self._sub_elements @@ -301,7 +329,7 @@ class SymmetricElement(FiniteElement): def __init__( self, symmetry: _typing.Dict[_typing.Tuple[int, ...], int], - sub_elements: _typing.List[AbstractFiniteElement] + sub_elements: _typing.List[AbstractFiniteElement], ): """Initialise a symmetric element. @@ -313,7 +341,7 @@ def __init__( """ self._sub_elements = sub_elements pullback = _SymmetricPullback(self, symmetry) - reference_value_shape = (sum(e.reference_value_size for e in sub_elements), ) + reference_value_shape = (sum(e.reference_value_size for e in sub_elements),) degree = max(e.embedded_superdegree for e in sub_elements) cell = sub_elements[0].cell for e in sub_elements: @@ -322,10 +350,16 @@ def __init__( sobolev_space = max(e.sobolev_space for e in sub_elements) super().__init__( - "Symmetric element", cell, degree, reference_value_shape, pullback, - sobolev_space, sub_elements=sub_elements, + "Symmetric element", + cell, + degree, + reference_value_shape, + pullback, + sobolev_space, + sub_elements=sub_elements, _repr=(f"ufl.finiteelement.SymmetricElement({symmetry!r}, {sub_elements!r})"), - _str=f"") + _str=f"", + ) class MixedElement(FiniteElement): @@ -344,7 +378,7 @@ def __init__(self, sub_elements): for e in sub_elements: assert e.cell == cell degree = max(e.embedded_superdegree for e in sub_elements) - reference_value_shape = (sum(e.reference_value_size for e in sub_elements), ) + reference_value_shape = (sum(e.reference_value_size for e in sub_elements),) if all(isinstance(e.pullback, _IdentityPullback) for e in sub_elements): pullback = _IdentityPullback() else: @@ -352,7 +386,13 @@ def __init__(self, sub_elements): sobolev_space = max(e.sobolev_space for e in sub_elements) super().__init__( - "Mixed element", cell, degree, reference_value_shape, pullback, sobolev_space, + "Mixed element", + cell, + degree, + reference_value_shape, + pullback, + sobolev_space, sub_elements=sub_elements, _repr=f"ufl.finiteelement.MixedElement({sub_elements!r})", - _str=f"") + _str=f"", + ) diff --git a/ufl/form.py b/ufl/form.py index 3f85b709d..34aa3d753 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -34,15 +34,20 @@ def _sorted_integrals(integrals): - """Sort integrals by domain id, integral type, subdomain id for a more stable signature computation.""" + """Sort integrals for a stable signature computation. + + Sort integrals by domain id, integral type, subdomain id for a more + stable signature computation. + """ # Group integrals in multilevel dict by keys # [domain][integral_type][subdomain_id] - integrals_dict = defaultdict( - lambda: defaultdict(lambda: defaultdict(list))) + integrals_dict = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) for integral in integrals: d = integral.ufl_domain() if d is None: - raise ValueError("Each integral in a form must have a uniquely defined integration domain.") + raise ValueError( + "Each integral in a form must have a uniquely defined integration domain." + ) it = integral.integral_type() si = integral.subdomain_id() integrals_dict[d][it][si] += [integral] @@ -82,7 +87,7 @@ class BaseForm(object, metaclass=UFLType): # classes __slots__ = () _ufl_is_abstract_ = True - _ufl_required_methods_ = ('_analyze_form_arguments', '_analyze_domains', "ufl_domains") + _ufl_required_methods_ = ("_analyze_form_arguments", "_analyze_domains", "ufl_domains") def __init__(self): """Initialise.""" @@ -191,6 +196,7 @@ def __mul__(self, coefficient): """Take the action of this form on the given coefficient.""" if isinstance(coefficient, Expr): from ufl.formoperators import action + return action(self, coefficient) return NotImplemented @@ -201,6 +207,7 @@ def __ne__(self, other): def __call__(self, x): """Take the action of this form on ``x``.""" from ufl.formoperators import action + return action(self, x) def _ufl_compute_hash_(self): @@ -221,7 +228,8 @@ class Form(BaseForm): """Description of a weak form consisting of a sum of integrals over subdomains.""" __slots__ = ( - # --- List of Integral objects (a Form is a sum of these Integrals, everything else is derived) + # --- List of Integral objects (a Form is a sum of these + # Integrals, everything else is derived) "_integrals", # --- Internal variables for caching various data "_integration_domains", @@ -271,6 +279,7 @@ def __init__(self, integrals): self._base_form_operators = None from ufl.algorithms.analysis import extract_constants + self._constants = extract_constants(self) # Internal variables for caching of hash and signature after @@ -289,8 +298,9 @@ def integrals(self): def integrals_by_type(self, integral_type): """Return a sequence of all integrals with a particular domain type.""" - return tuple(integral for integral in self.integrals() - if integral.integral_type() == integral_type) + return tuple( + integral for integral in self.integrals() if integral.integral_type() == integral_type + ) def integrals_by_domain(self, domain): """Return a sequence of all integrals with a particular integration domain.""" @@ -303,7 +313,8 @@ def empty(self): def ufl_domains(self): """Return the geometric integration domains occuring in the form. - NB! This does not include domains of coefficients defined on other meshes. + NB! This does not include domains of coefficients defined on + other meshes. The return type is a tuple even if only a single domain exists. """ @@ -324,26 +335,28 @@ def ufl_domain(self): Fails if multiple domains are found. NB! This does not include domains of coefficients defined on - other meshes, look at form data for that additional - information. + other meshes, look at form data for that additional information. """ # Collect all domains domains = self.ufl_domains() # Check that all are equal TODO: don't return more than one if # all are equal? if not all(domain == domains[0] for domain in domains): - raise ValueError("Calling Form.ufl_domain() is only valid if all integrals share domain.") + raise ValueError( + "Calling Form.ufl_domain() is only valid if all integrals share domain." + ) # Return the one and only domain return domains[0] def geometric_dimension(self): """Return the geometric dimension shared by all domains and functions in this form.""" - gdims = tuple( - set(domain.geometric_dimension() for domain in self.ufl_domains())) + gdims = tuple(set(domain.geometric_dimension() for domain in self.ufl_domains())) if len(gdims) != 1: - raise ValueError("Expecting all domains and functions in a form " - f"to share geometric dimension, got {tuple(sorted(gdims))}") + raise ValueError( + "Expecting all domains and functions in a form " + f"to share geometric dimension, got {tuple(sorted(gdims))}" + ) return gdims[0] def domain_numbering(self): @@ -477,9 +490,7 @@ def __add__(self, other): # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self - elif isinstance( - other, - Zero) and not (other.ufl_shape or other.ufl_free_indices): + elif isinstance(other, Zero) and not (other.ufl_shape or other.ufl_free_indices): # Allow adding ufl Zero as a no-op, needed for sum([a,b]) return self @@ -514,6 +525,7 @@ def __mul__(self, coefficient): """UFL form operator: Take the action of this form on the given coefficient.""" if isinstance(coefficient, Expr): from ufl.formoperators import action + return action(self, coefficient) return NotImplemented @@ -559,6 +571,7 @@ def __call__(self, *args, **kwargs): warnings.warn("Coefficient %s is not in form." % ufl_err_str(f)) if repdict: from ufl.formoperators import replace + return replace(self, repdict) else: return self @@ -570,16 +583,18 @@ def __call__(self, *args, **kwargs): def __str__(self): """Compute shorter string representation of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: - # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") - # Not caching this because it can be huge + # warning("Calling str on form is potentially expensive and + # should be avoided except during debugging.") Not caching this + # because it can be huge s = "\n + ".join(str(itg) for itg in self.integrals()) return s or "" def __repr__(self): """Compute repr string of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: - # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") - # Not caching this because it can be huge + # warning("Calling repr on form is potentially expensive and + # should be avoided except during debugging.") Not caching this + # because it can be huge itgs = ", ".join(repr(itg) for itg in self.integrals()) r = "Form([" + itgs + "])" return r @@ -626,17 +641,17 @@ def _analyze_subdomain_data(self): def _analyze_form_arguments(self): """Analyze which Argument and Coefficient objects can be found in the form.""" from ufl.algorithms.analysis import extract_arguments_and_coefficients + arguments, coefficients = extract_arguments_and_coefficients(self) # Define canonical numbering of arguments and coefficients - self._arguments = tuple( - sorted(set(arguments), key=lambda x: x.number())) - self._coefficients = tuple( - sorted(set(coefficients), key=lambda x: x.count())) + self._arguments = tuple(sorted(set(arguments), key=lambda x: x.number())) + self._coefficients = tuple(sorted(set(coefficients), key=lambda x: x.count())) def _analyze_base_form_operators(self): """Analyze which BaseFormOperator objects can be found in the form.""" from ufl.algorithms.analysis import extract_base_form_operators + base_form_ops = extract_base_form_operators(self) self._base_form_operators = tuple(sorted(base_form_ops, key=lambda x: x.count())) @@ -679,6 +694,7 @@ def _compute_renumbering(self): def _compute_signature(self): """Compute signature.""" from ufl.algorithms.signature import compute_form_signature + self._signature = compute_form_signature(self, self._compute_renumbering()) @@ -698,24 +714,28 @@ class FormSum(BaseForm): arg_weights is a list of tuples of component index and weight """ - __slots__ = ("_arguments", - "_coefficients", - "_weights", - "_components", - "ufl_operands", - "_domains", - "_domain_numbering", - "_hash") - _ufl_required_methods_ = ('_analyze_form_arguments') + __slots__ = ( + "_arguments", + "_coefficients", + "_weights", + "_components", + "ufl_operands", + "_domains", + "_domain_numbering", + "_hash", + ) + _ufl_required_methods_ = "_analyze_form_arguments" def __new__(cls, *args, **kwargs): """Create a new FormSum.""" # All the components are `ZeroBaseForm` if all(component == 0 for component, _ in args): - # Assume that the arguments of all the components have consistent with each other and select - # the first one to define the arguments of `ZeroBaseForm`. - # This might not always be true but `ZeroBaseForm`'s arguments are not checked anywhere - # because we can't reliably always infer them. + # Assume that the arguments of all the components have + # consistent with each other and select the first one to + # define the arguments of `ZeroBaseForm`. + # This might not always be true but `ZeroBaseForm`'s + # arguments are not checked anywhere because we can't + # reliably always infer them. ((arg, _), *_) = args arguments = arg.arguments() return ZeroBaseForm(arguments) @@ -731,7 +751,7 @@ def __init__(self, *components): weights = [] full_components = [] - for (component, w) in filtered_components: + for component, w in filtered_components: if isinstance(component, FormSum): full_components.extend(component.components()) weights.extend([w * wc for wc in component.weights()]) @@ -762,7 +782,7 @@ def _sum_variational_components(self): var_forms = None other_components = [] new_weights = [] - for (i, component) in enumerate(self._components): + for i, component in enumerate(self._components): if isinstance(component, Form): if var_forms: var_forms = var_forms + (self._weights[i] * component) @@ -785,10 +805,8 @@ def _analyze_form_arguments(self): arguments.extend(component.arguments()) coefficients.extend(component.coefficients()) # Define canonical numbering of arguments and coefficients - self._arguments = tuple( - sorted(set(arguments), key=lambda x: x.number())) - self._coefficients = tuple( - sorted(set(coefficients), key=lambda x: x.count())) + self._arguments = tuple(sorted(set(arguments), key=lambda x: x.number())) + self._coefficients = tuple(sorted(set(coefficients), key=lambda x: x.count())) def _analyze_domains(self): """Analyze which domains can be found in FormSum.""" @@ -809,13 +827,15 @@ def equals(self, other): return False if self is other: return True - return (len(self.components()) == len(other.components()) and # noqa: W504 - all(a == b for a, b in zip(self.components(), other.components()))) + return len(self.components()) == len(other.components()) and all( + a == b for a, b in zip(self.components(), other.components()) + ) def __str__(self): """Compute shorter string representation of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: - # warning("Calling str on form is potentially expensive and should be avoided except during debugging.") + # warning("Calling str on form is potentially expensive and + # should be avoided except during debugging.") # Not caching this because it can be huge s = "\n + ".join(str(component) for component in self.components()) return s or "" @@ -823,7 +843,8 @@ def __str__(self): def __repr__(self): """Compute repr string of form. This can be huge for complicated forms.""" # Warning used for making sure we don't use this in the general pipeline: - # warning("Calling repr on form is potentially expensive and should be avoided except during debugging.") + # warning("Calling repr on form is potentially expensive and + # should be avoided except during debugging.") # Not caching this because it can be huge itgs = ", ".join(repr(component) for component in self.components()) r = "FormSum([" + itgs + "])" @@ -838,12 +859,14 @@ class ZeroBaseForm(BaseForm): used for sake of simplifying base-form expressions. """ - __slots__ = ("_arguments", - "_coefficients", - "ufl_operands", - "_hash", - # Pyadjoint compatibility - "form") + __slots__ = ( + "_arguments", + "_coefficients", + "ufl_operands", + "_hash", + # Pyadjoint compatibility + "form", + ) def __init__(self, arguments): """Initialise.""" @@ -867,7 +890,7 @@ def __eq__(self, other): if type(other) is ZeroBaseForm: if self is other: return True - return (self._arguments == other._arguments) + return self._arguments == other._arguments elif isinstance(other, (int, float)): return other == 0 else: diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 0a5f0549f..0ed83ed1c 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -11,6 +11,7 @@ try: import colorama + has_colorama = True except ImportError: has_colorama = False @@ -26,6 +27,7 @@ def __init__(self): def highest(self, o): """Return the highest precendence.""" return 0 + terminal = highest list_tensor = highest component_tensor = highest @@ -33,12 +35,14 @@ def highest(self, o): def restricted(self, o): """Return precedence of a restriced.""" return 5 + cell_avg = restricted facet_avg = restricted def call(self, o): """Return precedence of a call.""" return 10 + indexed = call min_value = call max_value = call @@ -52,6 +56,7 @@ def power(self, o): def mathop(self, o): """Return precedence of a mathop.""" return 15 + derivative = mathop trace = mathop deviatoric = mathop @@ -66,6 +71,7 @@ def not_condition(self, o): def product(self, o): """Return precedence of a product.""" return 30 + division = product # mod = product dot = product @@ -76,12 +82,14 @@ def product(self, o): def add(self, o): """Return precedence of an add.""" return 40 + # sub = add index_sum = add def lt(self, o): """Return precedence of a lt.""" return 50 + le = lt gt = lt ge = lt @@ -89,6 +97,7 @@ def lt(self, o): def eq(self, o): """Return precedence of an eq.""" return 60 + ne = eq def and_condition(self, o): @@ -106,6 +115,7 @@ def conditional(self, o): def lowest(self, o): """Return precedence of a lowest.""" return 80 + operator = lowest @@ -121,70 +131,70 @@ class UC: """An enum-like class for unicode characters.""" # Letters in this alphabet have contiguous code point numbers - bold_math_a = u"𝐚" - bold_math_A = u"𝐀" + bold_math_a = "𝐚" + bold_math_A = "𝐀" - thin_space = u"\u2009" + thin_space = "\u2009" - superscript_plus = u"⁺" - superscript_minus = u"⁻" - superscript_equals = u"⁼" - superscript_left_paren = u"⁽" - superscript_right_paren = u"⁾" + superscript_plus = "⁺" + superscript_minus = "⁻" + superscript_equals = "⁼" + superscript_left_paren = "⁽" + superscript_right_paren = "⁾" superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"] - subscript_plus = u"₊" - subscript_minus = u"₋" - subscript_equals = u"₌" - subscript_left_paren = u"₍" - subscript_right_paren = u"₎" + subscript_plus = "₊" + subscript_minus = "₋" + subscript_equals = "₌" + subscript_left_paren = "₍" + subscript_right_paren = "₎" subscript_digits = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"] - sqrt = u"√" - transpose = u"ᵀ" + sqrt = "√" + transpose = "ᵀ" - integral = u"∫" - integral_double = u"∬" - integral_triple = u"∭" - integral_contour = u"∮" - integral_surface = u"∯" - integral_volume = u"∰" + integral = "∫" + integral_double = "∬" + integral_triple = "∭" + integral_contour = "∮" + integral_surface = "∯" + integral_volume = "∰" - sum = u"∑" + sum = "∑" division_slash = "∕" - partial = u"∂" - epsilon = u"ε" - omega = u"ω" - Omega = u"Ω" - gamma = u"γ" - Gamma = u"Γ" - nabla = u"∇" - for_all = u"∀" - - dot = u"⋅" - cross_product = u"⨯" - circled_times = u"⊗" - nary_product = u"∏" - - ne = u"≠" - lt = u"<" - le = u"≤" - gt = u">" - ge = u"≥" - - logical_and = u"∧" - logical_or = u"∨" - logical_not = u"¬" - - element_of = u"∈" - not_element_of = u"∉" - - left_white_square_bracket = u"⟦" - right_white_squared_bracket = u"⟧" - left_angled_bracket = u"⟨" - right_angled_bracket = u"⟩" - left_double_angled_bracket = u"⟪" - right_double_angled_bracket = u"⟫" + partial = "∂" + epsilon = "ε" + omega = "ω" + Omega = "Ω" + gamma = "γ" + Gamma = "Γ" + nabla = "∇" + for_all = "∀" + + dot = "⋅" + cross_product = "⨯" + circled_times = "⊗" + nary_product = "∏" + + ne = "≠" + lt = "<" + le = "≤" + gt = ">" + ge = "≥" + + logical_and = "∧" + logical_or = "∨" + logical_not = "¬" + + element_of = "∈" + not_element_of = "∉" + + left_white_square_bracket = "⟦" + right_white_squared_bracket = "⟧" + left_angled_bracket = "⟨" + right_angled_bracket = "⟩" + left_double_angled_bracket = "⟪" + right_double_angled_bracket = "⟫" combining_right_arrow_above = "\u20D7" combining_overline = "\u0305" @@ -193,9 +203,9 @@ class UC: def bolden_letter(c): """Bolden a letter.""" if ord("A") <= ord(c) <= ord("Z"): - c = chr(ord(c) - ord(u"A") + ord(UC.bold_math_A)) + c = chr(ord(c) - ord("A") + ord(UC.bold_math_A)) elif ord("a") <= ord(c) <= ord("z"): - c = chr(ord(c) - ord(u"a") + ord(UC.bold_math_a)) + c = chr(ord(c) - ord("a") + ord(UC.bold_math_a)) return c @@ -211,12 +221,12 @@ def subscript_digit(digit): def bolden_string(s): """Bolden a string.""" - return u"".join(bolden_letter(c) for c in s) + return "".join(bolden_letter(c) for c in s) def overline_string(f): """Overline a string.""" - return u"".join(f"{c}{UC.combining_overline}" for c in f) + return "".join(f"{c}{UC.combining_overline}" for c in f) def subscript_number(number): @@ -245,12 +255,7 @@ def measure_font(dx): return bolden_string(dx) -integral_by_dim = { - 3: UC.integral_triple, - 2: UC.integral_double, - 1: UC.integral, - 0: UC.integral -} +integral_by_dim = {3: UC.integral_triple, 2: UC.integral_double, 1: UC.integral, 0: UC.integral} integral_type_to_codim = { "cell": 0, @@ -366,8 +371,7 @@ def form2unicode(form, formdata): lines = [] integrals = form.integrals() for itg in integrals: - integrand_string = expression2unicode( - itg.integrand(), argument_names, coefficient_names) + integrand_string = expression2unicode(itg.integrand(), argument_names, coefficient_names) istr, dxstr = get_integral_symbol(itg.integral_type(), itg.ufl_domain(), itg.subdomain_id()) @@ -783,7 +787,7 @@ def conditional(self, o, c, t, f): f = par(t) If = opfont("if") Else = opfont("else") - return " ".join((t, If, c, Else, f)) + return f"{t} {If} {c} {Else} {f}" def min_value(self, o, a, b): """Format an min_value.""" diff --git a/ufl/formoperators.py b/ufl/formoperators.py index d5cc31117..6616c7fff 100644 --- a/ufl/formoperators.py +++ b/ufl/formoperators.py @@ -11,17 +11,32 @@ from ufl.action import Action from ufl.adjoint import Adjoint -from ufl.algorithms import replace # noqa: F401 -from ufl.algorithms import (compute_energy_norm, compute_form_action, compute_form_adjoint, compute_form_functional, - compute_form_lhs, compute_form_rhs, expand_derivatives, extract_arguments, formsplitter) +from ufl.algorithms import ( + compute_energy_norm, + compute_form_action, + compute_form_adjoint, + compute_form_functional, + compute_form_lhs, + compute_form_rhs, + expand_derivatives, + extract_arguments, + formsplitter, + replace, # noqa: F401 +) from ufl.argument import Argument from ufl.coefficient import Coefficient, Cofunction from ufl.constantvalue import as_ufl, is_true_ufl_scalar from ufl.core.base_form_operator import BaseFormOperator from ufl.core.expr import Expr, ufl_err_str from ufl.core.multiindex import FixedIndex, MultiIndex -from ufl.differentiation import (BaseFormCoordinateDerivative, BaseFormDerivative, BaseFormOperatorCoordinateDerivative, - BaseFormOperatorDerivative, CoefficientDerivative, CoordinateDerivative) +from ufl.differentiation import ( + BaseFormCoordinateDerivative, + BaseFormDerivative, + BaseFormOperatorCoordinateDerivative, + BaseFormOperatorDerivative, + CoefficientDerivative, + CoordinateDerivative, +) from ufl.exprcontainers import ExprList, ExprMapping from ufl.finiteelement import MixedElement from ufl.form import BaseForm, Form, FormSum, ZeroBaseForm, as_form @@ -107,8 +122,9 @@ def action(form, coefficient=None, derivatives_expanded=None): become expensive -> `derivatives_expanded` enables to use caching mechanisms to avoid that. """ form = as_form(form) - is_coefficient_valid = (not isinstance(coefficient, BaseForm) or - (isinstance(coefficient, BaseFormOperator) and len(coefficient.arguments()) == 1)) + is_coefficient_valid = not isinstance(coefficient, BaseForm) or ( + isinstance(coefficient, BaseFormOperator) and len(coefficient.arguments()) == 1 + ) # Can't expand derivatives on objects that are not Form or Expr (e.g. Matrix) if isinstance(form, (Form, BaseFormOperator)) and is_coefficient_valid: if not derivatives_expanded: @@ -191,8 +207,12 @@ def _handle_derivative_arguments(form, coefficient, argument): if argument is None: # Try to create argument if not provided - if not all(isinstance(c, (Coefficient, Cofunction, BaseFormOperator)) for c in coefficients): - raise ValueError("Can only create arguments automatically for non-indexed coefficients.") + if not all( + isinstance(c, (Coefficient, Cofunction, BaseFormOperator)) for c in coefficients + ): + raise ValueError( + "Can only create arguments automatically for non-indexed coefficients." + ) # Get existing arguments from form and position the new one # with the next argument number @@ -241,7 +261,7 @@ def _handle_derivative_arguments(form, coefficient, argument): # Build mapping from coefficient to argument m = {} - for (c, a) in zip(coefficients, arguments): + for c, a in zip(coefficients, arguments): if c.ufl_shape != a.ufl_shape: raise ValueError("Coefficient and argument shapes do not match!") if isinstance(c, (Coefficient, Cofunction, BaseFormOperator, SpatialCoordinate)): @@ -297,12 +317,16 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): """ if isinstance(form, FormSum): # Distribute derivative over FormSum components - return FormSum(*[(derivative(component, coefficient, argument, coefficient_derivatives), 1) - for component in form.components()]) + return FormSum( + *[ + (derivative(component, coefficient, argument, coefficient_derivatives), 1) + for component in form.components() + ] + ) elif isinstance(form, Adjoint): # Is `derivative(Adjoint(A), ...)` with A a 2-form even legal ? # -> If yes, what's the right thing to do here ? - raise NotImplementedError('Adjoint derivative is not supported.') + raise NotImplementedError("Adjoint derivative is not supported.") elif isinstance(form, Action): # Push derivative through Action slots left, right = form.ufl_operands @@ -314,13 +338,15 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): dleft = derivative(left, coefficient, argument, coefficient_derivatives) dright = derivative(right, coefficient, argument, coefficient_derivatives) # Leibniz formula - return (action(adjoint(dleft, derivatives_expanded=True), right, derivatives_expanded=True) - + action(left, dright, derivatives_expanded=True)) + return action( + adjoint(dleft, derivatives_expanded=True), right, derivatives_expanded=True + ) + action(left, dright, derivatives_expanded=True) else: - raise NotImplementedError('Action derivative not supported when the left argument is not a 1-form.') + raise NotImplementedError( + "Action derivative not supported when the left argument is not a 1-form." + ) - coefficients, arguments = _handle_derivative_arguments(form, coefficient, - argument) + coefficients, arguments = _handle_derivative_arguments(form, coefficient, argument) if coefficient_derivatives is None: coefficient_derivatives = ExprMapping() else: @@ -334,38 +360,46 @@ def derivative(form, coefficient, argument=None, coefficient_derivatives=None): integrals = [] for itg in form.integrals(): if isinstance(coefficient, SpatialCoordinate): - fd = CoordinateDerivative(itg.integrand(), coefficients, - arguments, coefficient_derivatives) - elif isinstance(coefficient, BaseForm) and not isinstance(coefficient, BaseFormOperator): + fd = CoordinateDerivative( + itg.integrand(), coefficients, arguments, coefficient_derivatives + ) + elif isinstance(coefficient, BaseForm) and not isinstance( + coefficient, BaseFormOperator + ): # Make the `ZeroBaseForm` arguments arguments = form.arguments() + coefficient.arguments() return ZeroBaseForm(arguments) else: - fd = CoefficientDerivative(itg.integrand(), coefficients, - arguments, coefficient_derivatives) + fd = CoefficientDerivative( + itg.integrand(), coefficients, arguments, coefficient_derivatives + ) integrals.append(itg.reconstruct(fd)) return Form(integrals) elif isinstance(form, BaseFormOperator): if not isinstance(coefficient, SpatialCoordinate): - return BaseFormOperatorDerivative(form, coefficients, arguments, coefficient_derivatives) + return BaseFormOperatorDerivative( + form, coefficients, arguments, coefficient_derivatives + ) else: - return BaseFormOperatorCoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) + return BaseFormOperatorCoordinateDerivative( + form, coefficients, arguments, coefficient_derivatives + ) elif isinstance(form, BaseForm): if not isinstance(coefficient, SpatialCoordinate): return BaseFormDerivative(form, coefficients, arguments, coefficient_derivatives) else: - return BaseFormCoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) + return BaseFormCoordinateDerivative( + form, coefficients, arguments, coefficient_derivatives + ) elif isinstance(form, Expr): # What we got was in fact an integrand if not isinstance(coefficient, SpatialCoordinate): - return CoefficientDerivative(form, coefficients, - arguments, coefficient_derivatives) + return CoefficientDerivative(form, coefficients, arguments, coefficient_derivatives) else: - return CoordinateDerivative(form, coefficients, - arguments, coefficient_derivatives) + return CoordinateDerivative(form, coefficients, arguments, coefficient_derivatives) raise ValueError(f"Invalid argument type {type(form)}.") @@ -397,8 +431,8 @@ def sensitivity_rhs(a, u, L, v): :: v = variable(v_expression) - L = IL(v)*dx - a = Ia(v)*dx + L = IL(v) * dx + a = Ia(v) * dx where ``IL`` and ``Ia`` are integrand expressions. Define a ``Coefficient u`` representing the solution @@ -425,9 +459,17 @@ def sensitivity_rhs(a, u, L, v): dL = sensitivity_rhs(a, u, L, v) """ - if not (isinstance(a, Form) and isinstance(u, Coefficient) and isinstance(L, Form) and isinstance(v, Variable)): - raise ValueError("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") + if not ( + isinstance(a, Form) + and isinstance(u, Coefficient) + and isinstance(L, Form) + and isinstance(v, Variable) + ): + raise ValueError( + "Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable)." + ) if not is_true_ufl_scalar(v): raise ValueError("Expecting scalar variable.") from ufl.operators import diff + return diff(L, v) - action(diff(a, v), u) diff --git a/ufl/functionspace.py b/ufl/functionspace.py index 38839bc20..cc047523c 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -49,7 +49,9 @@ def __init__(self, domain, element, label=""): try: domain_cell = domain.ufl_cell() except AttributeError: - raise ValueError("Expected non-abstract domain for initalization of function space.") + 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.") @@ -199,14 +201,15 @@ def ufl_sub_spaces(self): def _ufl_hash_data_(self): """UFL hash data.""" - return ("TensorProductFunctionSpace",) \ - + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) + return ("TensorProductFunctionSpace",) + tuple( + V._ufl_hash_data_() for V in self.ufl_sub_spaces() + ) def _ufl_signature_data_(self, renumbering): """UFL signature data.""" - return ("TensorProductFunctionSpace",) \ - + tuple(V._ufl_signature_data_(renumbering) - for V in self.ufl_sub_spaces()) + return ("TensorProductFunctionSpace",) + tuple( + V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces() + ) def __repr__(self): """Representation.""" @@ -232,10 +235,8 @@ def __init__(self, *args): raise ValueError("Expecting BaseFunctionSpace objects") # A mixed FS is only primal/dual if all the subspaces are primal/dual" - self._primal = all([is_primal(subspace) - for subspace in self._ufl_function_spaces]) - self._dual = all([is_dual(subspace) - for subspace in self._ufl_function_spaces]) + self._primal = all([is_primal(subspace) for subspace in self._ufl_function_spaces]) + self._dual = all([is_dual(subspace) for subspace in self._ufl_function_spaces]) def ufl_sub_spaces(self): """Return ufl sub spaces.""" @@ -257,13 +258,13 @@ def dual(self, *args): the original components in the other positions. """ if args: - spaces = [space.dual() if i in args else space - for i, space in enumerate(self._ufl_function_spaces)] + spaces = [ + space.dual() if i in args else space + for i, space in enumerate(self._ufl_function_spaces) + ] return MixedFunctionSpace(*spaces) else: - return MixedFunctionSpace( - *[space.dual()for space in self._ufl_function_spaces] - ) + return MixedFunctionSpace(*[space.dual() for space in self._ufl_function_spaces]) def ufl_elements(self): """Return ufl elements.""" @@ -277,7 +278,8 @@ def ufl_element(self): raise ValueError( "Found multiple elements. Cannot return only one. " "Consider building a FunctionSpace from a MixedElement " - "in case of homogeneous dimension.") + "in case of homogeneous dimension." + ) def ufl_domains(self): """Return ufl domains.""" @@ -302,14 +304,13 @@ def num_sub_spaces(self): def _ufl_hash_data_(self): """UFL hash data.""" - return ("MixedFunctionSpace",) \ - + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) + return ("MixedFunctionSpace",) + tuple(V._ufl_hash_data_() for V in self.ufl_sub_spaces()) def _ufl_signature_data_(self, renumbering): """UFL signature data.""" - return ("MixedFunctionSpace",) \ - + tuple(V._ufl_signature_data_(renumbering) - for V in self.ufl_sub_spaces()) + return ("MixedFunctionSpace",) + tuple( + V._ufl_signature_data_(renumbering) for V in self.ufl_sub_spaces() + ) def __repr__(self): """Representation.""" diff --git a/ufl/geometry.py b/ufl/geometry.py index 9566e8aa6..1acaf40a2 100644 --- a/ufl/geometry.py +++ b/ufl/geometry.py @@ -74,6 +74,7 @@ # --- Expression node types + @ufl_type(is_abstract=True) class GeometricQuantity(Terminal): """Geometric quantity.""" @@ -137,6 +138,7 @@ class GeometricFacetQuantity(GeometricQuantity): # --- Coordinate represented in different coordinate systems + @ufl_type() class SpatialCoordinate(GeometricCellQuantity): """The coordinate in a domain. @@ -244,6 +246,7 @@ def is_cellwise_constant(self): # --- Origin of coordinate systems in larger coordinate systems + @ufl_type() class CellOrigin(GeometricCellQuantity): """The spatial coordinate corresponding to origin of a reference cell.""" @@ -292,6 +295,7 @@ def ufl_shape(self): # --- Jacobians of mappings between coordinate systems + @ufl_type() class Jacobian(GeometricCellQuantity): r"""The Jacobian of the mapping from reference cell to spatial coordinates. @@ -534,11 +538,13 @@ def is_cellwise_constant(self): # --- Determinants (signed or pseudo) of geometry mapping Jacobians + @ufl_type() class JacobianDeterminant(GeometricCellQuantity): """The determinant of the Jacobian. - Represents the signed determinant of a square Jacobian or the pseudo-determinant of a non-square Jacobian. + Represents the signed determinant of a square Jacobian or the + pseudo-determinant of a non-square Jacobian. """ __slots__ = () @@ -580,11 +586,13 @@ def is_cellwise_constant(self): # --- Inverses (signed or pseudo) of geometry mapping Jacobians + @ufl_type() class JacobianInverse(GeometricCellQuantity): """The inverse of the Jacobian. - Represents the inverse of a square Jacobian or the pseudo-inverse of a non-square Jacobian. + Represents the inverse of a square Jacobian or the pseudo-inverse of + a non-square Jacobian. """ __slots__ = () @@ -616,7 +624,9 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - raise ValueError("FacetJacobianInverse is only defined for topological dimensions >= 2.") + raise ValueError( + "FacetJacobianInverse is only defined for topological dimensions >= 2." + ) @property def ufl_shape(self): @@ -644,7 +654,9 @@ def __init__(self, domain): GeometricFacetQuantity.__init__(self, domain) t = self._domain.topological_dimension() if t < 2: - raise ValueError("CellFacetJacobianInverse is only defined for topological dimensions >= 2.") + raise ValueError( + "CellFacetJacobianInverse is only defined for topological dimensions >= 2." + ) @property def ufl_shape(self): @@ -660,6 +672,7 @@ def is_cellwise_constant(self): # --- Types representing normal or tangent vectors + @ufl_type() class FacetNormal(GeometricFacetQuantity): """The outwards pointing normal vector of the current facet.""" @@ -718,7 +731,9 @@ def ufl_shape(self): t = self._domain.topological_dimension() return (t,) -# --- Types representing measures of the cell and entities of the cell, typically used for stabilisation terms + +# --- Types representing measures of the cell and entities of the cell, +# typically used for stabilisation terms # TODO: Clean up this set of types? Document! @@ -805,6 +820,7 @@ class MaxFacetEdgeLength(GeometricFacetQuantity): # --- Types representing other stuff + @ufl_type() class CellOrientation(GeometricCellQuantity): """The orientation (+1/-1) of the current cell. diff --git a/ufl/index_combination_utils.py b/ufl/index_combination_utils.py index 1f7682ae0..8bd5087a8 100644 --- a/ufl/index_combination_utils.py +++ b/ufl/index_combination_utils.py @@ -233,4 +233,9 @@ def merge_overlapping_indices(afi, afid, bfi, bfid): if len(free_indices) + 2 * len(repeated_indices) != an + bn: raise ValueError("Expecting only twice repeated indices.") - return tuple(free_indices), tuple(index_dimensions), tuple(repeated_indices), tuple(repeated_index_dimensions) + return ( + tuple(free_indices), + tuple(index_dimensions), + tuple(repeated_indices), + tuple(repeated_index_dimensions), + ) diff --git a/ufl/indexed.py b/ufl/indexed.py index b22f3d7ef..9f6525863 100644 --- a/ufl/indexed.py +++ b/ufl/indexed.py @@ -63,10 +63,13 @@ def __init__(self, expression, multiindex): if len(shape) != len(multiindex): raise ValueError( f"Invalid number of indices ({len(multiindex)}) for tensor " - f"expression of rank {len(expression.ufl_shape)}:\n {ufl_err_str(expression)}") - if any(int(di) >= int(si) or int(di) < 0 - for si, di in zip(shape, multiindex) - if isinstance(di, FixedIndex)): + f"expression of rank {len(expression.ufl_shape)}:\n {ufl_err_str(expression)}" + ) + if any( + int(di) >= int(si) or int(di) < 0 + for si, di in zip(shape, multiindex) + if isinstance(di, FixedIndex) + ): raise ValueError("Fixed index out of range!") # Build tuples of free index ids and dimensions @@ -99,8 +102,7 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): def __str__(self): """Format as a string.""" - return "%s[%s]" % (parstr(self.ufl_operands[0], self), - self.ufl_operands[1]) + return "%s[%s]" % (parstr(self.ufl_operands[0], self), self.ufl_operands[1]) def __getitem__(self, key): """Get an item.""" @@ -108,5 +110,7 @@ def __getitem__(self, key): # So that one doesn't have to special case indexing of # expressions without shape. return self - raise ValueError(f"Attempting to index with {ufl_err_str(key)}, " - f"but object is already indexed: {ufl_err_str(self)}") + raise ValueError( + f"Attempting to index with {ufl_err_str(key)}, " + f"but object is already indexed: {ufl_err_str(self)}" + ) diff --git a/ufl/indexsum.py b/ufl/indexsum.py index b13829484..df636b58c 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -36,25 +36,25 @@ def __new__(cls, summand, index): # Simplification to zero if isinstance(summand, Zero): sh = summand.ufl_shape - j, = index + (j,) = index fi = summand.ufl_free_indices fid = summand.ufl_index_dimensions pos = fi.index(j.count()) - fi = fi[:pos] + fi[pos + 1:] - fid = fid[:pos] + fid[pos + 1:] + fi = fi[:pos] + fi[pos + 1 :] + fid = fid[:pos] + fid[pos + 1 :] return Zero(sh, fi, fid) return Operator.__new__(cls) def __init__(self, summand, index): """Initialise.""" - j, = index + (j,) = index fi = summand.ufl_free_indices fid = summand.ufl_index_dimensions pos = fi.index(j.count()) self._dimension = fid[pos] - self.ufl_free_indices = fi[:pos] + fi[pos + 1:] - self.ufl_index_dimensions = fid[:pos] + fid[pos + 1:] + self.ufl_free_indices = fi[:pos] + fi[pos + 1 :] + self.ufl_index_dimensions = fid[:pos] + fid[pos + 1 :] Operator.__init__(self, (summand, index)) def index(self): @@ -72,16 +72,14 @@ def ufl_shape(self): def evaluate(self, x, mapping, component, index_values): """Evaluate.""" - i, = self.ufl_operands[1] + (i,) = self.ufl_operands[1] tmp = 0 for k in range(self._dimension): index_values.push(i, k) - tmp += self.ufl_operands[0].evaluate(x, mapping, component, - index_values) + tmp += self.ufl_operands[0].evaluate(x, mapping, component, index_values) index_values.pop() return tmp def __str__(self): """Format as a string.""" - return "sum_{%s} %s " % (str(self.ufl_operands[1]), - parstr(self.ufl_operands[0], self)) + return "sum_{%s} %s " % (str(self.ufl_operands[1]), parstr(self.ufl_operands[0], self)) diff --git a/ufl/integral.py b/ufl/integral.py index d680f5a77..510f4ebd5 100644 --- a/ufl/integral.py +++ b/ufl/integral.py @@ -12,7 +12,6 @@ import ufl from ufl.checks import is_python_scalar, is_scalar_constant_expression from ufl.core.expr import Expr -from ufl.measure import Measure # noqa from ufl.protocols import id_or_none # Export list for ufl.classes @@ -22,11 +21,16 @@ class Integral(object): """An integral over a single domain.""" - __slots__ = ("_integrand", "_integral_type", "_ufl_domain", "_subdomain_id", "_metadata", "_subdomain_data") + __slots__ = ( + "_integrand", + "_integral_type", + "_ufl_domain", + "_subdomain_id", + "_metadata", + "_subdomain_data", + ) - def __init__( - self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data - ): + def __init__(self, integrand, integral_type, domain, subdomain_id, metadata, subdomain_data): """Initialise.""" if not isinstance(integrand, Expr): raise ValueError("Expecting integrand to be an Expr instance.") @@ -38,9 +42,13 @@ def __init__( self._subdomain_data = subdomain_data def reconstruct( - self, integrand=None, - integral_type=None, domain=None, subdomain_id=None, - metadata=None, subdomain_data=None + self, + integrand=None, + integral_type=None, + domain=None, + subdomain_id=None, + metadata=None, + subdomain_data=None, ): """Construct a new Integral object with some properties replaced with new values. @@ -100,8 +108,9 @@ def __mul__(self, scalar): def __rmul__(self, scalar): """Multiply.""" if not is_scalar_constant_expression(scalar): - raise ValueError("An integral can only be multiplied by a " - "globally constant scalar expression.") + raise ValueError( + "An integral can only be multiplied by a globally constant scalar expression." + ) return self.reconstruct(scalar * self._integrand) def __str__(self): @@ -113,24 +122,33 @@ def __str__(self): def __repr__(self): """Representation.""" - return (f"Integral({self._integrand!r}, {self._integral_type!r}, {self._ufl_domain!r}, " - f"{self._subdomain_id!r}, {self._metadata!r}, {self._subdomain_data!r})") + return ( + f"Integral({self._integrand!r}, {self._integral_type!r}, {self._ufl_domain!r}, " + f"{self._subdomain_id!r}, {self._metadata!r}, {self._subdomain_data!r})" + ) def __eq__(self, other): """Check equality.""" - return (isinstance(other, Integral) and self._integral_type == other._integral_type and # noqa: W504 - self._ufl_domain == other._ufl_domain and self._subdomain_id == other._subdomain_id and # noqa: W504 - self._integrand == other._integrand and self._metadata == other._metadata and # noqa: W504 - id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data)) + return ( + isinstance(other, Integral) + and self._integral_type == other._integral_type + and self._ufl_domain == other._ufl_domain + and self._subdomain_id == other._subdomain_id + and self._integrand == other._integrand + and self._metadata == other._metadata + and id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) + ) def __hash__(self): """Hash.""" # Assuming few collisions by ignoring hash(self._metadata) (a # dict is not hashable but we assume it is immutable in # practice) - hashdata = (hash(self._integrand), - self._integral_type, - hash(self._ufl_domain), - self._subdomain_id, - id_or_none(self._subdomain_data)) + hashdata = ( + hash(self._integrand), + self._integral_type, + hash(self._ufl_domain), + self._subdomain_id, + id_or_none(self._subdomain_data), + ) return hash(hashdata) diff --git a/ufl/mathfunctions.py b/ufl/mathfunctions.py index 704304d6c..4c2d87996 100644 --- a/ufl/mathfunctions.py +++ b/ufl/mathfunctions.py @@ -13,8 +13,16 @@ import numbers import warnings -from ufl.constantvalue import (ComplexValue, ConstantValue, FloatValue, IntValue, RealValue, Zero, as_ufl, - is_true_ufl_scalar) +from ufl.constantvalue import ( + ComplexValue, + ConstantValue, + FloatValue, + IntValue, + RealValue, + Zero, + as_ufl, + is_true_ufl_scalar, +) from ufl.core.operator import Operator from ufl.core.ufl_type import ufl_type @@ -42,6 +50,7 @@ # --- Function representations --- + @ufl_type(is_abstract=True, is_scalar=True, num_ops=1) class MathFunction(Operator): """Base class for all unary scalar math functions.""" @@ -65,7 +74,9 @@ def evaluate(self, x, mapping, component, index_values): else: res = getattr(cmath, self._name)(a) except ValueError: - warnings.warn('Value error in evaluation of function %s with argument %s.' % (self._name, a)) + warnings.warn( + "Value error in evaluation of function %s with argument %s." % (self._name, a) + ) raise return res @@ -344,9 +355,11 @@ def evaluate(self, x, mapping, component, index_values): try: res = math.atan2(a, b) except TypeError: - raise ValueError('Atan2 does not support complex numbers.') + raise ValueError("Atan2 does not support complex numbers.") except ValueError: - warnings.warn('Value error in evaluation of function atan2 with arguments %s, %s.' % (a, b)) + warnings.warn( + "Value error in evaluation of function atan2 with arguments %s, %s." % (a, b) + ) raise return res @@ -383,7 +396,7 @@ def evaluate(self, x, mapping, component, index_values): class BesselFunction(Operator): """Base class for all bessel functions.""" - __slots__ = ("_name") + __slots__ = "_name" def __init__(self, name, nu, argument): """Initialise.""" @@ -410,22 +423,22 @@ def evaluate(self, x, mapping, component, index_values): try: import scipy.special except ImportError: - raise ValueError("You must have scipy installed to evaluate bessel functions in python.") + raise ValueError( + "You must have scipy installed to evaluate bessel functions in python." + ) name = self._name[-1] if isinstance(self.ufl_operands[0], IntValue): nu = int(self.ufl_operands[0]) - functype = 'n' if name != 'i' else 'v' + functype = "n" if name != "i" else "v" else: - nu = self.ufl_operands[0].evaluate(x, mapping, component, - index_values) - functype = 'v' + nu = self.ufl_operands[0].evaluate(x, mapping, component, index_values) + functype = "v" func = getattr(scipy.special, name + functype) return func(nu, a) def __str__(self): """Format as a string.""" - return "%s(%s, %s)" % (self._name, self.ufl_operands[0], - self.ufl_operands[1]) + return "%s(%s, %s)" % (self._name, self.ufl_operands[0], self.ufl_operands[1]) @ufl_type() diff --git a/ufl/matrix.py b/ufl/matrix.py index 7fb1f5c07..039d70e23 100644 --- a/ufl/matrix.py +++ b/ufl/matrix.py @@ -30,12 +30,12 @@ class Matrix(BaseForm, Counted): "_ufl_shape", "_arguments", "_coefficients", - "_domains") + "_domains", + ) def __getnewargs__(self): """Get new args.""" - return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], - self._count) + return (self._ufl_function_spaces[0], self._ufl_function_spaces[1], self._count) def __init__(self, row_space, column_space, count=None): """Initialise.""" @@ -53,7 +53,10 @@ def __init__(self, row_space, column_space, count=None): self.ufl_operands = () self._domains = None self._hash = None - self._repr = f"Matrix({self._ufl_function_spaces[0]!r}, {self._ufl_function_spaces[1]!r}, {self._count!r})" + self._repr = ( + f"Matrix({self._ufl_function_spaces[0]!r} " + f"{self._ufl_function_spaces[1]!r}, {self._count!r})" + ) def ufl_function_spaces(self): """Get the tuple of function spaces of this coefficient.""" @@ -61,8 +64,10 @@ def ufl_function_spaces(self): def _analyze_form_arguments(self): """Define arguments of a matrix when considered as a form.""" - self._arguments = (Argument(self._ufl_function_spaces[0], 0), - Argument(self._ufl_function_spaces[1], 1)) + self._arguments = ( + Argument(self._ufl_function_spaces[0], 0), + Argument(self._ufl_function_spaces[1], 1), + ) self._coefficients = () def _analyze_domains(self): @@ -96,4 +101,6 @@ def equals(self, other): return False if self is other: return True - return self._count == other._count and self._ufl_function_spaces == other._ufl_function_spaces + return ( + self._count == other._count and self._ufl_function_spaces == other._ufl_function_spaces + ) diff --git a/ufl/measure.py b/ufl/measure.py index 97d719501..08f1fa803 100644 --- a/ufl/measure.py +++ b/ufl/measure.py @@ -29,20 +29,22 @@ _integral_types = [ # === Integration over full topological dimension: ("cell", "dx"), # Over cells of a mesh - # === Integration over topological dimension - 1: ("exterior_facet", "ds"), # Over one-sided exterior facets of a mesh ("interior_facet", "dS"), # Over two-sided facets between pairs of adjacent cells of a mesh - # === Integration over topological dimension 0 ("vertex", "dP"), # Over vertices of a mesh - # === Integration over custom domains ("custom", "dc"), # Over custom user-defined domains (run-time quadrature points) ("cutcell", "dC"), # Over a cell with some part cut away (run-time quadrature points) - ("interface", "dI"), # Over a facet fragment overlapping with two or more cells (run-time quadrature points) - ("overlap", "dO"), # Over a cell fragment overlapping with two or more cells (run-time quadrature points) - + ( + "interface", + "dI", + ), # Over a facet fragment overlapping with two or more cells (run-time quadrature points) + ( + "overlap", + "dO", + ), # Over a cell fragment overlapping with two or more cells (run-time quadrature points) # === Firedrake specifics: ("exterior_facet_bottom", "ds_b"), # Over bottom facets on extruded mesh ("exterior_facet_top", "ds_t"), # Over top facets on extruded mesh @@ -73,8 +75,7 @@ def register_integral_type(integral_type, measure_name): def as_integral_type(integral_type): """Map short name to long name and require a valid one.""" integral_type = integral_type.replace(" ", "_") - integral_type = measure_name_to_integral_type.get(integral_type, - integral_type) + integral_type = measure_name_to_integral_type.get(integral_type, integral_type) if integral_type not in integral_type_to_measure_name: raise ValueError("Invalid integral_type.") return integral_type @@ -100,12 +101,14 @@ class Measure(object): __slots__ = ("_integral_type", "_domain", "_subdomain_id", "_metadata", "_subdomain_data") - def __init__(self, - integral_type, # "dx" etc - domain=None, - subdomain_id="everywhere", - metadata=None, - subdomain_data=None): + def __init__( + self, + integral_type, # "dx" etc + domain=None, + subdomain_id="everywhere", + metadata=None, + subdomain_data=None, + ): """Initialise. Args: @@ -177,12 +180,9 @@ def metadata(self): """ return self._metadata - def reconstruct(self, - integral_type=None, - subdomain_id=None, - domain=None, - metadata=None, - subdomain_data=None): + def reconstruct( + self, integral_type=None, subdomain_id=None, domain=None, metadata=None, subdomain_data=None + ): """Construct a new Measure object with some properties replaced with new values. Example: @@ -202,9 +202,13 @@ def reconstruct(self, metadata = self.metadata() if subdomain_data is None: subdomain_data = self.subdomain_data() - return Measure(self.integral_type(), - domain=domain, subdomain_id=subdomain_id, - metadata=metadata, subdomain_data=subdomain_data) + return Measure( + self.integral_type(), + domain=domain, + subdomain_id=subdomain_id, + metadata=metadata, + subdomain_data=subdomain_data, + ) def subdomain_data(self): """Return the integral subdomain_data. @@ -218,12 +222,18 @@ def subdomain_data(self): # Note: Must keep the order of the first two arguments here # (subdomain_id, metadata) for backwards compatibility, because # some tutorials write e.g. dx(0, {...}) to set metadata. - def __call__(self, subdomain_id=None, metadata=None, domain=None, - subdomain_data=None, degree=None, scheme=None): + def __call__( + self, + subdomain_id=None, + metadata=None, + domain=None, + subdomain_data=None, + degree=None, + scheme=None, + ): """Reconfigure measure with new domain specification or metadata.""" # Let syntax dx() mean integral over everywhere - all_args = (subdomain_id, metadata, domain, subdomain_data, - degree, scheme) + all_args = (subdomain_id, metadata, domain, subdomain_data, degree, scheme) if all(arg is None for arg in all_args): return self.reconstruct(subdomain_id="everywhere") @@ -234,7 +244,9 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, isinstance(subdomain_id, AbstractDomain) or hasattr(subdomain_id, "ufl_domain") ): if domain is not None: - raise ValueError("Ambiguous: setting domain both as keyword argument and first argument.") + raise ValueError( + "Ambiguous: setting domain both as keyword argument and first argument." + ) subdomain_id, domain = "everywhere", subdomain_id # If degree or scheme is set, inject into metadata. This is a @@ -250,9 +262,12 @@ def __call__(self, subdomain_id=None, metadata=None, domain=None, # If we get any keywords, use them to reconstruct Measure. # Note that if only one argument is given, it is the # subdomain_id, e.g. dx(3) == dx(subdomain_id=3) - return self.reconstruct(subdomain_id=subdomain_id, domain=domain, - metadata=metadata, - subdomain_data=subdomain_data) + return self.reconstruct( + subdomain_id=subdomain_id, + domain=domain, + metadata=metadata, + subdomain_data=subdomain_data, + ) def __str__(self): """Format as a string.""" @@ -268,7 +283,7 @@ def __str__(self): if self._subdomain_data is not None: args.append("subdomain_data=%s" % (self._subdomain_data,)) - return "%s(%s)" % (name, ', '.join(args)) + return "%s(%s)" % (name, ", ".join(args)) def __repr__(self): """Return a repr string for this Measure.""" @@ -284,17 +299,19 @@ def __repr__(self): if self._subdomain_data is not None: args.append("subdomain_data=%s" % repr(self._subdomain_data)) - r = "%s(%s)" % (type(self).__name__, ', '.join(args)) + r = "%s(%s)" % (type(self).__name__, ", ".join(args)) return r def __hash__(self): """Return a hash value for this Measure.""" metadata_hashdata = tuple(sorted((k, id(v)) for k, v in list(self._metadata.items()))) - hashdata = (self._integral_type, - self._subdomain_id, - hash(self._domain), - metadata_hashdata, - id_or_none(self._subdomain_data)) + hashdata = ( + self._integral_type, + self._subdomain_id, + hash(self._domain), + metadata_hashdata, + id_or_none(self._subdomain_data), + ) return hash(hashdata) def __eq__(self, other): @@ -302,10 +319,14 @@ def __eq__(self, other): sorted_metadata = sorted((k, id(v)) for k, v in list(self._metadata.items())) sorted_other_metadata = sorted((k, id(v)) for k, v in list(other._metadata.items())) - return (isinstance(other, Measure) and self._integral_type == other._integral_type and # noqa: W504 - self._subdomain_id == other._subdomain_id and self._domain == other._domain and # noqa: W504 - id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) and # noqa: W504 - sorted_metadata == sorted_other_metadata) + return ( + isinstance(other, Measure) + and self._integral_type == other._integral_type + and self._subdomain_id == other._subdomain_id + and self._domain == other._domain + and id_or_none(self._subdomain_data) == id_or_none(other._subdomain_data) + and sorted_metadata == sorted_other_metadata + ) def __add__(self, other): """Add two measures (self+other). @@ -361,18 +382,33 @@ def __rmul__(self, integrand): raise ValueError( "Can only integrate scalar expressions. The integrand is a " f"tensor expression with value shape {integrand.ufl_shape} and " - f"free indices with labels {integrand.ufl_free_indices}.") + f"free indices with labels {integrand.ufl_free_indices}." + ) # If we have a tuple of domain ids build the integrals one by # one and construct as a Form in one go. subdomain_id = self.subdomain_id() if isinstance(subdomain_id, tuple): - return Form(list(chain(*((integrand * self.reconstruct(subdomain_id=d)).integrals() - for d in subdomain_id)))) + return Form( + list( + chain( + *( + (integrand * self.reconstruct(subdomain_id=d)).integrals() + for d in subdomain_id + ) + ) + ) + ) # Check that we have an integer subdomain or a string # ("everywhere" or "otherwise", any more?) - if not isinstance(subdomain_id, (str, numbers.Integral,)): + if not isinstance( + subdomain_id, + ( + str, + numbers.Integral, + ), + ): raise ValueError("Expecting integer or string domain id.") # If we don't have an integration domain, try to find one in @@ -381,19 +417,23 @@ def __rmul__(self, integrand): if domain is None: domains = extract_domains(integrand) if len(domains) == 1: - domain, = domains + (domain,) = domains elif len(domains) == 0: raise ValueError("This integral is missing an integration domain.") else: - raise ValueError("Multiple domains found, making the choice of integration domain ambiguous.") + raise ValueError( + "Multiple domains found, making the choice of integration domain ambiguous." + ) # Otherwise create and return a one-integral form - integral = Integral(integrand=integrand, - integral_type=self.integral_type(), - domain=domain, - subdomain_id=subdomain_id, - metadata=self.metadata(), - subdomain_data=self.subdomain_data()) + integral = Integral( + integrand=integrand, + integral_type=self.integral_type(), + domain=domain, + subdomain_id=subdomain_id, + metadata=self.metadata(), + subdomain_data=self.subdomain_data(), + ) return Form([integral]) diff --git a/ufl/operators.py b/ufl/operators.py index 0394e65eb..2ca1158f5 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -22,17 +22,57 @@ from ufl.averaging import CellAvg, FacetAvg from ufl.checks import is_cellwise_constant from ufl.coefficient import Coefficient -from ufl.conditional import EQ, NE, AndCondition, Conditional, MaxValue, MinValue, NotCondition, OrCondition +from ufl.conditional import ( + EQ, + NE, + AndCondition, + Conditional, + MaxValue, + MinValue, + NotCondition, + OrCondition, +) from ufl.constantvalue import ComplexValue, RealValue, Zero, as_ufl from ufl.differentiation import Curl, Div, Grad, NablaDiv, NablaGrad, VariableDerivative from ufl.domain import extract_domains from ufl.form import Form from ufl.geometry import FacetNormal, SpatialCoordinate from ufl.indexed import Indexed -from ufl.mathfunctions import (Acos, Asin, Atan, Atan2, BesselI, BesselJ, BesselK, BesselY, Cos, Cosh, Erf, Exp, Ln, - Sin, Sinh, Sqrt, Tan, Tanh) -from ufl.tensoralgebra import (Cofactor, Cross, Determinant, Deviatoric, Dot, Inner, Inverse, Outer, Perp, Skew, Sym, - Trace, Transposed) +from ufl.mathfunctions import ( + Acos, + Asin, + Atan, + Atan2, + BesselI, + BesselJ, + BesselK, + BesselY, + Cos, + Cosh, + Erf, + Exp, + Ln, + Sin, + Sinh, + Sqrt, + Tan, + Tanh, +) +from ufl.tensoralgebra import ( + Cofactor, + Cross, + Determinant, + Deviatoric, + Dot, + Inner, + Inverse, + Outer, + Perp, + Skew, + Sym, + Trace, + Transposed, +) from ufl.tensors import ListTensor, as_matrix, as_tensor, as_vector from ufl.variable import Variable @@ -53,13 +93,15 @@ def shape(f): # --- Complex operators --- + def conj(f): """The complex conjugate of f.""" f = as_ufl(f) return Conj(f) -# Alias because both conj and conjugate are in numpy and we wish to be consistent. +# Alias because both conj and conjugate are in numpy and we wish to be +# consistent. conjugate = conj @@ -77,6 +119,7 @@ def imag(f): # --- Elementwise tensor operators --- + def elem_op_items(op_ind, indices, *args): """Elem op items.""" sh = args[0].ufl_shape @@ -93,17 +136,22 @@ def extind(ii): def elem_op(op, *args): - """Take the elementwise application of operator op on scalar values from one or more tensor arguments.""" + """Apply element-wise operations. + + Take the element-wise application of operator op on scalar values + from one or more tensor arguments. + """ args = [as_ufl(arg) for arg in args] sh = args[0].ufl_shape if not all(sh == x.ufl_shape for x in args): - raise ValueError("Cannot take elementwise operation with different shapes.") + raise ValueError("Cannot take element-wise operation with different shapes.") if sh == (): return op(*args) def op_ind(ind, *args): return op(*[x[ind] for x in args]) + return as_tensor(elem_op_items(op_ind, (), *args)) @@ -124,6 +172,7 @@ def elem_pow(A, B): # --- Tensor operators --- + def transpose(A): """Take the transposed of tensor A.""" A = as_ufl(A) @@ -223,7 +272,10 @@ def tr(A): def diag(A): - """Take the diagonal part of rank 2 tensor A or make a diagonal rank 2 tensor from a rank 1 tensor. + """Diagonal ranl-2 tensor. + + Take the diagonal part of rank 2 tensor A or make a diagonal rank 2 + tensor from a rank 1 tensor. Always returns a rank 2 tensor. See also diag_vector. """ @@ -232,7 +284,7 @@ def diag(A): # Get and check dimensions r = len(A.ufl_shape) if r == 1: - n, = A.ufl_shape + (n,) = A.ufl_shape elif r == 2: m, n = A.ufl_shape if m != n: @@ -287,6 +339,7 @@ def sym(A): # --- Differential operators + def Dx(f, *i): """Take the partial derivative of f with respect to spatial variable number i. @@ -315,6 +368,7 @@ def diff(f, v): # Apply to integrands if isinstance(f, Form): from ufl.algorithms.map_integrands import map_integrands + return map_integrands(lambda e: diff(e, v), f) # Apply to expression @@ -400,21 +454,24 @@ def curl(f): # --- DG operators --- + def jump(v, n=None): """Take the jump of v across a facet.""" v = as_ufl(v) is_constant = len(extract_domains(v)) > 0 if is_constant: if n is None: - return v('+') - v('-') + return v("+") - v("-") r = len(v.ufl_shape) if r == 0: - return v('+') * n('+') + v('-') * n('-') + return v("+") * n("+") + v("-") * n("-") else: - return dot(v('+'), n('+')) + dot(v('-'), n('-')) + return dot(v("+"), n("+")) + dot(v("-"), n("-")) else: - warnings.warn("Returning zero from jump of expression without a domain. " - "This may be erroneous if a dolfin.Expression is involved.") + warnings.warn( + "Returning zero from jump of expression without a domain. " + "This may be erroneous if a dolfin.Expression is involved." + ) # FIXME: Is this right? If v has no domain, it doesn't depend # on anything spatially variable or any form arguments, and # thus the jump is zero. In other words, I'm assuming that "v @@ -427,7 +484,7 @@ def jump(v, n=None): def avg(v): """Take the average of v across a facet.""" v = as_ufl(v) - return 0.5 * (v('+') + v('-')) + return 0.5 * (v("+") + v("-")) def cell_avg(f): @@ -442,6 +499,7 @@ def facet_avg(f): # --- Other operators --- + def variable(e): """Define a variable representing the given expression. @@ -453,6 +511,7 @@ def variable(e): # --- Conditional expressions --- + def conditional(condition, true_value, false_value): """A conditional expression. @@ -532,6 +591,7 @@ def min_value(x, y): # --- Math functions --- + def _mathfunction(f, cls): """A mat function.""" f = as_ufl(f) @@ -608,7 +668,7 @@ def atan2(f1, f2): f1 = as_ufl(f1) f2 = as_ufl(f2) if isinstance(f1, (ComplexValue, complex)) or isinstance(f2, (ComplexValue, complex)): - raise TypeError('atan2 is incompatible with complex numbers.') + raise TypeError("atan2 is incompatible with complex numbers.") r = Atan2(f1, f2) if isinstance(r, (RealValue, Zero, int, float)): return float(r) @@ -652,6 +712,7 @@ def bessel_K(nu, f): # --- Special function for exterior_derivative + def exterior_derivative(f): """Take the exterior derivative of f. diff --git a/ufl/precedence.py b/ufl/precedence.py index 0aea48b20..3b404dbda 100644 --- a/ufl/precedence.py +++ b/ufl/precedence.py @@ -16,7 +16,7 @@ def parstr(child, parent, pre="(", post=")", format=str): # Execute when needed instead of on import, which leads to all # kinds of circular trouble. Fixing this could be an optimization # of str(expr) though. - if not hasattr(parent, '_precedence'): + if not hasattr(parent, "_precedence"): assign_precedences(build_precedence_list()) # We want child to be evaluated fully first, and if the parent has @@ -41,8 +41,19 @@ def parstr(child, parent, pre="(", post=")", format=str): def build_precedence_list(): """Build precedence list.""" - from ufl.classes import (Abs, BesselFunction, Division, Indexed, IndexSum, MathFunction, Operator, Power, Product, - Sum, Terminal) + from ufl.classes import ( + Abs, + BesselFunction, + Division, + Indexed, + IndexSum, + MathFunction, + Operator, + Power, + Product, + Sum, + Terminal, + ) # TODO: Fill in other types... # Power <= Transposed @@ -57,7 +68,12 @@ def build_precedence_list(): # stronger than +, but weaker than product precedence_list.append((IndexSum,)) - precedence_list.append((Product, Division,)) + precedence_list.append( + ( + Product, + Division, + ) + ) # NB! Depends on language! precedence_list.append((Power, MathFunction, BesselFunction, Abs)) @@ -75,6 +91,7 @@ def build_precedence_mapping(precedence_list): Utility function used by some external code. """ from ufl.classes import Expr, abstract_classes, all_ufl_classes + pm = {} missing = set() # Assign integer values for each precedence level @@ -103,4 +120,7 @@ def assign_precedences(precedence_list): for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p if missing: - warnings.warn("Missing precedence levels for classes:\n" + "\n".join(f" {c}" for c in sorted(missing))) + warnings.warn( + "Missing precedence levels for classes:\n" + + "\n".join(f" {c}" for c in sorted(missing)) + ) diff --git a/ufl/protocols.py b/ufl/protocols.py index df8041473..ad7dbb7f7 100644 --- a/ufl/protocols.py +++ b/ufl/protocols.py @@ -15,7 +15,7 @@ def id_or_none(obj): """ if obj is None: return None - elif hasattr(obj, 'ufl_id'): + elif hasattr(obj, "ufl_id"): return obj.ufl_id() else: return id(obj) diff --git a/ufl/pullback.py b/ufl/pullback.py index cbc6b53db..d32fc3861 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -22,14 +22,26 @@ if TYPE_CHECKING: from ufl.finiteelement import AbstractFiniteElement as _AbstractFiniteElement -__all_classes__ = ["NonStandardPullbackException", "AbstractPullback", "IdentityPullback", - "ContravariantPiola", "CovariantPiola", "L2Piola", "DoubleContravariantPiola", - "DoubleCovariantPiola", "MixedPullback", "SymmetricPullback", - "PhysicalPullback", "CustomPullback", "UndefinedPullback"] +__all_classes__ = [ + "NonStandardPullbackException", + "AbstractPullback", + "IdentityPullback", + "ContravariantPiola", + "CovariantPiola", + "L2Piola", + "DoubleContravariantPiola", + "DoubleCovariantPiola", + "MixedPullback", + "SymmetricPullback", + "PhysicalPullback", + "CustomPullback", + "UndefinedPullback", +] class NonStandardPullbackException(BaseException): """Exception to raise if a map is non-standard.""" + pass @@ -144,7 +156,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim, ) + element.reference_value_shape[1:] + return (gdim,) + element.reference_value_shape[1:] class CovariantPiola(AbstractPullback): @@ -187,7 +199,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim, ) + element.reference_value_shape[1:] + return (gdim,) + element.reference_value_shape[1:] class L2Piola(AbstractPullback): @@ -257,7 +269,7 @@ def apply(self, expr): # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) *k, i, j, m, n = indices(len(expr.ufl_shape) + 2) kmn = (*k, m, n) - return as_tensor((1.0 / detJ)**2 * J[i, m] * expr[kmn] * J[j, n], (*k, i, j)) + return as_tensor((1.0 / detJ) ** 2 * J[i, m] * expr[kmn] * J[j, n], (*k, i, j)) def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: """Get the physical value shape when this pull back is applied to an element on a domain. @@ -351,9 +363,11 @@ def apply(self, expr): offset = 0 # For each unique piece in reference space, apply the appropriate pullback for subelem in self._element.sub_elements: - rsub = as_tensor(np.asarray( - rflat[offset: offset + subelem.reference_value_size] - ).reshape(subelem.reference_value_shape)) + rsub = as_tensor( + np.asarray(rflat[offset : offset + subelem.reference_value_size]).reshape( + subelem.reference_value_shape + ) + ) rmapped = subelem.pullback.apply(rsub) # Flatten into the pulled back expression for the whole thing g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) @@ -361,8 +375,10 @@ def apply(self, expr): # And reshape appropriately f = as_tensor(np.asarray(g_components).reshape(space.value_shape)) if f.ufl_shape != space.value_shape: - raise ValueError("Expecting pulled back expression with shape " - f"'{space.value_shape}', got '{f.ufl_shape}'") + raise ValueError( + "Expecting pulled back expression with shape " + f"'{space.value_shape}', got '{f.ufl_shape}'" + ) return f def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: @@ -377,18 +393,21 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: """ assert element == self._element dim = sum(FunctionSpace(domain, e).value_size for e in self._element.sub_elements) - return (dim, ) + return (dim,) class SymmetricPullback(AbstractPullback): """Pull back for an element with symmetry.""" - def __init__(self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing.tuple[int, ...], int]): + def __init__( + self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing.tuple[int, ...], int] + ): """Initalise. Args: element: The element - symmetry: A dictionary mapping from the component in physical space to the local component + symmetry: A dictionary mapping from the component in + physical space to the local component """ self._element = element self._symmetry = symmetry @@ -427,17 +446,21 @@ def apply(self, expr): for component in np.ndindex(self._block_shape): i = self._symmetry[component] subelem = self._element.sub_elements[i] - rsub = as_tensor(np.asarray( - rflat[offsets[i]:offsets[i+1]] - ).reshape(subelem.reference_value_shape)) + rsub = as_tensor( + np.asarray(rflat[offsets[i] : offsets[i + 1]]).reshape( + subelem.reference_value_shape + ) + ) rmapped = subelem.pullback.apply(rsub) # Flatten into the pulled back expression for the whole thing g_components.extend([rmapped[idx] for idx in np.ndindex(rmapped.ufl_shape)]) # And reshape appropriately f = as_tensor(np.asarray(g_components).reshape(space.value_shape)) if f.ufl_shape != space.value_shape: - raise ValueError(f"Expecting pulled back expression with shape " - f"'{space.value_shape}', got '{f.ufl_shape}'") + raise ValueError( + f"Expecting pulled back expression with shape " + f"'{space.value_shape}', got '{f.ufl_shape}'" + ) return f def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: diff --git a/ufl/referencevalue.py b/ufl/referencevalue.py index 5a4c5bb11..5dd82f4c3 100644 --- a/ufl/referencevalue.py +++ b/ufl/referencevalue.py @@ -10,10 +10,7 @@ from ufl.core.ufl_type import ufl_type -@ufl_type(num_ops=1, - is_index_free=True, - is_terminal_modifier=True, - is_in_reference_frame=True) +@ufl_type(num_ops=1, is_index_free=True, is_terminal_modifier=True, is_in_reference_frame=True) class ReferenceValue(Operator): """Representation of the reference cell value of a form argument.""" diff --git a/ufl/restriction.py b/ufl/restriction.py index 2871cd53f..430fefa41 100644 --- a/ufl/restriction.py +++ b/ufl/restriction.py @@ -12,11 +12,13 @@ # --- Restriction operators --- -@ufl_type(is_abstract=True, - num_ops=1, - inherit_shape_from_operand=0, - inherit_indices_from_operand=0, - is_restriction=True) +@ufl_type( + is_abstract=True, + num_ops=1, + inherit_shape_from_operand=0, + inherit_indices_from_operand=0, + is_restriction=True, +) class Restricted(Operator): """Restriction.""" @@ -34,8 +36,7 @@ def side(self): def evaluate(self, x, mapping, component, index_values): """Evaluate.""" - return self.ufl_operands[0].evaluate(x, mapping, component, - index_values) + return self.ufl_operands[0].evaluate(x, mapping, component, index_values) def __str__(self): """Format as a string.""" diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index 9500ae422..ebff0f24d 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -25,8 +25,9 @@ class SobolevSpace(object): """Symbolic representation of a Sobolev space. - This implements a subset of the methods of a Python set so that finite elements and - other Sobolev spaces can be tested for inclusion. + This implements a subset of the methods of a Python set so that + finite elements and other Sobolev spaces can be tested for + inclusion. """ def __init__(self, name, parents=None): @@ -52,7 +53,7 @@ def __init__(self, name, parents=None): "HCurl": 0, "HEin": 0, "HDivDiv": 0, - "DirectionalH": 0 + "DirectionalH": 0, }[self.name] def __str__(self): @@ -65,6 +66,7 @@ def __repr__(self): def __eq__(self, other): """Check equality.""" + print("XXXXX") return isinstance(other, SobolevSpace) and self.name == other.name def __ne__(self, other): @@ -82,11 +84,11 @@ def __getitem__(self, spatial_index): def __contains__(self, other): """Implement `fe in s` where `fe` is a FiniteElement and `s` is a SobolevSpace.""" if isinstance(other, SobolevSpace): - raise TypeError("Unable to test for inclusion of a " - "SobolevSpace in another SobolevSpace. " - "Did you mean to use <= instead?") - return (other.sobolev_space == self or - self in other.sobolev_space.parents) + raise TypeError( + "Unable to test for inclusion of a SobolevSpace in another SobolevSpace. " + "Did you mean to use <= instead?" + ) + return other.sobolev_space == self or self in other.sobolev_space.parents def __lt__(self, other): """In common with intrinsic Python sets, < indicates "is a proper subset of".""" @@ -98,7 +100,7 @@ class DirectionalSobolevSpace(SobolevSpace): """Directional Sobolev space. Symbolic representation of a Sobolev space with varying smoothness - in differerent spatial directions. + in different spatial directions. """ def __init__(self, orders): @@ -110,8 +112,8 @@ def __init__(self, orders): smoothness requirement is enforced. """ assert all( - isinstance(x, int) or isinf(x) - for x in orders), "Order must be an integer or infinity." + 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) @@ -126,17 +128,23 @@ def __getitem__(self, spatial_index): return spaces[self._orders[spatial_index]] def __contains__(self, other): - """Implement `fe in s` where `fe` is a FiniteElement and `s` is a DirectionalSobolevSpace.""" + """Check if one space is contained in another. + + Implement `fe in s` where `fe` is a FiniteElement and `s` is a + DirectionalSobolevSpace. + """ if isinstance(other, SobolevSpace): - raise TypeError("Unable to test for inclusion of a " - "SobolevSpace in another SobolevSpace. " - "Did you mean to use <= instead?") - return (other.sobolev_space == self or - all(self[i] in other.sobolev_space.parents - for i in self._spatial_indices)) + raise TypeError( + "Unable to test for inclusion of a SobolevSpace in another SobolevSpace. " + "Did you mean to use <= instead?" + ) + return other.sobolev_space == self or all( + self[i] in other.sobolev_space.parents for i in self._spatial_indices + ) def __eq__(self, other): """Check equality.""" + print("FFFFFF") if isinstance(other, DirectionalSobolevSpace): return self._orders == other._orders return all(self[i] == other for i in self._spatial_indices) @@ -146,8 +154,7 @@ def __lt__(self, other): if isinstance(other, DirectionalSobolevSpace): if self._spatial_indices != other._spatial_indices: return False - return any(self._orders[i] > other._orders[i] - for i in self._spatial_indices) + return any(self._orders[i] > other._orders[i] for i in self._spatial_indices) if other in [HDiv, HCurl]: return all(self._orders[i] >= 1 for i in self._spatial_indices) @@ -155,8 +162,7 @@ def __lt__(self, other): # Don't know how these spaces compare return NotImplementedError(f"Don't know how to compare with {other.name}") else: - return any( - self._orders[i] > other._order for i in self._spatial_indices) + return any(self._orders[i] > other._order for i in self._spatial_indices) def __str__(self): """Format as a string.""" diff --git a/ufl/sorting.py b/ufl/sorting.py index 5efe2a44a..6b9324514 100644 --- a/ufl/sorting.py +++ b/ufl/sorting.py @@ -141,7 +141,7 @@ def cmp_expr(a, b): bops = b.ufl_operands # Sort by children in natural order - for (r, s) in zip(aops, bops): + for r, s in zip(aops, bops): # Skip subtree if objects are the same if r is s: continue diff --git a/ufl/split_functions.py b/ufl/split_functions.py index 0931b74ae..a5c18da38 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -8,7 +8,6 @@ # Modified by Anders Logg, 2008 from ufl.functionspace import FunctionSpace - from ufl.indexed import Indexed from ufl.permutation import compute_indices from ufl.tensors import ListTensor, as_matrix, as_vector @@ -19,8 +18,8 @@ def split(v): """Split a coefficient or argument. - If v is a Coefficient or Argument in a mixed space, returns - a tuple with the function components corresponding to the subelements. + If v is a Coefficient or Argument in a mixed space, returns a tuple + with the function components corresponding to the subelements. """ domain = v.ufl_domain() @@ -42,8 +41,8 @@ def split(v): # Get innermost terminal here and its element v = args[0] # Get relevant range of v components - begin, = ops[0].ufl_operands[1] - end, = ops[-1].ufl_operands[1] + (begin,) = ops[0].ufl_operands[1] + (end,) = ops[-1].ufl_operands[1] begin = int(begin) end = int(end) + 1 else: @@ -58,7 +57,9 @@ def split(v): return (v,) if len(v.ufl_shape) != 1: - raise ValueError("Don't know how to split tensor valued mixed functions without flattened index space.") + raise ValueError( + "Don't know how to split tensor valued mixed functions without flattened index space." + ) # Compute value size and set default range end value_size = v.ufl_function_space().value_size @@ -88,26 +89,29 @@ def split(v): strides = shape_to_strides(shape) rank = len(shape) sub_size = product(shape) - subindices = [flatten_multiindex(c, strides) - for c in compute_indices(shape)] + subindices = [flatten_multiindex(c, strides) for c in compute_indices(shape)] components = [v[k + offset] for k in subindices] # Shape components into same shape as subelement if rank == 0: - subv, = components + (subv,) = components elif rank <= 1: subv = as_vector(components) elif rank == 2: - subv = as_matrix([components[i * shape[1]: (i + 1) * shape[1]] - for i in range(shape[0])]) + subv = as_matrix( + [components[i * shape[1] : (i + 1) * shape[1]] for i in range(shape[0])] + ) else: - raise ValueError(f"Don't know how to split functions with sub functions of rank {rank}.") + raise ValueError( + f"Don't know how to split functions with sub functions of rank {rank}." + ) offset += sub_size sub_functions.append(subv) if end != offset: raise ValueError( - "Function splitting failed to extract components for whole intended range. Something is wrong.") + "Function splitting failed to extract components for whole intended range." + ) return tuple(sub_functions) diff --git a/ufl/tensoralgebra.py b/ufl/tensoralgebra.py index 733b24388..4ec364191 100644 --- a/ufl/tensoralgebra.py +++ b/ufl/tensoralgebra.py @@ -41,6 +41,7 @@ # --- Classes representing compound tensor algebra operations --- + @ufl_type(is_abstract=True) class CompoundTensorOperator(Operator): """Compount tensor operator.""" @@ -51,6 +52,7 @@ def __init__(self, operands): """Initialise.""" Operator.__init__(self, operands) + # TODO: Use this and make Sum handle scalars only? # This would simplify some algorithms. The only # problem is we can't use + in many algorithms because @@ -144,8 +146,10 @@ def ufl_shape(self): def __str__(self): """Format as a string.""" - return "%s (X) %s" % (parstr(self.ufl_operands[0], self), - parstr(self.ufl_operands[1], self)) + return "%s (X) %s" % ( + parstr(self.ufl_operands[0], self), + parstr(self.ufl_operands[1], self), + ) @ufl_type(num_ops=2) @@ -187,8 +191,7 @@ def __init__(self, a, b): def __str__(self): """Format as a string.""" - return "%s : %s" % (parstr(self.ufl_operands[0], self), - parstr(self.ufl_operands[1], self)) + return "%s : %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(num_ops=2) @@ -202,13 +205,14 @@ def __new__(cls, a, b): ash = a.ufl_shape bsh = b.ufl_shape ar, br = len(ash), len(bsh) - scalar = (ar == 0 and br == 0) + scalar = ar == 0 and br == 0 # Checks if not ((ar >= 1 and br >= 1) or scalar): raise ValueError( "Dot product requires non-scalar arguments, " - f"got arguments with ranks {ar} and {br}.") + f"got arguments with ranks {ar} and {br}." + ) if not (scalar or ash[-1] == bsh[0]): raise ValueError("Dimension mismatch in dot product.") @@ -236,8 +240,7 @@ def ufl_shape(self): def __str__(self): """Format as a string.""" - return "%s . %s" % (parstr(self.ufl_operands[0], self), - parstr(self.ufl_operands[1], self)) + return "%s . %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(is_index_free=True, num_ops=1) @@ -288,7 +291,8 @@ def __new__(cls, a, b): if not (len(ash) == 1 and ash == bsh): raise ValueError( f"Cross product requires arguments of rank 1, got {ufl_err_str(a)} " - f"and {ufl_err_str(b)}.") + f"and {ufl_err_str(b)}." + ) # Simplification if isinstance(a, Zero) or isinstance(b, Zero): @@ -308,8 +312,7 @@ def __init__(self, a, b): def __str__(self): """Format as a string.""" - return "%s x %s" % (parstr(self.ufl_operands[0], self), - parstr(self.ufl_operands[1], self)) + return "%s x %s" % (parstr(self.ufl_operands[0], self), parstr(self.ufl_operands[1], self)) @ufl_type(num_ops=1, inherit_indices_from_operand=0) @@ -468,7 +471,9 @@ def __new__(cls, A): if len(sh) != 2: raise ValueError("Deviatoric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - raise ValueError(f"Cannot take deviatoric part of rectangular matrix with dimensions {sh}.") + raise ValueError( + f"Cannot take deviatoric part of rectangular matrix with dimensions {sh}." + ) if A.ufl_free_indices: raise ValueError("Not expecting free indices in Deviatoric.") @@ -536,7 +541,9 @@ def __new__(cls, A): if len(sh) != 2: raise ValueError("Symmetric part of tensor with rank != 2 is undefined.") if sh[0] != sh[1]: - raise ValueError(f"Cannot take symmetric part of rectangular matrix with dimensions {sh}.") + raise ValueError( + f"Cannot take symmetric part of rectangular matrix with dimensions {sh}." + ) if Afi: raise ValueError("Not expecting free indices in Sym.") diff --git a/ufl/tensors.py b/ufl/tensors.py index 2fe829990..3edb0759c 100644 --- a/ufl/tensors.py +++ b/ufl/tensors.py @@ -39,11 +39,17 @@ def __new__(cls, *expressions): # Obviously, each subexpression must have the same shape if any(sh != e.ufl_shape for e in expressions[1:]): - raise ValueError("Cannot create a tensor by joining subexpressions with different shapes.") + raise ValueError( + "Cannot create a tensor by joining subexpressions with different shapes." + ) if any(fi != e.ufl_free_indices for e in expressions[1:]): - raise ValueError("Cannot create a tensor where the components have different free indices.") + raise ValueError( + "Cannot create a tensor where the components have different free indices." + ) if any(fid != e.ufl_index_dimensions for e in expressions[1:]): - raise ValueError("Cannot create a tensor where the components have different free index dimensions.") + raise ValueError( + "Cannot create a tensor where the components have different free index dimensions." + ) # Simplify to Zero if possible if all(isinstance(e, Zero) for e in expressions): @@ -59,7 +65,9 @@ def __init__(self, *expressions): # Checks indexset = set(self.ufl_operands[0].ufl_free_indices) if not all(not (indexset ^ set(e.ufl_free_indices)) for e in self.ufl_operands): - raise ValueError("Can't combine subtensor expressions with different sets of free indices.") + raise ValueError( + "Can't combine subtensor expressions with different sets of free indices." + ) @property def ufl_shape(self): @@ -71,7 +79,8 @@ def evaluate(self, x, mapping, component, index_values, derivatives=()): if len(component) != len(self.ufl_shape): raise ValueError( "Can only evaluate scalars, expecting a component " - "tuple of length {len(self.ufl_shape)}, not {component}.") + "tuple of length {len(self.ufl_shape)}, not {component}." + ) a = self.ufl_operands[component[0]] component = component[1:] if derivatives: @@ -96,6 +105,7 @@ def __getitem__(self, key): def __str__(self): """Format as a string.""" + def substring(expressions, indent): ind = " " * indent if any(isinstance(e, ListTensor) for e in expressions): @@ -110,6 +120,7 @@ def substring(expressions, indent): else: s = ", ".join(str(e) for e in expressions) return "%s[%s]" % (ind, s) + return substring(self.ufl_operands, 0) @@ -123,9 +134,11 @@ def __new__(cls, expression, indices): """Create a new ComponentTensor.""" # Simplify if isinstance(expression, Zero): - fi, fid, sh = remove_indices(expression.ufl_free_indices, - expression.ufl_index_dimensions, - [ind.count() for ind in indices]) + fi, fid, sh = remove_indices( + expression.ufl_free_indices, + expression.ufl_index_dimensions, + [ind.count() for ind in indices], + ) return Zero(sh, fi, fid) # Construct @@ -144,9 +157,11 @@ def __init__(self, expression, indices): Operator.__init__(self, (expression, indices)) - fi, fid, sh = remove_indices(expression.ufl_free_indices, - expression.ufl_index_dimensions, - [ind.count() for ind in indices]) + fi, fid, sh = remove_indices( + expression.ufl_free_indices, + expression.ufl_index_dimensions, + [ind.count() for ind in indices], + ) self.ufl_free_indices = fi self.ufl_index_dimensions = fid self.ufl_shape = sh @@ -190,9 +205,11 @@ def __str__(self): # --- User-level functions to wrap expressions in the correct way --- + def numpy2nestedlists(arr): """Convert Numpy array to a nested list.""" from numpy import ndarray + if not isinstance(arr, ndarray): return arr return [numpy2nestedlists(arr[k]) for k in range(arr.shape[0])] @@ -210,8 +227,9 @@ def _as_list_tensor(expressions): def from_numpy_to_lists(expressions): """Convert Numpy array to lists.""" try: - import numpy - if isinstance(expressions, numpy.ndarray): + import numpy as np + + if isinstance(expressions, np.ndarray): if expressions.shape == (): # Unwrap scalar ndarray return expressions.item() @@ -389,6 +407,7 @@ def unit_indexed_tensor(shape, component): """Unit indexed tensor.""" from ufl.constantvalue import Identity from ufl.operators import outer # a bit of circular dependency issue here + r = len(shape) if r == 0: return 0, () diff --git a/ufl/utils/formatting.py b/ufl/utils/formatting.py index 4df63cd98..7c2d4336a 100644 --- a/ufl/utils/formatting.py +++ b/ufl/utils/formatting.py @@ -16,7 +16,7 @@ def camel2underscore(name): # Don't insert _ between multiple upper case letters if lastlower: letters.append("_") - i = i.lower() # noqa: E741 + i = i.lower() lastlower = thislower letters.append(i) return "".join(letters) @@ -45,7 +45,7 @@ def tstr(t, colsize=80): # Pretty-print table s = "" - for (key, value) in t: + for key, value in t: key = str(key) if isinstance(value, str): value = "'%s'" % value @@ -85,7 +85,10 @@ def _tree_format_expression(expression, indentation, parentheses): if expression._ufl_is_terminal_: s = "%s%s" % (ind, repr(expression)) else: - sops = [_tree_format_expression(o, indentation + 1, parentheses) for o in expression.ufl_operands] + sops = [ + _tree_format_expression(o, indentation + 1, parentheses) + for o in expression.ufl_operands + ] s = "%s%s\n" % (ind, expression._ufl_class_.__name__) if parentheses and len(sops) > 1: s += "%s(\n" % (ind,) diff --git a/ufl/utils/indexflattening.py b/ufl/utils/indexflattening.py index 0e5aea412..f9935e51a 100644 --- a/ufl/utils/indexflattening.py +++ b/ufl/utils/indexflattening.py @@ -1,4 +1,4 @@ -"""This module contains a collection of utilities for mapping between multiindices and a flattened index space.""" +"""Collection of utilities for mapping between multiindices and a flattened index space.""" # Copyright (C) 2008-2016 Martin Sandve Alnæs # diff --git a/ufl/utils/sequences.py b/ufl/utils/sequences.py index 9904287c4..71c845349 100644 --- a/ufl/utils/sequences.py +++ b/ufl/utils/sequences.py @@ -8,7 +8,7 @@ from functools import reduce -import numpy +import numpy as np def product(sequence): @@ -21,11 +21,11 @@ def product(sequence): def max_degree(degrees): """Maximum degree for mixture of scalar and tuple degrees.""" - # numpy.maximum broadcasts scalar degrees to tuple degrees if - # necessary. reduce applies numpy.maximum pairwise. - degree = reduce(numpy.maximum, map(numpy.asarray, degrees)) + # np.maximum broadcasts scalar degrees to tuple degrees if + # necessary. reduce applies np.maximum pairwise. + degree = reduce(np.maximum, map(np.asarray, degrees)) if degree.ndim: degree = tuple(map(int, degree)) # tuple degree else: - degree = int(degree) # scalar degree + degree = int(degree) # scalar degree return degree diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index bf02cfdf6..254a60d59 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -48,10 +48,12 @@ def sorted_by_count(seq): def sorted_by_key(mapping): """Sort dict items by key, allowing different key types.""" + # Python3 doesn't allow comparing builtins of different type, # therefore the typename trick here def _key(x): return (type(x[0]).__name__, x[0]) + return sorted(mapping.items(), key=_key) @@ -81,8 +83,10 @@ def canonicalize_metadata(metadata): elif isinstance(value, (int, float, str)) or value is None: value = str(value) else: - warnings.warn(f"Applying str() to a metadata value of type {type(value).__name__}, " - "don't know if this is safe.") + warnings.warn( + f"Applying str() to a metadata value of type {type(value).__name__}, " + "don't know if this is safe." + ) value = str(value) newvalues.append(value) diff --git a/ufl/utils/stacks.py b/ufl/utils/stacks.py index a19a713de..da4c39614 100644 --- a/ufl/utils/stacks.py +++ b/ufl/utils/stacks.py @@ -24,7 +24,11 @@ def peek(self): class StackDict(dict): - """A dict that can be changed incrementally with 'd.push(k,v)' and have changes rolled back with 'k,v = d.pop()'.""" + """A dictionary type. + + A dict that can be changed incrementally with 'd.push(k,v)' and have + changes rolled back with 'k,v = d.pop()'. + """ def __init__(self, *args, **kwargs): """Initialise.""" diff --git a/ufl/variable.py b/ufl/variable.py index 84a795434..ad671e10c 100644 --- a/ufl/variable.py +++ b/ufl/variable.py @@ -118,10 +118,12 @@ def label(self): def __eq__(self, other): """Check equality.""" - return (isinstance(other, Variable) and self.ufl_operands[1] == other.ufl_operands[1] and # noqa: W504 - self.ufl_operands[0] == other.ufl_operands[0]) + return ( + isinstance(other, Variable) + and self.ufl_operands[1] == other.ufl_operands[1] + and self.ufl_operands[0] == other.ufl_operands[0] + ) def __str__(self): """Format as a string.""" - return "var%d(%s)" % (self.ufl_operands[1].count(), - self.ufl_operands[0]) + return "var%d(%s)" % (self.ufl_operands[1].count(), self.ufl_operands[0]) From 56a499b4aff8c74f128d734893c14b7a2f9cc12a Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Mon, 19 Feb 2024 14:53:56 +0000 Subject: [PATCH 094/136] Remove functions that were marked as to be removed after Dec 2023 (#257) * Remove decprecated code * Lint fixes * Import update --- ufl/algorithms/__init__.py | 2 -- ufl/algorithms/expand_compounds.py | 24 ----------------------- ufl/compound_expressions.py | 15 --------------- ufl/domain.py | 31 +----------------------------- 4 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 ufl/algorithms/expand_compounds.py diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index 43eac078f..a2ca0ebb7 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -39,7 +39,6 @@ "replace_terminal_data", "post_traversal", "change_to_reference_grad", - "expand_compounds", "validate_form", "FormSplitter", "extract_arguments", @@ -71,7 +70,6 @@ from ufl.algorithms.checks import validate_form from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form from ufl.algorithms.estimate_degrees import estimate_total_polynomial_degree -from ufl.algorithms.expand_compounds import expand_compounds from ufl.algorithms.expand_indices import expand_indices from ufl.algorithms.formfiles import load_forms, load_ufl_file, read_ufl_file from ufl.algorithms.formsplitter import FormSplitter diff --git a/ufl/algorithms/expand_compounds.py b/ufl/algorithms/expand_compounds.py deleted file mode 100644 index 0f2bc9d14..000000000 --- a/ufl/algorithms/expand_compounds.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Algorithm for expanding compound expressions into equivalent representations.""" - -# Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg -# -# This file is part of UFL (https://www.fenicsproject.org) -# -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# Modified by Anders Logg, 2009-2010 - -import warnings - -from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering - - -def expand_compounds(e): - """Expand compounds.""" - warnings.warn( - "The use of expand_compounds is deprecated and will be removed after December 2023. " - "Please, use apply_algebra_lowering directly instead", - FutureWarning, - ) - - return apply_algebra_lowering(e) diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 5eedfdedf..840397caa 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -7,7 +7,6 @@ # # Modified by Anders Logg, 2009-2010 -import warnings from ufl.constantvalue import Zero, zero from ufl.core.multiindex import Index, indices @@ -113,20 +112,6 @@ def determinant_expr_2x2(B): return _det_2x2(B, 0, 1, 0, 1) -def old_determinant_expr_3x3(A): - """Determinant of a 3 by 3 matrix.""" - warnings.warn( - "The use of old_determinant_expr_3x3 is deprecated and will be removed " - "after December 2023. Please, use determinant_expr_3x3 instead", - FutureWarning, - ) - return ( - A[0, 0] * _det_2x2(A, 1, 2, 1, 2) - + A[0, 1] * _det_2x2(A, 1, 2, 2, 0) - + A[0, 2] * _det_2x2(A, 1, 2, 0, 1) - ) - - def determinant_expr_3x3(A): """Determinant of a 3 by 3 matrix.""" return codeterminant_expr_nxn(A, [0, 1, 2], [0, 1, 2]) diff --git a/ufl/domain.py b/ufl/domain.py index 50ee64d61..1b5544475 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -7,7 +7,6 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import numbers -import warnings from ufl.cell import AbstractCell from ufl.core.ufl_id import attach_ufl_id @@ -220,36 +219,8 @@ def join_domains(domains): gdims.add(domain.geometric_dimension()) if len(gdims) != 1: raise ValueError("Found domains with different geometric dimensions.") - (gdim,) = gdims - - # Split into legacy and modern style domains - legacy_domains = [] - modern_domains = [] - for domain in domains: - if isinstance(domain, Mesh) and domain.ufl_id() < 0: - assert domain.ufl_cargo() is None - legacy_domains.append(domain) - else: - modern_domains.append(domain) - - # Handle legacy domains checking - if legacy_domains: - warnings.warn( - "The use of Legacy domains will be deprecated by December 2023. " - "Please, use FunctionSpace instead", - DeprecationWarning, - ) - if modern_domains: - raise ValueError( - "Found both a new-style domain and a legacy default domain. " - "These should not be used interchangeably. To find the legacy " - "domain, note that it is automatically created from a cell so " - "look for constructors taking a cell." - ) - return tuple(legacy_domains) - # Handle modern domains checking (assuming correct by construction) - return tuple(modern_domains) + return domains # TODO: Move these to an analysis module? From 284d028c849bf79fdd512263ecc23c9de3075b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 19 Feb 2024 16:02:34 +0100 Subject: [PATCH 095/136] Remove print statement (#259) --- ufl/sobolevspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index ebff0f24d..a0069485e 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -66,7 +66,6 @@ def __repr__(self): def __eq__(self, other): """Check equality.""" - print("XXXXX") return isinstance(other, SobolevSpace) and self.name == other.name def __ne__(self, other): From 0f2cb2199476f9aaf98bbd942a22aa472ce5c639 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Mon, 19 Feb 2024 15:46:31 +0000 Subject: [PATCH 096/136] remove print statement (#258) * remove print * another one --- ufl/sobolevspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index a0069485e..77d10db6f 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -143,7 +143,6 @@ def __contains__(self, other): def __eq__(self, other): """Check equality.""" - print("FFFFFF") if isinstance(other, DirectionalSobolevSpace): return self._orders == other._orders return all(self[i] == other for i in self._spatial_indices) From b8fa3005b834601af8d7d1dddd91c809f19d581f Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:54:24 +0000 Subject: [PATCH 097/136] Fix zero simplification in BaseForm.__add__ (#262) * Add trimmed serendipity * Fix value shape for trimmed serendipity * ufl plumbing update for trimmed serendipity. * Plumbing for SminusDiv.py * Adding in element stuff for SminusCurl. * Fix typo * remove spurioius names * Fix BaseForm.__add__ simplification of Zero * Fix ruff * Fix test --------- Co-authored-by: Rob Kirby Co-authored-by: Justincrum Co-authored-by: David A. Ham Co-authored-by: ksagiyam Co-authored-by: ksagiyam <46749170+ksagiyam@users.noreply.github.com> Co-authored-by: Connor Ward Co-authored-by: Iglesia Dolci Co-authored-by: Jack Betteridge <43041811+JDBetteridge@users.noreply.github.com> Co-authored-by: Josh Hope-Collins Co-authored-by: JHopeCollins --- test/test_duals.py | 8 + ufl/__init__.py | 216 +++++++++++++-------------- ufl/algorithms/__init__.py | 1 - ufl/algorithms/apply_restrictions.py | 1 - ufl/algorithms/check_arities.py | 1 + ufl/compound_expressions.py | 1 - ufl/core/multiindex.py | 1 - ufl/form.py | 2 +- ufl/formatting/ufl2unicode.py | 2 +- ufl/indexsum.py | 1 - 10 files changed, 119 insertions(+), 115 deletions(-) diff --git a/test/test_duals.py b/test/test_duals.py index 7786e7400..909538d49 100644 --- a/test/test_duals.py +++ b/test/test_duals.py @@ -121,6 +121,9 @@ def test_addition(): V = FunctionSpace(domain_2d, f_2d) V_dual = V.dual() + fvector_2d = FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1) + W = FunctionSpace(domain_2d, fvector_2d) + u = TrialFunction(V) v = TestFunction(V) @@ -152,6 +155,11 @@ def test_addition(): res -= ZeroBaseForm((v,)) assert res == L + # Simplification with respect to ufl.Zero + a_W = Matrix(W, W) + res = a_W + Zero(W.value_shape) + assert res == a_W + def test_scalar_mult(): domain_2d = Mesh(FiniteElement("Lagrange", triangle, 1, (2,), identity_pullback, H1)) diff --git a/ufl/__init__.py b/ufl/__init__.py index e2656a76b..9d255211a 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -44,147 +44,147 @@ * Cells:: - - AbstractCell - - Cell - - TensorProductCell - - vertex - - interval - - triangle - - tetrahedron - - quadrilateral - - hexahedron - - prism - - pyramid - - pentatope - - tesseract + -AbstractCell + -Cell + -TensorProductCell + -vertex + -interval + -triangle + -tetrahedron + -quadrilateral + -hexahedron + -prism + -pyramid + -pentatope + -tesseract * Domains:: - - AbstractDomain - - Mesh - - MeshView + -AbstractDomain + -Mesh + -MeshView * Sobolev spaces:: - - L2 - - H1 - - H2 - - HInf - - HDiv - - HCurl - - HEin - - HDivDiv + -L2 + -H1 + -H2 + -HInf + -HDiv + -HCurl + -HEin + -HDivDiv * Pull backs:: - - identity_pullback - - contravariant_piola - - covariant_piola - - l2_piola - - double_contravariant_piola - - double_covariant_piola + -identity_pullback + -contravariant_piola + -covariant_piola + -l2_piola + -double_contravariant_piola + -double_covariant_piola * Function spaces:: - - FunctionSpace - - MixedFunctionSpace + -FunctionSpace + -MixedFunctionSpace * Arguments:: - - Argument - - TestFunction - - TrialFunction - - Arguments - - TestFunctions - - TrialFunctions + -Argument + -TestFunction + -TrialFunction + -Arguments + -TestFunctions + -TrialFunctions * Coefficients:: - - Coefficient - - Constant - - VectorConstant - - TensorConstant + -Coefficient + -Constant + -VectorConstant + -TensorConstant * Splitting form arguments in mixed spaces:: - - split + -split * Literal constants:: - - Identity - - PermutationSymbol + -Identity + -PermutationSymbol * Geometric quantities:: - - SpatialCoordinate - - FacetNormal - - CellNormal - - CellVolume - - CellDiameter - - Circumradius - - MinCellEdgeLength - - MaxCellEdgeLength - - FacetArea - - MinFacetEdgeLength - - MaxFacetEdgeLength - - Jacobian - - JacobianDeterminant - - JacobianInverse + -SpatialCoordinate + -FacetNormal + -CellNormal + -CellVolume + -CellDiameter + -Circumradius + -MinCellEdgeLength + -MaxCellEdgeLength + -FacetArea + -MinFacetEdgeLength + -MaxFacetEdgeLength + -Jacobian + -JacobianDeterminant + -JacobianInverse * Indices:: - - Index - - indices - - i, j, k, l - - p, q, r, s + -Index + -indices + -i, j, k, l + -p, q, r, s * Scalar to tensor expression conversion:: - - as_tensor - - as_vector - - as_matrix + -as_tensor + -as_vector + -as_matrix * Unit vectors and matrices:: - - unit_vector - - unit_vectors - - unit_matrix - - unit_matrices + -unit_vector + -unit_vectors + -unit_matrix + -unit_matrices * Tensor algebra operators:: - - outer, inner, dot, cross, perp - - det, inv, cofac - - transpose, tr, diag, diag_vector - - dev, skew, sym + -outer, inner, dot, cross, perp + -det, inv, cofac + -transpose, tr, diag, diag_vector + -dev, skew, sym * Elementwise tensor operators:: - - elem_mult - - elem_div - - elem_pow - - elem_op + -elem_mult + -elem_div + -elem_pow + -elem_op * Differential operators:: - - variable - - diff, - - grad, nabla_grad - - div, nabla_div - - curl, rot - - Dx, Dn + -variable + (-diff,) + -grad, nabla_grad + -div, nabla_div + -curl, rot + -Dx, Dn * Nonlinear functions:: - - max_value, min_value - - abs, sign - - sqrt - - exp, ln, erf - - cos, sin, tan - - acos, asin, atan, atan2 - - cosh, sinh, tanh - - bessel_J, bessel_Y, bessel_I, bessel_K + -max_value, min_value + -abs, sign + -sqrt + -exp, ln, erf + -cos, sin, tan + -acos, asin, atan, atan2 + -cosh, sinh, tanh + -bessel_J, bessel_Y, bessel_I, bessel_K * Complex operations:: @@ -193,10 +193,10 @@ * Discontinuous Galerkin operators:: - - v('+'), v('-') - - jump - - avg - - cell_avg, facet_avg + -v("+"), v("-") + -jump + -avg + -cell_avg, facet_avg * Conditional operators:: @@ -207,21 +207,21 @@ * Integral measures:: - - dx, ds, dS, dP - - dc, dC, dO, dI, dX - - ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v + -dx, ds, dS, dP + -dc, dC, dO, dI, dX + -ds_b, ds_t, ds_tb, ds_v, dS_h, dS_v * Form transformations:: - - rhs, lhs - - system - - functional - - replace - - adjoint - - action - - energy_norm, - - sensitivity_rhs - - derivative + -rhs, lhs + -system + -functional + -replace + -adjoint + -action + (-energy_norm,) + -sensitivity_rhs + -derivative """ # Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg diff --git a/ufl/algorithms/__init__.py b/ufl/algorithms/__init__.py index a2ca0ebb7..18dce39fd 100644 --- a/ufl/algorithms/__init__.py +++ b/ufl/algorithms/__init__.py @@ -14,7 +14,6 @@ # recommended to use. The __all__ list below is a start based # on grepping of other FEniCS code for ufl.algorithm imports. - __all__ = [ "estimate_total_polynomial_degree", "sort_elements", diff --git a/ufl/algorithms/apply_restrictions.py b/ufl/algorithms/apply_restrictions.py index b8c049ab7..a7550d722 100644 --- a/ufl/algorithms/apply_restrictions.py +++ b/ufl/algorithms/apply_restrictions.py @@ -10,7 +10,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later - from ufl.algorithms.map_integrands import map_integrand_dags from ufl.classes import Restricted from ufl.corealg.map_dag import map_expr_dag diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index 12a7e53c1..dc255b7bc 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -1,4 +1,5 @@ """Check arities.""" + from itertools import chain from ufl.classes import Argument, Zero diff --git a/ufl/compound_expressions.py b/ufl/compound_expressions.py index 840397caa..6f92dd00d 100644 --- a/ufl/compound_expressions.py +++ b/ufl/compound_expressions.py @@ -7,7 +7,6 @@ # # Modified by Anders Logg, 2009-2010 - from ufl.constantvalue import Zero, zero from ufl.core.multiindex import Index, indices from ufl.operators import sqrt diff --git a/ufl/core/multiindex.py b/ufl/core/multiindex.py index 523d21d20..2d91e40f3 100644 --- a/ufl/core/multiindex.py +++ b/ufl/core/multiindex.py @@ -8,7 +8,6 @@ # # Modified by Massimiliano Leoni, 2016. - from ufl.core.terminal import Terminal from ufl.core.ufl_type import ufl_type from ufl.utils.counted import Counted diff --git a/ufl/form.py b/ufl/form.py index 34aa3d753..1f4b1a20d 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -142,7 +142,7 @@ def __add__(self, other): if isinstance(other, (int, float)) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self - elif isinstance(other, Zero) and not (other.ufl_shape or other.ufl_free_indices): + elif isinstance(other, Zero): # Allow adding ufl Zero as a no-op, needed for sum([a,b]) return self diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 0ed83ed1c..04956f137 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -196,7 +196,7 @@ class UC: left_double_angled_bracket = "⟪" right_double_angled_bracket = "⟫" - combining_right_arrow_above = "\u20D7" + combining_right_arrow_above = "\u20d7" combining_overline = "\u0305" diff --git a/ufl/indexsum.py b/ufl/indexsum.py index df636b58c..38b0e1cce 100644 --- a/ufl/indexsum.py +++ b/ufl/indexsum.py @@ -6,7 +6,6 @@ # # SPDX-License-Identifier: LGPL-3.0-or-later - from ufl.constantvalue import Zero from ufl.core.expr import Expr, ufl_err_str from ufl.core.multiindex import MultiIndex From d25a40d638645d0edbf0844b90392c96cbb2e994 Mon Sep 17 00:00:00 2001 From: Joe Dean Date: Tue, 2 Apr 2024 09:41:53 +0100 Subject: [PATCH 098/136] Fix (#265) --- test/test_domains.py | 35 +++++++++++++++++++++++++++++++++++ ufl/domain.py | 5 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/test_domains.py b/test/test_domains.py index 44477cc9c..cf425cb2a 100755 --- a/test/test_domains.py +++ b/test/test_domains.py @@ -10,6 +10,8 @@ Constant, FunctionSpace, Mesh, + TestFunction, + TrialFunction, dS, ds, dx, @@ -20,6 +22,7 @@ triangle, ) from ufl.algorithms import compute_form_data +from ufl.domain import extract_domains from ufl.finiteelement import FiniteElement from ufl.pullback import ( IdentityPullback, # noqa: F401 @@ -374,3 +377,35 @@ def test_merge_sort_integral_data(): assert form_data[4].subdomain_id[0] == 2 assert form_data[4].subdomain_id[1] == 4 assert form_data[4].metadata == {} + + +def test_extract_domains(): + "Test that the domains are extracted properly from a mixed-domain expression" + + # Create domains of different topological dimensions + gdim = 2 + cell_type_0 = triangle + cell_type_1 = interval + dom_0 = Mesh(FiniteElement("Lagrange", cell_type_0, 1, (gdim,), identity_pullback, H1)) + dom_1 = Mesh(FiniteElement("Lagrange", cell_type_1, 1, (gdim,), identity_pullback, H1)) + + # Define some finite element spaces + k = 1 + ele_type = "Lagrange" + ele_0 = FiniteElement(ele_type, cell_type_0, k, (), identity_pullback, H1) + ele_1 = FiniteElement(ele_type, cell_type_1, k, (), identity_pullback, H1) + + V_0 = FunctionSpace(dom_0, ele_0) + V_1 = FunctionSpace(dom_1, ele_1) + + # Create test and trial functions + u = TrialFunction(V_0) + v = TestFunction(V_1) + + # Create a mixed-domain expression + expr = u * v + + domains = extract_domains(expr) + + assert domains[0] == dom_1 + assert domains[1] == dom_0 diff --git a/ufl/domain.py b/ufl/domain.py index 1b5544475..8f4ae5138 100644 --- a/ufl/domain.py +++ b/ufl/domain.py @@ -231,7 +231,10 @@ def extract_domains(expr): domainlist = [] for t in traverse_unique_terminals(expr): domainlist.extend(t.ufl_domains()) - return sorted(join_domains(domainlist)) + return sorted( + join_domains(domainlist), + key=lambda D: (D.topological_dimension(), D.ufl_cell(), D.ufl_id()), + ) def extract_unique_domain(expr): From aa94d9b4ac2829726c956e7086381b0fdd0cebbb Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 18 Apr 2024 16:56:56 +0100 Subject: [PATCH 099/136] i -> I (#270) --- ufl/argument.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ufl/argument.py b/ufl/argument.py index 82a23f570..a9326a237 100644 --- a/ufl/argument.py +++ b/ufl/argument.py @@ -41,7 +41,7 @@ def __getnewargs__(self): return (self._ufl_function_space, self._number, self._part) def __init__(self, function_space, number, part=None): - """initialise.""" + """Initialise.""" if not isinstance(function_space, AbstractFunctionSpace): raise ValueError("Expecting a FunctionSpace.") From ed982814223d5221657242867106d3dbf371516d Mon Sep 17 00:00:00 2001 From: Daiane Iglesia Dolci <63597005+Ig-dolci@users.noreply.github.com> Date: Fri, 19 Apr 2024 12:42:40 +0100 Subject: [PATCH 100/136] Check ufl_signature (#269) Co-authored-by: Matthew Scroggs --- ufl/utils/sorting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py index 254a60d59..896ca2099 100644 --- a/ufl/utils/sorting.py +++ b/ufl/utils/sorting.py @@ -82,6 +82,8 @@ def canonicalize_metadata(metadata): value = canonicalize_metadata(value) elif isinstance(value, (int, float, str)) or value is None: value = str(value) + elif hasattr(value, "ufl_signature"): + value = value.ufl_signature else: warnings.warn( f"Applying str() to a metadata value of type {type(value).__name__}, " From 1ec1df4a0a8adea73b6a2e4536f7730a0025acf3 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 07:57:18 +0200 Subject: [PATCH 101/136] Updated to .md README (#275) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8fb43220d..0c2fb0fe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ maintainers = [ { name = "FEniCS Steering Council" }, ] description = "Unified Form Language" -readme = "README.rst" +readme = "README.md" license = { file = "COPYING.lesser" } requires-python = ">=3.8.0" dependencies = ["numpy"] From efb6f6c55babd90068e8f2d596dd8ceea6fafd15 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 08:10:50 +0200 Subject: [PATCH 102/136] Kebab case in build-wheels.yml (#276) --- .github/workflows/build-wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index 7d0847457..e7118028c 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -70,7 +70,7 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - repository_url: https://upload.pypi.org/legacy/ + repository-url: https://upload.pypi.org/legacy/ - name: Push to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -78,4 +78,4 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_TEST_TOKEN }} - repository_url: https://test.pypi.org/legacy/ + repository-url: https://test.pypi.org/legacy/ From 75a0664bffbab74fd5a502de55d29ed3765e179d Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 09:12:30 +0200 Subject: [PATCH 103/136] Correct documentation links (#277) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5da66e02f..ad6cb8c89 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,10 @@ https://www.fenicsproject.org [![UFL CI](https://github.com/FEniCS/ufl/workflows/UFL%20CI/badge.svg)](https://github.com/FEniCS/ufl/workflows/UFL%20CI) [![Coverage Status](https://coveralls.io/repos/github/FEniCS/ufl/badge.svg?branch=master)](https://coveralls.io/github/FEniCS/ufl?branch=master) -[![Documentation Status](https://readthedocs.org/projects/fenics-ufl/badge/?version=latest)](https://fenics.readthedocs.io/projects/ufl/en/latest/?badge=latest) ## Documentation -Documentation can be viewed at https://fenics-ufl.readthedocs.org/. - +Documentation can be viewed at https://docs.fenicsproject.org ## License From c6b343c70f65782cbadff6d341cea4d6803e7548 Mon Sep 17 00:00:00 2001 From: "Garth N. Wells" Date: Thu, 25 Apr 2024 12:41:55 +0100 Subject: [PATCH 104/136] Remove unecessary pip pinning (#278) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0c2fb0fe6..aa740362b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=62", "wheel", "pip>=22.3"] +requires = ["setuptools>=62", "wheel"] build-backend = "setuptools.build_meta" [project] From 0f215e87d93a98af4705f4e5b82a807add4ea89a Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 25 Apr 2024 17:57:47 +0200 Subject: [PATCH 105/136] Bump version. (#279) * Bump version. * macos-latest is ARM, no 3.8/3.9 Python available. --- .github/workflows/pythonapp.yml | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index a3bdfc35a..27ecaa3f9 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -21,6 +21,11 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + exclude: + - os: macos-latest + python-version: '3.8' + - os: macos-latest + python-version: '3.9' steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index aa740362b..e7cdd0749 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fenics-ufl" -version = "2023.3.0.dev0" +version = "2024.2.0.dev0" authors = [{ name = "UFL contributors" }] maintainers = [ { email = "fenics-steering-council@googlegroups.com" }, From effa67e7a8dadec40086c7a7fb89e5550bfca60c Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 2 May 2024 15:01:06 +0200 Subject: [PATCH 106/136] Add Windows CI step (#281) * Bump version. * macos-latest is ARM, no 3.8/3.9 Python available. * Try tests on Windows. --- .github/workflows/pythonapp.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 27ecaa3f9..c6b79671c 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -26,6 +26,9 @@ jobs: python-version: '3.8' - os: macos-latest python-version: '3.9' + include: + - os: windows-latest + python-version: '3.11' steps: - uses: actions/checkout@v4 From 31c3a818ea2da764c0ba66f4a38f39d0c4ea7908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 21 May 2024 13:04:34 +0200 Subject: [PATCH 107/136] Clarify usage of pow and abs (#286) --- .github/workflows/fenicsx-tests.yml | 12 ++++++------ doc/sphinx/source/manual/form_language.rst | 13 ++++++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index 6363a55bf..a3f8581e8 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 - name: Install test dependencies run: | @@ -30,7 +30,7 @@ jobs: - name: Install UFL run: | - pip3 install . + pip3 install --break-system-packages . - name: Install Basix run: | @@ -65,12 +65,12 @@ jobs: - name: Install UFL run: | - pip3 install . + pip3 install --break-system-packages . - name: Install Basix and FFCx run: | - python3 -m pip install git+https://github.com/FEniCS/basix.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + 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 - name: Clone DOLFINx uses: actions/checkout@v4 @@ -83,6 +83,6 @@ jobs: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ cmake --build build cmake --install build - python3 -m pip -v install --no-build-isolation --check-build-dependencies --config-settings=cmake.build-type="Developer" dolfinx/python/ + python3 -m pip -v install --break-system-packages --no-build-isolation --check-build-dependencies --config-settings=cmake.build-type="Developer" dolfinx/python/ - name: Run DOLFINx unit tests run: python3 -m pytest -n auto dolfinx/python/test/unit diff --git a/doc/sphinx/source/manual/form_language.rst b/doc/sphinx/source/manual/form_language.rst index 3d20924de..1c9687893 100644 --- a/doc/sphinx/source/manual/form_language.rst +++ b/doc/sphinx/source/manual/form_language.rst @@ -764,11 +764,10 @@ Basic nonlinear functions Some basic nonlinear functions are also available, their meaning mostly obvious. -* ``abs(f)``: the absolute value of f. +The following functions are defined and should be imported from `ufl` -* ``sign(f)``: the sign of f (+1 or -1). -* ``pow(f, g)`` or ``f**g``: f to the power g, :math:`f^g` +* ``sign(f)``: the sign of f (+1 or -1). * ``sqrt(f)``: square root, :math:`\sqrt{f}` @@ -806,6 +805,14 @@ obvious. * ``bessel_K(nu, f)``: Modified Bessel function of the second kind, :math:`K_\nu(f)` +while the following Python built in functions can be used without an import statement + +* ``abs(f)``: the absolute value of f. + + +* ``pow(f, g)`` or ``f**g``: f to the power g, :math:`f^g` + + These functions do not accept non-scalar operands or operands with free indices or ``Argument`` dependencies. From c2947b166b288b2b15f62105ee8578b8c791c45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Tue, 21 May 2024 13:10:05 +0200 Subject: [PATCH 108/136] Remove broken/unused function (#287) --- ufl/form.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ufl/form.py b/ufl/form.py index 1f4b1a20d..620fb4e1e 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -371,12 +371,6 @@ def subdomain_data(self): self._analyze_subdomain_data() return self._subdomain_data - def max_subdomain_ids(self): - """Returns a mapping on the form ``{domain:{integral_type:max_subdomain_id}}``.""" - if self._max_subdomain_ids is None: - self._analyze_subdomain_data() - return self._max_subdomain_ids - def coefficients(self): """Return all ``Coefficient`` objects found in form.""" if self._coefficients is None: From 72a1bfabfead23ab39e4e1ab3fe07dd095a834a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Thu, 23 May 2024 11:29:11 +0200 Subject: [PATCH 109/136] Handling of extract_blocks when part is equal to zero. (#285) * Handling of extract_blocks when part is equal to zero. * Ruff formatting * tmp workaround * Ruff formatting * Add fix from Lawrence * Break system packages --- test/test_extract_blocks.py | 82 ++++++++++++++++++++++++++++++++++ ufl/algorithms/formsplitter.py | 29 +++++++++--- 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 test/test_extract_blocks.py diff --git a/test/test_extract_blocks.py b/test/test_extract_blocks.py new file mode 100644 index 000000000..4fdd923e7 --- /dev/null +++ b/test/test_extract_blocks.py @@ -0,0 +1,82 @@ +import pytest + +import ufl +import ufl.algorithms +from ufl.finiteelement import FiniteElement, MixedElement + + +def epsilon(u): + return ufl.sym(ufl.grad(u)) + + +def sigma(u, p): + return epsilon(u) - p * ufl.Identity(u.ufl_shape[0]) + + +@pytest.mark.parametrize("rank", [0, 1, 2]) +def test_extract_blocks(rank): + """Test extractions of blocks from mixed function space.""" + cell = ufl.triangle + domain = ufl.Mesh(FiniteElement("Lagrange", cell, 1, (2,), ufl.identity_pullback, ufl.H1)) + fe_scalar = FiniteElement("Lagrange", cell, 1, (), ufl.identity_pullback, ufl.H1) + fe_vector = FiniteElement("Lagrange", cell, 1, (2,), ufl.identity_pullback, ufl.H1) + + me = MixedElement([fe_vector, fe_scalar]) + + # # Function spaces + W = ufl.FunctionSpace(domain, me) + V = ufl.FunctionSpace(domain, fe_vector) + Q = ufl.FunctionSpace(domain, fe_scalar) + + if rank == 0: + wh = ufl.Coefficient(W) + uh, ph = ufl.split(wh) + # Test that functionals return the identity + J = ufl.inner(sigma(uh, ph), sigma(uh, ph)) * ufl.dx + J0 = ufl.extract_blocks(J, 0) + assert len(J0) == 1 + assert J == J0[0] + elif rank == 1: + + def rhs(uh, ph, v, q): + F_0 = ufl.inner(sigma(uh, ph), epsilon(v)) * ufl.dx(domain=domain) + F_1 = ufl.div(uh) * q * ufl.dx + return F_0, F_1 + + wh = ufl.Coefficient(W) + uh, ph = ufl.split(wh) + v, q = ufl.TestFunctions(W) + F = sum(rhs(uh, ph, v, q)) + + v_ = ufl.TestFunction(V) + q_ = ufl.TestFunction(Q) + F_sub = rhs(uh, ph, ufl.as_vector([vi for vi in v_]), q_) + + F_0_ext = ufl.extract_blocks(F, 0) + assert F_sub[0].signature() == F_0_ext.signature() + + F_1_ext = ufl.extract_blocks(F, 1) + assert F_sub[1].signature() == F_1_ext.signature() + elif rank == 2: + + def lhs(u, p, v, q): + J_00 = ufl.inner(u, v) * ufl.dx(domain=domain) + J_01 = ufl.div(v) * p * ufl.dx + J_10 = q * ufl.div(u) * ufl.dx + J_11 = ufl.inner(ufl.grad(p), ufl.grad(q)) * ufl.dx + return J_00, J_01, J_10, J_11 + + v_ = ufl.TestFunction(V) + q_ = ufl.TestFunction(Q) + u_ = ufl.TrialFunction(V) + p_ = ufl.TrialFunction(Q) + J_sub = lhs(ufl.as_vector([ui for ui in u_]), p_, ufl.as_vector([vi for vi in v_]), q_) + + v, q = ufl.TestFunctions(W) + uh, ph = ufl.TrialFunctions(W) + J = sum(lhs(uh, ph, v, q)) + + for i in range(2): + for j in range(2): + J_ij_ext = ufl.extract_blocks(J, i, j) + assert J_sub[2 * i + j].signature() == J_ij_ext.signature() diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index c96a63e99..2e7671cc3 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -1,6 +1,6 @@ """Extract part of a form in a mixed FunctionSpace.""" -# Copyright (C) 2016 Chris Richardson and Lawrence Mitchell +# Copyright (C) 2016-2024 Chris Richardson and Lawrence Mitchell # # This file is part of UFL (https://www.fenicsproject.org) # @@ -10,6 +10,7 @@ from ufl.algorithms.map_integrands import map_integrand_dags from ufl.argument import Argument +from ufl.classes import FixedIndex, ListTensor from ufl.constantvalue import Zero from ufl.corealg.multifunction import MultiFunction from ufl.functionspace import FunctionSpace @@ -49,7 +50,7 @@ def argument(self, obj): # whose sub-elements need their function space to be created Q = obj.ufl_function_space() dom = Q.ufl_domain() - sub_elements = obj.ufl_element().sub_elements() + sub_elements = obj.ufl_element().sub_elements # If not a mixed element, do nothing if len(sub_elements) == 0: @@ -71,6 +72,19 @@ def argument(self, obj): return as_vector(args) + def indexed(self, o, child, multiindex): + """Extract indexed entry if multindices are fixed. + + This avoids tensors like (v_0, 0)[1] to be created. + """ + indices = multiindex.indices() + if isinstance(child, ListTensor) and all(isinstance(i, FixedIndex) for i in indices): + if len(indices) == 1: + return child.ufl_operands[indices[0]._value] + else: + return ListTensor(*(child.ufl_operands[i._value] for i in multiindex.indices())) + return self.expr(o, child, multiindex) + def multi_index(self, obj): """Apply to multi_index.""" return obj @@ -83,15 +97,20 @@ def extract_blocks(form, i=None, j=None): fs = FormSplitter() arguments = form.arguments() forms = [] - numbers = tuple(sorted(set(a.number() for a in arguments))) arity = len(numbers) - parts = tuple(sorted(set(a.part() for a in arguments))) assert arity <= 2 - if arity == 0: return (form,) + parts = [] + for a in arguments: + if len(a.ufl_element().sub_elements) > 0: + return fs.split(form, i, j) + else: + # If standard element, extract only part + parts.append(a.part()) + parts = tuple(sorted(set(parts))) for pi in parts: if arity > 1: for pj in parts: From c73b28f2e7392293730a7f1f8d39e0c8058003ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 29 May 2024 21:08:33 +0200 Subject: [PATCH 110/136] Use extract_unique domain (#291) * Use extract_unique domain * Add build requirements install * Add pytest installation * Add all ci deps * Remove another deprecated call * Yet another deprecated call --- .github/workflows/fenicsx-tests.yml | 3 ++- ufl/pullback.py | 4 ++-- ufl/split_functions.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/fenicsx-tests.yml b/.github/workflows/fenicsx-tests.yml index a3f8581e8..192c4156d 100644 --- a/.github/workflows/fenicsx-tests.yml +++ b/.github/workflows/fenicsx-tests.yml @@ -83,6 +83,7 @@ jobs: cmake -G Ninja -DCMAKE_BUILD_TYPE=Developer -B build -S dolfinx/cpp/ cmake --build build cmake --install build - python3 -m pip -v install --break-system-packages --no-build-isolation --check-build-dependencies --config-settings=cmake.build-type="Developer" dolfinx/python/ + python3 -m pip install --break-system-packages -r dolfinx/python/build-requirements.txt + python3 -m pip -v install --break-system-packages --no-build-isolation --check-build-dependencies --config-settings=cmake.build-type="Developer" dolfinx/python/[ci] - name: Run DOLFINx unit tests run: python3 -m pytest -n auto dolfinx/python/test/unit diff --git a/ufl/pullback.py b/ufl/pullback.py index d32fc3861..0357c6d9b 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -356,7 +356,7 @@ def apply(self, expr): Returns: The function pulled back to the reference cell """ - domain = expr.ufl_domain() + domain = extract_unique_domain(expr) space = FunctionSpace(domain, self._element) rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] g_components = [] @@ -435,7 +435,7 @@ def apply(self, expr): Returns: The function pulled back to the reference cell """ - domain = expr.ufl_domain() + domain = extract_unique_domain(expr) space = FunctionSpace(domain, self._element) rflat = [expr[idx] for idx in np.ndindex(expr.ufl_shape)] g_components = [] diff --git a/ufl/split_functions.py b/ufl/split_functions.py index a5c18da38..4f4cb4aaf 100644 --- a/ufl/split_functions.py +++ b/ufl/split_functions.py @@ -7,6 +7,7 @@ # # Modified by Anders Logg, 2008 +from ufl.domain import extract_unique_domain from ufl.functionspace import FunctionSpace from ufl.indexed import Indexed from ufl.permutation import compute_indices @@ -21,7 +22,7 @@ def split(v): If v is a Coefficient or Argument in a mixed space, returns a tuple with the function components corresponding to the subelements. """ - domain = v.ufl_domain() + domain = extract_unique_domain(v) # Default range is all of v begin = 0 From ea144b0bd8304776818515ec2b2e6033c63c1d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 29 May 2024 21:23:20 +0200 Subject: [PATCH 111/136] Fix argument-formatter. (#288) * Check that we never get to _afmt on CI * Add failing test * Fix string formatter * Fix formatting --- test/test_check_arities.py | 16 ++++++++++++++++ ufl/algorithms/check_arities.py | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/test_check_arities.py b/test/test_check_arities.py index e4ede949f..e2c32f5f5 100755 --- a/test/test_check_arities.py +++ b/test/test_check_arities.py @@ -68,3 +68,19 @@ def test_complex_arities(): with pytest.raises(ArityMismatch): compute_form_data(inner(conj(v), u) * dx, complex_mode=True) + + +def test_product_arity(): + cell = tetrahedron + D = Mesh(FiniteElement("Lagrange", cell, 1, (3,), identity_pullback, H1)) + V = FunctionSpace(D, FiniteElement("Lagrange", cell, 2, (3,), identity_pullback, H1)) + v = TestFunction(V) + u = TrialFunction(V) + + with pytest.raises(ArityMismatch): + F = inner(u, u) * dx + compute_form_data(F, complex_mode=True) + + with pytest.raises(ArityMismatch): + L = inner(v, v) * dx + compute_form_data(L, complex_mode=False) diff --git a/ufl/algorithms/check_arities.py b/ufl/algorithms/check_arities.py index dc255b7bc..e1d9b366b 100644 --- a/ufl/algorithms/check_arities.py +++ b/ufl/algorithms/check_arities.py @@ -1,6 +1,7 @@ """Check arities.""" from itertools import chain +from typing import Tuple from ufl.classes import Argument, Zero from ufl.corealg.map_dag import map_expr_dag @@ -14,9 +15,10 @@ class ArityMismatch(BaseException): pass -def _afmt(atuple): +def _afmt(atuple: Tuple[Argument, bool]) -> str: """Return a string representation of an arity tuple.""" - return tuple(f"conj({arg})" if conj else str(arg) for arg, conj in atuple) + arg, conj = atuple + return f"conj({arg})" if conj else str(arg) class ArityChecker(MultiFunction): From 7bfef955df0a177b999586e6adf52ec72af9c8d7 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Thu, 4 Jul 2024 08:48:17 +0100 Subject: [PATCH 112/136] remove unused definition (#299) --- test/test_external_operator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 27e5d102e..51f9a7e71 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -275,7 +275,6 @@ def test_adjoint_action_jacobian(V1, V2, V3): # N(u, m; v*) N = ExternalOperator(u, m, function_space=V3) - (vstar_N,) = N.arguments() # Arguments for the Gateaux-derivative def u_hat(number): From e69de178c883b6db186cc21ebbdd4d75ca825b69 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 4 Jul 2024 09:08:35 +0100 Subject: [PATCH 113/136] Use subdegree instead of superdegree to check cell bendy-ness (#295) The reason subdegree is right is that you care about whether the edges are straight, not whether there are any quadratic functions on the interior. See https://github.com/firedrakeproject/firedrake/issues/3612. Co-authored-by: Matthew Scroggs --- ufl/algorithms/apply_geometry_lowering.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ufl/algorithms/apply_geometry_lowering.py b/ufl/algorithms/apply_geometry_lowering.py index 4ef00c1c1..1dbc8e1c0 100644 --- a/ufl/algorithms/apply_geometry_lowering.py +++ b/ufl/algorithms/apply_geometry_lowering.py @@ -304,7 +304,7 @@ def _reduce_cell_edge_length(self, o, reduction_op): domain = extract_unique_domain(o) - if domain.ufl_coordinate_element().embedded_superdegree > 1: + if domain.ufl_coordinate_element().embedded_subdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o @@ -329,7 +329,7 @@ def cell_diameter(self, o): domain = extract_unique_domain(o) - if domain.ufl_coordinate_element().embedded_superdegree > 1: + if domain.ufl_coordinate_element().embedded_subdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute cell diameter of P1 or Q1 cell.") return o @@ -366,7 +366,7 @@ def _reduce_facet_edge_length(self, o, reduction_op): if domain.ufl_cell().topological_dimension() < 3: raise ValueError("Facet edge lengths only make sense for topological dimension >= 3.") - elif domain.ufl_coordinate_element().embedded_superdegree > 1: + elif domain.ufl_coordinate_element().embedded_subdegree > 1: # Don't lower bendy cells, instead leave it to form compiler warnings.warn("Only know how to compute facet edge lengths of P1 or Q1 cell.") return o From b507f2fdfa7d99b6f433dcdf02dad921d4d6f8a0 Mon Sep 17 00:00:00 2001 From: Nacime Bouziani <48448063+nbouziani@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:45:01 +0100 Subject: [PATCH 114/136] Relax assumption on BaseFormOperator's dual argument slot (#283) * Update type check to get shape in replace * Add test * Fix ruff * Relax base form operators type check * Update BFO methods relying on the dual space argument slot * Fix Interpolate's function space * Fix typo: function_space -> ufl_function_space * Update adjoint numbering * Fix ruff * Fix rugg --------- Co-authored-by: David A. Ham --- test/test_external_operator.py | 42 +++++++++++++++++++++++++++++----- ufl/action.py | 4 +--- ufl/adjoint.py | 6 ++++- ufl/algorithms/replace.py | 4 ++-- ufl/core/base_form_operator.py | 11 +++++++-- ufl/core/interpolate.py | 12 ++++++---- 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 51f9a7e71..ab996cce1 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -8,10 +8,12 @@ from ufl import ( Action, Argument, + Coargument, Coefficient, Constant, Form, FunctionSpace, + Matrix, Mesh, TestFunction, TrialFunction, @@ -21,6 +23,7 @@ derivative, dx, inner, + replace, sin, triangle, ) @@ -266,7 +269,7 @@ def get_external_operators(form_base): elif isinstance(form_base, BaseForm): return form_base.base_form_operators() else: - raise ValueError("Expecting FormBase argument!") + raise ValueError("Expecting BaseForm argument!") def test_adjoint_action_jacobian(V1, V2, V3): @@ -339,16 +342,17 @@ def vstar_N(number): dFdu_adj = adjoint(dFdu) dFdm_adj = adjoint(dFdm) - assert dFdu_adj.arguments() == (u_hat(n_arg),) + v_F - assert dFdm_adj.arguments() == (m_hat(n_arg),) + v_F + V = v_F[0].ufl_function_space() + assert dFdu_adj.arguments() == (TestFunction(V1), TrialFunction(V)) + assert dFdm_adj.arguments() == (TestFunction(V2), TrialFunction(V)) # Action of the adjoint - q = Coefficient(v_F[0].ufl_function_space()) + q = Coefficient(V) action_dFdu_adj = action(dFdu_adj, q) action_dFdm_adj = action(dFdm_adj, q) - assert action_dFdu_adj.arguments() == (u_hat(n_arg),) - assert action_dFdm_adj.arguments() == (m_hat(n_arg),) + assert action_dFdu_adj.arguments() == (TestFunction(V1),) + assert action_dFdm_adj.arguments() == (TestFunction(V2),) def test_multiple_external_operators(V1, V2): @@ -486,3 +490,29 @@ def test_multiple_external_operators(V1, V2): dFdu = expand_derivatives(derivative(F, u)) assert dFdu == dFdu_partial + Action(dFdN1_partial, dN1du) + Action(dFdN5_partial, dN5du) + + +def test_replace(V1): + u = Coefficient(V1, count=0) + N = ExternalOperator(u, function_space=V1) + + # dN(u; uhat, v*) + dN = expand_derivatives(derivative(N, u)) + vstar, uhat = dN.arguments() + assert isinstance(vstar, Coargument) + + # Replace v* by a Form + v = TestFunction(V1) + F = inner(u, v) * dx + G = replace(dN, {vstar: F}) + + dN_replaced = dN._ufl_expr_reconstruct_(u, argument_slots=(F, uhat)) + assert G == dN_replaced + + # Replace v* by an Action + M = Matrix(V1, V1) + A = Action(M, u) + G = replace(dN, {vstar: A}) + + dN_replaced = dN._ufl_expr_reconstruct_(u, argument_slots=(A, uhat)) + assert G == dN_replaced diff --git a/ufl/action.py b/ufl/action.py index 3bb9fd533..78489ff6b 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -122,9 +122,7 @@ def _analyze_domains(self): from ufl.domain import join_domains # Collect domains - self._domains = join_domains( - chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) - ) + self._domains = join_domains(chain.from_iterable(e.ufl_domain() for e in self.ufl_operands)) def equals(self, other): """Check if two Actions are equal.""" diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 92447b985..7c1d5c63f 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -85,7 +85,11 @@ def form(self): def _analyze_form_arguments(self): """The arguments of adjoint are the reverse of the form arguments.""" - self._arguments = self._form.arguments()[::-1] + reversed_args = self._form.arguments()[::-1] + # Canonical numbering for arguments that is consistent with other BaseForm objects. + self._arguments = tuple( + type(arg)(arg.ufl_function_space(), number=i) for i, arg in enumerate(reversed_args) + ) self._coefficients = self._form.coefficients() def _analyze_domains(self): diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 5f3616949..fbe9cecd0 100644 --- a/ufl/algorithms/replace.py +++ b/ufl/algorithms/replace.py @@ -10,7 +10,7 @@ from ufl.algorithms.analysis import has_exact_type from ufl.algorithms.map_integrands import map_integrand_dags -from ufl.classes import CoefficientDerivative, Form +from ufl.classes import BaseForm, CoefficientDerivative from ufl.constantvalue import as_ufl from ufl.core.external_operator import ExternalOperator from ufl.core.interpolate import Interpolate @@ -28,7 +28,7 @@ def __init__(self, mapping): # One can replace Coarguments by 1-Forms def get_shape(x): """Get the shape of an object.""" - if isinstance(x, Form): + if isinstance(x, BaseForm): return x.arguments()[0].ufl_shape return x.ufl_shape diff --git a/ufl/core/base_form_operator.py b/ufl/core/base_form_operator.py index 90bc23dd0..aae943f75 100644 --- a/ufl/core/base_form_operator.py +++ b/ufl/core/base_form_operator.py @@ -133,14 +133,21 @@ def count(self): @property def ufl_shape(self): """Return the UFL shape of the coefficient.produced by the operator.""" - return self.arguments()[0]._ufl_shape + arg, *_ = self.argument_slots() + if isinstance(arg, BaseForm): + arg, *_ = arg.arguments() + return arg._ufl_shape def ufl_function_space(self): """Return the function space associated to the operator. I.e. return the dual of the base form operator's Coargument. """ - return self.arguments()[0]._ufl_function_space.dual() + arg, *_ = self.argument_slots() + if isinstance(arg, BaseForm): + arg, *_ = arg.arguments() + return arg._ufl_function_space + return arg._ufl_function_space.dual() def _ufl_expr_reconstruct_( self, *operands, function_space=None, derivatives=None, argument_slots=None diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 57e1506fd..3b5e3ba4d 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -8,13 +8,14 @@ # # Modified by Nacime Bouziani, 2021-2022 +from ufl.action import Action from ufl.argument import Argument, Coargument from ufl.coefficient import Cofunction from ufl.constantvalue import as_ufl from ufl.core.base_form_operator import BaseFormOperator from ufl.core.ufl_type import ufl_type from ufl.duals import is_dual -from ufl.form import Form +from ufl.form import BaseForm, Form from ufl.functionspace import AbstractFunctionSpace @@ -35,7 +36,7 @@ def __init__(self, expr, v): defined on the dual of the FunctionSpace to interpolate into. """ # This check could be more rigorous. - dual_args = (Coargument, Cofunction, Form) + dual_args = (Coargument, Cofunction, Form, Action, BaseFormOperator) if isinstance(v, AbstractFunctionSpace): if is_dual(v): @@ -53,8 +54,11 @@ def __init__(self, expr, v): # Reversed order convention argument_slots = (v, expr) # Get the primal space (V** = V) - vv = v if not isinstance(v, Form) else v.arguments()[0] - function_space = vv.ufl_function_space().dual() + if isinstance(v, BaseForm): + arg, *_ = v.arguments() + function_space = arg.ufl_function_space() + else: + function_space = v.ufl_function_space().dual() # Set the operand as `expr` for DAG traversal purpose. operand = expr BaseFormOperator.__init__( From 004a678320e5c65015d56071b968ee08314aecc1 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Sat, 20 Jul 2024 07:44:26 +0100 Subject: [PATCH 115/136] ConstantValue: Support general dtypes (#292) --- test/test_literals.py | 8 ++++++++ ufl/constantvalue.py | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/test/test_literals.py b/test/test_literals.py index 7e63c89bf..41ca4460b 100755 --- a/test/test_literals.py +++ b/test/test_literals.py @@ -1,6 +1,8 @@ __authors__ = "Martin Sandve Alnæs" __date__ = "2011-04-14" +import numpy as np + from ufl import PermutationSymbol, as_matrix, as_vector, indices, product from ufl.classes import Indexed from ufl.constantvalue import ComplexValue, FloatValue, IntValue, Zero, as_ufl @@ -29,6 +31,7 @@ def test_float(self): f4 = FloatValue(1.0) f5 = 3 - FloatValue(1) - 1 f6 = 3 * FloatValue(2) / 6 + f7 = as_ufl(np.ones((1,), dtype="d")[0]) assert f1 == f1 self.assertNotEqual(f1, f2) # IntValue vs FloatValue, == compares representations! @@ -36,6 +39,7 @@ def test_float(self): assert f2 == f4 assert f2 == f5 assert f2 == f6 + assert f2 == f7 def test_int(self): @@ -45,6 +49,7 @@ def test_int(self): f4 = IntValue(1.0) f5 = 3 - IntValue(1) - 1 f6 = 3 * IntValue(2) / 6 + f7 = as_ufl(np.ones((1,), dtype="int")[0]) assert f1 == f1 self.assertNotEqual(f1, f2) # IntValue vs FloatValue, == compares representations! @@ -52,6 +57,7 @@ def test_int(self): assert f1 == f4 assert f1 == f5 assert f2 == f6 # Division produces a FloatValue + assert f1 == f7 def test_complex(self): @@ -62,6 +68,7 @@ def test_complex(self): f5 = ComplexValue(1.0 + 1.0j) f6 = as_ufl(1.0) f7 = as_ufl(1.0j) + f8 = as_ufl(np.array([1 + 1j], dtype="complex")[0]) assert f1 == f1 assert f1 == f4 @@ -71,6 +78,7 @@ def test_complex(self): assert f5 == f2 + f3 assert f4 == f5 assert f6 + f7 == f2 + f3 + assert f4 == f8 def test_scalar_sums(self): diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py index 0aa320d8f..b918a670b 100644 --- a/ufl/constantvalue.py +++ b/ufl/constantvalue.py @@ -9,6 +9,7 @@ # Modified by Anders Logg, 2011. # Modified by Massimiliano Leoni, 2016. +import numbers from math import atan2 import ufl @@ -506,12 +507,12 @@ def as_ufl(expression): """Converts expression to an Expr if possible.""" if isinstance(expression, (Expr, ufl.BaseForm)): return expression - elif isinstance(expression, complex): - return ComplexValue(expression) - elif isinstance(expression, float): - return FloatValue(expression) - elif isinstance(expression, int): + elif isinstance(expression, numbers.Integral): return IntValue(expression) + elif isinstance(expression, numbers.Real): + return FloatValue(expression) + elif isinstance(expression, numbers.Complex): + return ComplexValue(expression) else: raise ValueError( f"Invalid type conversion: {expression} can not be converted to any UFL type." From b06ecc7342d74c255ffd91b8a230ff390c81249d Mon Sep 17 00:00:00 2001 From: "David A. Ham" Date: Tue, 24 Sep 2024 12:42:48 +0100 Subject: [PATCH 116/136] Make cofunctionals terminal, and test (#300) * Make cofunctionals terminal, and test * ruff --- test/test_equals.py | 5 +++++ ufl/coefficient.py | 1 + ufl/formatting/ufl2unicode.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/test/test_equals.py b/test/test_equals.py index 3956463e8..b34a3c4de 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -1,6 +1,7 @@ """Test of expression comparison.""" from ufl import Coefficient, Cofunction, FunctionSpace, Mesh, triangle +from ufl.exprcontainers import ExprList from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 @@ -69,6 +70,10 @@ def test_comparison_of_cofunctions(): assert not v1 == u1 assert not v2 == u2 + # Objects in ExprList as happens when taking derivatives. + assert ExprList(v1, v1) == ExprList(v1, v1b) + assert not ExprList(v1, v2) == ExprList(v1, v1) + def test_comparison_of_products(): V = FiniteElement("Lagrange", triangle, 1, (), identity_pullback, H1) diff --git a/ufl/coefficient.py b/ufl/coefficient.py index 8dbcfdb5a..bd35f8275 100644 --- a/ufl/coefficient.py +++ b/ufl/coefficient.py @@ -118,6 +118,7 @@ class Cofunction(BaseCoefficient, BaseForm): ) _primal = False _dual = True + _ufl_is_terminal_ = True __eq__ = BaseForm.__eq__ diff --git a/ufl/formatting/ufl2unicode.py b/ufl/formatting/ufl2unicode.py index 04956f137..5d8d5f0c5 100644 --- a/ufl/formatting/ufl2unicode.py +++ b/ufl/formatting/ufl2unicode.py @@ -485,10 +485,26 @@ def coefficient(self, o): return f"{var}{subscript_number(i)}" return self.coefficient_names[o.count()] + def cofunction(self, o): + """Format a cofunction.""" + if self.coefficient_names is None: + i = o.count() + var = "cofunction" + if len(o.ufl_shape) == 1: + var += UC.combining_right_arrow_above + elif len(o.ufl_shape) > 1 and self.colorama_bold: + var = f"{colorama.Style.BRIGHT}{var}{colorama.Style.RESET_ALL}" + return f"{var}{subscript_number(i)}" + return self.coefficient_names[o.count()] + def base_form_operator(self, o): """Format a base_form_operator.""" return "BaseFormOperator" + def action(self, o, a, b): + """Format an Action.""" + return f"Action({a}, {b})" + def constant(self, o): """Format a constant.""" i = o.count() From 60a6956a553a014d9b9dc5fbd66c7e764dea0f72 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Tue, 24 Sep 2024 12:44:17 +0100 Subject: [PATCH 117/136] BaseForm: ensure that subclasses implement ufl_domains() (#302) --- ufl/action.py | 10 ++++++++- ufl/adjoint.py | 12 +++++++++- ufl/form.py | 59 ++++++++++++++++++++++++++------------------------ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/ufl/action.py b/ufl/action.py index 78489ff6b..fb6416865 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -122,7 +122,15 @@ def _analyze_domains(self): from ufl.domain import join_domains # Collect domains - self._domains = join_domains(chain.from_iterable(e.ufl_domain() for e in self.ufl_operands)) + self._domains = join_domains( + chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + ) + + def ufl_domains(self): + """Return all domains found in the base form.""" + if self._domains is None: + self._analyze_domains() + return self._domains def equals(self, other): """Check if two Actions are equal.""" diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 7c1d5c63f..987372a73 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -8,6 +8,8 @@ # # Modified by Nacime Bouziani, 2021-2022. +from itertools import chain + from ufl.argument import Coargument from ufl.core.ufl_type import ufl_type from ufl.form import BaseForm, FormSum, ZeroBaseForm @@ -97,7 +99,15 @@ def _analyze_domains(self): from ufl.domain import join_domains # Collect unique domains - self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands]) + self._domains = join_domains( + chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + ) + + def ufl_domains(self): + """Return all domains found in the base form.""" + if self._domains is None: + self._analyze_domains() + return self._domains def equals(self, other): """Check if two Adjoints are equal.""" diff --git a/ufl/form.py b/ufl/form.py index 620fb4e1e..8aadd57d5 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -113,13 +113,12 @@ def ufl_domain(self): Fails if multiple domains are found. """ - if self._domains is None: - self._analyze_domains() - - if len(self._domains) > 1: + try: + (domain,) = set(self.ufl_domains()) + except ValueError: raise ValueError("%s must have exactly one domain." % type(self).__name__) - # Return the single geometric domain - return self._domains[0] + # Return the one and only domain + return domain # --- Operator implementations --- @@ -139,7 +138,7 @@ def __radd__(self, other): def __add__(self, other): """Add.""" - if isinstance(other, (int, float)) and other == 0: + if isinstance(other, numbers.Number) and other == 0: # Allow adding 0 or 0.0 as a no-op, needed for sum([a,b]) return self elif isinstance(other, Zero): @@ -329,26 +328,6 @@ def ufl_cell(self): """ return self.ufl_domain().ufl_cell() - def ufl_domain(self): - """Return the single geometric integration domain occuring in the form. - - Fails if multiple domains are found. - - NB! This does not include domains of coefficients defined on - other meshes, look at form data for that additional information. - """ - # Collect all domains - domains = self.ufl_domains() - # Check that all are equal TODO: don't return more than one if - # all are equal? - if not all(domain == domains[0] for domain in domains): - raise ValueError( - "Calling Form.ufl_domain() is only valid if all integrals share domain." - ) - - # Return the one and only domain - return domains[0] - def geometric_dimension(self): """Return the geometric dimension shared by all domains and functions in this form.""" gdims = tuple(set(domain.geometric_dimension() for domain in self.ufl_domains())) @@ -807,7 +786,15 @@ def _analyze_domains(self): from ufl.domain import join_domains # Collect unique domains - self._domains = join_domains([component.ufl_domain() for component in self._components]) + self._domains = join_domains( + chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + ) + + def ufl_domains(self): + """Return all domains found in the base form.""" + if self._domains is None: + self._analyze_domains() + return self._domains def __hash__(self): """Hash.""" @@ -857,6 +844,7 @@ class ZeroBaseForm(BaseForm): "_arguments", "_coefficients", "ufl_operands", + "_domains", "_hash", # Pyadjoint compatibility "form", @@ -875,6 +863,21 @@ def _analyze_form_arguments(self): # `self._arguments` is already set in `BaseForm.__init__` self._coefficients = () + def _analyze_domains(self): + """Analyze which domains can be found in ZeroBaseForm.""" + from ufl.domain import join_domains + + # Collect unique domains + self._domains = join_domains( + chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + ) + + def ufl_domains(self): + """Return all domains found in the base form.""" + if self._domains is None: + self._analyze_domains() + return self._domains + def __ne__(self, other): """Overwrite BaseForm.__neq__ which relies on `equals`.""" return not self == other From bde4f2f98f80a8e1f1a6ef9c004c0f13615dbc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 2 Oct 2024 09:32:21 +0200 Subject: [PATCH 118/136] Fix extract block for tensor spaces (#308) * Fix extract block for tensor spaces * Remove conditional from text, as it is described in detail above. * Various sanity checks and error handling * Add handling of zero (happens with extract_block) --- ufl/algorithms/apply_derivatives.py | 2 + ufl/algorithms/formsplitter.py | 85 +++++++++++++++++++---------- ufl/finiteelement.py | 2 +- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index 6fcbb1b5f..bd9624f12 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -1139,6 +1139,8 @@ def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): gprimesum = gprimesum + compute_gprimeterm( ngrads, vval, vcomp, wshape, wcomp ) + elif isinstance(v, Zero): + pass else: if wshape != (): diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index 2e7671cc3..00519963d 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -8,6 +8,8 @@ # # Modified by Cecile Daversin-Catty, 2018 +from typing import Optional + from ufl.algorithms.map_integrands import map_integrand_dags from ufl.argument import Argument from ufl.classes import FixedIndex, ListTensor @@ -31,20 +33,10 @@ def argument(self, obj): if obj.part() is not None: # Mixed element built from MixedFunctionSpace, # whose sub-function spaces are indexed by obj.part() - if len(obj.ufl_shape) == 0: - if obj.part() == self.idx[obj.number()]: - return obj - else: - return Zero() + if obj.part() == self.idx[obj.number()]: + return obj else: - indices = [()] - for m in obj.ufl_shape: - indices = [(k + (j,)) for k in indices for j in range(m)] - - if obj.part() == self.idx[obj.number()]: - return as_vector([obj[j] for j in indices]) - else: - return as_vector([Zero() for j in indices]) + return Zero(obj.ufl_shape) else: # Mixed element built from MixedElement, # whose sub-elements need their function space to be created @@ -92,33 +84,63 @@ def multi_index(self, obj): expr = MultiFunction.reuse_if_untouched -def extract_blocks(form, i=None, j=None): - """Extract blocks.""" +def extract_blocks(form, i: Optional[int] = None, j: Optional[None] = None): + """Extract blocks of a form. + + If arity is 0, returns the form. + If arity is 1, return the ith block. If ``i`` is ``None``, return all blocks. + If arity is 2, return the ``(i,j)`` entry. If ``j`` is ``None``, return the ith row. + + If neither `i` nor `j` are set, return all blocks (as a scalar, vector or tensor). + + Args: + form: A form + i: Index of the block to extract. If set to ``None``, ``j`` must be None. + j: Index of the block to extract. + """ + if i is None and j is not None: + raise RuntimeError(f"Cannot extract block with {j=} and {i=}.") + fs = FormSplitter() arguments = form.arguments() - forms = [] numbers = tuple(sorted(set(a.number() for a in arguments))) arity = len(numbers) assert arity <= 2 if arity == 0: return (form,) - parts = [] - for a in arguments: - if len(a.ufl_element().sub_elements) > 0: - return fs.split(form, i, j) + # If mixed element, each argument has no sub-elements + parts = tuple(sorted(set(part for a in arguments if (part := a.part()) is not None))) + if parts == (): + if i is None and j is None: + num_sub_elements = arguments[0].ufl_element().num_sub_elements + forms = [] + for pi in range(num_sub_elements): + form_i = [] + for pj in range(num_sub_elements): + f = fs.split(form, pi, pj) + if f.empty(): + form_i.append(None) + else: + form_i.append(f) + forms.append(tuple(form_i)) + return tuple(forms) else: - # If standard element, extract only part - parts.append(a.part()) - parts = tuple(sorted(set(parts))) - for pi in parts: + return fs.split(form, i, j) + + # If mixed function space, each argument has sub-elements + forms = [] + num_parts = len(parts) + for pi in range(num_parts): + form_i = [] if arity > 1: - for pj in parts: + for pj in range(num_parts): f = fs.split(form, pi, pj) if f.empty(): - forms.append(None) + form_i.append(None) else: - forms.append(f) + form_i.append(f) + forms.append(tuple(form_i)) else: f = fs.split(form, pi) if f.empty(): @@ -131,10 +153,15 @@ def extract_blocks(form, i=None, j=None): except TypeError: # Only one form returned forms_tuple = (forms,) - if i is not None: + if (num_rows := len(forms_tuple)) <= i: + raise RuntimeError(f"Cannot extract block {i} from form with {num_rows} blocks.") if arity > 1 and j is not None: - return forms_tuple[i * len(parts) + j] + if (num_cols := len(forms_tuple[i])) <= j: + raise RuntimeError( + f"Cannot extract block {i},{j} from form with {num_rows}x{num_cols} blocks." + ) + return forms_tuple[i][j] else: return forms_tuple[i] else: diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py index a5ffff6a6..413746b45 100644 --- a/ufl/finiteelement.py +++ b/ufl/finiteelement.py @@ -148,7 +148,7 @@ def components(self) -> _typing.Dict[_typing.Tuple[int, ...], int]: offset = 0 c_offset = 0 for e in self.sub_elements: - for i, j in enumerate(np.ndindex(e.value_shape)): + for i, j in enumerate(np.ndindex(e.reference_value_shape)): components[(offset + i,)] = c_offset + e.components[j] c_offset += max(e.components.values()) + 1 offset += e.value_size From cfd864e981ea76ff797e3ac78ade91c822c2a1c4 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Wed, 2 Oct 2024 09:10:54 +0100 Subject: [PATCH 119/136] Move FiniteElement.components to FunctionSpace.components (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * missing reference_ * remove IPython embed * remove components property * ruff * move components to function space * import numpy as np --------- Co-authored-by: Jørgen Schartum Dokken --- ufl/algorithms/expand_indices.py | 6 +++--- ufl/finiteelement.py | 26 -------------------------- ufl/functionspace.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ufl/algorithms/expand_indices.py b/ufl/algorithms/expand_indices.py index 1ae1cbfc3..316998341 100644 --- a/ufl/algorithms/expand_indices.py +++ b/ufl/algorithms/expand_indices.py @@ -49,7 +49,7 @@ def form_argument(self, x): if sh == (): return x else: - e = x.ufl_element() + space = x.ufl_function_space() r = len(sh) # Get component @@ -58,8 +58,8 @@ def form_argument(self, x): raise ValueError("Component size mismatch.") # Map it through an eventual symmetry mapping - if len(e.components) > 1: - c = min(i for i, j in e.components.items() if j == e.components[c]) + if len(space.components) > 1: + c = min(i for i, j in space.components.items() if j == space.components[c]) if r != len(c): raise ValueError("Component size mismatch after symmetry mapping.") diff --git a/ufl/finiteelement.py b/ufl/finiteelement.py index 413746b45..8dbc76ed1 100644 --- a/ufl/finiteelement.py +++ b/ufl/finiteelement.py @@ -15,8 +15,6 @@ import abc as _abc import typing as _typing -import numpy as np - from ufl.cell import Cell as _Cell from ufl.pullback import AbstractPullback as _AbstractPullback from ufl.pullback import IdentityPullback as _IdentityPullback @@ -130,30 +128,6 @@ def _ufl_signature_data_(self) -> str: """Return UFL signature data.""" return repr(self) - @property - def components(self) -> _typing.Dict[_typing.Tuple[int, ...], int]: - """Get the numbering of the components of the element. - - Returns: - A map from the components of the values on a physical cell (eg (0, 1)) - to flat component numbers on the reference cell (eg 1) - """ - if isinstance(self.pullback, _SymmetricPullback): - return self.pullback._symmetry - - if len(self.sub_elements) == 0: - return {(): 0} - - components = {} - offset = 0 - c_offset = 0 - for e in self.sub_elements: - for i, j in enumerate(np.ndindex(e.reference_value_shape)): - components[(offset + i,)] = c_offset + e.components[j] - c_offset += max(e.components.values()) + 1 - offset += e.value_size - return components - @property def reference_value_size(self) -> int: """Return the integer product of the reference value shape.""" diff --git a/ufl/functionspace.py b/ufl/functionspace.py index cc047523c..834734274 100644 --- a/ufl/functionspace.py +++ b/ufl/functionspace.py @@ -11,6 +11,8 @@ import typing +import numpy as np + from ufl.core.ufl_type import UFLObject from ufl.domain import join_domains from ufl.duals import is_dual, is_primal @@ -60,6 +62,32 @@ def __init__(self, domain, element, label=""): self._ufl_domain = domain self._ufl_element = element + @property + def components(self) -> typing.Dict[typing.Tuple[int, ...], int]: + """Get the numbering of the components of the element of this space. + + Returns: + A map from the components of the values on a physical cell (eg (0, 1)) + to flat component numbers on the reference cell (eg 1) + """ + from ufl.pullback import SymmetricPullback + + if isinstance(self._ufl_element.pullback, SymmetricPullback): + return self._ufl_element.pullback._symmetry + + if len(self._ufl_element.sub_elements) == 0: + return {(): 0} + + components = {} + offset = 0 + c_offset = 0 + for s in self.ufl_sub_spaces(): + for i, j in enumerate(np.ndindex(s.value_shape)): + components[(offset + i,)] = c_offset + s.components[j] + c_offset += max(s.components.values()) + 1 + offset += s.value_size + return components + def label(self): """Return label of boundary domains to differentiate restricted and unrestricted.""" return self._label From 3b85665337f337cf98c1af779102eca47d187b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 2 Oct 2024 17:39:42 +0200 Subject: [PATCH 120/136] Move grouping of integral from `build_integral_data` to `group_form_integrals` (#305) * Move grouping of integral from build integral data to group form integrals * Ruff formatting * Add missing key * Add index renumbering * Fix zero index-renumbering for scalar zero * Ruff format * Update ufl/algorithms/domain_analysis.py * Add documentation and motivation * Doc fixes * Fix name * Fix lawrence name * Apply suggestions from code review Co-authored-by: Joe Dean * Simplify using Lawrence instructions * Ruff formatting --------- Co-authored-by: Matthew Scroggs Co-authored-by: Joe Dean --- test/test_indices.py | 34 ++++++++++- ufl/algorithms/domain_analysis.py | 60 +++++++++++-------- ufl/algorithms/renumbering.py | 98 +++++++++++-------------------- 3 files changed, 103 insertions(+), 89 deletions(-) diff --git a/test/test_indices.py b/test/test_indices.py index ec85f7aa0..417648095 100755 --- a/test/test_indices.py +++ b/test/test_indices.py @@ -1,5 +1,7 @@ import pytest +import ufl.algorithms +import ufl.classes from ufl import ( Argument, Coefficient, @@ -15,6 +17,7 @@ exp, i, indices, + interval, j, k, l, @@ -305,4 +308,33 @@ def test_spatial_derivative(self): def test_renumbering(self): - pass + """Test that kernels with common integral data, but different index numbering, + are correctly renumbered.""" + cell = interval + mesh = Mesh(FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) + V = FunctionSpace(mesh, FiniteElement("Lagrange", cell, 1, (2,), identity_pullback, H1)) + v = TestFunction(V) + u = TrialFunction(V) + i = indices(1) + a0 = u[i].dx(0) * v[i].dx(0) * ufl.dx((1)) + a1 = ( + u[i].dx(0) + * v[i].dx(0) + * ufl.dx( + ( + 2, + 3, + ) + ) + ) + form_data = ufl.algorithms.compute_form_data( + a0 + a1, + do_apply_function_pullbacks=True, + do_apply_integral_scaling=True, + do_apply_geometry_lowering=True, + preserve_geometry_types=(ufl.classes.Jacobian,), + do_apply_restrictions=True, + do_append_everywhere_integrals=False, + ) + + assert len(form_data.integral_data) == 1 diff --git a/ufl/algorithms/domain_analysis.py b/ufl/algorithms/domain_analysis.py index 7d7c34042..19e0f4e3c 100644 --- a/ufl/algorithms/domain_analysis.py +++ b/ufl/algorithms/domain_analysis.py @@ -15,6 +15,7 @@ attach_coordinate_derivatives, strip_coordinate_derivatives, ) +from ufl.algorithms.renumbering import renumber_indices from ufl.form import Form from ufl.integral import Integral from ufl.protocols import id_or_none @@ -262,37 +263,16 @@ def build_integral_data(integrals): itgs = defaultdict(list) # --- Merge integral data that has the same integrals, - unique_integrals = defaultdict(tuple) - metadata_table = defaultdict(dict) for integral in integrals: - integrand = integral.integrand() integral_type = integral.integral_type() ufl_domain = integral.ufl_domain() - metadata = integral.metadata() - meta_hash = hash(canonicalize_metadata(metadata)) - subdomain_id = integral.subdomain_id() - subdomain_data = id_or_none(integral.subdomain_data()) - if subdomain_id == "everywhere": + subdomain_ids = integral.subdomain_id() + if "everywhere" in subdomain_ids: raise ValueError( "'everywhere' not a valid subdomain id. " "Did you forget to call group_form_integrals?" ) - unique_integrals[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] += ( - subdomain_id, - ) - metadata_table[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] = metadata - - for integral_data, subdomain_ids in unique_integrals.items(): - (integral_type, ufl_domain, metadata, integrand, subdomain_data) = integral_data - integral = Integral( - integrand, - integral_type, - ufl_domain, - subdomain_ids, - metadata_table[integral_data], - subdomain_data, - ) # Group for integral data (One integral data object for all # integrals with same domain, itype, (but possibly different metadata). itgs[(ufl_domain, integral_type, subdomain_ids)].append(integral) @@ -380,7 +360,39 @@ def calc_hash(cd): ) integral = attach_coordinate_derivatives(integral, samecd_integrals[0]) integrals.append(integral) - return Form(integrals) + + # Group integrals by common integrand + # u.dx(0)*dx(1) + u.dx(0)*dx(2) -> u.dx(0)*dx((1,2)) + # to avoid duplicate kernels generated after geometry lowering + unique_integrals = defaultdict(tuple) + metadata_table = defaultdict(dict) + for integral in integrals: + integral_type = integral.integral_type() + ufl_domain = integral.ufl_domain() + metadata = integral.metadata() + meta_hash = hash(canonicalize_metadata(metadata)) + subdomain_id = integral.subdomain_id() + subdomain_data = id_or_none(integral.subdomain_data()) + integrand = renumber_indices(integral.integrand()) + unique_integrals[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] += ( + subdomain_id, + ) + metadata_table[(integral_type, ufl_domain, meta_hash, integrand, subdomain_data)] = metadata + + grouped_integrals = [] + for integral_data, subdomain_ids in unique_integrals.items(): + (integral_type, ufl_domain, metadata, integrand, subdomain_data) = integral_data + integral = Integral( + integrand, + integral_type, + ufl_domain, + subdomain_ids, + metadata_table[integral_data], + subdomain_data, + ) + grouped_integrals.append(integral) + + return Form(grouped_integrals) def reconstruct_form_from_integral_data(integral_data): diff --git a/ufl/algorithms/renumbering.py b/ufl/algorithms/renumbering.py index 87e08203d..0e0408c8d 100644 --- a/ufl/algorithms/renumbering.py +++ b/ufl/algorithms/renumbering.py @@ -1,87 +1,57 @@ """Algorithms for renumbering of counted objects, currently variables and indices.""" -# Copyright (C) 2008-2016 Martin Sandve Alnæs and Anders Logg +# Copyright (C) 2008-2024 Martin Sandve Alnæs, Anders Logg, Jørgen S. Dokken and Lawrence Mitchell # # This file is part of UFL (https://www.fenicsproject.org) # # SPDX-License-Identifier: LGPL-3.0-or-later -from ufl.algorithms.transformer import ReuseTransformer, apply_transformer -from ufl.classes import Zero -from ufl.core.expr import Expr -from ufl.core.multiindex import FixedIndex, Index, MultiIndex -from ufl.variable import Label, Variable +from collections import defaultdict +from itertools import count as _count +from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.core.multiindex import Index +from ufl.corealg.multifunction import MultiFunction -class VariableRenumberingTransformer(ReuseTransformer): - """Variable renumbering transformer.""" - - def __init__(self): - """Initialise.""" - ReuseTransformer.__init__(self) - self.variable_map = {} - - def variable(self, o): - """Apply to variable.""" - e, l = o.ufl_operands # noqa: E741 - v = self.variable_map.get(l) - if v is None: - e = self.visit(e) - l2 = Label(len(self.variable_map)) - v = Variable(e, l2) - self.variable_map[l] = v - return v +class IndexRelabeller(MultiFunction): + """Renumber indices to have a consistent index numbering starting from 0.""" -class IndexRenumberingTransformer(VariableRenumberingTransformer): - """Index renumbering transformer. + def __init__(self): + """Initialize index relabeller with a zero count.""" + super().__init__() + count = _count() + self.index_cache = defaultdict(lambda: Index(next(count))) - This is a poorly designed algorithm. It is used in some tests, - please do not use for anything else. - """ + expr = MultiFunction.reuse_if_untouched - def __init__(self): - """Initialise.""" - VariableRenumberingTransformer.__init__(self) - self.index_map = {} + def multi_index(self, o): + """Apply to multi-indices.""" + return type(o)( + tuple(self.index_cache[i] if isinstance(i, Index) else i for i in o.indices()) + ) def zero(self, o): """Apply to zero.""" fi = o.ufl_free_indices fid = o.ufl_index_dimensions - mapped_fi = tuple(self.index(Index(count=i)) for i in fi) - paired_fid = [(mapped_fi[pos], fid[pos]) for pos, a in enumerate(fi)] - new_fi, new_fid = zip(*tuple(sorted(paired_fid))) - return Zero(o.ufl_shape, new_fi, new_fid) - - def index(self, o): - """Apply to index.""" - if isinstance(o, FixedIndex): + new_indices = [self.index_cache[Index(i)].count() for i in fi] + if fi == () and fid == (): return o - else: - c = o._count - i = self.index_map.get(c) - if i is None: - i = Index(count=len(self.index_map)) - self.index_map[c] = i - return i + new_fi, new_fid = zip(*sorted(zip(new_indices, fid), key=lambda x: x[0])) + return type(o)(o.ufl_shape, tuple(new_fi), tuple(new_fid)) - def multi_index(self, o): - """Apply to multi_index.""" - new_indices = tuple(self.index(i) for i in o.indices()) - return MultiIndex(new_indices) +def renumber_indices(form): + """Renumber indices to have a consistent index numbering starting from 0. -def renumber_indices(expr): - """Renumber indices.""" - if isinstance(expr, Expr): - num_free_indices = len(expr.ufl_free_indices) + This is useful to avoid multiple kernels for the same integrand, + but with different subdomain ids. - result = apply_transformer(expr, IndexRenumberingTransformer()) + Args: + form: A UFL form, integral or expression. - if isinstance(expr, Expr): - if num_free_indices != len(result.ufl_free_indices): - raise ValueError( - "The number of free indices left in expression " - "should be invariant w.r.t. renumbering." - ) - return result + Returns: + A new form, integral or expression with renumbered indices. + """ + reindexer = IndexRelabeller() + return map_integrand_dags(reindexer, form) From 838420206c3d73e3d81125e03171068210f8e772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Wed, 2 Oct 2024 20:12:51 +0200 Subject: [PATCH 121/136] Extend `FormSplitter` to handle restrictions (#310) * Add handling of case where a split returns a form with no arguments (they have all been reduced to zeros). * Add test case * Add test and some error handling * Apply restriction in formsplitter * Fix docstring --- test/test_extract_blocks.py | 21 +++++++++++++++++++++ ufl/algorithms/formsplitter.py | 13 ++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/test/test_extract_blocks.py b/test/test_extract_blocks.py index 4fdd923e7..0ce712418 100644 --- a/test/test_extract_blocks.py +++ b/test/test_extract_blocks.py @@ -80,3 +80,24 @@ def lhs(u, p, v, q): for j in range(2): J_ij_ext = ufl.extract_blocks(J, i, j) assert J_sub[2 * i + j].signature() == J_ij_ext.signature() + + +def test_postive_restricted_extract_none(): + cell = ufl.triangle + d = cell.topological_dimension() + domain = ufl.Mesh(FiniteElement("Lagrange", cell, 1, (d,), ufl.identity_pullback, ufl.H1)) + el_u = FiniteElement("Lagrange", cell, 2, (d,), ufl.identity_pullback, ufl.H1) + el_p = FiniteElement("Lagrange", cell, 1, (), ufl.identity_pullback, ufl.H1) + V = ufl.FunctionSpace(domain, el_u) + Q = ufl.FunctionSpace(domain, el_p) + W = ufl.MixedFunctionSpace(V, Q) + u, p = ufl.TrialFunctions(W) + v, q = ufl.TestFunctions(W) + a = ( + ufl.inner(ufl.grad(u), ufl.grad(v)) * ufl.dx + + ufl.div(u) * q * ufl.dx + + ufl.div(v) * p * ufl.dx + ) + a += ufl.inner(u("+"), v("+")) * ufl.dS + a_blocks = ufl.extract_blocks(a) + assert a_blocks[1][1] is None diff --git a/ufl/algorithms/formsplitter.py b/ufl/algorithms/formsplitter.py index 00519963d..e9fefaf68 100644 --- a/ufl/algorithms/formsplitter.py +++ b/ufl/algorithms/formsplitter.py @@ -10,7 +10,7 @@ from typing import Optional -from ufl.algorithms.map_integrands import map_integrand_dags +from ufl.algorithms.map_integrands import map_expr_dag, map_integrand_dags from ufl.argument import Argument from ufl.classes import FixedIndex, ListTensor from ufl.constantvalue import Zero @@ -81,6 +81,15 @@ def multi_index(self, obj): """Apply to multi_index.""" return obj + def restricted(self, o): + """Apply to a restricted function.""" + # If we hit a restriction first apply form splitter to argument, then check for zero + op_split = map_expr_dag(self, o.ufl_operands[0]) + if isinstance(op_split, Zero): + return op_split + else: + return op_split(o._side) + expr = MultiFunction.reuse_if_untouched @@ -139,6 +148,8 @@ def extract_blocks(form, i: Optional[int] = None, j: Optional[None] = None): if f.empty(): form_i.append(None) else: + if (num_args := len(f.arguments())) != 2: + raise RuntimeError(f"Expected 2 arguments, got {num_args}") form_i.append(f) forms.append(tuple(form_i)) else: From 2125dcec5df6dd005cf54175687d43fc64564bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Schartum=20Dokken?= Date: Mon, 7 Oct 2024 11:17:19 +0200 Subject: [PATCH 122/136] Fix pullback of double derivatives on Piola type elements (#312) * Add handling of case where a split returns a form with no arguments (they have all been reduced to zeros). * Add test case * Add test and some error handling * Apply restriction in formsplitter * Fix docstring * Fix apply pullback on piola mapped elements for double derivatives --- ufl/algorithms/apply_derivatives.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ufl/algorithms/apply_derivatives.py b/ufl/algorithms/apply_derivatives.py index bd9624f12..da7b61da1 100644 --- a/ufl/algorithms/apply_derivatives.py +++ b/ufl/algorithms/apply_derivatives.py @@ -30,6 +30,8 @@ Imag, Indexed, IndexSum, + Jacobian, + JacobianDeterminant, JacobianInverse, ListTensor, Product, @@ -672,7 +674,7 @@ def reference_grad(self, o): f = o.ufl_operands[0] valid_operand = f._ufl_is_in_reference_frame_ or isinstance( - f, (JacobianInverse, SpatialCoordinate) + f, (JacobianInverse, SpatialCoordinate, Jacobian, JacobianDeterminant) ) if not valid_operand: raise ValueError("ReferenceGrad can only wrap a reference frame type!") From 737cd7cf272ec47d2d001116bbaa22ae45159c56 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 10 Oct 2024 12:40:00 +0200 Subject: [PATCH 123/136] pypi trusted publishing (#314) * Update version number. * Fix * Correct tag ref * Use BaseArgument.__eq__ in Argument (#147) (cherry picked from commit e68314821b6f3c157bd396a8c457cd9e4628296e) * Oops! Bump version number. * Fix. * Bump version. * Updated to .md README (#275) * Kebab case in build-wheels.yml (#276) * Fixes for pypa packaging. * Correct documentation links (#277) * Remove unecessary pip pinning (#278) * Bump version. * Update version to 2024.2.0 * Try trusted publishing on pypi. * Remove version change. * Fix. * Tidy. * Simplify. --------- Co-authored-by: Chris Richardson Co-authored-by: Michal Habera Co-authored-by: Garth N. Wells --- .github/workflows/build-wheels.yml | 50 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index e7118028c..7301b1b6c 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -11,11 +11,11 @@ on: default: "main" type: string test_pypi_publish: - description: "Publish to Test PyPi (true | false)" + description: "Publish to Test PyPi" default: false type: boolean pypi_publish: - description: "Publish to PyPi (true | false)" + description: "Publish to PyPi" default: false type: boolean @@ -26,11 +26,11 @@ on: default: "main" type: string test_pypi_publish: - description: "Publish to Test PyPi (true | false)" + description: "Publish to Test PyPi" default: false type: boolean pypi_publish: - description: "Publish to PyPi (true | false))" + description: "Publish to PyPi" default: false type: boolean @@ -54,28 +54,44 @@ jobs: with: path: dist/* - upload_pypi: - name: Upload to PyPI (optional) + upload_test_pypi: + name: Upload to test PyPI (optional) + if: ${{ github.event.inputs.test_pypi_publish == 'true' }} needs: [build] runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/p/fenics-ufl + permissions: + id-token: write + steps: - uses: actions/download-artifact@v4 with: name: artifact path: dist - - name: Push to PyPI + - name: Push to test PyPI uses: pypa/gh-action-pypi-publish@release/v1 - if: ${{ github.event.inputs.pypi_publish == 'true' }} with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - repository-url: https://upload.pypi.org/legacy/ + repository-url: https://test.pypi.org/legacy/ + + upload_pypi: + name: Upload to PyPI (optional) + if: ${{ github.event.inputs.pypi_publish == 'true' }} + needs: [build] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/fenics-ufl + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + name: artifact + path: dist - - name: Push to Test PyPI + - name: Push to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - if: ${{ github.event.inputs.test_pypi_publish == 'true' }} - with: - user: __token__ - password: ${{ secrets.PYPI_TEST_TOKEN }} - repository-url: https://test.pypi.org/legacy/ From 9baf3def2505a652795ca4f4b6d5bd8b85eafed0 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Thu, 10 Oct 2024 16:16:04 +0200 Subject: [PATCH 124/136] Bump version number to 0.10.0 (#315) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e7cdd0749..555a334d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fenics-ufl" -version = "2024.2.0.dev0" +version = "2024.3.0.dev0" authors = [{ name = "UFL contributors" }] maintainers = [ { email = "fenics-steering-council@googlegroups.com" }, From c3b01b92d5426da85430151c127cffa31b2df7b4 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Fri, 11 Oct 2024 08:51:51 +0200 Subject: [PATCH 125/136] Add dependabot (#316) --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..4e7559dcb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" From b4d5c6721e0b245b71f296b4e3a59f4cb64d85a0 Mon Sep 17 00:00:00 2001 From: "Jack S. Hale" Date: Fri, 11 Oct 2024 16:05:21 +0200 Subject: [PATCH 126/136] Add GitHub Token to coveralls step (#319) As suggested by CI failure --- .github/workflows/pythonapp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index c6b79671c..8264069a5 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -49,6 +49,7 @@ jobs: - name: Upload to Coveralls if: ${{ github.repository == 'FEniCS/ufl' && github.head_ref == '' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' }} env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} run: coveralls From 57ec6dfd20441649c17c3cc645748d8aa5d3aa68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 08:12:14 +0200 Subject: [PATCH 127/136] Bump actions/setup-python from 3 to 5 (#317) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tsfc-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index eea8c3860..c0a096b7e 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.9 From 25f376a5809e45b49db2a9c64c54ea2cca53913c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 06:12:54 +0000 Subject: [PATCH 128/136] Bump actions/checkout from 3 to 4 (#318) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tsfc-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index c0a096b7e..bc80bc171 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -17,7 +17,7 @@ jobs: CXX: g++-10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: @@ -28,7 +28,7 @@ jobs: pip3 install . - name: Clone tsfc - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: ./tsfc repository: firedrakeproject/tsfc From 84ecfa6a746e7c095428b7cbf8080980638720b6 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Mon, 28 Oct 2024 11:13:59 +0000 Subject: [PATCH 129/136] Add HCurlDiv space and covariant-contravariant Piola mapping (#320) --- ufl/__init__.py | 7 ++++++- ufl/pullback.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ ufl/sobolevspace.py | 4 +++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ufl/__init__.py b/ufl/__init__.py index 9d255211a..0782ebad7 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -74,6 +74,7 @@ -HCurl -HEin -HDivDiv + -HCurlDiv * Pull backs:: @@ -84,6 +85,7 @@ -l2_piola -double_contravariant_piola -double_covariant_piola + -covariant_contravariant_piola * Function spaces:: @@ -412,13 +414,14 @@ MixedPullback, SymmetricPullback, contravariant_piola, + covariant_contravariant_piola, covariant_piola, double_contravariant_piola, double_covariant_piola, identity_pullback, l2_piola, ) -from ufl.sobolevspace import H1, H2, L2, HCurl, HDiv, HDivDiv, HEin, HInf +from ufl.sobolevspace import H1, H2, L2, HCurl, HCurlDiv, HDiv, HDivDiv, HEin, HInf from ufl.split_functions import split from ufl.tensors import ( as_matrix, @@ -448,12 +451,14 @@ "HInf", "HEin", "HDivDiv", + "HCurlDiv", "identity_pullback", "l2_piola", "contravariant_piola", "covariant_piola", "double_contravariant_piola", "double_covariant_piola", + "covariant_contravariant_piola", "l2_piola", "MixedPullback", "SymmetricPullback", diff --git a/ufl/pullback.py b/ufl/pullback.py index 0357c6d9b..06cfac9ef 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -31,6 +31,7 @@ "L2Piola", "DoubleContravariantPiola", "DoubleCovariantPiola", + "CovariantContravariantPiola", "MixedPullback", "SymmetricPullback", "PhysicalPullback", @@ -328,6 +329,51 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: return (gdim, gdim) +class CovariantContravariantPiola(AbstractPullback): + """The covariant contravariant Piola pull back.""" + + def __repr__(self) -> str: + """Return a representation of the object.""" + return "CovariantContravariantPiola()" + + @property + def is_identity(self) -> bool: + """Is this pull back the identity (or the identity applied to mutliple components).""" + return False + + def apply(self, expr): + """Apply the pull back. + + Args: + expr: A function on a physical cell + + Returns: The function pulled back to the reference cell + """ + from ufl.classes import Jacobian, JacobianDeterminant, JacobianInverse + + domain = extract_unique_domain(expr) + J = Jacobian(domain) + detJ = JacobianDeterminant(J) + K = JacobianInverse(domain) + # Apply transform "row-wise" to TensorElement(PiolaMapped, ...) + *k, i, j, m, n = indices(len(expr.ufl_shape) + 2) + kmn = (*k, m, n) + return as_tensor((1.0 / detJ) * K[m, i] * expr[kmn] * J[j, n], (*k, i, j)) + + def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: + """Get the physical value shape when this pull back is applied to an element. + + Args: + element: The element that the pull back is applied to + domain: The domain + + Returns: + The value shape when the pull back is applied to the given element + """ + gdim = domain.geometric_dimension() + return (gdim, gdim) + + class MixedPullback(AbstractPullback): """Pull back for a mixed element.""" @@ -589,6 +635,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: l2_piola = L2Piola() double_covariant_piola = DoubleCovariantPiola() double_contravariant_piola = DoubleContravariantPiola() +covariant_contravariant_piola = CovariantContravariantPiola() physical_pullback = PhysicalPullback() custom_pullback = CustomPullback() undefined_pullback = UndefinedPullback() diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py index 77d10db6f..6dfa83633 100644 --- a/ufl/sobolevspace.py +++ b/ufl/sobolevspace.py @@ -53,6 +53,7 @@ def __init__(self, name, parents=None): "HCurl": 0, "HEin": 0, "HDivDiv": 0, + "HCurlDiv": 0, "DirectionalH": 0, }[self.name] @@ -156,7 +157,7 @@ def __lt__(self, other): if other in [HDiv, HCurl]: return all(self._orders[i] >= 1 for i in self._spatial_indices) - elif other.name in ["HDivDiv", "HEin"]: + elif other.name in ["HDivDiv", "HEin", "HCurlDiv"]: # Don't know how these spaces compare return NotImplementedError(f"Don't know how to compare with {other.name}") else: @@ -175,3 +176,4 @@ def __str__(self): HInf = SobolevSpace("HInf", [H2, H1, HDiv, HCurl, L2]) HEin = SobolevSpace("HEin", [L2]) HDivDiv = SobolevSpace("HDivDiv", [L2]) +HCurlDiv = SobolevSpace("HCurlDiv", [L2]) From f7ef9fcb6feb175f4b284755d0f8552287a7f313 Mon Sep 17 00:00:00 2001 From: Francesco Ballarin Date: Sat, 2 Nov 2024 12:16:20 +0100 Subject: [PATCH 130/136] Upload to coveralls and docs from CI job running against python 3.12 (#321) --- .github/workflows/pythonapp.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 8264069a5..127601b1b 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -47,7 +47,7 @@ jobs: run: | python -m pytest -n auto --cov=ufl/ --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml test/ - name: Upload to Coveralls - if: ${{ github.repository == 'FEniCS/ufl' && github.head_ref == '' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' }} + if: ${{ github.repository == 'FEniCS/ufl' && github.head_ref == '' && matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} @@ -67,25 +67,25 @@ jobs: if-no-files-found: error - name: Checkout FEniCS/docs - if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} + if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }} uses: actions/checkout@v4 with: repository: "FEniCS/docs" path: "docs" ssh-key: "${{ secrets.SSH_GITHUB_DOCS_PRIVATE_KEY }}" - name: Set version name - if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} + if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }} run: | echo "VERSION_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Copy documentation into repository - if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} + if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }} run: | cd docs git rm -r --ignore-unmatch ufl/${{ env.VERSION_NAME }} mkdir -p ufl/${{ env.VERSION_NAME }} cp -r ../doc/sphinx/build/html/* ufl/${{ env.VERSION_NAME }} - name: Commit and push documentation to FEniCS/docs - if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.8 }} + if: ${{ github.repository == 'FEniCS/ufl' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') ) && runner.os == 'Linux' && matrix.python-version == 3.12 }} run: | cd docs git config --global user.email "fenics@github.com" From d5c7b337e17ffc9414728487c5c8425cda579a0c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Nov 2024 10:22:14 +0000 Subject: [PATCH 131/136] Fix pullback for symmetric tensors with non-scalar subelements (#322) --- ufl/pullback.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ufl/pullback.py b/ufl/pullback.py index 06cfac9ef..831dda026 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -519,8 +519,10 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - assert element == self._element - return tuple(i + 1 for i in max(self._symmetry.keys())) + assert isinstance(element, type(self._element)) + subelem = element.sub_elements[0] + pvs = subelem.pullback.physical_value_shape(subelem, domain) + return tuple(i + 1 for i in max(self._symmetry.keys())) + pvs class PhysicalPullback(AbstractPullback): From 3be472e7f483be33ffe5239b7afc92ab83d77e77 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Nov 2024 14:13:46 +0000 Subject: [PATCH 132/136] Fix Piola pullbacks for VectorElement (#323) --- ufl/pullback.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ufl/pullback.py b/ufl/pullback.py index 831dda026..c2bf18d84 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -157,7 +157,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim,) + element.reference_value_shape[1:] + return element.reference_value_shape[:-1] + (gdim,) class CovariantPiola(AbstractPullback): @@ -200,7 +200,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim,) + element.reference_value_shape[1:] + return element.reference_value_shape[:-1] + (gdim,) class L2Piola(AbstractPullback): @@ -283,7 +283,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim, gdim) + return element.reference_value_shape[:-2] + (gdim, gdim) class DoubleCovariantPiola(AbstractPullback): @@ -326,7 +326,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim, gdim) + return element.reference_value_shape[:-2] + (gdim, gdim) class CovariantContravariantPiola(AbstractPullback): @@ -371,7 +371,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: The value shape when the pull back is applied to the given element """ gdim = domain.geometric_dimension() - return (gdim, gdim) + return element.reference_value_shape[:-2] + (gdim, gdim) class MixedPullback(AbstractPullback): From d8350119ac162caf949f8aad6cee76223a5c637f Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Thu, 14 Nov 2024 19:49:38 +0000 Subject: [PATCH 133/136] Return reference_value_shape as default (#324) --- ufl/pullback.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ufl/pullback.py b/ufl/pullback.py index c2bf18d84..0dc780e52 100644 --- a/ufl/pullback.py +++ b/ufl/pullback.py @@ -560,7 +560,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - raise NotImplementedError() + return element.reference_value_shape class CustomPullback(AbstractPullback): @@ -598,9 +598,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - if element.reference_value_shape == (): - return () - raise NotImplementedError() + return element.reference_value_shape class UndefinedPullback(AbstractPullback): @@ -628,7 +626,7 @@ def physical_value_shape(self, element, domain) -> typing.Tuple[int, ...]: Returns: The value shape when the pull back is applied to the given element """ - raise NotImplementedError() + return element.reference_value_shape identity_pullback = IdentityPullback() From 3b29b6cb03885238f68468581b4b33da0a87b29c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 15 Nov 2024 14:10:13 +0000 Subject: [PATCH 134/136] FormSum: sort_domains (#325) --- ufl/form.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ufl/form.py b/ufl/form.py index 8aadd57d5..4fc7c2165 100644 --- a/ufl/form.py +++ b/ufl/form.py @@ -783,11 +783,11 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in FormSum.""" - from ufl.domain import join_domains + from ufl.domain import join_domains, sort_domains # Collect unique domains - self._domains = join_domains( - chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + self._domains = sort_domains( + join_domains(chain.from_iterable(e.ufl_domains() for e in self.ufl_operands)) ) def ufl_domains(self): @@ -865,11 +865,11 @@ def _analyze_form_arguments(self): def _analyze_domains(self): """Analyze which domains can be found in ZeroBaseForm.""" - from ufl.domain import join_domains + from ufl.domain import join_domains, sort_domains # Collect unique domains - self._domains = join_domains( - chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + self._domains = sort_domains( + join_domains(chain.from_iterable(e.ufl_domains() for e in self.ufl_operands)) ) def ufl_domains(self): From 6432a72edf54064a6e501d9a69d0d2ddecceb87a Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 15 Nov 2024 14:51:45 +0000 Subject: [PATCH 135/136] use main firedrake branches in tests (#326) --- .github/workflows/tsfc-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index bc80bc171..98fd4696b 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -32,14 +32,14 @@ jobs: with: path: ./tsfc repository: firedrakeproject/tsfc - ref: mscroggs/gdim + ref: master - name: Install tsfc run: | cd tsfc pip install -r requirements-ext.txt pip install git+https://github.com/coneoproject/COFFEE.git#egg=coffee pip install git+https://github.com/firedrakeproject/fiat.git#egg=fenics-fiat - pip install git+https://github.com/FInAT/FInAT.git@mscroggs/gdim#egg=finat + pip install git+https://github.com/FInAT/FInAT.git#egg=finat pip install git+https://github.com/firedrakeproject/loopy.git#egg=loopy pip install .[ci] pip install pytest From 08351d027d8d8a049c99472c81554b9733e55691 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Fri, 15 Nov 2024 17:04:14 +0000 Subject: [PATCH 136/136] Python 3.12 in tsfc CI workflow (#328) --- .github/workflows/tsfc-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tsfc-tests.yml b/.github/workflows/tsfc-tests.yml index 98fd4696b..e240009f7 100644 --- a/.github/workflows/tsfc-tests.yml +++ b/.github/workflows/tsfc-tests.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.9 + python-version: 3.12 - name: Install UFL run: |