Source code for src.form.main
# -*- coding: utf-8 -*-
# noinspection PyUnresolvedReferences
r"""
.. _docs-form:
Form
====
A form is simply an element of a space. Thus, it is logical to define a form through a space. To do that,
just call the ``make_form`` method of the space instance (see :meth:`src.spaces.base.SpaceBase.make_form`).
The following code makes an outer oriented 2-form, ``a``, in space ``Out2`` and
an outer-oriented 1-form, ``b``, in space ``Out1``.
>>> a = Out2.make_form(r'\tilde{\alpha}', 'variable1')
>>> b = Out1.make_form(r'\tilde{\beta}', 'variable2')
The arguments are their symbolic representations (``r'\tilde{\alpha}'``, ``r'\tilde{\beta}'``) and
linguistic representations (``'variable1'``, ``'variable2'``), represectively. To list all defined forms, do
>>> ph.list_forms()
<Figure size ...
If you have turned off the *py cache* , see :ref:`docs-presetting-set`, a figure should have popped out. Otherwise,
it is saved to ``./__phcache__/Pr_current/``. A form is an instance of :class:`Form`.
.. autoclass:: src.form.main.Form
:members: mesh, manifold, orientation, space, wedge, codifferential, exterior_derivative, cross_product,
time_derivative, degree
The forms ``a`` and ``b`` are root forms since they are directly defined through ``make_form`` method.
With these elementary forms, it is possible to build more complicated non-root forms through operators.
Implemented operators are
.. admonition:: Implemented operators for forms
+----------------------+--------------------------------------+--------------------------------------+
| **operator** |**symbolic representation** | **usage** |
+----------------------+--------------------------------------+--------------------------------------+
| exterior derivative | :math:`\mathrm{d}` | ``a.exterior_derivative()`` |
| | | or ``ph.exteror_derivative(a)`` |
| | | or ``ph.d(a)`` |
+----------------------+--------------------------------------+--------------------------------------+
| codifferential | :math:`\mathrm{d}^\ast` | ``a.codifferential()`` |
| | | or ``ph.codifferential(a)`` |
+----------------------+--------------------------------------+--------------------------------------+
| time derivative | :math:`\frac{\partial}{\partial t}` | ``a.time_derivative()`` |
| | | or ``ph.time_derivative(a)`` |
+----------------------+--------------------------------------+--------------------------------------+
| wedge product | :math:`\wedge` | Given two forms, |
| | | ``a``: :math:`\alpha` and |
| | | ``b``: :math:`\beta`, |
| | | The wedge product between |
| | | them, i.e. |
| | | :math:`\alpha\wedge\beta`, |
| | | is ``a.wedge(b)`` or |
| | | ``ph.wedge(a, b)``. |
| | | |
| | | |
+----------------------+--------------------------------------+--------------------------------------+
| inner product | :math:`\left(\cdot,\cdot\right)` | Given two forms, |
| | | ``a``: :math:`\alpha` and |
| | | ``b``: :math:`\beta`, |
| | | The inner product between |
| | | them, i.e. |
| | | :math:`\left(\alpha,\beta\right)`, |
| | | is ``ph.inner(a,b)``. |
| | | |
+----------------------+--------------------------------------+--------------------------------------+
| Hodge | :math:`\star` | ``ph.Hodge(a)`` |
+----------------------+--------------------------------------+--------------------------------------+
| trace | :math:`\mathrm{tr}` | ``ph.trace(a)`` |
+----------------------+--------------------------------------+--------------------------------------+
..
| cross product | :math:`\times` | Given two forms, |
| | | ``a``: :math:`\alpha` and |
| | | ``b``: :math:`\beta`, |
| | | The cross product between |
| | | them, i.e. |
| | | :math:`\alpha\times\beta`, |
| | | is ``a.cross_product(b)``. |
| | | |
+----------------------+--------------------------------------+--------------------------------------+
For example,
>>> da_dt = a.time_derivative()
>>> db_dt = b.time_derivative()
>>> cd_a = a.codifferential()
>>> d_b = b.exterior_derivative()
This generates four more forms, ``da_dt``, ``db_dt``, ``cd_a`` and ``d_b``, which are
- time derivative of ``a``
- time derivative of ``b``
- codifferential of ``a``
- exterior derivative of ``b``
respectively. These non-root forms will appear in the form list if you do
>>> ph.list_forms()
<Figure size ...
All these forms, both root and non-root ones, are the ingredients for making partial differential equaitons (DPE)
which is introduced in the next section.
"""
from tools.frozen import Frozen
from typing import Dict
import matplotlib.pyplot as plt
import matplotlib
plt.rcParams.update({
"text.usetex": True,
"font.family": "DejaVu Sans",
"text.latex.preamble": r"\usepackage{amsmath}"
})
matplotlib.use('TkAgg')
from src.config import _global_lin_repr_setting
from src.config import _parse_lin_repr
from src.form.operators import wedge, time_derivative, d, codifferential, cross_product, tensor_product
from src.form.operators import convect
from src.form.operators import _project_to
from src.config import _check_sym_repr
from src.form.parameters import constant_scalar
from src.config import _global_operator_lin_repr_setting
from src.config import _global_operator_sym_repr_setting
from src.config import _form_evaluate_at_repr_setting
from src.spaces.main import _default_space_degree_repr
from src.spaces.main import _degree_str_maker
_global_forms = dict() # cache keys are id
_global_root_forms_lin_dict = dict() # keys are root form lin_repr
_global_form_variables = {
'update_cache': True, # the global switcher ---------- (1)
}
def _clear_forms():
""""""
for key in list(_global_forms.keys()):
del _global_forms[key]
for key in list(_global_root_forms_lin_dict.keys()):
del _global_root_forms_lin_dict[key]
from src.form.ap import _parse_root_form_ap
[docs]
class Form(Frozen):
"""The form class."""
def __init__(
self, space,
sym_repr, lin_repr,
is_root,
update_cache=True, # the local switcher ------------ (1)
):
if is_root is None: # we will parse is_root from lin_repr
assert isinstance(lin_repr, str) and len(lin_repr) > 0, f"lin_repr={lin_repr} illegal."
is_root, lin_repr = self._parse_is_root(lin_repr)
else:
pass
assert isinstance(is_root, bool), f"is_root must be bool."
self._space = space
if is_root: # we check the `sym_repr` only for root forms.
lin_repr, self._pure_lin_repr = _parse_lin_repr('form', lin_repr)
for form_id in _global_forms:
form = _global_forms[form_id]
assert sym_repr != form._sym_repr, \
f"root form symbolic representation={sym_repr} is taken. Pls use another one."
assert lin_repr != form._lin_repr, \
f"root form linguistic representation={lin_repr} is taken. Pls use another one."
else:
self._pure_lin_repr = None
sym_repr = _check_sym_repr(sym_repr)
self._sym_repr = sym_repr
self._lin_repr = lin_repr
self._is_root = is_root
self._efs = None # elementary elements
self._orientation = space.orientation
if update_cache:
if _global_form_variables['update_cache']: # cache it
_global_forms[id(self)] = self
if self._is_root:
_global_root_forms_lin_dict[self._lin_repr] = self
else:
pass
else:
pass
else:
pass
self._pAti_form: Dict = {
'base_form': None,
'ats': None,
'ati': None
}
self._ats_forms = dict() # the abstract ats forms based on this form.
self._degree = None
self._ap = None
self._dual_representation = False
self._freeze()
def is_dual_representation(self):
return self._dual_representation
def set_dual_representation(self, _bool):
assert isinstance(_bool, bool)
self._dual_representation = _bool
# noinspection PyBroadException
@staticmethod
def _parse_is_root(lin_repr):
"""Study is_root through lin_repr."""
try:
_parse_lin_repr('form', lin_repr)
except Exception:
pass
else:
return True, lin_repr
start, end = _global_lin_repr_setting['form']
if lin_repr[:len(start)] == start and lin_repr[-len(end):] == end:
try:
_parse_lin_repr('form', lin_repr[len(start):-len(end)])
except Exception:
return False, lin_repr
else:
return True, lin_repr[len(start):-len(end)]
else:
return False, lin_repr
def pr(self, figsize=(12, 6)):
"""Print this form with matplotlib and latex."""
from src.config import RANK, MASTER_RANK
if RANK != MASTER_RANK:
return
else:
my_id = r'\texttt{' + str(id(self)) + '}'
if self._pAti_form['base_form'] is None:
pti_text = ''
else:
base_form, ats, ati = self._pAti_form['base_form'], self._pAti_form['ats'], self._pAti_form['ati']
pti_text = rf"\\(${base_form._sym_repr}$ at abstract time instant ${ati._sym_repr}$"
space_text = f'spaces: ${self.space._sym_repr}$'
space_text += rf"\ \ \ \ on ({self.mesh._lin_repr})"
fig = plt.figure(figsize=figsize)
plt.axis((0, 1, 0, 5))
plt.text(0, 4.5, f'form id: {my_id}', ha='left', va='center', size=15)
plt.text(0, 3.5, space_text, ha='left', va='center', size=15)
plt.text(0, 2.5,
rf'\noindent symbolic: ' + f"${self._sym_repr}$" + pti_text,
ha='left', va='center', size=15)
plt.text(0, 1.5, 'linguistic: ' + self._lin_repr, ha='left', va='center', size=15)
root_text = rf'is_root: {self.is_root()}'
plt.text(0, 0.5, root_text, ha='left', va='center', size=15)
plt.axis('off')
from src.config import _setting
plt.show(block=_setting['block'])
return fig
def __repr__(self):
""""""
super_repr = super().__repr__().split('object')[-1]
return '<Form ' + self._sym_repr + super_repr # this will be unique.
@property
def elementary_forms(self):
"""parse the elementary_forms from the linguistic representation only. A texting solution only!"""
if self._efs is None:
efs = list()
for root_lin_repr in _global_root_forms_lin_dict:
if root_lin_repr in self._lin_repr:
efs.append(_global_root_forms_lin_dict[root_lin_repr])
self._efs = set(efs)
return self._efs
@property
def degree(self):
"""This form is in the space of particular finite dimensional ``degree``."""
assert self._degree is not None, f"degree of form {self} is empty, set it firstly."
return self._degree
@degree.setter
def degree(self, _degree):
"""Limit this form to a particular finite dimensional space of degree ``_degree``."""
assert isinstance(_degree, (int, float, list, tuple)), f"Can only use int, float, list or tuple for the degree."
for _lin_repr in _global_root_forms_lin_dict:
root_form = _global_root_forms_lin_dict[_lin_repr]
if root_form._pAti_form['base_form'] is self:
root_form._degree = _degree
self.space.finite.specify_form(self, _degree)
def ap(self, sym_repr=None):
"""Algebraic proxy."""
if self._ap is None:
if self.is_root():
self._ap = _parse_root_form_ap(self, sym_repr)
else:
raise Exception("None root form has no symbolic representation, do not try to access.")
else:
assert sym_repr is None, f"form {self} already have an algebraic proxy, change its symbolic " \
f"representation is not allowed (cause it may not be safe)."
return self._ap
def _ap_shape(self):
"""ap shape."""
return self.space._sym_repr + _default_space_degree_repr + _degree_str_maker(self._degree)
@property
def orientation(self):
"""My orientation."""
return self._orientation
def is_root(self):
"""Return True this form is a root form."""
return self._is_root
@property
def space(self):
"""The space this form is in."""
return self._space
@property
def mesh(self):
"""The mesh this form is on."""
return self.space.mesh
@property
def manifold(self):
"""The manifold this form is on."""
return self.mesh.manifold
[docs]
def wedge(self, other):
r"""The wedge, :math:`\wedge`, between this form and another form."""
return wedge(self, other)
[docs]
def time_derivative(self, degree=1):
r"""The time derivative, :math:`\dfrac{\partial}{\partial t}`, of this form."""
return time_derivative(self, degree=degree)
[docs]
def exterior_derivative(self):
r"""The exterior derivative, :math:`\mathrm{d}`, of this form."""
return d(self)
def d(self):
r""""""
return self.exterior_derivative()
[docs]
def codifferential(self):
r"""The codifferential, :math:`\mathrm{d}^\ast`, of this form."""
return codifferential(self)
[docs]
def cross_product(self, other):
r"""The cross product, :math:`\times`, between this form and another form."""
return cross_product(self, other)
def convect(self, other):
"""Let self be u, other be w, we compute u dot(grad(w))."""
return convect(self, other)
def tensor_product(self, other):
""""""
return tensor_product(self, other)
def project_to(self, to_space):
return _project_to(self, to_space)
def __neg__(self):
"""- self"""
raise NotImplementedError()
def __add__(self, other):
"""self + other"""
if other.__class__.__name__ == 'Form':
assert other.mesh == self.mesh, f"mesh does not match."
assert self.orientation == other.orientation
assert self.space == other.space
self_lr = self._lin_repr
self_sr = self._sym_repr
other_lr = other._lin_repr
other_sr = other._sym_repr
operator_lin = _global_operator_lin_repr_setting['plus']
operator_sym = _global_operator_sym_repr_setting['plus']
lin_repr = self_lr + operator_lin + other_lr
sym_repr = self_sr + operator_sym + other_sr
f = Form(
self.space, # space
sym_repr, # symbolic representation
lin_repr, # linguistic representation
False, # must not be a root-form anymore.
)
return f
else:
raise NotImplementedError(f"{other}")
def __sub__(self, other):
"""self-other"""
if other.__class__.__name__ == 'Form':
assert other.mesh == self.mesh, f"mesh does not match."
assert self.orientation == other.orientation
assert self.space == other.space
self_lr = self._lin_repr
self_sr = self._sym_repr
other_lr = other._lin_repr
other_sr = other._sym_repr
operator_lin = _global_operator_lin_repr_setting['minus']
operator_sym = _global_operator_sym_repr_setting['minus']
lin_repr = self_lr + operator_lin + other_lr
sym_repr = self_sr + operator_sym + other_sr
f = Form(
self.space, # space
sym_repr, # symbolic representation
lin_repr, # linguistic representation
False, # must not be a root-form anymore.
)
return f
else:
raise NotImplementedError(f"{other}")
def __mul__(self, other):
"""self * other"""
raise NotImplementedError()
def __rmul__(self, other):
"""other * self"""
if isinstance(other, (int, tuple)):
cs = constant_scalar(other)
return cs * self
elif other.__class__.__name__ == 'ConstantScalar0Form':
operator_lin = _global_operator_lin_repr_setting['multiply']
lr = self._lin_repr
sr = self._sym_repr
cs = other
if self.is_root():
lr = cs._lin_repr + operator_lin + lr
sr = cs._sym_repr + sr
else:
lr = cs._lin_repr + operator_lin + r'\{' + lr + r'\}'
if cs.is_root():
sr = cs._sym_repr + sr
else:
sr = cs._sym_repr + r'\left(' + sr + r'\right)'
f = Form(
self.space, # space
sr, # symbolic representation
lr, # linguistic representation
False, # not a root-form anymore.
)
return f
def __truediv__(self, other):
"""self / other"""
operator_lin = _global_operator_lin_repr_setting['division']
operator_sym = _global_operator_sym_repr_setting['division']
if isinstance(other, (int, tuple)):
cs = constant_scalar(other)
return self / cs
elif other.__class__.__name__ == 'AbstractTimeInterval':
ati = other
return self / ati._as_scalar()
elif other.__class__.__name__ == 'ConstantScalar0Form':
lr = self._lin_repr
sr = self._sym_repr
cs = other
if self.is_root():
lr = lr + operator_lin + cs._lin_repr
else:
lr = r'\{' + lr + r'\}' + operator_lin + cs._lin_repr
sr = operator_sym[0] + sr + operator_sym[1] + cs._sym_repr + operator_sym[2]
f = Form(
self.space, # space
sr, # symbolic representation
lr, # linguistic representation
False, # not a root-form anymore.
)
return f
else:
raise NotImplementedError(f"form divided by <{other.__class__.__name__}> is not implemented.")
def _evaluate_at(self, other):
"""evaluate_at"""
from src.time_sequence import AbstractTimeInstant
from src.time_sequence import _global_abstract_time_sequence
if isinstance(other, str) and len(_global_abstract_time_sequence) == 1:
# when there is only one abstract time sequence at behind, we can
# access its abstract time instant by str directly.
the_only_ats_lin_repr = list(_global_abstract_time_sequence.keys())[0]
the_only_ats = _global_abstract_time_sequence[the_only_ats_lin_repr]
other = the_only_ats[other]
else:
pass
if other.__class__ is AbstractTimeInstant:
ati = other
assert self.is_root(), f"Can only evaluate a root form at an abstract time instant."
sym_repr = self._sym_repr
lin_repr = self._pure_lin_repr
s = _form_evaluate_at_repr_setting['sym']
sym_repr = s[0] + sym_repr + s[1] + ati.k + s[2]
lin_repr += _form_evaluate_at_repr_setting['lin'] + ati._pure_lin_repr
if lin_repr in self._ats_forms: # we must cache it, this is very important.
pass
else:
ftk = Form(
self._space,
sym_repr, lin_repr,
self.is_root(), # must be True.
)
ftk._pAti_form['base_form'] = self
ftk._pAti_form['ats'] = ati.time_sequence
ftk._pAti_form['ati'] = ati
ftk.set_dual_representation(self._dual_representation)
self._ats_forms[lin_repr] = ftk
return self._ats_forms[lin_repr]
else:
raise NotImplementedError(f"Cannot evaluate {self} at {other}.")
def __matmul__(self, other):
"""self @ other"""
return self._evaluate_at(other)
def replace(self, f, by, which='all'):
"""replace `which` `f` by `by`."""
assert by.space == f.space, f"spaces do not match."
if f._lin_repr not in self._lin_repr:
return self
elif self._lin_repr == f._lin_repr:
return by
else:
if which == 'all':
lin_repr = self._lin_repr.replace(f._lin_repr, by._lin_repr)
sym_repr = self._sym_repr.replace(f._sym_repr, by._sym_repr)
elif isinstance(which, (list, tuple)) and all([isinstance(_, int) and _ >= 0 for _ in which]):
# use 1, or [0,1] to indicate which targets to be replaced.
lin_list = self._lin_repr.split(f._lin_repr)
sym_list = self._sym_repr.split(f._sym_repr)
assert len(lin_list) == len(sym_list), f'must be!'
if len(lin_list) == 1: # no replace target found!
raise Exception('found no target to replace.')
else:
pass
amount_places = len(lin_list) - 1
to_join_lin = ['' for _ in range(amount_places)]
to_join_sym = ['' for _ in range(amount_places)]
for i in which:
assert i < amount_places, (f"cannot deal with `which={which}` for form replace, "
f"not that many targets.)")
to_join_lin[i] = by._lin_repr
to_join_sym[i] = by._sym_repr
for i, s in enumerate(to_join_lin):
if s == '':
to_join_lin[i] = f._lin_repr
else:
pass
for i, s in enumerate(to_join_sym):
if s == '':
to_join_sym[i] = f._sym_repr
else:
pass
final_lin_repr = ''
final_sym_repr = ''
for i in range(amount_places):
final_lin_repr += lin_list[i] + to_join_lin[i]
final_sym_repr += sym_list[i] + to_join_sym[i]
final_lin_repr += lin_list[-1]
final_sym_repr += sym_list[-1]
sym_repr = final_sym_repr
lin_repr = final_lin_repr
else:
raise NotImplementedError(f"cannot deal with `which={which}` for form replace.)")
return Form(
self.space,
sym_repr,
lin_repr,
None,
)
def split(self, into):
"""reform self into a few forms."""
raise NotImplementedError()