Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some updates related to dualspace #194

Merged
merged 55 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ebb10c1
Add trimmed serendipity
rckirby Feb 27, 2020
3ff2d50
Fix value shape for trimmed serendipity
rckirby Feb 27, 2020
c9bb809
ufl plumbing update for trimmed serendipity.
Justincrum Apr 10, 2020
87cd5a0
Plumbing for SminusDiv.py
Justincrum Apr 14, 2020
c17ccb5
Adding in element stuff for SminusCurl.
Justincrum Sep 8, 2020
0449576
Merge remote-tracking branch 'origin' into trimmedSerendipity
Justincrum Sep 22, 2020
5eea76b
Merge remote-tracking branch 'origin/master' into trimmedSerendipity
rckirby Jun 23, 2022
5b141d8
Merge pull request #23 from firedrakeproject/trimmedSerendipity
rckirby Jun 27, 2022
9135be1
Merge branch 'FEniCS:main' into master
dham Aug 4, 2022
3f5ad50
Merge branch 'main' into merge_fenics
dham Oct 13, 2022
0c592ec
Merge pull request #30 from firedrakeproject/merge_fenics
dham Oct 13, 2022
e2d2268
Fix typo
nbouziani Jan 15, 2023
13435fb
Merge pull request #32 from firedrakeproject/merge_fenics
dham Jan 25, 2023
f4babc4
Merge remote-tracking branch 'upstream/main' into ksagiyam/merge_upst…
ksagiyam Feb 24, 2023
772485d
Merge pull request #34 from firedrakeproject/ksagiyam/merge_upstream
ksagiyam Mar 13, 2023
16e3bbe
Merge remote-tracking branch 'fenics/main' into dham/merge_fenics
dham Jun 1, 2023
3c62318
Merge pull request #37 from firedrakeproject/dham/merge_fenics
dham Jun 2, 2023
6ecc1b2
Equip Cofunctions with an Argument in the primal space
nbouziani Jun 29, 2023
033a7a3
Equip FormSum objects with a ufl_domain
nbouziani Jun 29, 2023
f460c97
Fix arguments collection for BaseFormDerivative
nbouziani Jun 29, 2023
6cb2969
Equip BaeForm with coefficients
nbouziani Jun 29, 2023
653e8d9
Equip BaseForm with a ufl_domain
nbouziani Jul 6, 2023
45d2dbf
Add BaseFormCoordinateDerivative
nbouziani Jul 6, 2023
49b155d
Update AUTHORS
nbouziani Nov 30, 2021
f29fd39
Fix weight analysis for FormSum composition
nbouziani Jul 6, 2023
ec8f0cc
Handle ufl.Zero in rhs
nbouziani Jul 6, 2023
92b6cc8
Remove support for adjoint derivative
nbouziani Jul 14, 2023
3435530
Remove support for Action derivative when left is a 2-form
nbouziani Jul 14, 2023
1674ee8
Extend form arguments analysis to coefficients for BaseFormDerivative
nbouziani Jul 14, 2023
434d3b4
Update rules for Action arguments
nbouziani Jul 18, 2023
3225fae
Coarguments have one argument in the primal space and one in the dual…
nbouziani Jul 18, 2023
de4a52f
Add preprocess_form
nbouziani Jul 7, 2021
fce0461
Fix FormSum reconstruction in map_integrands
nbouziani Jul 18, 2023
330438d
DerivativeRuleDispatcher: Add handler for base form coordinate deriva…
nbouziani Jul 18, 2023
3cffabc
Simplify Action/Adjoint of Coarguments
nbouziani Jul 19, 2023
c08a760
Remove checks on ZeroBaseForms' arguments
nbouziani Jul 19, 2023
9f225aa
Update Action differentiation test
nbouziani Jul 19, 2023
e20d2a3
Return primal space argument for Coargument's adjoint
nbouziani Aug 1, 2023
55c281d
Merge remote-tracking branch 'upstream/main'
connorjward Aug 8, 2023
8cf030b
Merge master
nbouziani Aug 20, 2023
9fcb066
Fix flake8
nbouziani Aug 20, 2023
56eba31
Extend Action simplification cases to ufl.Argument
nbouziani Sep 7, 2023
37f7a64
Fix equals for Action/Adjoint
nbouziani Sep 10, 2023
d225ac2
Update ufl/argument.py
nbouziani Sep 12, 2023
67bf48e
Update ufl/coefficient.py
nbouziani Sep 12, 2023
babc1a9
Fix some bugs and address PR comments
nbouziani Sep 12, 2023
dddc3a2
Merge branch 'fix_dualspace' of github.com:firedrakeproject/ufl into …
nbouziani Sep 12, 2023
72b38fa
Fix spurious firedrake UFL code
nbouziani Sep 12, 2023
1953c4d
Merge branch 'main' into fix_dualspace
nbouziani Sep 12, 2023
f04daac
Fix flake8
nbouziani Sep 12, 2023
2fb8603
Merge branch 'fix_dualspace' of github.com:firedrakeproject/ufl into …
nbouziani Sep 12, 2023
64e84ff
Fix test
nbouziani Sep 12, 2023
1db06eb
Merge branch 'main' into fix_dualspace
nbouziani Sep 12, 2023
46b5bbd
Lift test change from Intepr branch
nbouziani Sep 12, 2023
c64ebcf
Merge branch 'main' into fix_dualspace
nbouziani Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ Contributors:
| Jack S. Hale <[email protected]>
| Tuomas Airaksinen <[email protected]>
| Reuben W. Hill <[email protected]>
| Nacime Bouziani <[email protected]>
34 changes: 12 additions & 22 deletions test/test_duals.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ufl import (FiniteElement, FunctionSpace, MixedFunctionSpace,
Coefficient, Matrix, Cofunction, FormSum, Argument, Coargument,
TestFunction, TrialFunction, Adjoint, Action,
action, adjoint, derivative, tetrahedron, triangle, interval, dx)
action, adjoint, derivative, inner, tetrahedron, triangle, interval, dx)
from ufl.constantvalue import Zero
from ufl.form import ZeroBaseForm

