"""Differential operators."""
# Copyright (C) 2008-2016 Martin Sandve Alnæs
#
# This file is part of UFL (https://www.fenicsproject.org)
#
# SPDX-License-Identifier: LGPL-3.0-or-later
from ufl.argument import Argument, Coargument
from ufl.checks import is_cellwise_constant
from ufl.coefficient import Coefficient
from ufl.constantvalue import Zero
from ufl.core.base_form_operator import BaseFormOperator
from ufl.core.expr import Expr
from ufl.core.operator import Operator
from ufl.core.terminal import Terminal
from ufl.core.ufl_type import ufl_type
from ufl.domain import extract_unique_domain, find_geometric_dimension
from ufl.exprcontainers import ExprList, ExprMapping
from ufl.form import BaseForm
from ufl.precedence import parstr
from ufl.variable import Variable
# --- Basic differentiation objects ---
[docs]@ufl_type(is_abstract=True, is_differential=True)
class Derivative(Operator):
"""Base class for all derivative types."""
__slots__ = ()
def __init__(self, operands):
"""Initalise."""
Operator.__init__(self, operands)
[docs]@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0)
class CoefficientDerivative(Derivative):
"""Derivative of form integrand w.r.t. the degrees of freedom in a discrete Coefficient."""
__slots__ = ()
def __new__(cls, integrand, coefficients, arguments, coefficient_derivatives):
"""Create a new CoefficientDerivative."""
if not isinstance(coefficients, ExprList):
raise ValueError("Expecting ExprList instance with Coefficients.")
if not isinstance(arguments, ExprList):
raise ValueError("Expecting ExprList instance with Arguments.")
if not isinstance(coefficient_derivatives, ExprMapping):
raise ValueError("Expecting ExprMapping for coefficient derivatives.")
if isinstance(integrand, Zero):
return integrand
return Derivative.__new__(cls)
def __init__(self, integrand, coefficients, arguments, coefficient_derivatives):
"""Initalise."""
if not isinstance(coefficient_derivatives, ExprMapping):
coefficient_derivatives = ExprMapping(coefficient_derivatives)
Derivative.__init__(self, (integrand, coefficients, arguments, coefficient_derivatives))
def __str__(self):
"""Format as a string."""
return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coefficient derivatives %s" % (
self.ufl_operands[0],
self.ufl_operands[1],
self.ufl_operands[2],
self.ufl_operands[3],
)
[docs]@ufl_type(num_ops=4, inherit_shape_from_operand=0, inherit_indices_from_operand=0)
class CoordinateDerivative(CoefficientDerivative):
"""Derivative of the integrand of a form w.r.t. the SpatialCoordinates."""
__slots__ = ()
def __str__(self):
"""Format as a string."""
return "d/dfj { %s }, with fh=%s, dfh/dfj = %s, and coordinate derivatives %s" % (
self.ufl_operands[0],
self.ufl_operands[1],
self.ufl_operands[2],
self.ufl_operands[3],
)
[docs]@ufl_type(num_ops=2)
class VariableDerivative(Derivative):
"""Variable Derivative."""
__slots__ = (
"ufl_shape",
"ufl_free_indices",
"ufl_index_dimensions",
)
def __new__(cls, f, v):
"""Create a new VariableDerivative."""
# Checks
if not isinstance(f, Expr):
raise ValueError("Expecting an Expr in VariableDerivative.")
if not isinstance(v, (Variable, Coefficient)):
raise ValueError("Expecting a Variable in VariableDerivative.")
if v.ufl_free_indices:
raise ValueError("Differentiation variable cannot have free indices.")
# Simplification
# Return zero if expression is trivially independent of variable
if f._ufl_is_terminal_ and f != v:
return Zero(f.ufl_shape + v.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions)
# Construction
return Derivative.__new__(cls)
def __init__(self, f, v):
"""Initalise."""
Derivative.__init__(self, (f, v))
self.ufl_free_indices = f.ufl_free_indices
self.ufl_index_dimensions = f.ufl_index_dimensions
self.ufl_shape = f.ufl_shape + v.ufl_shape
def __str__(self):
"""Format as a string."""
if isinstance(self.ufl_operands[0], Terminal):
return "d%s/d[%s]" % (self.ufl_operands[0], self.ufl_operands[1])
return "d/d[%s] %s" % (self.ufl_operands[1], parstr(self.ufl_operands[0], self))
# --- Compound differentiation objects ---
[docs]@ufl_type(is_abstract=True)
class CompoundDerivative(Derivative):
"""Base class for all compound derivative types."""
__slots__ = ()
def __init__(self, operands):
"""Initalise."""
Derivative.__init__(self, operands)
[docs]@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True)
class Grad(CompoundDerivative):
"""Grad."""
__slots__ = ("_dim",)
def __new__(cls, f):
"""Create a new Grad."""
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
dim = find_geometric_dimension(f)
return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions)
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
self._dim = find_geometric_dimension(f)
def _ufl_expr_reconstruct_(self, op):
"""Return a new object of the same type with new operands."""
if is_cellwise_constant(op):
if op.ufl_shape != self.ufl_operands[0].ufl_shape:
raise ValueError("Operand shape mismatch in Grad reconstruct.")
if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices:
raise ValueError("Free index mismatch in Grad reconstruct.")
return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions)
return self._ufl_class_(op)
[docs] def evaluate(self, x, mapping, component, index_values, derivatives=()):
"""Get child from mapping and return the component asked for."""
component, i = component[:-1], component[-1]
derivatives = derivatives + (i,)
result = self.ufl_operands[0].evaluate(
x, mapping, component, index_values, derivatives=derivatives
)
return result
@property
def ufl_shape(self):
"""Get the UFL shape."""
return self.ufl_operands[0].ufl_shape + (self._dim,)
def __str__(self):
"""Format as a string."""
return "grad(%s)" % self.ufl_operands[0]
[docs]@ufl_type(
num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True
)
class ReferenceGrad(CompoundDerivative):
"""Reference grad."""
__slots__ = ("_dim",)
def __new__(cls, f):
"""Create a new ReferenceGrad."""
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
dim = extract_unique_domain(f).topological_dimension()
return Zero(f.ufl_shape + (dim,), f.ufl_free_indices, f.ufl_index_dimensions)
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
self._dim = extract_unique_domain(f).topological_dimension()
def _ufl_expr_reconstruct_(self, op):
"""Return a new object of the same type with new operands."""
if is_cellwise_constant(op):
if op.ufl_shape != self.ufl_operands[0].ufl_shape:
raise ValueError("Operand shape mismatch in ReferenceGrad reconstruct.")
if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices:
raise ValueError("Free index mismatch in ReferenceGrad reconstruct.")
return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions)
return self._ufl_class_(op)
[docs] def evaluate(self, x, mapping, component, index_values, derivatives=()):
"""Get child from mapping and return the component asked for."""
component, i = component[:-1], component[-1]
derivatives = derivatives + (i,)
result = self.ufl_operands[0].evaluate(
x, mapping, component, index_values, derivatives=derivatives
)
return result
@property
def ufl_shape(self):
"""Get the UFL shape."""
return self.ufl_operands[0].ufl_shape + (self._dim,)
def __str__(self):
"""Format as a string."""
return "reference_grad(%s)" % self.ufl_operands[0]
[docs]@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True)
class Div(CompoundDerivative):
"""Div."""
__slots__ = ()
def __new__(cls, f):
"""Create a new Div."""
if f.ufl_free_indices:
raise ValueError("Free indices in the divergence argument is not allowed.")
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
return Zero(f.ufl_shape[:-1]) # No free indices asserted above
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
@property
def ufl_shape(self):
"""Get the UFL shape."""
return self.ufl_operands[0].ufl_shape[:-1]
def __str__(self):
"""Format as a string."""
return "div(%s)" % self.ufl_operands[0]
[docs]@ufl_type(
num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True
)
class ReferenceDiv(CompoundDerivative):
"""Reference divergence."""
__slots__ = ()
def __new__(cls, f):
"""Create a new ReferenceDiv."""
if f.ufl_free_indices:
raise ValueError("Free indices in the divergence argument is not allowed.")
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
return Zero(f.ufl_shape[:-1]) # No free indices asserted above
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
@property
def ufl_shape(self):
"""Get the UFL shape."""
return self.ufl_operands[0].ufl_shape[:-1]
def __str__(self):
"""Format as a string."""
return "reference_div(%s)" % self.ufl_operands[0]
[docs]@ufl_type(num_ops=1, inherit_indices_from_operand=0)
class NablaGrad(CompoundDerivative):
"""Nabla grad."""
__slots__ = ("_dim",)
def __new__(cls, f):
"""Create a new NablaGrad."""
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
dim = find_geometric_dimension(f)
return Zero((dim,) + f.ufl_shape, f.ufl_free_indices, f.ufl_index_dimensions)
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
self._dim = find_geometric_dimension(f)
def _ufl_expr_reconstruct_(self, op):
"""Return a new object of the same type with new operands."""
if is_cellwise_constant(op):
if op.ufl_shape != self.ufl_operands[0].ufl_shape:
raise ValueError("Operand shape mismatch in NablaGrad reconstruct.")
if self.ufl_operands[0].ufl_free_indices != op.ufl_free_indices:
raise ValueError("Free index mismatch in NablaGrad reconstruct.")
return Zero(self.ufl_shape, self.ufl_free_indices, self.ufl_index_dimensions)
return self._ufl_class_(op)
@property
def ufl_shape(self):
"""Get the UFL shape."""
return (self._dim,) + self.ufl_operands[0].ufl_shape
def __str__(self):
"""Format as a string."""
return "nabla_grad(%s)" % self.ufl_operands[0]
[docs]@ufl_type(num_ops=1, inherit_indices_from_operand=0)
class NablaDiv(CompoundDerivative):
"""Nabla div."""
__slots__ = ()
def __new__(cls, f):
"""Create a new NablaDiv."""
if f.ufl_free_indices:
raise ValueError("Free indices in the divergence argument is not allowed.")
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
return Zero(f.ufl_shape[1:]) # No free indices asserted above
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
@property
def ufl_shape(self):
"""Get the UFL shape."""
return self.ufl_operands[0].ufl_shape[1:]
def __str__(self):
"""Format as a string."""
return "nabla_div(%s)" % self.ufl_operands[0]
_curl_shapes = {(): (2,), (2,): (), (3,): (3,)}
[docs]@ufl_type(num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True)
class Curl(CompoundDerivative):
"""Compound derivative."""
__slots__ = ("ufl_shape",)
def __new__(cls, f):
"""Create a new CompoundDerivative."""
# Validate input
sh = f.ufl_shape
if f.ufl_shape not in ((), (2,), (3,)):
raise ValueError("Expecting a scalar, 2D vector or 3D vector.")
if f.ufl_free_indices:
raise ValueError("Free indices in the curl argument is not allowed.")
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
sh = {(): (2,), (2,): (), (3,): (3,)}[sh]
return Zero(sh) # No free indices asserted above
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
self.ufl_shape = _curl_shapes[f.ufl_shape]
def __str__(self):
"""Format as a string."""
return "curl(%s)" % self.ufl_operands[0]
[docs]@ufl_type(
num_ops=1, inherit_indices_from_operand=0, is_terminal_modifier=True, is_in_reference_frame=True
)
class ReferenceCurl(CompoundDerivative):
"""Reference curl."""
__slots__ = ("ufl_shape",)
def __new__(cls, f):
"""Create a new ReferenceCurl."""
# Validate input
sh = f.ufl_shape
if f.ufl_shape not in ((), (2,), (3,)):
raise ValueError("Expecting a scalar, 2D vector or 3D vector.")
if f.ufl_free_indices:
raise ValueError("Free indices in the curl argument is not allowed.")
# Return zero if expression is trivially constant
if is_cellwise_constant(f):
sh = {(): (2,), (2,): (), (3,): (3,)}[sh]
return Zero(sh) # No free indices asserted above
return CompoundDerivative.__new__(cls)
def __init__(self, f):
"""Initalise."""
CompoundDerivative.__init__(self, (f,))
self.ufl_shape = _curl_shapes[f.ufl_shape]
def __str__(self):
"""Format as a string."""
return "reference_curl(%s)" % self.ufl_operands[0]