Source code for einsteinpy.symbolic.tensor

import numpy as np
import sympy
from sympy import simplify, tensorcontraction, tensorproduct
from sympy.core.expr import Expr
from sympy.core.function import AppliedUndef, UndefinedFunction

from einsteinpy.symbolic.helpers import (
    _change_name,
    simplify_sympy_array,
    sympy_to_np_array,
)


def _config_checker(config):
    # check if the string for config contains 'u' and 'l' only
    if not isinstance(config, str):
        return False
    for ch in config:
        if (not ch == "l") and (not ch == "u"):
            return False
    return True


def _difference_list(newconfig, oldconfig):
    # defines a list of actions to be taken on a tensor
    difflist = list()
    for n_ch, o_ch in zip(newconfig, oldconfig):
        if n_ch == o_ch:
            difflist.append(0)
        elif n_ch == "u":
            difflist.append(1)
        else:
            difflist.append(-1)
    return difflist


def _change_config(tensor, metric, newconfig):
    # check length and validity of new configuration
    if not (len(newconfig) == len(tensor.config) and _config_checker(newconfig)):
        raise ValueError

    # seperate the contravariant & covariant metric tensors
    met_dict = {
        -1: metric.lower_config().tensor(),
        1: metric.lower_config().inv().tensor(),
    }

    # main code
    def chain_config_change():
        t = sympy.Array(tensor.tensor())
        difflist = _difference_list(newconfig, tensor.config)
        for i, action in enumerate(difflist):
            if action == 0:
                continue
            else:
                t = simplify(
                    tensorcontraction(tensorproduct(met_dict[action], t), (1, 2 + i))
                )
                # reshuffle the indices
                dest = list(range(len(t.shape)))
                dest.remove(0)
                dest.insert(i, 0)
                t = sympy.permutedims(t, dest)
        return t

    return chain_config_change()