Expand Down Expand Up @@ -102,8 +102,6 @@ def test_addition():
domain_2d = default_domain(triangle)
f_2d = FiniteElement("CG", triangle, 1)
V = FunctionSpace(domain_2d, f_2d)
f_2d_2 = FiniteElement("CG", triangle, 2)
V2 = FunctionSpace(domain_2d, f_2d_2)
V_dual = V.dual()

u = TrialFunction(V)
Expand Down Expand Up @@ -137,11 +135,6 @@ def test_addition():
res -= ZeroBaseForm((v,))
assert res == L

with pytest.raises(ValueError):
# Raise error for incompatible arguments
v2 = TestFunction(V2)
res = L + ZeroBaseForm((v2, u))


def test_scalar_mult():
domain_2d = default_domain(triangle)
Expand Down Expand Up @@ -256,7 +249,7 @@ def test_differentiation():
w = Cofunction(U.dual())
dwdu = expand_derivatives(derivative(w, u))
assert isinstance(dwdu, ZeroBaseForm)
assert dwdu.arguments() == (Argument(u.ufl_function_space(), 0),)
assert dwdu.arguments() == (Argument(w.ufl_function_space().dual(), 0), Argument(u.ufl_function_space(), 1))
# Check compatibility with int/float
assert dwdu == 0

Expand Down Expand Up @@ -285,24 +278,21 @@ def test_differentiation():
assert dMdu == 0

# -- Action -- #
Ac = Action(M, u)
dAcdu = expand_derivatives(derivative(Ac, u))

# Action(dM/du, u) + Action(M, du/du) = Action(M, uhat) since dM/du = 0.
# Multiply by 1 to get a FormSum (type compatibility).
assert dAcdu == 1 * Action(M, v)
Ac = Action(w, u)
dAcdu = derivative(Ac, u)
assert dAcdu == Action(Adjoint(derivative(w, u)), u) + Action(w, derivative(u, u))

