Source code for galgebra.lt

"""
Multivector Linear Transformation
"""

import inspect
import types
import itertools
import warnings
from copy import copy
from functools import reduce
from typing import Mapping

from sympy import (
    expand, symbols, Matrix, Transpose, zeros, Symbol, Function, S, Add, Expr
)
from sympy.printing.latex import LatexPrinter as _LatexPrinter
from sympy.printing.str import StrPrinter as _StrPrinter

from ._utils import cached_property as _cached_property

from . import printer
from . import metric
from . import mv


# Add custom settings to the builtin latex printer
_LatexPrinter._default_settings.update({
    'galgebra_mlt_lcnt': 1
})
_StrPrinter._default_settings.update({
    'galgebra_mlt_lcnt': 1
})


def Symbolic_Matrix(root, coords=None, mode='g', f=False, sub=True):
    if sub:
        pos = '_'
    else:
        pos = '__'
    if isinstance(coords, (list, tuple)):
        n = len(coords)
        n_range = range(n)
        mat = zeros(n)
        if mode == 'g':  # General symbolic matrix
            for row in n_range:
                row_index = str(coords[row])
                for col in n_range:
                    col_index = str(coords[col])
                    element = root + pos + row_index + col_index
                    if not f:
                        mat[row, col] = Symbol(element, real=True)
                    else:
                        mat[row, col] = Function(element)(*coords)

        elif mode == 's':  # Symmetric symbolic matrix
            for row in n_range:
                row_index = str(coords[row])
                for col in n_range:
                    col_index = str(coords[col])
                    if row <= col:
                        element = root + pos + row_index + col_index
                    else:
                        element = root + pos + col_index + row_index
                    if not f:
                        mat[row, col] = Symbol(element, real=True)
                    else:
                        mat[row, col] = Function(element)(*coords)

        elif mode == 'a':  # Asymmetric symbolic matrix
            for row in n_range:
                row_index = str(coords[row])
                for col in n_range:
                    col_index = str(coords[col])
                    if row <= col:
                        sign = S.One
                        element = root + pos + row_index + col_index
                    else:
                        sign = -S.One
                        element = root + pos + col_index + row_index
                    if row == col:
                        sign = S.Zero
                    if not f:
                        mat[row, col] = sign * Symbol(element, real=True)
                    else:
                        mat[row, col] = sign * Function(element)(*coords)
        else:
            raise ValueError('In Symbolic_Matrix mode = ' + str(mode))
    else:
        raise ValueError('In Symbolic_Matrix coords = ' + str(coords))
    return mat


