Source code for src.wf.mp.main

# -*- coding: utf-8 -*-
# noinspection PyUnresolvedReferences
r"""

.. _ap-mp:

============
Matrix proxy
============

With the fully discrete weak formulation ``wf``, we can bring it into its algebraic proxy by calling its method
``mp``, standing for *matrix proxy*,

>>> mp = wf.mp()

which is an instance of :class:`MatrixProxy`,

    .. autoclass:: MatrixProxy
        :members:

Similarly, its ``pr`` method can illustrate it properly,

>>> mp.pr()
<Figure size ...


.. _ap-ap:

========================
Algebraic representation
========================

Depend on ``mp`` is linear or nonlinear, an algebraic system can be produced
through either method ``ls`` or ``nls`` of ``mp``,
see :meth:`MatrixProxy.ls` and :meth:`MatrixProxy.nls`.

Method ``ls`` gives an instance of :class:`MatrixProxyLinearSystem`, i.e.,

    .. autoclass:: MatrixProxyLinearSystem
        :members:

And method ``nls`` leads to an instance of :class:`MatrixProxyNoneLinearSystem`, namely,

    .. autoclass:: MatrixProxyNoneLinearSystem
        :members:

In this case, ``mp`` is a linear system. Thus, we should call ``ls`` method of it,

>>> ls = mp.ls()
>>> ls.pr()
<Figure size ...

Eventually, a fully discrete abstract linear system is obtained. We can send it a particular implementation
which will *objectivize* it, for example by making matrices 2-dimensional arrays and making the vectors
1-dimensional arrays. These implementations will be introduced in the following section.

"""
import matplotlib.pyplot as plt
import matplotlib
plt.rcParams.update({
    "text.usetex": True,
    "font.family": "DejaVu Sans",
    "text.latex.preamble": r"\usepackage{amsmath, amssymb}",
})
matplotlib.use('TkAgg')

from src.form.others import _find_form
from src.config import _root_form_ap_vec_setting
from tools.frozen import Frozen
from src.algebra.array import AbstractArray
from src.wf.term.ap import TermLinearAlgebraicProxy
from src.wf.term.ap import TermNonLinearOperatorAlgebraicProxy
from src.algebra.linear_system import BlockMatrix, BlockColVector, LinearSystem
from src.algebra.nonlinear_system import NonLinearSystem
from src.wf.mp.linear_system import MatrixProxyLinearSystem
from src.wf.mp.nonlinear_system import MatrixProxyNoneLinearSystem