[docs] def tensor_product(tensor1, tensor2, i=None, j=None): """Tensor Product of ``tensor1`` and ``tensor2`` Parameters ---------- tensor1 : ~einsteinpy.symbolic.BaseRelativityTensor tensor2 : ~einsteinpy.symbolic.BaseRelativityTensor i : int, optional contract ``i``th index of ``tensor1`` j : int, optional contract ``j``th index of ``tensor2`` Returns ------- ~einsteinpy.symbolic.BaseRelativityTensor tensor of appropriate rank Raises ------ ValueError Raised when ``i`` and ``j`` both indicate 'u' or 'l' indices """ product = tensorproduct(tensor1.arr, tensor2.arr) if (i or j) is None: newconfig = tensor1.config + tensor2.config else: if tensor1.config[i] == tensor2.config[j]: raise ValueError( "Index summation not allowed between %s and %s indices" % (tensor1.config[i], tensor2.config[j]) ) product = simplify(tensorcontraction(product, (i, len(tensor1.config) + j))) con = tensor1.config[:i] + tensor1.config[i + 1 :] fig = tensor2.config[:j] + tensor2.config[j + 1 :] newconfig = con + fig return BaseRelativityTensor( product, syms=tensor1.syms, config=newconfig, parent_metric=tensor1.parent_metric, variables=tensor1.variables, functions=tensor1.functions, )
[docs] class Tensor: """ Base Class for Tensor manipulation """ def __init__(self, arr, config="ll", name=None): """ Constructor and Initializer Parameters ---------- arr : ~sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray or list Sympy Array, multi-dimensional list containing Sympy Expressions, or Sympy Expressions or int or float scalar config : str Configuration of contravariant and covariant indices in tensor. 'u' for upper and 'l' for lower indices. Defaults to 'll'. name : str or None Name of the tensor. Raises ------ TypeError Raised when arr is not a list or sympy array TypeError Raised when config is not of type str or contains characters other than 'l' or 'u' ValueError Raised when ``config`` implies order of Tensor different than that indicated by shape of ``arr`` """ if isinstance(arr, (list, tuple, np.ndarray, int, float, np.number, Expr)): self.arr = sympy.Array(arr) elif isinstance(arr, sympy.Array): self.arr = arr else: raise TypeError("Only multi-dimensional list or Sympy Array is expected") if _config_checker(config): self._config = config self._order = len(config) else: raise TypeError( "config is either not of type 'str' or does contain characters other than 'l' or 'u'" ) if len(self.arr.shape) != len(config): raise ValueError( "invalid shape of array for tensor of order implied by config: '{}'".format( config ) ) self.name = name @property def order(self): """ Returns the order of the Tensor """ return self._order @property def config(self): """ Returns the configuration of covariant and contravariant indices """ return self._config def __getitem__(self, index): return self.arr[index] def __str__(self): """ Returns a String with a readable representation of the object of class Tensor """ representation = "Tensor" if self.name is not None: representation = " ".join((representation, self.name)) representation += "\n" representation += self.arr.__str__() return representation def __repr__(self): """ Returns a String with a representation of the state of the object of class Tensor """ interpretable_representation = self.__class__.__name__ interpretable_representation += self.arr.__repr__() return interpretable_representation
[docs] def tensor(self): """ Returns the sympy Array Returns ------- ~sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray Sympy Array object """ return self.arr
[docs] def subs(self, *args): """ Substitute the variables/expressions in a Tensor with other sympy variables/expressions. Parameters ---------- args : one argument or two argument - two arguments, e.g foo.subs(old, new) - one iterable argument, e.g foo.subs([(old1, new1), (old2, new2)]) for multiple substitutions at once. Returns ------- ~einsteinpy.symbolic.tensor.Tensor: Tensor with substituted values """ return Tensor(self.tensor().subs(*args))
[docs] def simplify(self, set_self=True): """ Returns a simplified Tensor Parameters ---------- set_self : bool Replaces the tensor contained the class with its simplified version, if ``True``. Defaults to ``True``. Returns ------- ~einsteinpy.symbolic.tensor.Tensor Simplified Tensor """ if set_self: self.arr = simplify_sympy_array(self.tensor()) return self.tensor() # return sympy.simplify(self.tensor()) # this used to work with older sympy versions return simplify_sympy_array(self.tensor())
[docs] class BaseRelativityTensor(Tensor): """ Generic class for defining tensors in General Relativity. This would act as a base class for other Tensorial quantities in GR. Attributes ---------- arr : ~sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray Raw Tensor in sympy array syms : list or tuple List of symbols denoting space and time axis dims : int dimension of the space-time. variables : list free variables in the tensor expression other than the variables describing space-time axis. functions : list Undefined functions in the tensor expression. name : str or None Name of the tensor. Defaults to "GenericTensor". """ def __init__( self, arr, syms, config="ll", parent_metric=None, variables=list(), functions=list(), name="GenericTensor", ): """ Constructor and Initializer Parameters ---------- arr : ~sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray or list Sympy Array or multi-dimensional list containing Sympy Expressions syms : tuple or list List of crucial symbols dentoting time-axis and/or spacial axis. For example, in case of 4D space-time, the arrangement would look like [t, x1, x2, x3]. config : str Configuration of contravariant and covariant indices in tensor. 'u' for upper and 'l' for lower indices. Defaults to 'll'. parent_metric : ~einsteinpy.symbolic.metric.MetricTensor or None Metric Tensor for some particular space-time which is associated with this Tensor. variables : tuple or list or set List of symbols used in expressing the tensor, other than symbols associated with denoting the space-time axis. Calculates in real-time if left blank. functions : tuple or list or set List of symbolic functions used in epressing the tensor. Calculates in real-time if left blank. name : str or None Name of the Tensor. Defaults to "GenericTensor". Raises ------ TypeError Raised when arr is not a list, sympy array or numpy array. TypeError Raised when config is not of type str or contains characters other than 'l' or 'u' TypeError Raised when arguments syms, variables, functions have data type other than list, tuple or set. TypeError Raised when argument parent_metric does not belong to MetricTensor class and isn't None. ValueError Raised when argument ``syms`` does not agree with shape of argument ``arr`` """ super(BaseRelativityTensor, self).__init__(arr=arr, config=config, name=name) if len(self.arr.shape) != 0 and self.arr.shape[0] != len(syms): raise ValueError("invalid shape of argument arr for syms: {}".format(syms)) # Cannot implement the check that parent metric belongs to the class MetricTensor # Due to the issue of cyclic imports, would find a workaround self._parent_metric = parent_metric if isinstance(syms, (list, tuple)): self.syms = syms self.dims = len(self.syms) else: raise TypeError("syms should be a list or tuple") if isinstance(variables, (list, tuple, set)) and isinstance( functions, (list, tuple, set) ): # compute free variables and functions if list if empty if not variables: self.variables = [ v for v in self.arr.free_symbols if v not in self.syms ] self.variables.sort(key=(lambda var: var.name)) else: self.variables = list(variables) if not functions: self.functions = [ f for f in self.arr.atoms(AppliedUndef).union( self.arr.atoms(UndefinedFunction) ) ] else: self.functions = list(functions) else: raise TypeError( "arguments variables and functions should be a list, tuple or set" ) @property def parent_metric(self): """ Returns the Metric from which Tensor was derived/associated, if available. """ return self._parent_metric
[docs] def symbols(self): """ Returns the symbols used for defining the time & spacial axis Returns ------- tuple tuple containing (t,x1,x2,x3) in case of 4D space-time """ return self.syms
[docs] def tensor_lambdify(self, *args): """ Returns lambdified function of symbolic tensors. This means that the returned functions can accept numerical values and return numerical quantities. Parameters ---------- *args The variable number of arguments accept sympy symbols. The returned function accepts arguments in same order as initially defined in ``*args``. Uses sympy symbols from class attributes ``syms`` and ``variables`` (in the same order) if no ``*args`` is passed Leaving ``*args`` empty is recommended. Returns ------- tuple arguments to be passed in the returned function in exact order. function Lambdified function which accepts and returns numerical quantities. """ if len(args) == 0: numeric_arr = sympy.lambdify( [*self.syms, *self.variables], self.arr, modules="numpy" ) arg_list = (*self.syms, *self.variables) else: numeric_arr = sympy.lambdify(args, self.arr, modules="numpy") arg_list = tuple(args) return arg_list, numeric_arr
[docs] def lorentz_transform(self, transformation_matrix): """ Performs a Lorentz transform on the tensor. Parameters ---------- transformation_matrix : ~sympy.tensor.array.dense_ndim_array.ImmutableDenseNDimArray or list Sympy Array or multi-dimensional list containing Sympy Expressions Returns ------- ~einsteinpy.symbolic.tensor.BaseRelativityTensor lorentz transformed tensor(or vector) """ tm = sympy.Array(transformation_matrix) t = self.tensor() for i in range(self.order): if self.config[i] == "u": t = simplify(tensorcontraction(tensorproduct(tm, t), (1, 2 + i))) else: t = simplify(tensorcontraction(tensorproduct(tm, t), (0, 2 + i))) dest = list(range(len(t.shape))) dest.remove(0) dest.insert(i, 0) t = sympy.permutedims(t, dest) return BaseRelativityTensor( t, syms=self.syms, config=self.config, parent_metric=None, variables=self.variables, functions=self.functions, name=_change_name(self.name, context="__lt"), )