# The basics of `generalrelativity` 

`generalrelativity.py` contains some algorithms related to the basics of multilinear algebra and differential geometry, algorithms which relate to some of the calculations that appear in the everyday work of a physicist or a mathematician who works in general relativity. These algorithms are built upon sympy.

In this first tutorial, we deal with multilinear algebra and the manipulation of the `Tensor` object. In the next one we learn about index manipulation, and in the third one we use these algorithms to implement the Schwarzschild metric.


```python
import generalrelativity as gr
import sympy
sympy.init_printing(use_latex=True)
```


```python
from pprint import pprint
```

# Multiindices

The main object of the library (i.e. the `Tensor` object) relies heavily on multiindices and their manipulation. For us, a $p$-multiindex is just a tuple $(a_1, \dots, a_p)$ in which every $a_i$ is between $0$ and $n-1$ for some $n$ (the dimension of a vector space in our particular case).

This library includes a function for getting all multiindices of certain length $p$ and certain dimension $n$:


```python
n = 4
p = 2
list_of_multiindices = gr.get_all_multiindices(p, n)
list_of_multiindices
```

## Special types of multiindices

The only $0$-multiindex is the python object `None`, and $1$-multiindices must still be tuples, so one must write them like `(a_1, )` in python.


```python
n = 4
p = 1
list_of_multiindices = gr.get_all_multiindices(p, n)
print(list_of_multiindices)
```

    [(0,), (1,), (2,), (3,)]


# The `Tensor` object 

`generalrelativity` implements the tensor object in a very algebraic and generic sense. Sadly, we have the bound of dealing always in coordinates (that is, always talking about the coefficients with respect to some basis). To create a tensor $\Gamma \in T^{(p,q)}(V)$ we must specify a **basis**, a **type** and a **dict_of_values**:
- The **basis** is a list of sympy symbols, which represent a basis for the generic vector space $V$.
- The **type** is a tuple $(p, q)$ of nonnegative integers.
- The **dict_of_vales** has for keys tuples of multiindices $(a, b)$ and values the actual value $\Gamma^a_b$.

For example, say we want to create the tensor

$$T = 3e_0\otimes e_0\otimes e_0^*\otimes e_1^* - 2 e_0\otimes e_1\otimes e_1^*\otimes e_3^* \in T^{(2,2)}(V)$$

we do so like this:


```python
e0, e1, e2, e3 = sympy.symbols('e_0 e_1 e_2 e_3')
basis = [e0, e1, e2, e3]
dict_of_values = {
    ((0,0), (0,1)): 3, # The first sumand
    ((0,1), (1,3)): -2 # The second sumand
}
T = gr.Tensor(basis, (2, 2), dict_of_values)
T
```




$(3)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-2)e_0 \otimes e_1 \otimes e_1^* \otimes e_3^*$



The tensors object have a LaTeX printing function (but this depends obviously on the availability of IPython, LaTeX and on the running of `sympy.init_printing(use_latex=True)`). We could print it raw:


```python
print(T)
```

    (3)e_0 \otimes e_0 \otimes e_0* \otimes e_1* + (-2)e_0 \otimes e_1 \otimes e_1* \otimes e_3*


## More details about the `dict_of_values` 

Say we want to construct a $(0,2)$ Tensor. Initially, the `dict_of_values` should be of the form


```python
dict_of_values_1 = {
    (None, (0,0)): -1,
    (None, (1,1)): 1,
    (None, (2,2)): 1,
    (None, (3,3)): 1,
}
```

That is, one should specify the $0$-multiindex `None`. In the long run, this becomes tedious, and it is because of this that the `Tensor` object can also accept, for this case, the following `dict_of_values`:


```python
dict_of_values_2 = {
    (0,0): -1,
    (1,1): 1,
    (2,2): 1,
    (3,3): 1,
}
```

and the two tensors generated would be equal (once we specify equal bases and equal types)


```python
T1 = gr.Tensor(basis, (0, 2), dict_of_values_1)
T2 = gr.Tensor(basis, (0, 2), dict_of_values_2)
print(T1 == T2)
```

    True


The same happens for $1$-multiindices: a priori a $1$-multiindex is of the form `(a_1, )`, so if we wanted to create for example a $(1,2)$ tensor one should create a `dict_of_values` of the form


