From c384c94965e6f7a06a0bd1981d19a880a78e450a Mon Sep 17 00:00:00 2001 From: kamal20122012 Date: Thu, 30 Apr 2026 17:36:23 +0530 Subject: [PATCH] Add user-facing ufl.reference_grad wrapper Mirrors the existing ufl.grad helper for the corresponding ReferenceGrad class so users can write ufl.reference_grad(f) instead of reaching into ufl.differentiation. Closes #450 --- test/test_reference_grad.py | 43 +++++++++++++++++++++++++++++++++++++ ufl/__init__.py | 2 ++ ufl/operators.py | 28 +++++++++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 test/test_reference_grad.py diff --git a/test/test_reference_grad.py b/test/test_reference_grad.py new file mode 100644 index 000000000..be4784720 --- /dev/null +++ b/test/test_reference_grad.py @@ -0,0 +1,43 @@ +"""Tests for the user-facing ``ufl.reference_grad`` wrapper.""" + +from utils import LagrangeElement + +import ufl +from ufl import Coefficient, FunctionSpace, Mesh, interval, reference_grad, triangle +from ufl.classes import ReferenceGrad, ReferenceValue + + +def test_reference_grad_is_exported(): + assert ufl.reference_grad is reference_grad + assert "reference_grad" in ufl.__all__ + + +def test_reference_grad_returns_reference_grad(): + domain = Mesh(LagrangeElement(triangle, 1, (2,))) + V = FunctionSpace(domain, LagrangeElement(triangle, 1)) + f = Coefficient(V) + + expr = reference_grad(ReferenceValue(f)) + + assert isinstance(expr, ReferenceGrad) + assert expr == ReferenceGrad(ReferenceValue(f)) + + +def test_reference_grad_shape_appends_topological_dimension(): + domain = Mesh(LagrangeElement(triangle, 1, (2,))) + V = FunctionSpace(domain, LagrangeElement(triangle, 1)) + f = Coefficient(V) + + expr = reference_grad(ReferenceValue(f)) + + assert expr.ufl_shape == (2,) + + +def test_reference_grad_on_interval(): + domain = Mesh(LagrangeElement(interval, 1, (1,))) + V = FunctionSpace(domain, LagrangeElement(interval, 1)) + f = Coefficient(V) + + expr = reference_grad(ReferenceValue(f)) + + assert expr.ufl_shape == (1,) diff --git a/ufl/__init__.py b/ufl/__init__.py index 415914d77..72f05f18f 100644 --- a/ufl/__init__.py +++ b/ufl/__init__.py @@ -404,6 +404,7 @@ perp, rank, real, + reference_grad, rot, shape, sign, @@ -645,6 +646,7 @@ "r", "rank", "real", + "reference_grad", "register_integral_type", "replace", "rhs", diff --git a/ufl/operators.py b/ufl/operators.py index ccbeaac98..03f310955 100644 --- a/ufl/operators.py +++ b/ufl/operators.py @@ -33,7 +33,15 @@ OrCondition, ) from ufl.constantvalue import ComplexValue, RealValue, Zero, as_ufl -from ufl.differentiation import Curl, Div, Grad, NablaDiv, NablaGrad, VariableDerivative +from ufl.differentiation import ( + Curl, + Div, + Grad, + NablaDiv, + NablaGrad, + ReferenceGrad, + VariableDerivative, +) from ufl.domain import extract_domains from ufl.form import Form from ufl.geometry import FacetNormal, SpatialCoordinate @@ -403,6 +411,24 @@ def grad(f): return Grad(f) +def reference_grad(f): + """Take the gradient of f with respect to reference cell coordinates. + + Unlike :py:func:`grad`, which differentiates with respect to physical + coordinates, ``reference_grad`` differentiates with respect to the + coordinates of the reference cell. + + The operand must already be in the reference frame; for example, + a :py:class:`Coefficient` should be wrapped in + :py:class:`ReferenceValue` before applying ``reference_grad``. + + See also: :py:func:`grad` + + """ + f = as_ufl(f) + return ReferenceGrad(f) + + def div(f): """Take the divergence of f.