Source code for layercake.formatters.base
"""
Base classes for formatting symbolic equations output
=====================================================
Defines base classes to format tendencies and Jacobian symbolic equations output.
Description of the classes
--------------------------
* :class:`EquationFormatter`: Base class for symbolic equations formatting.
* :class:`JacobianEquationFormatter`: Base class for symbolic Jacobian equations formatting.
"""
from sympy.core.add import Add
from sympy import ImmutableSparseNDimArray
from abc import ABC, abstractmethod
from layercake.utils.symbolic_tensor import get_coords_from_index
[docs]
class EquationFormatter(ABC):
"""Base class for symbolic equations formatting.
Parameters
----------
lang_translation: dict(str)
Language translation mapping dictionary, mapping replacements for converting
Sympy symbolic output strings to the target language.
Attributes
----------
lang_translation: dict(str)
Language translation mapping dictionary, mapping replacements for converting
Sympy symbolic output strings to the target language.
index_offset: int
Number that accesses the first element in an array. Defaults to 0.
"""
def __init__(self, lang_translation=None):
self.lang_translation = dict()
self.index_offset = 0
if lang_translation is not None:
self.lang_translation.update(lang_translation)
[docs]
def __call__(self, tensor, variable='U', tendencies='F'):
"""Convert a model symbolic tendencies terms tensor to a list of symbolic equations in
string format.
Parameters
----------
tensor: ~sympy.tensor.array.ImmutableSparseNDimArray
Symbolic tendencies terms tensor to convert.
variable: str
Name of the state variable to use for the output equations strings.
Default to `'U'`.
tendencies: str
Name of the tendencies variable to use for the output equations strings.
Default to `'F`.
"""
if not isinstance(tensor, ImmutableSparseNDimArray):
raise ValueError('Only symbolic tensor can be converted to symbolic equations.')
ndim = tensor.shape[0]
shape_len = len(tensor.shape)
equations_list = list()
for i in range(ndim):
equations_list.append(f'{self._format_components(tendencies, i)} = ')
for i in range(1, ndim):
for n, val in tensor[i]._args[0].items():
coords = get_coords_from_index(n, ndim, shape_len-1)
new_term = f'{val} '
if isinstance(val, Add):
new_term = '+ (' + new_term + ')'
elif new_term[0] != '-':
new_term = '+' + new_term
for c in coords:
if c != 0:
new_term += f'* {self._format_components(variable, c)} '
equations_list[i] += new_term
for i in range(1, ndim):
for code, new_code in self.lang_translation.items():
equations_list[i] = equations_list[i].replace(code, new_code)
return equations_list[1:]
def _format_components(self, s, idx):
# Index offset included to allow for differnet language index bases
return f'{s}{self.opening_character}{idx + self.index_offset - 1}{self.closing_character}'
@property
@abstractmethod
def opening_character(self):
"""str: Character opening the arrays specification index in the target language.
Must be defined in the subclasses."""
pass
@property
@abstractmethod
def closing_character(self):
"""str: Character closing the arrays specification index in the target language.
Must be defined in the subclasses."""
pass
[docs]
class JacobianEquationFormatter(EquationFormatter):
"""Base class for symbolic Jacobian equations formatting.
Parameters
----------
lang_translation: dict(str)
Language translation mapping dictionary, mapping replacements for converting
Sympy symbolic output strings to the target language.
Attributes
----------
lang_translation: dict(str)
Language translation mapping dictionary, mapping replacements for converting
Sympy symbolic output strings to the target language.
index_offset: int
Number that accesses the first element in an array. Defaults to 0.
"""
def __init__(self, lang_translation=None):
EquationFormatter.__init__(self, lang_translation=lang_translation)
[docs]
def __call__(self, tensor, variable='U', tendencies='J'):
"""Convert a model Jacobian symbolic tendencies terms tensor to a list of symbolic equations in
string format.
Parameters
----------
tensor: ~sympy.tensor.array.ImmutableSparseNDimArray
Jacobian symbolic tendencies terms tensor to convert.
variable: str
Name of the state variable to use for the output equations strings.
Default to `'U'`.
tendencies: str
Name of the tendencies variable to use for the output equations strings.
Default to `'F`.
"""
if not isinstance(tensor, ImmutableSparseNDimArray):
raise ValueError('Only symbolic tensor can be converted to symbolic equations.')
ndim = tensor.shape[0]
shape_len = len(tensor.shape)
equations_matrix = list()
for i in range(ndim):
equations_matrix.append(list())
for j in range(ndim):
equations_matrix[-1].append(None)
for i in range(1, ndim):
for n, val in tensor[i]._args[0].items():
coords = get_coords_from_index(n, ndim, shape_len-1)
j = coords[0]
if equations_matrix[i][j] is None:
equations_matrix[i][j] = f'{tendencies}{self.opening_character}' \
f'{i + self.index_offset - 1},' \
f'{j + self.index_offset - 1}{self.closing_character} = '
new_term = f'{val} '
if isinstance(val, Add):
new_term = '+ (' + new_term + ')'
elif new_term[0] != '-':
new_term = '+' + new_term
for c in coords[1:]:
if c != 0:
new_term += f'* {self._format_components(variable, c)} '
equations_matrix[i][j] += new_term
equations_list = list()
for i in range(1, ndim):
for j in range(1, ndim):
if equations_matrix[i][j] is not None:
eq = equations_matrix[i][j]
for code, new_code in self.lang_translation.items():
eq = eq.replace(code, new_code)
equations_list.append(eq)
return equations_list