"""
Inner products definition module
================================
Module containing classes to define the `inner products`_ used by the model.
.. _inner products: https://en.wikipedia.org/wiki/Inner_product_space
"""
from abc import ABC, abstractmethod
# from sympy.simplify import trigsimp
from sympy.simplify.fu import TR8, TR10
from sympy import diff, integrate, symbols, pi, Integral
_n = symbols('n', real=True, nonnegative=True)
_x, _y = symbols('x y')
[docs]class InnerProductDefinition(ABC):
"""Base class to define the model's basis inner products.
Parameters
----------
optimizer: None or callable, optional
A function to optimize the computation of the integrals or the integrand.
If `None`, does not optimize.
Attributes
----------
optimizer: None or callable
A function to optimize the computation of the integrals or the integrand.
If `None`, does not optimize the computation.
"""
def __init__(self, optimizer=None):
self.optimizer = None
if optimizer is not None:
self.set_optimizer(optimizer)
else:
self.set_optimizer(self._no_optimizer)
[docs] def set_optimizer(self, optimizer):
"""Function to set the optimizer.
Parameters
----------
optimizer: callable
A function to optimize the computation of the integrals or the integrand.
"""
self.optimizer = optimizer
@staticmethod
def _no_optimizer(expr):
return expr
[docs] @staticmethod
@abstractmethod
def jacobian(S, G):
"""Jacobian present in the advection terms:
Parameters
----------
S:
1st argument the Jacobian.
G:
2nd argument the Jacobian.
"""
pass
[docs] @staticmethod
@abstractmethod
def laplacian(S):
"""Laplacian :math:`\\nabla^2 S` of a function :math:`S`."""
pass
[docs] @abstractmethod
def ip_lap(self, S, G, symbolic_expr=False):
"""Function to compute the inner product :math:`(S, \\nabla^2 G)`.
Parameters
----------
S:
Left-hand side function of the product.
G:
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
"""
pass
[docs] @abstractmethod
def ip_diff_x(self, S, G, symbolic_expr=False):
"""Function to compute the inner product :math:`(S, \partial_x G)`.
Parameters
----------
S:
Left-hand side function of the product.
G:
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
"""
pass
[docs] @abstractmethod
def ip_jac(self, S, G, H, symbolic_expr=False):
"""Function to compute the inner product :math:`(S, J(G, H))`, where
:math:`J` is the :meth:`jacobian`.
Parameters
----------
S:
Left-hand side function of the product.
G:
1st argument of the right-hand side function of the product.
H:
2nd argument of the right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
"""
pass
[docs] @abstractmethod
def ip_jac_lap(self, S, G, H, symbolic_expr=False):
"""Function to compute the inner product :math:`(S, J(G, \\nabla^2 H))`, where
:math:`J` is the :meth:`jacobian`.
Parameters
----------
S:
Left-hand side function of the product.
G:
1st argument of the right-hand side function of the product.
H:
2nd argument of the right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
"""
pass
[docs]class SymbolicInnerProductDefinition(InnerProductDefinition):
"""Base class to define symbolic inner products using `Sympy`_.
Parameters
----------
optimizer: None or callable, optional
A function to simplify the integrand to optimize the computation of the integrals.
Should return a `Sympy`_ expression.
If `None`, does not optimize the computation.
Attributes
----------
optimizer: None or callable
A function to simplify the integrand to optimize the computation of the integrals.
Should return a `Sympy`_ expression.
If `None`, does not optimize the computation.
.. _Sympy: https://www.sympy.org/
"""
def __init__(self, optimizer=None):
InnerProductDefinition.__init__(self, optimizer)
[docs] @abstractmethod
def symbolic_inner_product(self, S, G, symbolic_expr=False, integrand=False):
"""Symbolic definition of the inner product :math:`(S, G)`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The symbolic result of the inner product.
"""
pass
[docs] def ip_lap(self, S, G, symbolic_expr=False, integrand=False):
"""Function to compute the inner product :math:`(S, \\nabla^2 G)`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The symbolic result of the inner product.
"""
return self.symbolic_inner_product(S, self.laplacian(G), symbolic_expr=symbolic_expr, integrand=integrand)
[docs] def ip_diff_x(self, S, G, symbolic_expr=False, integrand=False):
"""Function to compute the inner product :math:`(S, \partial_x G)`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The symbolic result of the inner product.
"""
return self.symbolic_inner_product(S, diff(G, _x), symbolic_expr=symbolic_expr, integrand=integrand)
[docs] def ip_jac(self, S, G, H, symbolic_expr=False, integrand=False):
"""Function to compute the inner product :math:`(S, J(G, H))`, where
:math:`J` is the :meth:`jacobian`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
1st argument of the right-hand side function of the product.
H: Sympy expression
2nd argument of the right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The symbolic result of the inner product.
"""
return self.symbolic_inner_product(S, self.jacobian(G, H), symbolic_expr=symbolic_expr, integrand=integrand)
[docs] def ip_jac_lap(self, S, G, H, symbolic_expr=False, integrand=False):
"""Function to compute the inner product :math:`(S, J(G, \\nabla^2 H))`, where
:math:`J` is the :meth:`jacobian`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
1st argument of the right-hand side function of the product.
H: Sympy expression
2nd argument of the right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The symbolic result of the inner product.
"""
return self.symbolic_inner_product(S, self.jacobian(G, self.laplacian(H)), symbolic_expr=symbolic_expr, integrand=integrand)
[docs]class StandardSymbolicInnerProductDefinition(SymbolicInnerProductDefinition):
"""Standard `qgs` class to define symbolic inner products using `Sympy`_.
Parameters
----------
optimizer: None or callable, optional
A function to simplify the integrand to optimize the computation of the integrals.
Should return a `Sympy`_ expression.
If `None`, does not optimize the computation.
Attributes
----------
optimizer: None or callable
A function to simplify the integrand to optimize the computation of the integrals.
Should return a `Sympy`_ expression.
If `None`, does not optimize the computation.
.. _Sympy: https://www.sympy.org/
"""
def __init__(self, optimizer=None):
if optimizer is None:
SymbolicInnerProductDefinition.__init__(self, self._trig_optimizer)
else:
SymbolicInnerProductDefinition.__init__(self, optimizer)
@staticmethod
def _trig_optimizer(expr):
return TR10(TR8(expr))
[docs] @staticmethod
def jacobian(S, G):
"""Jacobian present in the advection terms:
.. math:
J(S, G) = \partial_x S\, \partial_y G - \partial_y S\, \partial_x G
Parameters
----------
S: Sympy expression
1st argument the Jacobian.
G: Sympy expression
2nd argument the Jacobian.
Returns
-------
Sympy expression
The Jacobian.
"""
return diff(S, _x) * diff(G, _y) - diff(G, _x) * diff(S, _y)
[docs] @staticmethod
def laplacian(S):
"""Laplacian :math:`\\nabla^2 S` of a given function :math:`S` in 2D cartesian coordinates:
:math:`\\nabla^2 S = \\partial^2_x S + \\partial^2_y S`.
Parameters
----------
S: Sympy expression
Functions to take the Laplacian of.
Returns
-------
Sympy expression
The Laplacian.
"""
return diff(S, _x, 2) + diff(S, _y, 2)
[docs] @staticmethod
def integrate_over_domain(expr, symbolic_expr=False):
"""Definition of the normalized integrals over the spatial domain used by the inner products:
:math:`\\frac{n}{2\\pi^2}\\int_0^\\pi\\int_0^{2\\pi/n} \, \\mathrm{expr}(x, y) \, \\mathrm{d} x \, \\mathrm{d} y`.
Parameters
----------
expr: Sympy expression
The expression to integrate.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The result of the symbolic integration.
"""
if symbolic_expr:
return Integral(expr, (_x, 0, 2 * pi / _n), (_y, 0, pi))
else:
return integrate(expr, (_x, 0, 2 * pi / _n), (_y, 0, pi))
[docs] def symbolic_inner_product(self, S, G, symbolic_expr=False, integrand=False):
"""Function defining the inner product to be computed symbolically:
:math:`(S, G) = \\frac{n}{2\\pi^2}\\int_0^\\pi\\int_0^{2\\pi/n} S(x,y)\, G(x,y)\, \\mathrm{d} x \, \\mathrm{d} y`.
Parameters
----------
S: Sympy expression
Left-hand side function of the product.
G: Sympy expression
Right-hand side function of the product.
symbolic_expr: bool, optional
If `True`, return the integral as a symbolic expression object. Else, return the integral performed symbolically.
integrand: bool, optional
If `True`, return the integrand of the integral and its integration limits as a list of symbolic expression object. Else, return the integral performed symbolically.
Returns
-------
Sympy expression
The result of the symbolic integration
"""
expr = (_n / (2 * pi ** 2)) * S * G
if integrand:
return expr, (_x, 0, 2 * pi / _n), (_y, 0, pi)
else:
return self.integrate_over_domain(self.optimizer(expr), symbolic_expr=symbolic_expr)