```python
dict_of_values_1 = {
    ((0, ), (1, 1)): 3,
    ((1, ), (0, 1)): -1,
    ((0, ), (2, 3)): 2
}
```

We relax the conditions to `dict_of_values` like


```python
dict_of_values_2 = {
    (0, (1, 1)): 3,
    (1, (0, 1)): -1,
    (0, (2, 3)): 2
}
```


```python
T1 = gr.Tensor(basis, (1, 2), dict_of_values_1)
T2 = gr.Tensor(basis, (1, 2), dict_of_values_2)
print(T1 == T2)
```

    True


# Operations with `Tensor` objects 

One can **add** tensor objects if they have the same basis and type, one can **multiply** a tensor object with `int`s, `float`s, $(0,0)$-tensors and even `sympy` objects and symbols. Moreover, one can contract indices and raise and lower indices according to a special $(0,2)$-tensor, but we will explain this on the second tutorial.


```python
dict_of_values_S = {
    ((0,0), (0,1)): 4,
    ((1,1), (2,1)): -1,
}
S = gr.Tensor(basis, (2, 2), dict_of_values_S)
```


```python
T
```




$(3)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-2)e_0 \otimes e_1 \otimes e_1^* \otimes e_3^*$




```python
S
```




$(4)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-1)e_1 \otimes e_1 \otimes e_2^* \otimes e_1^*$




```python
T + S
```




$(7)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-1)e_1 \otimes e_1 \otimes e_2^* \otimes e_1^* + (-2)e_0 \otimes e_1 \otimes e_1^* \otimes e_3^*$




```python
3*T
```




$(9)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-6)e_0 \otimes e_1 \otimes e_1^* \otimes e_3^*$




```python
sympy.pi*T
```




$(3*pi)e_0 \otimes e_0 \otimes e_0^* \otimes e_1^* + (-2*pi)e_0 \otimes e_1 \otimes e_1^* \otimes e_3^*$



## Substitution of `sympy.symbols` in tensors 

Nothing impedes us of using `sympy.symbols` in our tensors. For example:


```python
t, r, theta, phi = sympy.symbols('t r \\theta \\phi')
dict_of_values = {
    (1, (0, 0)): t**2,
    (2, (1, 0)): r*t,
    (0, (1, 3)): sympy.sin(theta + phi)
}
basis = [t, r, theta, phi]
T = gr.Tensor(basis, (1, 2), dict_of_values)
T
```




$(t^2)r \otimes t^* \otimes t^* + (r*t)\theta \otimes r^* \otimes t^* + (sin(\phi + \theta))t \otimes r^* \otimes \phi^*$



we can substitute values with the usual `subs` function, which takes a list of tuples of the substitutions to be performed:


```python
T.subs([(t, 5), (r, 2.5), (theta, (1/2)*sympy.pi)])
```




$(25)r \otimes t^* \otimes t^* + (12.5000000000000)\theta \otimes r^* \otimes t^* + (sin(\phi + 0.5*pi))t \otimes r^* \otimes \phi^*$



This creates a new object, $T$ remains unmodified.


```python
T
```




$(t^2)r \otimes t^* \otimes t^* + (r*t)\theta \otimes r^* \otimes t^* + (sin(\phi + \theta))t \otimes r^* \otimes \phi^*$



2781b0ea74d5aa71b2e91f8a1ee00212478c8482
ipynb
Jupyter Notebook
.ipynb_checkpoints/Tutorial 1 - The basics-checkpoint.ipynb
miguelgondu/generalrepytivity
81d6f118d8a5ca53a747a7e9bbf75318343025a9
MIT
2017-11-11T02:28:18.000Z
2018-05-08T22:40:09.000Z
.ipynb_checkpoints/Tutorial 1 - The basics-checkpoint.ipynb
miguelgondu/generalrepytivity
81d6f118d8a5ca53a747a7e9bbf75318343025a9
MIT
2018-04-12T14:05:44.000Z
2021-11-15T17:47:07.000Z
.ipynb_checkpoints/Tutorial 1 - The basics-checkpoint.ipynb
miguelgondu/generalrepytivity
81d6f118d8a5ca53a747a7e9bbf75318343025a9
MIT
2018-04-10T18:47:58.000Z
2020-01-19T05:57:50.000Z
Qwen/Qwen-72B
1. YES
2. YES