[docs] def Matrix_to_dictionary(mat_rep, basis): """ Convert matrix representation of linear transformation to dictionary """ n = len(basis) if mat_rep.rows != n or mat_rep.cols != n: raise ValueError('Matrix and Basis dimensions not equal for Matrix = ' + str(mat_rep)) n_range = list(range(n)) return { basis[col]: sum( (mat_rep[row, col]*basis[row] for row in n_range), S.Zero ) for col in n_range }
[docs] def Dictionary_to_Matrix(dict_rep, ga): """ Convert dictionary representation of linear transformation to matrix """ lst_mat = [] # list representation of sympy matrix for e_row in ga.basis: lst_mat_row = len(ga.basis) * [S.Zero] element = dict_rep.get(e_row, S.Zero) if isinstance(element, mv.Mv): element = element.obj for coef, base in metric.linear_expand_terms(element): index = ga.basis.index(base) lst_mat_row[index] = coef lst_mat.append(lst_mat_row) # expand the transpose return Transpose(Matrix(lst_mat)).doit()
[docs] class Lt(printer.GaPrintable): r""" A Linear Transformation Except for the spinor representation the linear transformation is stored as a dictionary with basis vector keys and vector values ``self.lt_dict`` so that a is a vector :math:`a = a^{i}e_{i}` then .. math:: \mathtt{self(}a\mathtt{)} = a^{i} * \mathtt{self.lt\_dict[}e_{i}\mathtt{]}. For the spinor representation the linear transformation is stored as the even multivector ``self.R`` so that if a is a vector:: self(a) = self.R * a * self.R.rev(). Attributes ---------- lt_dict : dict the keys are the basis symbols, :math:`e_i`, and the dictionary entries are the object vector images (linear combination of sympy non-commutative basis symbols) of the keys so that if ``L`` is the linear transformation then:: L(e_i) = self.Ga.mv(L.lt_dict[e_i]) """ @staticmethod def setup(ga): # galgebra 0.5.0 warnings.warn( "Lt.setup(ga) is deprecated, use `ga.coords` and `ga.coord_vec` " "directly.", DeprecationWarning, stacklevel=2) return ga.coords, ga.coord_vec @property def coords(self): # galgebra 0.6.0 warnings.warn( "lt.coords is deprecated, use `lt.Ga.coords` instead.", DeprecationWarning, stacklevel=2) return self.Ga.coords @property def X(self): # galgebra 0.6.0 warnings.warn( "lt.X is deprecated, use `lt.Ga.coord_vec` instead.", DeprecationWarning, stacklevel=2) return self.Ga.coord_vec @property def mode(self): # galgebra 0.6.0 warnings.warn( "lt.mode is deprecated, inspect lt.matrix() and its transpose to " "determine symmetry", DeprecationWarning, stacklevel=2) m = self.matrix() if m == m.T: return 's' elif m == -m.T: return 'a' else: return 'g' @property def fct_flg(self): # galgebra 0.6.0 warnings.warn( "lt.fct_flg is deprecated, inspect lt.matrix().free_symbols to " "determine coordinate-dependence", DeprecationWarning, stacklevel=2) if self.Ga.coords is None: return False return set(self.Ga.coords) <= self.matrix().free_symbols def __init__(self, *args, ga, f=False, mode='g'): """ __init__(self, *args, ga, **kwargs) Note this constructor is overloaded, based on the type of the positional argument: .. class:: Lt(lt_dict: Dict[Expr, Expr], /, *, ga) :noindex: Construct from a dictionary mapping source basis blade expressions to multivectors. .. class:: Lt(lt_matrix: Matrix, /, *, ga) :noindex: Construct from the operation of matrix pre-multiplication. .. class:: Lt(spinor: mv.Mv, /, *, ga) :noindex: Construct from a spinor / rotor, which need not square to one. .. class:: Lt(func: Callable[[mv.Mv], mv.Mv], /, *, ga) :noindex: Construct from a function, which is tested for linearity. .. class:: Lt(s: str, /, *, ga, f=False, mode='g') :noindex: Construct an appropriate matrix from a string `s`. Parameters ---------- ga : Ga Geometric algebra which is the domain and codomain of this transform f : bool True if Lt if function of coordinates. Only supported in the string constructor mode : str g:general, s:symmetric, a:antisymmetric transformation. Only supported in the string constructor. """ mat_rep = args[0] self.Ga = ga self.spinor = False self.rho_sq = None self.lt_dict = {} self.mat = None if isinstance(mat_rep, dict): # Dictionary input for key in mat_rep: self.lt_dict[key] = mat_rep[key] elif isinstance(mat_rep, list): # List of lists input if not isinstance(mat_rep[0], list): for lt_i, base in zip(mat_rep, self.Ga.basis): self.lt_dict[base] = lt_i else: # mat_rep = map(list, zip(*mat_rep)) # Transpose list of lists for row, base1 in zip(mat_rep, self.Ga.basis): tmp = 0 for col, base2 in zip(row, self.Ga.basis): tmp += col * base2 self.lt_dict[base1] = tmp elif isinstance(mat_rep, Matrix): # Matrix input self.mat = mat_rep mat_rep = self.mat * self.Ga.g_inv self.lt_dict = Matrix_to_dictionary(mat_rep, self.Ga.basis) elif isinstance(mat_rep, mv.Mv): # Spinor input self.spinor = True self.R = mat_rep self.Rrev = mat_rep.rev() self.rho_sq = self.R * self.Rrev if self.rho_sq.is_scalar(): self.rho_sq = self.rho_sq.scalar() if self.rho_sq == S.One: self.rho_sq = None else: raise ValueError('In Spinor input for Lt, S*S.rev() not a scalar!\n') elif isinstance(mat_rep, str): # String input Amat = Symbolic_Matrix(mat_rep, coords=self.Ga.coords, mode=mode, f=f) self.__init__(Amat, ga=self.Ga) elif callable(mat_rep): # Linear multivector function input # F is a multivector function to be tested for linearity F = mat_rep a = mv.Mv('a', 'vector', ga=self.Ga) b = mv.Mv('b', 'vector', ga=self.Ga) if F(a + b) != F(a) + F(b): raise ValueError('{} is not linear'.format(F)) self.lt_dict = {} for base in self.Ga.basis: out = F(mv.Mv(base, ga=self.Ga)) if not out.is_vector(): raise ValueError('{} must return vectors'.format(F)) self.lt_dict[base] = out.obj else: raise TypeError("Unsupported argument type {}".format(type(mat_rep))) @_cached_property def mv_dict(self) -> Mapping[Expr, Expr]: # dict for linear transformation of multivector if self.spinor: # no lt_dict return None return { blade: reduce( self.Ga.wedge, (self.Ga.basis[i].xreplace(self.lt_dict) for i in index), S.One ) for index, blade in self.Ga.indexes_to_blades_dict.items() }
[docs] def __call__(self, v, obj=False): r""" Returns the image of the multivector :math:`A` under the linear transformation :math:`L`. :math:`{{L}\lp {A} \rp }` is defined by the linearity of :math:`L`, the vector values :math:`{{L}\lp {{{\eb}}_{i}} \rp }`, and the definition :math:`{{L}\lp {{{\eb}}_{i_{1}}{\wedge}\dots{\wedge}{{\eb}}_{i_{r}}} \rp } = {{L}\lp {{{\eb}}_{i_{1}}} \rp }{\wedge}\dots{\wedge}{{L}\lp {{{\eb}}_{i_{r}}} \rp }`. """ if isinstance(v, mv.Mv) and self.Ga != v.Ga: raise ValueError('In Lt call Lt and argument refer to different vector spaces') if self.spinor: if not isinstance(v, mv.Mv): v = mv.Mv(v, ga=self.Ga) if self.rho_sq is None: R_v_Rrev = self.R * v * self.Rrev else: R_v_Rrev = self.rho_sq * self.R * v * self.Rrev if obj: return R_v_Rrev.obj else: return R_v_Rrev if isinstance(v, mv.Mv): if v.is_vector(): lt_v = v.obj.xreplace(self.lt_dict) if obj: return lt_v else: return mv.Mv(lt_v, ga=self.Ga) else: mv_obj = v.obj else: mv_obj = mv.Mv(v, ga=self.Ga).obj lt_v = mv_obj.xreplace(self.mv_dict) if obj: return lt_v else: return mv.Mv(lt_v, ga=self.Ga)
def __add__(self, LT): if self.Ga != LT.Ga: raise ValueError("Attempting addition of Lt's from different geometric algebras") self_add_LT = copy(self.lt_dict) for key in list(LT.lt_dict.keys()): if key in self_add_LT: self_add_LT[key] = metric.collect(self_add_LT[key] + LT.lt_dict[key], self.Ga.basis) else: self_add_LT[key] = LT.lt_dict[key] return Lt(self_add_LT, ga=self.Ga) def __sub__(self, LT): if self.Ga != LT.Ga: raise ValueError("Attempting subtraction of Lt's from different geometric algebras") self_add_LT = copy(self.lt_dict) for key in list(LT.lt_dict.keys()): if key in self_add_LT: self_add_LT[key] = metric.collect(self_add_LT[key] - LT.lt_dict[key], self.Ga.basis) else: self_add_LT[key] = -LT.lt_dict[key] return Lt(self_add_LT, ga=self.Ga) def __mul__(self, LT): if isinstance(LT, Lt): if self.Ga != LT.Ga: raise ValueError("Attempting multiplication of Lt's from different geometric algebras") self_mul_LT = {} for base in LT.lt_dict: self_mul_LT[base] = self(LT(base, obj=True), obj=True) for key in self_mul_LT: self_mul_LT[key] = metric.collect(expand(self_mul_LT[key]), self.Ga.basis) return Lt(self_mul_LT, ga=self.Ga) else: self_mul_LT = {} for key in self.lt_dict: self_mul_LT[key] = LT * self.lt_dict[key] return Lt(self_mul_LT, ga=self.Ga) def __rmul__(self, LT): if not isinstance(LT, Lt): self_mul_LT = {} for key in self.lt_dict: self_mul_LT[key] = LT * self.lt_dict[key] return Lt(self_mul_LT, ga=self.Ga) else: raise TypeError('Cannot have LT as left argument in Lt __rmul__\n')
[docs] def det(self) -> Expr: # det(L) defined by L(I) = det(L)I r""" Returns the determinant (a scalar) of the linear transformation, :math:`L`, defined by :math:`{{\det}\lp {L} \rp }I = {{L}\lp {I} \rp }`. """ lt_I = self(self.Ga.i, obj=True) det_lt_I = lt_I.subs(self.Ga.i.obj, S.One) return det_lt_I
[docs] def tr(self) -> Expr: # tr(L) defined by tr(L) = grad|L(x) r""" Returns the trace (a scalar) of the linear transformation, :math:`L`, defined by :math:`{{\operatorname{tr}}\lp {L} \rp }=\nabla_{a}\cdot{{L}\lp {a} \rp }` where :math:`a` is a vector in the tangent space. """ connect_flg = self.Ga.connect_flg self.Ga.connect_flg = False F_x = mv.Mv(self(self.Ga.coord_vec, obj=True), ga=self.Ga) tr_F = (self.Ga.grad | F_x).scalar() self.Ga.connect_flg = connect_flg return tr_F
[docs] def adj(self) -> 'Lt': r""" Returns the adjoint (a linear transformation) of the linear transformation, :math:`L`, defined by :math:`a\cdot{{L}\lp {b} \rp } = b\cdot{{\bar{L}}\lp {a} \rp }` where :math:`a` and :math:`b` are any two vectors in the tangent space and :math:`\bar{L}` is the adjoint of :math:`L`. """ self_adj = [] for e_j in self.Ga.basis: s = S.Zero for e_i, er_i in zip(self.Ga.basis, self.Ga.r_basis): s += er_i * self.Ga.hestenes_dot(e_j, self(e_i, obj=True)) if self.Ga.is_ortho: self_adj.append(expand(s)) else: self_adj.append(expand(s) / self.Ga.e_sq) return Lt(self_adj, ga=self.Ga)
def inv(self): if self.spinor: Lt_inv = Lt(self.Rrev, ga=self.Ga) Lt_inv.rho_sq = S.One/(self.rho_sq**2) else: raise ValueError('Lt inverse currently implemented only for spinor!\n') return Lt_inv def _sympystr(self, print_obj): if self.spinor: return 'R = ' + print_obj._print(self.R) else: pre = 'Lt(' s = '' for base in self.Ga.basis: if base in self.lt_dict: s += pre + print_obj._print(base) + ') = ' + print_obj._print(mv.Mv(self.lt_dict[base], ga=self.Ga)) + '\n' else: s += pre + print_obj._print(base) + ') = 0\n' return s[:-1] def _latex(self, print_obj): parts = [] for base in self.Ga.basis: if self.spinor: val = self.R * mv.Mv(base, ga=self.Ga) * self.Rrev else: val = mv.Mv(self.lt_dict.get(base, S.Zero), ga=self.Ga) parts.append(print_obj._print(base) + ' &\\mapsto ' + print_obj._print(val)) return '\\left\\{ \\begin{aligned} ' + ' \\\\ '.join(parts) + ' \\end{aligned} \\right\\}' def Fmt(self, fmt=1, title=None) -> printer.GaPrintable: return printer._FmtResult(self, title)
[docs] def matrix(self) -> Matrix: r""" Returns the matrix representation of the linear transformation, :math:`L`, defined by :math:`{{L}\lp {{{\eb}}_{i}} \rp } = L_{ij}{{\eb}}_{j}` where :math:`L_{ij}` is the matrix representation. """ if self.mat is not None: return self.mat else: if self.spinor: self.lt_dict = {} for base in self.Ga.basis: self.lt_dict[base] = self(base).simplify() self.spinor = False mat = self.matrix() self.spinor = True return mat else: """ mat_rep = [] for base in self.Ga.basis: if base in self.lt_dict: row = [] image = (self.lt_dict[base]) if isinstance(image, mv.Mv): image = image.obj coefs, bases = metric.linear_expand(image) for base in self.Ga.basis: try: i = bases.index(base) row.append(coefs[i]) except: row.append(0) mat_rep.append(row) else: mat_rep.append(self.Ga.n * [0]) return Matrix(mat_rep).transpose() """ self.mat = Dictionary_to_Matrix(self.lt_dict, self.Ga) * self.Ga.g return self.mat
[docs] class Mlt(printer.GaPrintable): r""" A multilinear transformation (mlt) is a multilinear multivector function of a list of vectors (``*args``) :math:`F(v_1,...,v_r)` where for any argument slot :math:`j` we have (:math:`a` is a scalar and :math:`u_j` a vector) .. math:: F(v_1,...,a*v_j,...,v_r) &= a*F(v_1,...,v_j,...,v_r) \\ F(v_1,...,v_j+u_j,...,v_r) &= F(v_1,...,v_j,...,v_r) + F(v_1,...,u_j,...,v_r). If F and G are two :class:`Mlt`\ s with the same number of argument slots then the sum is .. math:: (F+G)F(v_1,...,v_r) = F(v_1,...,v_r) + G(v_1,...,v_r). If :math:`F` and :math:`G` are two :class:`Mlt`\ s with :math:`r` and :math:`s` argument slots then their product is .. math:: (F*G)(v_1,...,v_r,...,v_{r+s}) = F(v_1,...,v_r)*G(v_{r+1},...,v_{r+s}), where :math:`*` is any of the multivector multiplicative operations. The derivative of a :class:`Mlt` with is defined as the directional derivative with respect to the coordinate vector (we assume :math:`F` is implicitely a function of the coordinates) .. math:: F(v_1,...,v_r;v_{r+1}) = (v_{r+1} \bullet \nabla)F(v_1,...,v_j,...,v_r). The contraction of a :class:`Mlt` between slots :math:`j` and :math:`k` is defined as the geometric derivative of :math:`F` with respect to slot :math:`k` and the inner geometric derivative with respect to slot :math:`j` (this gives the standard tensor definition of contraction for the case that :math:`F` is a scalar function) .. math:: \operatorname{Contract}(i,j,F) &= \nabla_i \bullet (\nabla_j F(v_1,...,v_i,...,v_j,...,v_r)) \\ &= \nabla_j \bullet (\nabla_i F(v_1,...,v_i,...,v_j,...,v_r)). This returns a :class:`Mlt`\ with slot :math:`i` and :math:`j` removed. """ @staticmethod def subs(Ga, anew): # Generate coefficient substitution list for new Mlt slot # vectors (arguments) where anew is a list of slot vectors # to be substituted for the old slot vectors. # This is used when one wishes to substitute specific vector # values into the Mlt such as the basis/reciprocal basis vectors. sub_lst = [] for i, a in enumerate(anew): acoefs = a.get_coefs(1) sub_lst += list(zip(Ga._mlt_pdiffs[i], acoefs)) return sub_lst @staticmethod def increment_slots(nargs, Ga): # Increment cache of available slots (vector variables) if needed for Mlt class n_a = len(Ga._mlt_a) if n_a < nargs: for i in range(n_a, nargs): # New slot variable with coefficients a_{n_a}__k a = Ga.mv('a_' + str(i + 1), 'vector') # Append new slot variable a_j Ga._mlt_a.append(a) # Append slot variable coefficients a_j__k for purpose # of differentiation coefs = a.get_coefs(1) Ga._mlt_pdiffs.append(coefs) Ga._mlt_acoefs += coefs @staticmethod def extact_basis_indexes(Ga): # galgebra 0.5.0 warnings.warn( "`Mlt.extact_basis_indexes(ga)` is deprecated, use `ga.basis_super_scripts`", DeprecationWarning, stacklevel=2) return Ga.basis_super_scripts def _sympystr(self, print_obj): return print_obj._print(self.fvalue) def _latex(self, print_obj): if self.nargs <= 1: return print_obj._print(self.fvalue) expr_lst = Mlt.expand_expr(self.fvalue, self.Ga) latex_str = '\\begin{aligned} ' first = True lcnt = print_obj._settings['galgebra_mlt_lcnt'] cnt = 1 # Component count on line for term in expr_lst: coef_str = str(term[0]) coef_latex = print_obj._print(term[0]) term_add_flg = isinstance(term[0], Add) if term_add_flg: coef_latex = r'\left ( ' + coef_latex + r'\right ) ' if first: first = False else: if coef_str[0].strip() != '-' or term_add_flg: coef_latex = ' + ' + coef_latex for aij in term[1]: coef_latex += print_obj._print(aij) + ' ' if cnt == 1: latex_str += ' & ' + coef_latex else: latex_str += coef_latex if cnt % lcnt == 0: latex_str += '\\\\ ' cnt = 1 else: cnt += 1 if lcnt == len(expr_lst) or lcnt == 1: latex_str = latex_str[:-3] latex_str = latex_str + ' \\end{aligned} ' return latex_str
[docs] def Fmt(self, lcnt=1, title=None) -> printer.GaPrintable: """ Set format for printing of Tensors Parameters ---------- lcnt : Number of components per line Notes ----- Usage for tensor T example is:: T.fmt('2', 'T') output is:: print 'T = '+str(A) with two components per line. Works for both standard printing and for latex. """ obj = printer._WithSettings(self, dict(galgebra_mlt_lcnt=lcnt)) return printer._FmtResult(obj, title)
@staticmethod def expand_expr(expr, ga): lst_expr = [] expr = expand(expr) for term in expr.args: coef = S.One a_lst = [] for factor in term.args: if factor in ga._mlt_acoefs: a_lst.append(factor) else: coef *= factor a_lst = tuple([x for x in a_lst if x in ga._mlt_acoefs]) b_lst = tuple([ga._mlt_acoefs.index(x) for x in a_lst]) lst_expr.append((coef, a_lst, b_lst)) lst_expr = sorted(lst_expr, key=lambda x: x[2]) new_lst_expr = [] previous = (-1,) first = True a = None for term in lst_expr: if previous == term[2]: coef += term[0] previous = term[2] else: if not first: new_lst_expr.append((coef, a)) else: first = False coef = term[0] previous = term[2] a = term[1] new_lst_expr.append((coef, a)) return new_lst_expr def __init__(self, f, Ga, nargs=None, fct=False): # f is a function, a multivector, a string, or a component expression # self.f is a function or None such as T | a_1 where T and a_1 are vectors # self.fvalue is a component expression such as # T_x*a_1__x+T_y*a_1__y+T_z*a_1__z for a rank 1 tensor in 3 space and all # symbols are sympy real scalar symbols self.Ga = Ga if isinstance(f, mv.Mv): if f.is_vector(): # f is vector T = f | a1 self.nargs = 1 Mlt.increment_slots(1, Ga) self.fvalue = (f | Ga._mlt_a[0]).obj self.f = None else: # To be inplemented for f a general pure grade mulitvector self.nargs = nargs self.fvalue = f self.f = None elif isinstance(f, Lt): # f is linear transformation T = a1 | f(a2) self.nargs = 2 Mlt.increment_slots(2, Ga) self.fvalue = (Ga._mlt_a[0] | f(Ga._mlt_a[1])).obj self.f = None elif isinstance(f, str) and nargs is not None: self.f = None self.nargs = nargs Mlt.increment_slots(nargs, Ga) self.fvalue = S.Zero for t_index, a_prod in zip(itertools.product(self.Ga.basis_super_scripts, repeat=self.nargs), itertools.product(*self.Ga._mlt_pdiffs)): name = '{}_{}'.format(f, ''.join(map(str, t_index))) if fct: # Tensor field coef = Function(name, real=True)(*self.Ga.coords) else: # Constant Tensor coef = symbols(name, real=True) self.fvalue += reduce(lambda x, y: x*y, a_prod, coef) else: if isinstance(f, types.FunctionType): # Tensor defined by general multi-linear function args = inspect.getfullargspec(f)[0] self.nargs = len(args) self.f = f Mlt.increment_slots(self.nargs, Ga) self.fvalue = f(*tuple(Ga._mlt_a[0:self.nargs])) else: # Tensor defined by component expression raise NotImplementedError # self.f = None # self.nargs = len(args) # args isn't defined, which is why we raise NotImplementedError # Mlt.increment_slots(self.nargs, Ga) # self.fvalue = f
[docs] def __call__(self, *args): """ Evaluate the multilinear function for the given vector arguments. Note that a sympy scalar is returned, *not* a multilinear function. """ if len(args) == 0: return self.fvalue if self.f is not None: return self.f(*args) else: sub_lst = [] for x, ai in zip(args, self.Ga._mlt_pdiffs): for r_base, aij in zip(self.Ga.r_basis_mv, ai): sub_lst.append((aij, (r_base | x).scalar())) return self.fvalue.subs(sub_lst, simultaneous=True)
def __add__(self, X): if isinstance(X, Mlt): if self.nargs == X.nargs: return Mlt(self.fvalue + X.fvalue, self.Ga, self.nargs) else: raise ValueError('In Mlt add number of args not the same\n') else: raise TypeError('In Mlt add second argument not an Mkt\n') def __sub__(self, X): if isinstance(X, Mlt): if self.nargs == X.nargs: return Mlt(self.fvalue - X.fvalue, self.Ga, self.nargs) else: raise ValueError('In Mlt sub number of args not the same\n') else: raise TypeError('In Mlt sub second argument not an Mlt\n') def __mul__(self, X): if isinstance(X, Mlt): nargs = self.nargs + X.nargs Mlt.increment_slots(nargs, self.Ga) self_args = self.Ga._mlt_a[:self.nargs] X_args = X.Ga._mlt_a[self.nargs:nargs] value = (self(*self_args) * X(*X_args)).expand() return Mlt(value, self.Ga, nargs) else: return Mlt(X * self.fvalue, self.Ga, self.nargs) def __xor__(self, X): if isinstance(X, Mlt): nargs = self.nargs + X.nargs Mlt.increment_slots(nargs, self.Ga) value = self(*self.Ga._mlt_a[:self.nargs]) ^ X(*X.Ga._mlt_a[self.nargs:nargs]) return Mlt(value, self.Ga, nargs) else: return Mlt(X * self.fvalue, self.Ga, self.nargs) def __or__(self, X): if isinstance(X, Mlt): nargs = self.nargs + X.nargs Mlt.increment_slots(nargs, self.Ga) value = self(*self.Ga._mlt_a[:self.nargs]) | X(*X.Ga._mlt_a[self.nargs:nargs]) return Mlt(value, self.Ga, nargs) else: return Mlt(X * self.fvalue, self.Ga, self.nargs) def dd(self): Mlt.increment_slots(self.nargs + 1, self.Ga) dd_fvalue = (self.Ga._mlt_a[self.nargs] | self.Ga.grad) * self.fvalue return Mlt(dd_fvalue, self.Ga, self.nargs + 1)
[docs] def pdiff(self, slot: int): r""" Returns gradient of tensor, ``T``, with respect to slot vector. For example if the tensor is :math:`{{T}\lp {a_{1},a_{2}} \rp }` then ``T.pdiff(2)`` is :math:`\nabla_{a_{2}}T`. Since ``T`` is a scalar function, ``T.pdiff(2)`` is a vector function. """ # Take geometric derivative of mlt with respect to slot argument self.Ga.dslot = slot - 1 return self.Ga.grad * self.Ga.mv(self.fvalue)
@staticmethod def remove_slot(mv, slot, nargs, ga): if slot == nargs: return mv for islot in range(slot, nargs): mv = mv.subs(list(zip(ga._mlt_pdiffs[islot], ga._mlt_pdiffs[islot - 1]))) return mv
[docs] def contract(self, slot1: int, slot2: int): """ Returns contraction of tensor between ``slot1`` and ``slot2`` where ``slot1`` is the index of the first vector argument and ``slot2`` is the index of the second vector argument of the tensor. For example if we have a rank two tensor, ``T(a1, a2)``, then ``T.contract(1, 2)`` is the contraction of ``T``. For this case since there are only two slots, there can only be one contraction. """ min_slot = min(slot1, slot2) max_slot = max(slot1, slot2) cnargs = self.nargs - 2 self.Ga.dslot = min_slot - 1 grad_self = self.Ga.grad * self.Ga.mv(self.fvalue) grad_self = Mlt.remove_slot(grad_self.obj, min_slot, self.nargs, self.Ga) self.Ga.dslot = max_slot - 2 div_grad_self = self.Ga.grad | self.Ga.mv(grad_self) div_grad_self = Mlt.remove_slot(div_grad_self.obj, max_slot - 1, self.nargs - 1, self.Ga) return Mlt(div_grad_self, self.Ga, cnargs)
[docs] def cderiv(self): """ Returns covariant derivative of tensor field. If ``T`` is a tensor of rank :math:`k` then ``T.cderiv()`` is a tensor of rank :math:`k+1`. The operation performed is defined in section :ref:`MLtrans`. """ Mlt.increment_slots(self.nargs + 1, self.Ga) agrad = self.Ga._mlt_a[self.nargs] | self.Ga.grad CD = Mlt((agrad * self.Ga.mv(self.fvalue)).obj, self.Ga, self.nargs + 1) if CD != 0: CD = CD.fvalue for i in range(self.nargs): args = self.Ga._mlt_a[:self.nargs] tmp = agrad * self.Ga._mlt_a[i] if tmp.obj != 0: args[i] = tmp CD = CD - self(*args) CD = Mlt(CD, self.Ga, self.nargs + 1) return CD
def expand(self): self.fvalue = expand(self.fvalue) return self def comps(self): basis = self.Ga.mv() rank = self.nargs ndim = len(basis) i_indexes = itertools.product(list(range(ndim)), repeat=rank) indexes = itertools.product(basis, repeat=rank) output = '' for i, (e, i_index) in enumerate(zip(indexes, i_indexes)): if i_index[-1] % ndim == 0: print('') output += str(i)+':'+str(i_index)+':'+str(self(*e)) + '\n' return output