Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
jhale committed Oct 9, 2024
2 parents ba1a37e + 2125dce commit ebbeb61
Show file tree
Hide file tree
Showing 30 changed files with 524 additions and 221 deletions.
13 changes: 7 additions & 6 deletions .github/workflows/fenicsx-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.12

- name: Install test dependencies
run: |
sudo apt-get install -y graphviz libgraphviz-dev ninja-build pkg-config
- name: Install UFL
run: |
pip3 install .
pip3 install --break-system-packages .
- name: Install Basix
run: |
Expand Down Expand Up @@ -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
Expand All @@ -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 --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
8 changes: 8 additions & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ 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'
include:
- os: windows-latest
python-version: '3.11'

steps:
- uses: actions/checkout@v4
Expand Down
13 changes: 10 additions & 3 deletions doc/sphinx/source/manual/form_language.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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}`

Expand Down Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fenics-ufl"
version = "2024.1.0.post1"
version = "2024.2.0.dev0"
authors = [{ name = "UFL contributors" }]
maintainers = [
{ email = "[email protected]" },
Expand Down
16 changes: 16 additions & 0 deletions test/test_check_arities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
5 changes: 5 additions & 0 deletions test/test_equals.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
Expand Down
43 changes: 36 additions & 7 deletions test/test_external_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from ufl import (
Action,
Argument,
Coargument,
Coefficient,
Constant,
Form,
FunctionSpace,
Matrix,
Mesh,
TestFunction,
TrialFunction,
Expand All @@ -21,6 +23,7 @@
derivative,
dx,
inner,
replace,
sin,
triangle,
)
Expand Down Expand Up @@ -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):
Expand All @@ -275,7 +278,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):
Expand Down Expand Up @@ -340,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):
Expand Down Expand Up @@ -487,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
103 changes: 103 additions & 0 deletions test/test_extract_blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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()


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
34 changes: 33 additions & 1 deletion test/test_indices.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pytest

import ufl.algorithms
import ufl.classes
from ufl import (
Argument,
Coefficient,
Expand All @@ -15,6 +17,7 @@
exp,
i,
indices,
interval,
j,
k,
l,
Expand Down Expand Up @@ -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
Loading

0 comments on commit ebbeb61

Please sign in to comment.