[docs] class MatrixProxy(Frozen): """""" def __init__(self, wf): """""" ap = wf.ap() # make an algebraic proxy in real time. if ap._fully_resolved: assert ap.linearity in ('linear', 'nonlinear'), \ f"ap linearity must be linear or nonlinear when it is fully resolved." else: raise Exception( f"there is(are) term(s) in the wf not-resolved as 'algebraic proxy', check 'ap' of " f"the 'wf' to see which term(s) is(are) not resolved!." ) self._wf = wf self._ap = ap self._num_equations = len(ap._term_dict) self._unknowns = ap.unknowns self._test_vectors = ap.test_vectors self.___total_indexing_length___ = None self._lbv = BlockColVector(self._num_equations) # left block vector part self._rbv = BlockColVector(self._num_equations) # right block vector part self._left_nonlinear_terms = [[] for _ in range(self._num_equations)] self._left_nonlinear_signs = [[] for _ in range(self._num_equations)] self._right_nonlinear_terms = [[] for _ in range(self._num_equations)] self._right_nonlinear_signs = [[] for _ in range(self._num_equations)] self._linearity = 'linear' for i in ap._term_dict: terms = ap._term_dict[i] signs = ap._sign_dict[i] for j in range(2): lr_terms = terms[j] lr_signs = signs[j] k = 0 for term, sign in zip(lr_terms, lr_signs): linearity, term = self._test_vector_remover(i, term) if linearity == 'linear': assert self._ap._linearity_dict[i][j][k] == 'linear', f'Must be this case.' if j == 0: self._lbv._add(i, term, sign) else: self._rbv._add(i, term, sign) elif linearity == 'nonlinear': assert self._ap._linearity_dict[i][j][k] == 'nonlinear', f'Must be this case.' if self._linearity == 'linear': self._linearity = 'nonlinear' else: pass if j == 0: # noinspection PyTypeChecker self._left_nonlinear_terms[i].append(term) self._left_nonlinear_signs[i].append(sign) else: # noinspection PyTypeChecker self._right_nonlinear_terms[i].append(term) self._right_nonlinear_signs[i].append(sign) else: raise NotImplementedError() k += 1 self._l_mvs = list() # left matrix@vector sections self._r_mvs = list() # right matrix@vector sections self.parse(self._unknowns) self._bc = wf._bc self._freeze() @property def linearity(self): """""" assert self._linearity in ('linear', 'nonlinear'), f"Linearity must be among ('linear', 'nonlinear')." return self._linearity def _test_vector_remover(self, i, term): """""" test_vector = self._test_vectors[i] if term.__class__ is TermLinearAlgebraicProxy: aa = term._abstract_array factor = aa._factor components = aa._components transposes = aa._transposes assert components[0] == test_vector, f"cannot remove test vector {test_vector} from term {term}." assert transposes[0] is True, f"cannot remove test vector {test_vector} from term {term}." new_aa = AbstractArray( factor=factor, components=components[1:], transposes=transposes[1:], ) return 'linear', new_aa elif term.__class__ is TermNonLinearOperatorAlgebraicProxy: tf_pure_lin_repr = test_vector._pure_lin_repr[:-len(_root_form_ap_vec_setting['lin'])] tf = _find_form(tf_pure_lin_repr) if term._tf is None: term.set_test_form(tf) else: assert term._tf is tf, f"double check the test form." return 'nonlinear', term else: raise NotImplementedError(term.__class__) def parse(self, targets): """""" targets = list(targets) lbv = self._lbv rbv = self._rbv bb = BlockColVector(self._num_equations) for i, target in enumerate(targets): if target.__class__.__name__ == 'Form': target = target.ap() targets[i] = target bb._add(i, target, '+') for lor, bv in enumerate((lbv, rbv)): bm = BlockMatrix((self._num_equations, len(targets))) remaining_bv = BlockColVector(self._num_equations) for i, entry in enumerate(bv._entries): for j, term in enumerate(entry): sign = bv._signs[i][j] if term.__class__ is AbstractArray: t = term._components[-1] trans = term._transposes[-1] if t in targets and not trans: # found a correct term to be put int matrix block k = targets.index(t) components = term._components[:-1] transposes = term._transposes[:-1] factor = term._factor bm_aa = AbstractArray( factor=factor, components=components, transposes=transposes, ) bm._add(i, k, bm_aa, sign) else: remaining_bv._add(i, term, sign) else: raise NotImplementedError() if lor == 0: if bm._is_empty(): pass else: self._l_mvs.append((bm, bb)) self._lbv = remaining_bv else: if bm._is_empty(): pass else: self._r_mvs.append((bm, bb)) self._rbv = remaining_bv self.___total_indexing_length___ = None def _total_indexing_length(self): """""" if self.___total_indexing_length___ is None: a = len(self._l_mvs) c = len(self._r_mvs) if self._lbv._is_empty(): b = 0 else: b = 1 if self._rbv._is_empty(): d = 0 else: d = 1 self.___total_indexing_length___ = (a, b, c, d), a + b + c + d return self.___total_indexing_length___ def __getitem__(self, index): """To retrieve a linear term: do 'a-b' or 'a-b,c', where a, b, c are str(integer)-s. when index = 'a-b' `a` refer to the `ath` block. `b` refer to the `b`th entry of the ColVec of the block. when index = 'a-b,c' `a` refer to the `ath` block. `b,c` refer to the `b,c`th entry of the BlockMatrix of the block. So when `a`th block is a Matrix @ Vector, we can use either 'a-b' (vector block) or 'a-b,c' (matrix block). But when `a`th block is a ColVec, we can only use 'a-b'. To retrieve a nonlinear term, to be continued. """ assert isinstance(index, str), f"pls put index in string." assert index.count('-') == 1, f"linear term index={index} is illegal." block_num, local_index = index.split('-') assert block_num.isnumeric(), f"linear term index={index} is illegal." block_num = int(block_num) abcd, total_length = self._total_indexing_length() a, b, c, d = abcd assert 0 <= block_num < total_length, f"linear term index={index} is illegal; it beyonds the length." if block_num < a: # retrieving a term in left l_mvs block = self._l_mvs[block_num] elif block_num < a+b: # retrieving a term in left remaining vector block = self._lbv elif block_num < a+b+c: # retrieving a term in right l_mvs block_num -= a+b block = self._r_mvs[block_num] else: # retrieving a term in right remaining vector block = self._rbv indices = eval('[' + local_index + ']') if isinstance(block, tuple): if len(indices) == 2: block = block[0] elif len(indices) == 1: block = block[1] else: raise Exception(f"linear term index={index} is illegal.") else: pass try: return block(*indices) except (IndexError, TypeError): raise Exception(f"linear term index={index} is illegal.") def _pr_text(self): symbolic = '' plus = '' variant = 0 for bm_bb in self._l_mvs: bm, bb = bm_bb assert not bm._is_empty() if variant == 0: _v_plus = '' else: _v_plus = '+' symbolic += _v_plus symbolic += bm._pr_text() symbolic += bb._pr_text() variant += 1 plus = '+' if self._lbv._is_empty(): pass else: symbolic += plus + self._lbv._pr_text() nonlinear_text = '' if self.linearity == 'nonlinear': nonlinear_terms = self._left_nonlinear_terms nonlinear_signs = self._left_nonlinear_signs num_terms = 0 for terms in nonlinear_terms: num_terms += len(terms) if num_terms == 0: pass else: # there are nonlinear terms on the left hand side for terms, signs in zip(nonlinear_terms, nonlinear_signs): if len(terms) == 0: nonlinear_text += r'\boldsymbol{0}' else: for i, term in enumerate(terms): sign = signs[i] if i == 0: if sign == '-': pass else: sign = '' else: pass nonlinear_text += sign + term._sym_repr nonlinear_text += r'\\' nonlinear_text = nonlinear_text[:-len(r'\\')] nonlinear_text = r" + \begin{bmatrix}" + nonlinear_text + r"\end{bmatrix}" symbolic += nonlinear_text + '=' plus = '' variant = 0 for bm_bb in self._r_mvs: bm, bb = bm_bb assert not bm._is_empty() if variant == 0: _v_plus = '' else: _v_plus = '+' symbolic += _v_plus symbolic += bm._pr_text() symbolic += bb._pr_text() variant += 1 plus = '+' if self._rbv._is_empty(): pass else: symbolic += plus + self._rbv._pr_text() nonlinear_text = '' if self.linearity == 'nonlinear': nonlinear_terms = self._right_nonlinear_terms nonlinear_signs = self._right_nonlinear_signs num_terms = 0 for terms in nonlinear_terms: num_terms += len(terms) if num_terms == 0: pass else: # there are nonlinear terms on the left hand side for terms, signs in zip(nonlinear_terms, nonlinear_signs): if len(terms) == 0: nonlinear_text += r'\boldsymbol{0}' else: for i, term in enumerate(terms): sign = signs[i] if i == 0: if sign == '-': pass else: sign = '' else: pass nonlinear_text += sign + term._sym_repr nonlinear_text += r'\\' nonlinear_text = nonlinear_text[:-len(r'\\')] nonlinear_text = r" + \begin{bmatrix}" + nonlinear_text + r"\end{bmatrix}" symbolic += nonlinear_text return symbolic def _mp_seek_text(self): """seek text""" seek_text = self._wf._mesh.manifold._manifold_text() seek_text += r'seek $\left(' form_sr_list = list() space_sr_list = list() for un in self._ap.unknowns: form_sr_list.append(rf' {un._sym_repr}') space_sr_list.append(rf"{un._shape_text()}") seek_text += ','.join(form_sr_list) seek_text += r'\right) \in ' seek_text += r'\times '.join(space_sr_list) seek_text += '$, such that\n' return seek_text
[docs] def pr(self, figsize=(12, 8)): """Print the representation, a figure, of this weak formulation. Parameters ---------- figsize : tuple, optional The figure size. It has no effect when the figure is over-sized. A tight configuration will be applied when it is the case. The default value is ``(12, 8)``. """ from src.config import RANK, MASTER_RANK if RANK != MASTER_RANK: return else: pass seek_text = self._mp_seek_text() symbolic = r"$" + self._pr_text() + r"$" if self._bc is None or len(self._bc) == 0: bc_text = '' else: bc_text = self._bc._bc_text() fig = plt.figure(figsize=figsize) plt.axis((0, 1, 0, 1)) plt.axis('off') plt.text(0.05, 0.5, seek_text + symbolic + bc_text, ha='left', va='center', size=15) from src.config import _setting, _pr_cache if _setting['pr_cache']: _pr_cache(fig, filename='matrixProxy') else: plt.tight_layout() plt.show(block=_setting['block']) return fig
def _parse_ls(self): """""" assert self._lbv._is_empty(), f"Format is illegal, must be like Ax=b, do '.pr()' to check!" assert len(self._l_mvs) == 1, f"Format is illegal, must be like Ax=b, do '.pr()' to check!" A, x = self._l_mvs[0] b = BlockColVector(self._rbv._shape) for Mv in self._r_mvs: M, v = Mv for i in range(M._shape[0]): for j in range(M._shape[1]): vj, sj = v(j) Mij, Sij = M(i, j) assert len(vj) == len(sj) == 1, f"Format is illegal, must be like Ax=b, do pr() to check!" vj, sj = vj[0], sj[0] for mij, sij in zip(Mij, Sij): if sj == sij: sign = '+' else: sign = '-' mij_at_vj = mij @ vj b._add(i, mij_at_vj, sign) for i in range(self._rbv._shape): bi, si = self._rbv(i) for bj, sj in zip(bi, si): b._add(i, bj, sj) ls = LinearSystem(A, x, b) return ls
[docs] def ls(self): """Convert self to an abstract linear system. Returns ------- ls : :class:`MatrixProxyLinearSystem` The linear system instance. """ assert self.linearity == 'linear', f"'mp' is not linear, try to use '.nls'." ls = self._parse_ls() return MatrixProxyLinearSystem(self, ls, self.bc)
[docs] def nls(self): """Convert self to an abstract nonlinear system. Returns ------- nls : :class:`MatrixProxyNoneLinearSystem` The nonlinear system instance. """ assert self.linearity == 'nonlinear', f"'mp' is linear, just use '.ls'." ls = self._parse_ls() mp_ls = MatrixProxyLinearSystem(self, ls, self.bc) len_right_nonlinear_terms = 0 for terms in self._right_nonlinear_terms: len_right_nonlinear_terms += len(terms) assert len_right_nonlinear_terms == 0, \ f"To initialize a nonlinear system, move all nonlinear terms to the left hand side first." nls = NonLinearSystem( ls, (self._left_nonlinear_terms, self._left_nonlinear_signs), ) return MatrixProxyNoneLinearSystem(self, mp_ls, nls)
@property def bc(self): """""" return self._bc def _pr_temporal_advancing(self, *args, **kwargs): """""" return self._wf._pr_temporal_advancing(*args, **kwargs)