From ac21658cd8efb6d0c6242039101f1cc14c78d89e Mon Sep 17 00:00:00 2001 From: nbouziani Date: Thu, 2 May 2024 19:44:55 +0100 Subject: [PATCH 01/10] Update type check to get shape in replace --- ufl/algorithms/replace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 5f3616949..1aa94f7ce 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 CoefficientDerivative, BaseForm 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 From e26e8a5f9887a324f9bbbaaf3e437be035eff827 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Thu, 2 May 2024 22:11:36 +0100 Subject: [PATCH 02/10] Add test --- test/test_external_operator.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 27e5d102e..183087e4b 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -7,8 +7,10 @@ from ufl import ( Action, + Matrix, Argument, Coefficient, + Coargument, Constant, Form, FunctionSpace, @@ -17,6 +19,7 @@ TrialFunction, action, adjoint, + replace, cos, derivative, dx, @@ -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 From 36778b96c7dbebc6aa69d4ebe59ee84c3b58b9aa Mon Sep 17 00:00:00 2001 From: nbouziani Date: Thu, 2 May 2024 22:29:35 +0100 Subject: [PATCH 03/10] Fix ruff --- test/test_external_operator.py | 6 +++--- ufl/algorithms/replace.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 183087e4b..9ccb318dc 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -7,23 +7,23 @@ from ufl import ( Action, - Matrix, Argument, - Coefficient, Coargument, + Coefficient, Constant, Form, FunctionSpace, + Matrix, Mesh, TestFunction, TrialFunction, action, adjoint, - replace, cos, derivative, dx, inner, + replace, sin, triangle, ) diff --git a/ufl/algorithms/replace.py b/ufl/algorithms/replace.py index 1aa94f7ce..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, BaseForm +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 From afc57527c4e1dc671696610b01c70a6c3340ec8a Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 04:59:49 +0100 Subject: [PATCH 04/10] Relax base form operators type check --- ufl/core/interpolate.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 57e1506fd..82072c6fa 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) if isinstance(v, AbstractFunctionSpace): if is_dual(v): @@ -53,7 +54,7 @@ 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] + vv = v if not isinstance(v, BaseForm) else v.arguments()[0] function_space = vv.ufl_function_space().dual() # Set the operand as `expr` for DAG traversal purpose. operand = expr From 2a9a0a2d4bdce642905409cbd7e710a3db001a94 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 05:44:13 +0100 Subject: [PATCH 05/10] Update BFO methods relying on the dual space argument slot --- ufl/core/base_form_operator.py | 11 +++++++++-- ufl/core/interpolate.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) 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 82072c6fa..08885bd0c 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -36,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, Action) + dual_args = (Coargument, Cofunction, Form, Action, BaseFormOperator) if isinstance(v, AbstractFunctionSpace): if is_dual(v): From 1e18ca1465adfbe977798cc78d2178fb93b655c6 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 13:57:04 +0100 Subject: [PATCH 06/10] Fix Interpolate's function space --- ufl/core/interpolate.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 08885bd0c..57ffedd89 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -54,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, BaseForm) else v.arguments()[0] - function_space = vv.ufl_function_space().dual() + if isinstance(v, BaseForm): + arg, *_ = v.arguments() + function_space = arg.function_space() + else: + function_space = v.function_space().dual() # Set the operand as `expr` for DAG traversal purpose. operand = expr BaseFormOperator.__init__( From 12a30e6a17aaf602104e7e733cba2e2dcfe4cae8 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 14:02:38 +0100 Subject: [PATCH 07/10] Fix typo: function_space -> ufl_function_space --- ufl/core/interpolate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ufl/core/interpolate.py b/ufl/core/interpolate.py index 57ffedd89..3b5e3ba4d 100644 --- a/ufl/core/interpolate.py +++ b/ufl/core/interpolate.py @@ -56,9 +56,9 @@ def __init__(self, expr, v): # Get the primal space (V** = V) if isinstance(v, BaseForm): arg, *_ = v.arguments() - function_space = arg.function_space() + function_space = arg.ufl_function_space() else: - function_space = v.function_space().dual() + function_space = v.ufl_function_space().dual() # Set the operand as `expr` for DAG traversal purpose. operand = expr BaseFormOperator.__init__( From 4892a108082e8bf39f08b65b3293ee110f191695 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 20:08:36 +0100 Subject: [PATCH 08/10] Update adjoint numbering --- test/test_external_operator.py | 13 +++++++------ ufl/action.py | 2 +- ufl/adjoint.py | 4 +++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/test_external_operator.py b/test/test_external_operator.py index 9ccb318dc..ae94e1dcb 100644 --- a/test/test_external_operator.py +++ b/test/test_external_operator.py @@ -269,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): @@ -343,16 +343,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): diff --git a/ufl/action.py b/ufl/action.py index 3bb9fd533..04b7ee8ad 100644 --- a/ufl/action.py +++ b/ufl/action.py @@ -123,7 +123,7 @@ def _analyze_domains(self): # Collect domains self._domains = join_domains( - chain.from_iterable(e.ufl_domains() for e in self.ufl_operands) + chain.from_iterable(e.ufl_domain() for e in self.ufl_operands) ) def equals(self, other): diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 92447b985..01b4b30dc 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -85,7 +85,9 @@ 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): From c901af2afc6a1dbb2d8ba6d188765d8583d90318 Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 20:19:28 +0100 Subject: [PATCH 09/10] Fix ruff --- ufl/adjoint.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ufl/adjoint.py b/ufl/adjoint.py index 01b4b30dc..de27bb622 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -87,7 +87,8 @@ def _analyze_form_arguments(self): """The arguments of adjoint are the reverse of the form arguments.""" 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._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): From f40637d06d7113cc651b3826bfe317fe0c03530e Mon Sep 17 00:00:00 2001 From: nbouziani Date: Fri, 3 May 2024 20:58:23 +0100 Subject: [PATCH 10/10] Fix rugg --- ufl/action.py | 4 +--- ufl/adjoint.py | 5 +++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ufl/action.py b/ufl/action.py index 04b7ee8ad..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_domain() 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 de27bb622..7c1d5c63f 100644 --- a/ufl/adjoint.py +++ b/ufl/adjoint.py @@ -87,8 +87,9 @@ def _analyze_form_arguments(self): """The arguments of adjoint are the reverse of the form arguments.""" 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._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):