# -- Adjoint -- #
Ad = Adjoint(M)
dAddu = expand_derivatives(derivative(Ad, u))
# Push differentiation through Adjoint
assert dAddu == 0
dAcdu = expand_derivatives(dAcdu)
# Since dw/du = 0
assert dAcdu == 1 * Action(w, v)

# -- Form sum -- #
Fs = M + Ac
uhat = Argument(U, 1)
what = Argument(U, 2)
Fs = M + inner(u * uhat, v) * dx
dFsdu = expand_derivatives(derivative(Fs, u))
# Distribute differentiation over FormSum components
assert dFsdu == 1 * Action(M, v)
assert dFsdu == FormSum([inner(what * uhat, v) * dx, 1])


def test_zero_base_form_mult():
Expand Down
73 changes: 56 additions & 17 deletions ufl/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from ufl.form import BaseForm, FormSum, Form, ZeroBaseForm
from ufl.core.ufl_type import ufl_type
from ufl.algebra import Sum
from ufl.argument import Argument
from ufl.constantvalue import Zero
from ufl.argument import Argument, Coargument
from ufl.coefficient import BaseCoefficient, Coefficient, Cofunction
from ufl.differentiation import CoefficientDerivative
from ufl.matrix import Matrix
Expand Down Expand Up @@ -40,19 +41,26 @@ class Action(BaseForm):
"ufl_operands",
"_repr",
"_arguments",
"_coefficients",
"_domains",
"_hash")

def __new__(cls, *args, **kw):
left, right = args

# Check trivial case
if left == 0 or right == 0:
# Check compatibility of function spaces
_check_function_spaces(left, right)
# Still need to work out the ZeroBaseForm arguments.
new_arguments = _get_action_form_arguments(left, right)
new_arguments, _ = _get_action_form_arguments(left, right)
return ZeroBaseForm(new_arguments)

# Coarguments (resp. Argument) from V* to V* (resp. from V to V) are identity matrices,
# i.e. we have: V* x V -> R (resp. V x V* -> R).
if isinstance(left, (Coargument, Argument)):
return right
if isinstance(right, (Coargument, Argument)):
return left

