Source code for phyem.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:: phyem.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 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 phyem.tools.frozen import Frozen
from phyem.src.config import _global_lin_repr_setting
from phyem.src.config import _parse_lin_repr
from phyem.src.form.operators import wedge, time_derivative, d, codifferential, cross_product, tensor_product
from phyem.src.form.operators import Cross_Product, CrossProduct, crossProduct
from phyem.src.form.operators import convect, multi
from phyem.src.form.operators import _project_to
from phyem.src.config import _check_sym_repr
from phyem.src.form.parameters import constant_scalar
from phyem.src.config import _global_operator_lin_repr_setting
from phyem.src.config import _global_operator_sym_repr_setting
from phyem.src.config import _form_evaluate_at_repr_setting
from phyem.src.spaces.main import _default_space_degree_repr
from phyem.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 phyem.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 phyem.src.config import RANK, MASTER_RANK
if RANK != MASTER_RANK:
return None
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 phyem.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
# ------------------------------------------------------------------------------------
def representing(self):
r"""What does this form representing?
So, return m, n, and scalar or vector or tensor or somthing similar!
"""
m = self.space.m
n = self.space.n
indicator = self.space.indicator
if indicator == 'Lambda':
k = self.space.k
if m == n == 2:
if k == 1:
v_type = 'vector'
elif k == 0 or k == 2:
v_type = 'scalar'
else:
raise Exception()
elif m == n == 3:
if k == 1 or k == 2:
v_type = 'vector'
elif k == 0 or k == 3:
v_type = 'scalar'
else:
raise Exception()
else:
raise NotImplementedError()
return ['Lambda', m, n, v_type]
else:
raise NotImplementedError()
# --------------- OPERATORS ----------------------------------------------------------
[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)
def x(self, other, form_space=None):
r"""
Parameters
----------
other
form_space :
i.e. the output space. We want to put the output to this space.
Returns
-------
"""
try:
output1 = self.cross_product(other)
except NotImplementedError:
output1 = None
try:
output2 = self.Cross_Product(other)
except NotImplementedError:
output2 = None
try:
output3 = self.CrossProduct(other)
except NotImplementedError:
output3 = None
try:
output4 = self.crossProduct(other)
except NotImplementedError:
output4 = None
outputs = [output1, output2, output3, output4]
if all([_ is None for _ in outputs]):
raise NotImplementedError(
f"implement a cross-product somewhere!")
else:
if form_space.__class__ is self.__class__:
target_space = form_space.space
else:
raise NotImplementedError(f"which space?")
CANNOT_implement_in = []
if output1 is not None:
if output1.space is target_space:
return output1
else:
CANNOT_implement_in.append('cross_product')
else:
pass
if output2 is not None:
if output2.space is target_space:
return output2
else:
CANNOT_implement_in.append('Cross_Product')
else:
pass
if output3 is not None:
if output3.space is target_space:
return output3
else:
CANNOT_implement_in.append('CrossProduct')
else:
pass
if output4 is not None:
if output4.space is target_space:
return output4
else:
CANNOT_implement_in.append('crossProduct')
else:
pass
if len(CANNOT_implement_in) == 4:
raise Exception()
else:
cp = f"{self.space} x {other.space}"
if 'cross_product' not in CANNOT_implement_in:
raise NotImplementedError(f"implement this cross-product: {cp} -> {target_space} in cross_product")
elif 'Cross_Product' not in CANNOT_implement_in:
raise NotImplementedError(f"implement this cross-product: {cp} -> {target_space} in Cross_Product")
elif 'CrossProduct' not in CANNOT_implement_in:
raise NotImplementedError(f"implement this cross-product: {cp} -> {target_space} in CrossProduct")
elif 'crossProduct' not in CANNOT_implement_in:
raise NotImplementedError(f"implement this cross-product: {cp} -> {target_space} in crossProduct")
else:
raise Exception()
[docs]
def cross_product(self, other):
r"""The cross product, :math:`\times`, between this form and another form."""
return cross_product(self, other)
def Cross_Product(self, other):
r"""Another branch of cross-product."""
return Cross_Product(self, other)
def CrossProduct(self, other):
r"""Another branch of cross-product."""
return CrossProduct(self, other)
def crossProduct(self, other):
r"""Another branch of cross-product."""
return crossProduct(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 multi(self, other, output_space):
r"""Do f1 f2 -> a form in output space. For example, c = ab where a, b are both scalars, or D = a B where
a is scalar and BD are vectors.
"""
if output_space.__class__ is self.__class__:
output_space = output_space.space
else:
raise NotImplementedError()
return multi(self, other, output_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:
if cs.is_root():
lr = cs._lin_repr + operator_lin + r'\{' + lr + r'\}'
sr = cs._sym_repr + r'\left(' + sr + r'\right)'
else:
lr = r'\{' + cs._lin_repr + r'\}' + operator_lin + r'\{' + lr + r'\}'
sr = r'\left(' + cs._sym_repr + r'\right)' + r'\left(' + sr + r'\right)'
f = Form(
self.space, # space
sr, # symbolic representation
lr, # linguistic representation
False, # not a root-form anymore.
)
return f
else:
raise NotImplementedError()
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 phyem.src.time_sequence import AbstractTimeInstant
from phyem.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()