Skip to content

Commit 7d4a395

Browse files
committed
Adjoint(FormProduct)
1 parent b3068a7 commit 7d4a395

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

test/test_form.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
Argument,
66
Coefficient,
77
Cofunction,
8+
Adjoint,
89
Form,
910
FormProduct,
1011
FormSum,
1112
FunctionSpace,
13+
Matrix,
1214
Mesh,
1315
SpatialCoordinate,
1416
TestFunction,
@@ -240,6 +242,16 @@ def test_form_product_constructor_and_arguments(domain):
240242
assert tuple(argument.number() for argument in nested.arguments()) == (0, 1, 2)
241243

242244

245+
def test_form_product_of_one_factor_simplifies(domain):
246+
element = LagrangeElement(triangle, 1)
247+
V = FunctionSpace(domain, element)
248+
v = TestFunction(V)
249+
f = Coefficient(V)
250+
L = f * v * dx
251+
252+
assert FormProduct(L) is L
253+
254+
243255
def test_form_product_rejects_invalid_inputs(domain):
244256
element = LagrangeElement(triangle, 1)
245257
V = FunctionSpace(domain, element)
@@ -248,7 +260,9 @@ def test_form_product_rejects_invalid_inputs(domain):
248260
L = f * v * dx
249261

250262
with pytest.raises(ValueError):
251-
FormProduct(L)
263+
FormProduct()
264+
with pytest.raises(TypeError):
265+
FormProduct(1)
252266
with pytest.raises(TypeError):
253267
FormProduct(L, 1)
254268

@@ -266,6 +280,38 @@ def test_form_product_is_explicit_not_mul_overload(domain):
266280
Lf * Lg
267281

268282

283+
def test_adjoint_form_product_reverses_adjoint_factors(domain):
284+
element = LagrangeElement(triangle, 1)
285+
V = FunctionSpace(domain, element)
286+
A = Matrix(V, V)
287+
B = Matrix(V, V)
288+
C = Matrix(V, V)
289+
290+
product = FormProduct(A, B, C)
291+
adjoint_product = Adjoint(product)
292+
293+
assert isinstance(adjoint_product, FormProduct)
294+
assert tuple(factor.form() for factor in adjoint_product.factors()) == (C, B, A)
295+
assert adjoint_product.factors() == (Adjoint(C), Adjoint(B), Adjoint(A))
296+
297+
298+
def test_adjoint_form_product_leaves_rank_zero_and_one_factors_unadjointed(domain):
299+
element = LagrangeElement(triangle, 1)
300+
V = FunctionSpace(domain, element)
301+
v = TestFunction(V)
302+
f = Coefficient(V)
303+
functional = f * dx
304+
linear = f * v * dx
305+
A = Matrix(V, V)
306+
307+
product = FormProduct(functional, linear, A)
308+
adjoint_product = Adjoint(product)
309+
310+
assert isinstance(adjoint_product, FormProduct)
311+
assert adjoint_product.factors() == (Adjoint(A), linear, functional)
312+
assert Adjoint(FormProduct(functional, linear)).factors() == (linear, functional)
313+
314+
269315
def test_form_product_replace(domain):
270316
element = LagrangeElement(triangle, 1)
271317
V = FunctionSpace(domain, element)

ufl/adjoint.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from ufl.argument import Coargument
1414
from ufl.core.ufl_type import ufl_type
15-
from ufl.form import BaseForm, FormSum, ZeroBaseForm
15+
from ufl.form import BaseForm, FormProduct, FormSum, ZeroBaseForm
1616

1717
# --- The Adjoint class represents the adjoint of a numerical object that
1818
# needs to be computed at assembly time ---
@@ -50,6 +50,12 @@ def __new__(cls, *args, **kw):
5050
elif isinstance(form, FormSum):
5151
# Adjoint distributes over sums
5252
return FormSum(*((Adjoint(c), w) for c, w in zip(form.components(), form.weights())))
53+
elif isinstance(form, FormProduct):
54+
# Reverse product order and take the adjoint of rank-2 factors.
55+
return FormProduct(
56+
*(factor if len(factor.arguments()) < 2 else Adjoint(factor)
57+
for factor in reversed(form.factors()))
58+
)
5359
elif isinstance(form, Coargument):
5460
# The adjoint of a coargument `c: V* -> V*` is the identity
5561
# matrix mapping from V to V (i.e. V x V* -> R).

ufl/form.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,15 @@ class FormProduct(BaseForm):
717717
)
718718
_ufl_required_methods_ = "_analyze_form_arguments" # type: ignore
719719

720+
def __new__(cls, *factors):
721+
"""Create a new FormProduct."""
722+
if len(factors) == 1:
723+
(factor,) = factors
724+
if isinstance(factor, BaseForm):
725+
return factor
726+
raise TypeError(f"Expected a UFL BaseForm instance, got {type(factor)}.")
727+
return super().__new__(cls)
728+
720729
def __init__(self, *factors):
721730
"""Initialise."""
722731
BaseForm.__init__(self)

0 commit comments

Comments
 (0)