From f3b9865104cdee487cc3e76b037f0a21b54419d7 Mon Sep 17 00:00:00 2001 From: Matthew Scroggs Date: Sat, 10 Feb 2024 10:49:23 +0000 Subject: [PATCH 01/46] 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 02/46] 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 03/46] 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 04/46] 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 05/46] 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 06/46] 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 07/46] 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 08/46] 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 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] 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 32/46] 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 33/46] 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 34/46] 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 35/46] 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 36/46] 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 37/46] 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 38/46] 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 39/46] 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 40/46] 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 41/46] 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 42/46] 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 43/46] 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 e6c87b562987228a79bf12315499f5bf52301155 Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 15 Nov 2024 12:15:13 +0000 Subject: [PATCH 44/46] Fix ruff --- test/test_equals.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_equals.py b/test/test_equals.py index 4783d42f9..b34a3c4de 100755 --- a/test/test_equals.py +++ b/test/test_equals.py @@ -5,7 +5,6 @@ from ufl.finiteelement import FiniteElement from ufl.pullback import identity_pullback from ufl.sobolevspace import H1 -from ufl.exprcontainers import ExprList def test_comparison_of_coefficients(): From 3b29b6cb03885238f68468581b4b33da0a87b29c Mon Sep 17 00:00:00 2001 From: Pablo Brubeck Date: Fri, 15 Nov 2024 14:10:13 +0000 Subject: [PATCH 45/46] 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 46/46] 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