# GraviPy - tutorial

## _Coordinates_ and _MetricTensor_

To start working with the gravipy package you must load the package and initialize a pretty-printing mode in Jupyter environment


```python
from gravipy.tensorial import * # import GraviPy package
from sympy import init_printing
import inspect
init_printing()
```

The next step is to choose coordinates and define a metric tensor of a particular space. Let's take, for example, the Schwarzschild metric - vacuum solution to the Einstein's field equations which describes the gravitational field of a spherical mass distribution.


```python
# define some symbolic variables
t, r, theta, phi, M = symbols('t, r, \\theta, \phi, M')
# create a coordinate four-vector object instantiating 
# the Coordinates class
x = Coordinates('\chi', [t, r, theta, phi])
# define a matrix of a metric tensor components
Metric = diag(-(1-2*M/r), 1/(1-2*M/r), r**2, r**2*sin(theta)**2)  
# create a metric tensor object instantiating the MetricTensor class
g = MetricTensor('g', x, Metric)
```

Each component of any tensor object, can be computed by calling the appropriate instance of the _GeneralTensor_ subclass with indices as arguments. The covariant indices take positive integer values (1, 2, ..., dim). The contravariant indices take negative values (-dim, ..., -2, -1).


```python
x(-1)
```


```python
g(1, 1)
```


```python
x(1)
```

Matrix representation of a tensor can be obtained in the following way


```python
x(-All)
```


```python
g(All, All)
```


```python
g(All, 4)
```

## Predefined _Tensor_ Classes

The GraviPy package contains a number of the _Tensor_ subclasses that can be used to calculate a tensor components. The _Tensor_ subclasses available in the current version of GraviPy package are


```python
print([cls.__name__ for cls in vars()['Tensor'].__subclasses__()])
```

    ['Christoffel', 'Ricci', 'Riemann', 'Einstein', 'Geodesic']


### The _Christoffel_ symbols

The first one is the _Christoffel_ class that represents Christoffel symbols of the first and second kind. (Note that the Christoffel symbols are not tensors) Components of the _Christoffel_ objects are computed from the below formula 

$$ \Gamma_{\rho \mu \nu} = g_{\rho \sigma}\Gamma^{\sigma}_{\ \mu \nu} = \frac{1}{2}(g_{\rho \mu, \nu} + g_{\rho \nu, \mu} - g_{\mu \nu, \rho})$$


Let's create an instance of the _Christoffel_ class for the Schwarzschild metric g and compute some components of the object


```python
Ga = Christoffel('Ga', g)
Ga(1, 2, 1)
```

Each component of the _Tensor_ object is computed only once due to memoization procedure implemented in the _Tensor_ class. Computed value of a tensor component is stored in _components_ dictionary (attribute of a _Tensor_ instance) and returned by the next call to the instance.


```python
Ga.components
```

The above dictionary consists of two elements because the symmetry of the Christoffel symbols is implemented in the _Christoffel_ class. 

If necessary, you can clear the _components_ dictionary


```python
Ga.components = {}
Ga.components
```

The _Matrix_ representation of the Christoffel symbols is the following


```python
Ga(All, All, All)
```

You can get help on any of classes mentioned before by running the command