if isinstance(left, (FormSum, Sum)):
# Action distributes over sums on the LHS
return FormSum(*[(Action(component, right), 1)
Expand All @@ -70,6 +78,7 @@ def __init__(self, left, right):
self._left = left
self._right = right
self.ufl_operands = (self._left, self._right)
self._domains = None

# Check compatibility of function spaces
_check_function_spaces(left, right)
Expand Down Expand Up @@ -97,14 +106,22 @@ def _analyze_form_arguments(self):
The highest number Argument of the left operand and the lowest number
Argument of the right operand are consumed by the action.
"""
self._arguments = _get_action_form_arguments(self._left, self._right)
self._arguments, self._coefficients = _get_action_form_arguments(self._left, self._right)

def _analyze_domains(self):
"""Analyze which domains can be found in Action."""
from ufl.domain import join_domains
# Collect unique domains
self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands])

def equals(self, other):
if type(other) is not Action:
return False
if self is other:
return True
return self._left == other._left and self._right == other._right
# Make sure we are returning a boolean as left and right equalities can be `ufl.Equation`s
# if the underlying objects are `ufl.BaseForm`.
return bool(self._left == other._left) and bool(self._right == other._right)

def __str__(self):
return f"Action({self._left}, {self._right})"
Expand All @@ -127,29 +144,51 @@ def _check_function_spaces(left, right):
# right as a consequence of Leibniz formula.
right, *_ = right.ufl_operands

# `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`.
left_arg = left.arguments()[-1] if not isinstance(left, Coefficient) else left
if isinstance(right, (Form, Action, Matrix, ZeroBaseForm)):
if left.arguments()[-1].ufl_function_space().dual() != right.arguments()[0].ufl_function_space():
if left_arg.ufl_function_space().dual() != right.arguments()[0].ufl_function_space():
raise TypeError("Incompatible function spaces in Action")
elif isinstance(right, (Coefficient, Cofunction, Argument)):
if left.arguments()[-1].ufl_function_space() != right.ufl_function_space():
if left_arg.ufl_function_space() != right.ufl_function_space():
raise TypeError("Incompatible function spaces in Action")
else:
# `Zero` doesn't contain any information about the function space.
# -> Not a problem since Action will get simplified with a `ZeroBaseForm`
# which won't take into account the arguments on the right because of argument contraction.
# This occurs for:
# `derivative(Action(A, B), u)` with B is an `Expr` such that dB/du == 0
# -> `derivative(B, u)` becomes `Zero` when expanding derivatives since B is an Expr.
elif not isinstance(right, Zero):
raise TypeError("Incompatible argument in Action: %s" % type(right))


def _get_action_form_arguments(left, right):
"""Perform argument contraction to work out the arguments of Action"""

if isinstance(right, CoefficientDerivative):
coefficients = ()
# `left` can also be a Coefficient in V (= V**), e.g. `action(Coefficient(V), Cofunction(V.dual()))`.
left_args = left.arguments()[:-1] if not isinstance(left, Coefficient) else ()
if isinstance(right, BaseForm):
arguments = left_args + right.arguments()[1:]
coefficients += right.coefficients()
elif isinstance(right, CoefficientDerivative):
# Action differentiation pushes differentiation through
# right as a consequence of Leibniz formula.
right, *_ = right.ufl_operands

if isinstance(right, BaseForm):
return left.arguments()[:-1] + right.arguments()[1:]
elif isinstance(right, BaseCoefficient):
return left.arguments()[:-1]
from ufl.algorithms.analysis import extract_arguments_and_coefficients
right_args, right_coeffs = extract_arguments_and_coefficients(right)
arguments = left_args + tuple(right_args)
coefficients += tuple(right_coeffs)
elif isinstance(right, (BaseCoefficient, Zero)):
arguments = left_args
# When right is ufl.Zero, Action gets simplified so updating
# coefficients here doesn't matter
coefficients += (right,)
elif isinstance(right, Argument):
return left.arguments()[:-1] + (right,)
arguments = left_args + (right,)
else:
raise TypeError

if isinstance(left, BaseForm):
coefficients += left.coefficients()

return arguments, coefficients
23 changes: 22 additions & 1 deletion ufl/adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# Modified by Nacime Bouziani, 2021-2022.

from ufl.form import BaseForm, FormSum, ZeroBaseForm
from ufl.argument import Coargument
from ufl.core.ufl_type import ufl_type
# --- The Adjoint class represents the adjoint of a numerical object that
# needs to be computed at assembly time ---
Expand All @@ -28,6 +29,8 @@ class Adjoint(BaseForm):
"_form",
"_repr",
"_arguments",
"_coefficients",
"_domains",
"ufl_operands",
"_hash")

Expand All @@ -44,6 +47,14 @@ def __new__(cls, *args, **kw):
# Adjoint distributes over sums
return FormSum(*[(Adjoint(component), 1)
for component in form.components()])
elif isinstance(form, Coargument):
# The adjoint of a coargument `c: V* -> V*` is the identity matrix mapping from V to V (i.e. V x V* -> R).
# Equivalently, the adjoint of `c` is its first argument, which is a ufl.Argument defined on the
# primal space of `c`.
primal_arg, _ = form.arguments()
# Returning the primal argument avoids explicit argument reconstruction, making it
# a robust strategy for handling subclasses of `ufl.Coargument`.
return primal_arg

return super(Adjoint, cls).__new__(cls)

Expand All @@ -55,6 +66,7 @@ def __init__(self, form):

self._form = form
self.ufl_operands = (self._form,)
self._domains = None
self._hash = None
self._repr = "Adjoint(%s)" % repr(self._form)

Expand All @@ -68,13 +80,22 @@ def form(self):
def _analyze_form_arguments(self):
"""The arguments of adjoint are the reverse of the form arguments."""
self._arguments = self._form.arguments()[::-1]
self._coefficients = self._form.coefficients()

def _analyze_domains(self):
"""Analyze which domains can be found in Adjoint."""
from ufl.domain import join_domains
# Collect unique domains
self._domains = join_domains([e.ufl_domain() for e in self.ufl_operands])

def equals(self, other):
if type(other) is not Adjoint:
return False
if self is other:
return True
return self._form == other._form
# Make sure we are returning a boolean as the equality can result in a `ufl.Equation`
# if the underlying objects are `ufl.BaseForm`.
return bool(self._form == other._form)

def __str__(self):
return f"Adjoint({self._form})"
Expand Down
3 changes: 2 additions & 1 deletion ufl/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"estimate_total_polynomial_degree",
"sort_elements",
"compute_form_data",
"preprocess_form",
"apply_transformer",
"ReuseTransformer",
"load_ufl_file",
Expand Down Expand Up @@ -79,7 +80,7 @@

# Preprocessing a form to extract various meta data
# from ufl.algorithms.formdata import FormData
from ufl.algorithms.compute_form_data import compute_form_data
from ufl.algorithms.compute_form_data import compute_form_data, preprocess_form

# Utilities for checking properties of forms
from ufl.algorithms.signature import compute_form_signature
Expand Down
8 changes: 0 additions & 8 deletions ufl/algorithms/ad.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import warnings

from ufl.adjoint import Adjoint
from ufl.algorithms.apply_algebra_lowering import apply_algebra_lowering
from ufl.algorithms.apply_derivatives import apply_derivatives

Expand All @@ -29,13 +28,6 @@ def expand_derivatives(form, **kwargs):
if kwargs:
warnings("Deprecation: expand_derivatives no longer takes any keyword arguments")

if isinstance(form, Adjoint):
dform = expand_derivatives(form._form)
if dform == 0:
return dform
# Adjoint is taken on a 3-form which can't happen
raise NotImplementedError('Adjoint derivative is not supported.')

# Lower abstractions for tensor-algebra types into index notation
form = apply_algebra_lowering(form)

Expand Down
10 changes: 6 additions & 4 deletions ufl/algorithms/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ def extract_type(a, ufl_types):
if not isinstance(ufl_types, (list, tuple)):
ufl_types = (ufl_types,)

# BaseForms that aren't forms only have arguments
# BaseForms that aren't forms only contain arguments & coefficients
if isinstance(a, BaseForm) and not isinstance(a, Form):
objects = set()
if any(issubclass(t, BaseArgument) for t in ufl_types):
return set(a.arguments())
else:
return set()
objects.update(a.arguments())
if any(issubclass(t, BaseCoefficient) for t in ufl_types):
objects.update(a.coefficients())
return objects

if all(issubclass(t, Terminal) for t in ufl_types):
# Optimization
Expand Down
12 changes: 10 additions & 2 deletions ufl/algorithms/apply_derivatives.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from ufl.core.terminal import Terminal
from ufl.corealg.map_dag import map_expr_dag
from ufl.corealg.multifunction import MultiFunction
from ufl.differentiation import CoordinateDerivative
from ufl.differentiation import CoordinateDerivative, BaseFormCoordinateDerivative
from ufl.domain import extract_unique_domain
from ufl.operators import (bessel_I, bessel_J, bessel_K, bessel_Y, cell_avg,
conditional, cos, cosh, exp, facet_avg, ln, sign,
Expand Down Expand Up @@ -1032,7 +1032,7 @@ def cofunction(self, o):
dc = self.coefficient(o)
if dc == 0:
# Convert ufl.Zero into ZeroBaseForm
return ZeroBaseForm(self._v)
return ZeroBaseForm(o.arguments() + self._v)
return dc

def coargument(self, o):
Expand Down Expand Up @@ -1102,6 +1102,14 @@ def coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd):
rcache=self.rcaches[key]),
o_[1], o_[2], o_[3])

def base_form_coordinate_derivative(self, o, f, dummy_w, dummy_v, dummy_cd):
o_ = o.ufl_operands
key = (BaseFormCoordinateDerivative, o_[0])
return BaseFormCoordinateDerivative(map_expr_dag(self, o_[0],
vcache=self.vcaches[key],
rcache=self.rcaches[key]),
o_[1], o_[2], o_[3])

def indexed(self, o, Ap, ii): # TODO: (Partially) duplicated in generic rules
# Reuse if untouched
if Ap is o.ufl_operands[0]:
Expand Down
Loading