```python
help(Christoffel)
```

    Help on class Christoffel in module gravipy.tensorial:
    
    class Christoffel(Tensor)
     |  Christoffel(symbol, metric, *args, **kwargs)
     |  
     |  Christoffel.
     |  
     |  Represents a class of Christoffel symbols of the first and second kind.
     |  
     |  Parameters
     |  ==========
     |  
     |  symbol : python string - name of the Christoffel symbol
     |  metric : GraviPy MtricTensor object
     |  
     |  Examples
     |  ========
     |  
     |  Define a Christoffel symbols for the Schwarzschild metric:
     |  
     |  >>> from gravipy import *
     |  >>> t, r, theta, phi = symbols('t, r, \\theta, \phi')
     |  >>> chi = Coordinates('\chi', [t, r, theta, phi])
     |  >>> M = Symbol('M')
     |  >>> Metric = diag(-(1 - 2 * M / r), 1 / (1 - 2 * M / r), r ** 2,
     |  ...                  r ** 2 * sin(theta) ** 2)
     |  >>> g = MetricTensor('g', chi, Metric)
     |  >>> Ga = Christoffel('Ga', g)
     |  >>> Ga(-1, 2, 1)
     |  -M/(r*(2*M - r))
     |  >>> Ga(2, All, All)
     |  Matrix([
     |  [M/r**2,               0,  0,                 0],
     |  [     0, -M/(2*M - r)**2,  0,                 0],
     |  [     0,               0, -r,                 0],
     |  [     0,               0,  0, -r*sin(\theta)**2]])
     |  >>> Ga(1, -1, 2) # doctest: +IGNORE_EXCEPTION_DETAIL
     |  Traceback (most recent call last):
     |  GraviPyError: "Tensor component Ga(1, -1, 2) doesn't  exist"
     |  
     |  Method resolution order:
     |      Christoffel
     |      Tensor
     |      GeneralTensor
     |      builtins.object
     |  
     |  Methods defined here:
     |  
     |  __init__(self, symbol, metric, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from Tensor:
     |  
     |  TensorObjects = [<gravipy.tensorial.Christoffel object>]
     |  
     |  ----------------------------------------------------------------------
     |  Methods inherited from GeneralTensor:
     |  
     |  __call__(self, *idxs)
     |      Call self as a function.
     |  
     |  covariantD(self, *idxs)
     |  
     |  partialD(self, *idxs)
     |  
     |  ----------------------------------------------------------------------
     |  Static methods inherited from GeneralTensor:
     |  
     |  get_nmatrixel(M, idxs)
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from GeneralTensor:
     |  
     |  __dict__
     |      dictionary for instance variables (if defined)
     |  
     |  __weakref__
     |      list of weak references to the object (if defined)
     |  
     |  ----------------------------------------------------------------------
     |  Data and other attributes inherited from GeneralTensor:
     |  
     |  GeneralTensorObjects = [<gravipy.tensorial.Coordinates object>, <gravi...
    


Try also "_Christoffel?_" and  "_Christoffel??_"

### The _Ricci_ tensor

$$ R_{\mu \nu} = \frac{\partial \Gamma^{\sigma}_{\ \mu \nu}}{\partial x^{\sigma}} - \frac{\partial \Gamma^{\sigma}_{\ \mu \sigma}}{\partial x^{\nu}} + \Gamma^{\sigma}_{\ \mu \nu}\Gamma^{\rho}_{\ \sigma \rho} - \Gamma^{\rho}_{\ \mu \sigma}\Gamma^{\sigma}_{\ \nu \rho} $$


```python
Ri = Ricci('Ri', g)
Ri(All, All)
```

Contraction of the _Ricci_ tensor $R = R_{\mu}^{\ \mu} = g^{\mu \nu}R_{\mu \nu}$


```python
Ri.scalar()
```

### The _Riemann_ tensor

$$ R_{\mu \nu \rho \sigma} = \frac{\partial \Gamma_{\mu \nu \sigma}}{\partial x^{\rho}} - \frac{\partial \Gamma_{\mu \nu \rho}}{\partial x^{\sigma}} + \Gamma^{\alpha}_{\ \nu \sigma}\Gamma_{\mu \rho \alpha} - \Gamma^{\alpha}_{\ \nu \rho}\Gamma_{\mu \sigma \alpha} - \frac{\partial g_{\mu \alpha}}{\partial x^{\rho}}\Gamma^{\alpha}_{\ \nu \sigma} + \frac{\partial g_{\mu \alpha}}{\partial x^{\sigma}}\Gamma^{\alpha}_{\ \nu \rho} $$


```python
Rm = Riemann('Rm', g)
```

Some nonzero components of the _Riemann_ tensor are


```python
from IPython.display import display, Math
from sympy import latex
for i, j, k, l in list(variations(range(1, 5), 4, True)):
    if Rm(i, j, k, l) != 0 and k<l and i<j:
        display(Math('R_{'+str(i)+str(j)+str(k)+str(l)+'} = '+ latex(Rm(i, j, k, l))))
```


$\displaystyle R_{1212} = - \frac{2 M}{r^{3}}$



$\displaystyle R_{1313} = \frac{M \left(- 2 M + r\right)}{r^{2}}$



$\displaystyle R_{1414} = \frac{M \left(- 2 M + r\right) \sin^{2}{\left(\theta \right)}}{r^{2}}$



$\displaystyle R_{2323} = \frac{M}{2 M - r}$



$\displaystyle R_{2424} = \frac{M \sin^{2}{\left(\theta \right)}}{2 M - r}$



$\displaystyle R_{3434} = 2 M r \sin^{2}{\left(\theta \right)}$


You can also display the matrix representation of the tensor


```python
# Rm(All, All, All, All)
```

Contraction of the _Riemann_ tensor $R_{\mu \nu} = R^{\rho}_{\ \mu \rho \nu} $


```python
ricci = sum([Rm(i, All, k, All)*g(-i, -k)
             for i, k in list(variations(range(1, 5), 2, True))],
            zeros(4))
ricci.simplify()
ricci
```

### The _Einstein_ tensor

$$ G_{\mu \nu} = R_{\mu \nu} - \frac{1}{2}g_{\mu \nu}R $$


```python
G = Einstein('G', Ri)
G(All, All)
```

### _Geodesics_

$$ w_{\mu} = \frac{Du_{\mu}}{d\tau} = \frac{d^2x_{\mu}}{d\tau^2} - \frac{1}{2}g_{\rho \sigma, \mu} \frac{dx^{\rho}}{d\tau}\frac{dx^{\sigma}}{d\tau} $$


```python
tau = Symbol('\\tau')
w = Geodesic('w', g, tau)
w(All).transpose()
```

Please note that instantiation of a _Geodesic_ class for the metric $g$ automatically turns on a _Parametrization_ mode for the metric $g$. Then all coordinates are functions of a world line parameter $\tau$


```python
Parametrization.info()
```


```python
x(-All)
```


```python
g(All, All)
```

_Parametrization_ mode can be deactivated by typing


```python
Parametrization.deactivate(x)
Parametrization.info()
```

    No parametrization activated



```python
x(-All)
```


```python
g(All, All)
```

## Derivatives

### Partial derivative

All instances of a _GeneralTensor_ subclasses inherits _partialD_ method which works exactly the same way as SymPy _diff_ method.


```python
T = Tensor('T', 2, g)
T(1, 2)
```


```python
T.partialD(1, 2, 1, 3) # The first two indices belongs to second rank tensor T
```


```python
T(1, 2).diff(x(-1), x(-3))
```

The only difference is that computed value of _partialD_ is saved in  "_partial_derivative_components_" dictionary an then returned by the next call to the _partialD_ method.


```python
T.partial_derivative_components
```

### Covariant derivative

Covariant derivative components of the tensor ___T___  can be computed by the covariantD method from the formula

$$ \nabla_{\sigma} T_{\mu}^{\ \nu} = T_{\mu \ ;\sigma}^{\ \nu} = \frac{\partial T_{\mu}^{\ \nu}}{\partial x^{\sigma}} - \Gamma^{\rho}_{\ \mu \sigma}T_{\rho}^{\ \nu} + \Gamma^{\nu}_{\ \rho \sigma}T_{\mu}^{\ \rho}$$

Let's compute some covariant derivatives of a scalar field C


```python
C = Tensor('C', 0, g)
C()
```


```python
C.covariantD(1)
```


```python
C.covariantD(2, 3)
```

All _covariantD_ components of every _Tensor_ object are also memoized


```python
for k in C.covariant_derivative_components:
    display(Math(str(k) + ': '
                 + latex(C.covariant_derivative_components[k])))
```


$\displaystyle (1,): \frac{\partial}{\partial t} C{\left(t,r,\theta,\phi \right)}$



$\displaystyle (2,): \frac{\partial}{\partial r} C{\left(t,r,\theta,\phi \right)}$



$\displaystyle (3,): \frac{\partial}{\partial \theta} C{\left(t,r,\theta,\phi \right)}$



$\displaystyle (4,): \frac{\partial}{\partial \phi} C{\left(t,r,\theta,\phi \right)}$



$\displaystyle (2, 3): \frac{r \frac{\partial^{2}}{\partial r\partial \theta} C{\left(t,r,\theta,\phi \right)} - \frac{\partial}{\partial \theta} C{\left(t,r,\theta,\phi \right)}}{r}$



```python
C.covariantD(1, 2, 3)
```

Proof that the covariant derivative of the metric tensor $g$ is zero


```python
not any([g.covariantD(i, j, k).simplify()
         for i, j, k in list(variations(range(1, 5), 3, True))])
```




    True



Bianchi identity in the Schwarzschild spacetime

$$ R_{\mu \nu \sigma \rho ;\gamma} + R_{\mu \nu \gamma \sigma ;\rho} + R_{\mu \nu \rho \gamma ;\sigma} = 0$$


```python
not any([(Rm.covariantD(i, j, k, l, m) + Rm.covariantD(i, j, m, k, l)
          + Rm.covariantD(i, j, l, m, k)).simplify()
         for i, j, k, l, m in list(variations(range(1, 5), 5, True))])
```




    True



## User-defined tensors

To define a new scalar/vector/tensor field in some space you should __extend__ the _Tensor_ class or __create an instance__ of the _Tensor_ class.

### _Tensor_ class instantiation

Let's create a third-rank tensor field living in the Schwarzshild spacetime as an instance of the _Tensor_ class


```python
S = Tensor('S', 3, g)
```

Until you define (override) the _\_compute\_covariant\_component_ method of the __S__ object, all of $4^3$ components are arbitrary functions of coordinates 


```python
S(1, 2, 3)
```


```python
inspect.getsourcelines(T._compute_covariant_component)
```




    (['    def _compute_covariant_component(self, idxs):\n',
      '        if len(idxs) == 0:\n',
      '            component = Function(str(self.symbol))(*self.coords.c)\n',
      '        elif len(idxs) == 1:\n',
      '            component = Function(str(self.symbol) +\n',
      "                                 '(' + str(idxs[0]) + ')')(*self.coords.c)\n",
      '        else:\n',
      '            component = Function(str(self.symbol) + str(idxs))(*self.coords.c)\n',
      '        return component\n'],
     157)



Let's assume that tensor __S__ is the commutator of the covariant derivatives of some arbitrary vector field __V__ and create a new _\_compute\_covariant\_component_ method for the object __S__


```python
V = Tensor('V', 1, g)
V(All)
```


```python
def S_new_method(idxs): # definition
    component = (V.covariantD(idxs[0], idxs[1], idxs[2])
                 - V.covariantD(idxs[0], idxs[2], idxs[1])).simplify()
    S.components.update({idxs: component}) # memoization
    return component
S._compute_covariant_component = S_new_method
# _compute_covariant_component method was overriden
```


```python
S(1, 1, 3)
```

One can check that the well known formula is correct

$$ V_{\mu ;\nu \rho} - V_{\mu ;\rho \nu} = R^{\sigma}_{\ \mu \nu \rho}V_{\sigma} $$


```python
zeros = reduce(Matrix.add, [Rm(-i, All, All, All)*V(i)
                            for i  in range(1, 5)]) - S(All, All, All)
zeros.simplify()
zeros
```

Another way of tensor creation is to make an instance of the _Tensor_ class with components option. Tensor components stored in _Matrix_ object are  writen to the _components_ dictionary of the instance by this method. 


```python
Z = Tensor('Z', 3, g, components=zeros, components_type=(1, 1, 1))
```


```python
not any(Z.components.values())
```




    True



### _Tensor_ class extension

As an example of the _Tensor_ class extension you can get the source code of any of the predefined _Tensor_ subclasses


```python
print([cls.__name__ for cls in vars()['Tensor'].__subclasses__()])
```

    ['Christoffel', 'Ricci', 'Riemann', 'Einstein', 'Geodesic']



```python
inspect.getsourcelines(Christoffel)
```




    (['class Christoffel(Tensor):\n',
      '    r"""Christoffel.\n',
      '\n',
      '    Represents a class of Christoffel symbols of the first and second kind.\n',
      '\n',
      '    Parameters\n',
      '    ==========\n',
      '\n',
      '    symbol : python string - name of the Christoffel symbol\n',
      '    metric : GraviPy MtricTensor object\n',
      '\n',
      '    Examples\n',
      '    ========\n',
      '\n',
      '    Define a Christoffel symbols for the Schwarzschild metric:\n',
      '\n',
      '    >>> from gravipy import *\n',
      "    >>> t, r, theta, phi = symbols('t, r, \\\\theta, \\phi')\n",
      "    >>> chi = Coordinates('\\chi', [t, r, theta, phi])\n",
      "    >>> M = Symbol('M')\n",
      '    >>> Metric = diag(-(1 - 2 * M / r), 1 / (1 - 2 * M / r), r ** 2,\n',
      '    ...                  r ** 2 * sin(theta) ** 2)\n',
      "    >>> g = MetricTensor('g', chi, Metric)\n",
      "    >>> Ga = Christoffel('Ga', g)\n",
      '    >>> Ga(-1, 2, 1)\n',
      '    -M/(r*(2*M - r))\n',
      '    >>> Ga(2, All, All)\n',
      '    Matrix([\n',
      '    [M/r**2,               0,  0,                 0],\n',
      '    [     0, -M/(2*M - r)**2,  0,                 0],\n',
      '    [     0,               0, -r,                 0],\n',
      '    [     0,               0,  0, -r*sin(\\theta)**2]])\n',
      '    >>> Ga(1, -1, 2) # doctest: +IGNORE_EXCEPTION_DETAIL\n',
      '    Traceback (most recent call last):\n',
      '    GraviPyError: "Tensor component Ga(1, -1, 2) doesn\'t  exist"\n',
      '\n',
      '    """\n',
      '\n',
      '    def __init__(self, symbol, metric, *args, **kwargs):\n',
      '        super(Christoffel, self).__init__(\n',
      '            symbol, 3, metric, index_types=(0, 1, 1), *args, **kwargs)\n',
      '        self.is_connection = True\n',
      '        self.conn = self\n',
      '        self.metric.conn = self\n',
      '\n',
      '    def _compute_covariant_component(self, idxs):\n',
      '        component = Rational(1, 2) * (\n',
      '            self.metric(idxs[0], idxs[1]).diff(self.coords(-idxs[2])) +\n',
      '            self.metric(idxs[0], idxs[2]).diff(self.coords(-idxs[1])) -\n',
      '            self.metric(idxs[1], idxs[2]).diff(self.coords(-idxs[0]))) \\\n',
      '            .together().simplify()\n',
      '        self.components.update({idxs: component})\n',
      '        if self.apply_tensor_symmetry:\n',
      '            self.components.update({(idxs[0], idxs[2], idxs[1]): component})\n',
      '        return component\n'],
     515)



c5c3f01f97741eff9bd626dd6e36dd982af3e783
ipynb
Jupyter Notebook
docs/GraviPy-tutorial.ipynb
a3ahmad/GraviPy-engine
4809438f8d505731c47932f5db578ca8f315132f
BSD-3-Clause
2019-11-21T17:09:22.000Z
2022-03-22T22:12:52.000Z
docs/GraviPy-tutorial.ipynb
a3ahmad/GraviPy-engine
4809438f8d505731c47932f5db578ca8f315132f
BSD-3-Clause
docs/GraviPy-tutorial.ipynb
a3ahmad/GraviPy-engine
4809438f8d505731c47932f5db578ca8f315132f
BSD-3-Clause
2019-09-26T07:20:09.000Z
2021-07-16T03:50:48.000Z
Qwen/Qwen-72B
1. YES
2. YES