einsteinengine 0.5.0__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- einsteinengine-1.0.0/LICENSE +21 -0
- einsteinengine-1.0.0/PKG-INFO +133 -0
- einsteinengine-1.0.0/README.md +112 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/christoffel.py +38 -38
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/connection.py +111 -111
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/core.py +137 -135
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/einstein_tensor.py +57 -57
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/energy_momentum.py +44 -44
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/geodesics.py +95 -95
- einsteinengine-1.0.0/einsteinengine/symbolic/metric.py +102 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/ricci_scalar.py +33 -33
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/ricci_tensor.py +20 -20
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/riemann.py +116 -116
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/spin_connection.py +42 -42
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/tensor.py +504 -439
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/tetrad.py +50 -50
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/weyl.py +82 -82
- einsteinengine-1.0.0/einsteinengine.egg-info/PKG-INFO +133 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine.egg-info/SOURCES.txt +2 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/pyproject.toml +29 -29
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/setup.cfg +4 -4
- einsteinengine-1.0.0/tests/test_extended_symbolic_features.py +159 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/tests/test_schwarchild.py +92 -56
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/tests/test_weyl.py +37 -37
- einsteinengine-0.5.0/PKG-INFO +0 -63
- einsteinengine-0.5.0/README.md +0 -44
- einsteinengine-0.5.0/einsteinengine/symbolic/metric.py +0 -60
- einsteinengine-0.5.0/einsteinengine.egg-info/PKG-INFO +0 -63
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/__init__.py +0 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine/symbolic/__init__.py +0 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine.egg-info/dependency_links.txt +0 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine.egg-info/requires.txt +0 -0
- {einsteinengine-0.5.0 → einsteinengine-1.0.0}/einsteinengine.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rafael Salazar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: einsteinengine
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A high-performance, object-oriented symbolic tensor engine for General Relativity.
|
|
5
|
+
Author-email: Rafael Salazar <rsalazard2005@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/RSalazarD/EinsteinEngine
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/RSalazarD/EinsteinEngine/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: sympy>=1.12
|
|
17
|
+
Requires-Dist: symengine>=0.11.0
|
|
18
|
+
Requires-Dist: numpy>=1.24.0
|
|
19
|
+
Requires-Dist: scipy>=1.10.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# EinsteinEngine
|
|
23
|
+
|
|
24
|
+
EinsteinEngine is a symbolic tensor library for General Relativity built around a simple idea: make tensor calculations faster and more structured without giving up exact symbolic mathematics.
|
|
25
|
+
|
|
26
|
+
The project combines Python with SymEngine for the heavy symbolic work, and exposes an object-oriented API for common relativistic objects such as metrics, Christoffel symbols, curvature tensors, Ricci tensors, Einstein tensors, geodesics, tetrads and spin connections.
|
|
27
|
+
|
|
28
|
+
## What the library can do today
|
|
29
|
+
|
|
30
|
+
EinsteinEngine currently provides a practical toolkit for symbolic GR workflows, including:
|
|
31
|
+
|
|
32
|
+
- Metric tensor construction and validation
|
|
33
|
+
- Metric inversion for diagonal and standard symbolic cases
|
|
34
|
+
- Christoffel symbol computation
|
|
35
|
+
- Riemann tensor construction
|
|
36
|
+
- Ricci tensor and Ricci scalar computation
|
|
37
|
+
- Einstein tensor construction
|
|
38
|
+
- Geodesic equation generation
|
|
39
|
+
- Tetrad and spin connection support
|
|
40
|
+
- Tensor component access, contraction, index raising/lowering and basic tensor arithmetic
|
|
41
|
+
- Rich LaTeX-style rendering for notebooks and a plain-text fallback for lighter output
|
|
42
|
+
|
|
43
|
+
The focus of the library is not only correctness, but also performance in symbolic pipelines that would otherwise become very expensive in pure SymPy.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
From PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install einsteinengine
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
From source:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/RSalazarD/EinsteinEngine.git
|
|
57
|
+
cd EinsteinEngine
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import sympy as sp
|
|
65
|
+
from einsteinengine.symbolic.metric import MetricTensor
|
|
66
|
+
from einsteinengine.symbolic.riemann import RiemannCurvatureTensor
|
|
67
|
+
|
|
68
|
+
# Coordinates and parameters
|
|
69
|
+
t, r, theta, phi = sp.symbols('t r theta phi', real=True)
|
|
70
|
+
M = sp.symbols('M', real=True)
|
|
71
|
+
|
|
72
|
+
# Schwarzschild metric
|
|
73
|
+
g_schwarzschild = [
|
|
74
|
+
[-(1 - 2*M/r), 0, 0, 0],
|
|
75
|
+
[0, 1/(1 - 2*M/r), 0, 0],
|
|
76
|
+
[0, 0, r**2, 0],
|
|
77
|
+
[0, 0, 0, r**2 * sp.sin(theta)**2],
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
metric = MetricTensor(g_schwarzschild, [t, r, theta, phi], name="Schwarzschild")
|
|
81
|
+
riemann = RiemannCurvatureTensor.from_metric(metric, verbose=False)
|
|
82
|
+
|
|
83
|
+
print(riemann.get_component(1, 0, 1, 0))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A typical output for the Schwarzschild case is the known exact component:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
2*M*(2*M - r)/r**4
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Current strengths
|
|
93
|
+
|
|
94
|
+
EinsteinEngine is particularly useful when you want to:
|
|
95
|
+
|
|
96
|
+
- build symbolic curvature tensors from a metric,
|
|
97
|
+
- test GR expressions quickly in notebooks or scripts,
|
|
98
|
+
- compare symbolic performance against pure SymPy,
|
|
99
|
+
- explore exact tensor structures without writing all the algebra by hand.
|
|
100
|
+
|
|
101
|
+
## Benchmarks
|
|
102
|
+
|
|
103
|
+
A simple benchmark script is available in the benchmarks folder to compare EinsteinEngine against pure SymPy on a representative symbolic workload.
|
|
104
|
+
|
|
105
|
+
Run it from the project root with:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python benchmarks/compare_sympy_vs_einsteinengine.py
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The script prints timing information for both engines and a rough speedup estimate.
|
|
112
|
+
|
|
113
|
+
## Testing
|
|
114
|
+
|
|
115
|
+
The project includes a pytest suite covering core computations such as metric handling, curvature tensors, geodesics and rendering behaviour.
|
|
116
|
+
|
|
117
|
+
Run tests with:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pytest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Roadmap and future directions
|
|
124
|
+
|
|
125
|
+
This project is already usable for a broad set of symbolic GR tasks, but there is still room to grow. Possible future work includes:
|
|
126
|
+
|
|
127
|
+
- more optimized tensor contractions and higher-rank workflows,
|
|
128
|
+
- broader support for non-diagonal and more general metrics,
|
|
129
|
+
- improved simplification strategies with configurable modes,
|
|
130
|
+
- additional GR utilities such as more advanced invariants and field equations,
|
|
131
|
+
- better documentation and more benchmark examples.
|
|
132
|
+
|
|
133
|
+
The goal is to keep the library practical today while expanding toward a more complete symbolic GR toolkit over time.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# EinsteinEngine
|
|
2
|
+
|
|
3
|
+
EinsteinEngine is a symbolic tensor library for General Relativity built around a simple idea: make tensor calculations faster and more structured without giving up exact symbolic mathematics.
|
|
4
|
+
|
|
5
|
+
The project combines Python with SymEngine for the heavy symbolic work, and exposes an object-oriented API for common relativistic objects such as metrics, Christoffel symbols, curvature tensors, Ricci tensors, Einstein tensors, geodesics, tetrads and spin connections.
|
|
6
|
+
|
|
7
|
+
## What the library can do today
|
|
8
|
+
|
|
9
|
+
EinsteinEngine currently provides a practical toolkit for symbolic GR workflows, including:
|
|
10
|
+
|
|
11
|
+
- Metric tensor construction and validation
|
|
12
|
+
- Metric inversion for diagonal and standard symbolic cases
|
|
13
|
+
- Christoffel symbol computation
|
|
14
|
+
- Riemann tensor construction
|
|
15
|
+
- Ricci tensor and Ricci scalar computation
|
|
16
|
+
- Einstein tensor construction
|
|
17
|
+
- Geodesic equation generation
|
|
18
|
+
- Tetrad and spin connection support
|
|
19
|
+
- Tensor component access, contraction, index raising/lowering and basic tensor arithmetic
|
|
20
|
+
- Rich LaTeX-style rendering for notebooks and a plain-text fallback for lighter output
|
|
21
|
+
|
|
22
|
+
The focus of the library is not only correctness, but also performance in symbolic pipelines that would otherwise become very expensive in pure SymPy.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
From PyPI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install einsteinengine
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
From source:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/RSalazarD/EinsteinEngine.git
|
|
36
|
+
cd EinsteinEngine
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import sympy as sp
|
|
44
|
+
from einsteinengine.symbolic.metric import MetricTensor
|
|
45
|
+
from einsteinengine.symbolic.riemann import RiemannCurvatureTensor
|
|
46
|
+
|
|
47
|
+
# Coordinates and parameters
|
|
48
|
+
t, r, theta, phi = sp.symbols('t r theta phi', real=True)
|
|
49
|
+
M = sp.symbols('M', real=True)
|
|
50
|
+
|
|
51
|
+
# Schwarzschild metric
|
|
52
|
+
g_schwarzschild = [
|
|
53
|
+
[-(1 - 2*M/r), 0, 0, 0],
|
|
54
|
+
[0, 1/(1 - 2*M/r), 0, 0],
|
|
55
|
+
[0, 0, r**2, 0],
|
|
56
|
+
[0, 0, 0, r**2 * sp.sin(theta)**2],
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
metric = MetricTensor(g_schwarzschild, [t, r, theta, phi], name="Schwarzschild")
|
|
60
|
+
riemann = RiemannCurvatureTensor.from_metric(metric, verbose=False)
|
|
61
|
+
|
|
62
|
+
print(riemann.get_component(1, 0, 1, 0))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A typical output for the Schwarzschild case is the known exact component:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
2*M*(2*M - r)/r**4
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Current strengths
|
|
72
|
+
|
|
73
|
+
EinsteinEngine is particularly useful when you want to:
|
|
74
|
+
|
|
75
|
+
- build symbolic curvature tensors from a metric,
|
|
76
|
+
- test GR expressions quickly in notebooks or scripts,
|
|
77
|
+
- compare symbolic performance against pure SymPy,
|
|
78
|
+
- explore exact tensor structures without writing all the algebra by hand.
|
|
79
|
+
|
|
80
|
+
## Benchmarks
|
|
81
|
+
|
|
82
|
+
A simple benchmark script is available in the benchmarks folder to compare EinsteinEngine against pure SymPy on a representative symbolic workload.
|
|
83
|
+
|
|
84
|
+
Run it from the project root with:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python benchmarks/compare_sympy_vs_einsteinengine.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The script prints timing information for both engines and a rough speedup estimate.
|
|
91
|
+
|
|
92
|
+
## Testing
|
|
93
|
+
|
|
94
|
+
The project includes a pytest suite covering core computations such as metric handling, curvature tensors, geodesics and rendering behaviour.
|
|
95
|
+
|
|
96
|
+
Run tests with:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pytest
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Roadmap and future directions
|
|
103
|
+
|
|
104
|
+
This project is already usable for a broad set of symbolic GR tasks, but there is still room to grow. Possible future work includes:
|
|
105
|
+
|
|
106
|
+
- more optimized tensor contractions and higher-rank workflows,
|
|
107
|
+
- broader support for non-diagonal and more general metrics,
|
|
108
|
+
- improved simplification strategies with configurable modes,
|
|
109
|
+
- additional GR utilities such as more advanced invariants and field equations,
|
|
110
|
+
- better documentation and more benchmark examples.
|
|
111
|
+
|
|
112
|
+
The goal is to keep the library practical today while expanding toward a more complete symbolic GR toolkit over time.
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import symengine as se
|
|
2
|
-
from einsteinengine.symbolic.connection import BaseConnection
|
|
3
|
-
class ChristoffelSymbols(BaseConnection):
|
|
4
|
-
"""
|
|
5
|
-
Computes the Christoffel Symbols of the second kind (Gamma^lambda_{mu nu})
|
|
6
|
-
optimized via SymEngine backend processing.
|
|
7
|
-
"""
|
|
8
|
-
@classmethod
|
|
9
|
-
def from_metric(cls, metric, verbose=False):
|
|
10
|
-
"""
|
|
11
|
-
Computes the Christoffel Symbols of the second kind (Gamma^lambda_{mu nu})
|
|
12
|
-
optimized via SymEngine backend processing.
|
|
13
|
-
"""
|
|
14
|
-
if verbose:
|
|
15
|
-
print(f"Computing Christoffel Symbols for '{metric.name}' in C++...")
|
|
16
|
-
|
|
17
|
-
g = metric._data
|
|
18
|
-
g_inv = metric.inv()._data
|
|
19
|
-
syms = metric.syms
|
|
20
|
-
dims = metric.dims
|
|
21
|
-
|
|
22
|
-
# Initialize a 3D grid structure for Gamma (4x4x4)
|
|
23
|
-
Gamma = [[[se.sympify(0) for _ in range(dims)] for _ in range(dims)] for _ in range(dims)]
|
|
24
|
-
|
|
25
|
-
# Heavy-lifting partial derivative loops triggered directly in C++
|
|
26
|
-
for lambda_ in range(dims):
|
|
27
|
-
for mu in range(dims):
|
|
28
|
-
for nu in range(dims):
|
|
29
|
-
tmp_sum = se.sympify(0)
|
|
30
|
-
for sigma in range(dims):
|
|
31
|
-
term1 = se.diff(g[nu][sigma], syms[mu])
|
|
32
|
-
term2 = se.diff(g[mu][sigma], syms[nu])
|
|
33
|
-
term3 = se.diff(g[mu][nu], syms[sigma])
|
|
34
|
-
tmp_sum += g_inv[lambda_][sigma] * (term1 + term2 - term3)
|
|
35
|
-
|
|
36
|
-
Gamma[lambda_][mu][nu] = se.Rational(1, 2) * tmp_sum
|
|
37
|
-
|
|
38
|
-
# Devuelve instanciando la nueva clase madre BaseConnection
|
|
1
|
+
import symengine as se
|
|
2
|
+
from einsteinengine.symbolic.connection import BaseConnection
|
|
3
|
+
class ChristoffelSymbols(BaseConnection):
|
|
4
|
+
"""
|
|
5
|
+
Computes the Christoffel Symbols of the second kind (Gamma^lambda_{mu nu})
|
|
6
|
+
optimized via SymEngine backend processing.
|
|
7
|
+
"""
|
|
8
|
+
@classmethod
|
|
9
|
+
def from_metric(cls, metric, verbose=False):
|
|
10
|
+
"""
|
|
11
|
+
Computes the Christoffel Symbols of the second kind (Gamma^lambda_{mu nu})
|
|
12
|
+
optimized via SymEngine backend processing.
|
|
13
|
+
"""
|
|
14
|
+
if verbose:
|
|
15
|
+
print(f"Computing Christoffel Symbols for '{metric.name}' in C++...")
|
|
16
|
+
|
|
17
|
+
g = metric._data
|
|
18
|
+
g_inv = metric.inv()._data
|
|
19
|
+
syms = metric.syms
|
|
20
|
+
dims = metric.dims
|
|
21
|
+
|
|
22
|
+
# Initialize a 3D grid structure for Gamma (4x4x4)
|
|
23
|
+
Gamma = [[[se.sympify(0) for _ in range(dims)] for _ in range(dims)] for _ in range(dims)]
|
|
24
|
+
|
|
25
|
+
# Heavy-lifting partial derivative loops triggered directly in C++
|
|
26
|
+
for lambda_ in range(dims):
|
|
27
|
+
for mu in range(dims):
|
|
28
|
+
for nu in range(dims):
|
|
29
|
+
tmp_sum = se.sympify(0)
|
|
30
|
+
for sigma in range(dims):
|
|
31
|
+
term1 = se.diff(g[nu][sigma], syms[mu])
|
|
32
|
+
term2 = se.diff(g[mu][sigma], syms[nu])
|
|
33
|
+
term3 = se.diff(g[mu][nu], syms[sigma])
|
|
34
|
+
tmp_sum += g_inv[lambda_][sigma] * (term1 + term2 - term3)
|
|
35
|
+
|
|
36
|
+
Gamma[lambda_][mu][nu] = se.Rational(1, 2) * tmp_sum
|
|
37
|
+
|
|
38
|
+
# Devuelve instanciando la nueva clase madre BaseConnection
|
|
39
39
|
return cls(Gamma, syms, config="ull", name=f"Christoffel_{metric.name}", verbose=verbose)
|
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
from einsteinengine.symbolic.core import BaseRelativityObject
|
|
2
|
-
|
|
3
|
-
class BaseConnection(BaseRelativityObject):
|
|
4
|
-
"""
|
|
5
|
-
Base class for mathematical Connections (e.g., Levi-Civita, Spin, Affine).
|
|
6
|
-
Inherits coordinate and array management from BaseRelativityObject.
|
|
7
|
-
|
|
8
|
-
IMPORTANT: Connections are NOT tensors. They do not transform homogeneously
|
|
9
|
-
under coordinate changes, and operations like raising/lowering all indices
|
|
10
|
-
using the metric do not apply in the standard tensorial way.
|
|
11
|
-
"""
|
|
12
|
-
def __init__(self, arr, syms, config="ull", name="GenericConnection", verbose=False):
|
|
13
|
-
# 1. Delegate the heavy lifting to the parent object
|
|
14
|
-
super().__init__(arr, syms, name=name, verbose=verbose)
|
|
15
|
-
|
|
16
|
-
# 2. Connection index configuration (usually one upper, two lower)
|
|
17
|
-
self.config = config
|
|
18
|
-
|
|
19
|
-
# 3. Validation: Connections in standard GR are 3-index objects
|
|
20
|
-
if self._data and isinstance(arr, list):
|
|
21
|
-
rank = self._calculate_rank(arr)
|
|
22
|
-
if rank != 3:
|
|
23
|
-
raise ValueError(f"A connection must have exactly 3 indices. Got {rank}.")
|
|
24
|
-
|
|
25
|
-
if self.verbose:
|
|
26
|
-
print(f"[{self.name}] Connection initialized. Note: This object is NOT a tensor.")
|
|
27
|
-
|
|
28
|
-
def _calculate_rank(self, arr):
|
|
29
|
-
"""
|
|
30
|
-
Calculates the mathematical rank (number of indices) of the array
|
|
31
|
-
by measuring the depth of the nested lists.
|
|
32
|
-
"""
|
|
33
|
-
if isinstance(arr, list):
|
|
34
|
-
return 1 + self._calculate_rank(arr[0])
|
|
35
|
-
return 0
|
|
36
|
-
|
|
37
|
-
# --- Core geometric operations for Connections ---
|
|
38
|
-
|
|
39
|
-
def covariant_derivative(self, tensor_obj, verbose=False):
|
|
40
|
-
r"""
|
|
41
|
-
Computes the covariant derivative (\nabla_\mu) of a given tensor.
|
|
42
|
-
The derivative adds a new covariant index (lower 'l') at the beginning of the tensor.
|
|
43
|
-
Nabla_\mu T^{a}_{b} = \partial_\mu T^{a}_{b} + \Gamma^a_{\mu \sigma} T^\sigma_b - \Gamma^\sigma_{\mu b} T^a_\sigma
|
|
44
|
-
"""
|
|
45
|
-
import symengine as se
|
|
46
|
-
|
|
47
|
-
if verbose:
|
|
48
|
-
print(f"[{self.name}] Computing covariant derivative for tensor '{tensor_obj.name}'...")
|
|
49
|
-
|
|
50
|
-
dims = self.dims
|
|
51
|
-
syms = self.syms
|
|
52
|
-
rank = len(tensor_obj.config)
|
|
53
|
-
T_data = tensor_obj.get_raw_data()
|
|
54
|
-
Gamma = self._data # self is the connection, config 'ull': Gamma[up][down][down]
|
|
55
|
-
|
|
56
|
-
# Helper to extract the tensor component safely regardless of rank
|
|
57
|
-
def get_T(indices):
|
|
58
|
-
val = T_data
|
|
59
|
-
for idx in indices:
|
|
60
|
-
val = val[idx]
|
|
61
|
-
return val
|
|
62
|
-
|
|
63
|
-
# The covariant derivative ADDS a new covariant index at the front.
|
|
64
|
-
# e.g., T^\alpha_\beta (config "ul") -> \nabla_\mu T^\alpha_\beta (config "lul")
|
|
65
|
-
new_config = 'l' + tensor_obj.config
|
|
66
|
-
|
|
67
|
-
def build_cov_dir(current_indices):
|
|
68
|
-
# Base case: we have selected all coordinates for the new tensor
|
|
69
|
-
if len(current_indices) == rank + 1:
|
|
70
|
-
mu = current_indices[0] # The derivative index (\nabla_\mu)
|
|
71
|
-
T_indices = current_indices[1:] # The original tensor indices
|
|
72
|
-
|
|
73
|
-
# 1. Base term: Partial derivative \partial_\mu T^{...}
|
|
74
|
-
term = se.diff(get_T(T_indices), syms[mu])
|
|
75
|
-
|
|
76
|
-
# 2. Correction terms: loop over each index of the original tensor
|
|
77
|
-
for p in range(rank):
|
|
78
|
-
idx_type = tensor_obj.config[p]
|
|
79
|
-
current_idx_val = T_indices[p]
|
|
80
|
-
|
|
81
|
-
if idx_type == 'u':
|
|
82
|
-
# Add connection term for contravariant index
|
|
83
|
-
# + \sum_\sigma \Gamma^{current_idx_val}_{\mu \sigma} * T^{... \sigma ...}
|
|
84
|
-
for sigma in range(dims):
|
|
85
|
-
mod_indices = list(T_indices)
|
|
86
|
-
mod_indices[p] = sigma
|
|
87
|
-
term += Gamma[current_idx_val][mu][sigma] * get_T(mod_indices)
|
|
88
|
-
|
|
89
|
-
elif idx_type == 'l':
|
|
90
|
-
# Subtract connection term for covariant index
|
|
91
|
-
# - \sum_\sigma \Gamma^{\sigma}_{\mu current_idx_val} * T^{... \sigma ...}
|
|
92
|
-
for sigma in range(dims):
|
|
93
|
-
mod_indices = list(T_indices)
|
|
94
|
-
mod_indices[p] = sigma
|
|
95
|
-
term -= Gamma[sigma][mu][current_idx_val] * get_T(mod_indices)
|
|
96
|
-
|
|
97
|
-
return term
|
|
98
|
-
|
|
99
|
-
# Recursive case: dive deeper into the dimensions
|
|
100
|
-
else:
|
|
101
|
-
return [build_cov_dir(current_indices + [d]) for d in range(dims)]
|
|
102
|
-
|
|
103
|
-
cov_data = build_cov_dir([])
|
|
104
|
-
|
|
105
|
-
# Local import to avoid circular dependencies
|
|
106
|
-
from einsteinengine.symbolic.tensor import BaseRelativityTensor
|
|
107
|
-
|
|
108
|
-
final_name = f"CovDeriv_{tensor_obj.name}"
|
|
109
|
-
return BaseRelativityTensor(cov_data, syms, config=new_config, name=final_name, verbose=verbose)
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
from einsteinengine.symbolic.core import BaseRelativityObject
|
|
2
|
+
|
|
3
|
+
class BaseConnection(BaseRelativityObject):
|
|
4
|
+
"""
|
|
5
|
+
Base class for mathematical Connections (e.g., Levi-Civita, Spin, Affine).
|
|
6
|
+
Inherits coordinate and array management from BaseRelativityObject.
|
|
7
|
+
|
|
8
|
+
IMPORTANT: Connections are NOT tensors. They do not transform homogeneously
|
|
9
|
+
under coordinate changes, and operations like raising/lowering all indices
|
|
10
|
+
using the metric do not apply in the standard tensorial way.
|
|
11
|
+
"""
|
|
12
|
+
def __init__(self, arr, syms, config="ull", name="GenericConnection", verbose=False):
|
|
13
|
+
# 1. Delegate the heavy lifting to the parent object
|
|
14
|
+
super().__init__(arr, syms, name=name, verbose=verbose)
|
|
15
|
+
|
|
16
|
+
# 2. Connection index configuration (usually one upper, two lower)
|
|
17
|
+
self.config = config
|
|
18
|
+
|
|
19
|
+
# 3. Validation: Connections in standard GR are 3-index objects
|
|
20
|
+
if self._data and isinstance(arr, list):
|
|
21
|
+
rank = self._calculate_rank(arr)
|
|
22
|
+
if rank != 3:
|
|
23
|
+
raise ValueError(f"A connection must have exactly 3 indices. Got {rank}.")
|
|
24
|
+
|
|
25
|
+
if self.verbose:
|
|
26
|
+
print(f"[{self.name}] Connection initialized. Note: This object is NOT a tensor.")
|
|
27
|
+
|
|
28
|
+
def _calculate_rank(self, arr):
|
|
29
|
+
"""
|
|
30
|
+
Calculates the mathematical rank (number of indices) of the array
|
|
31
|
+
by measuring the depth of the nested lists.
|
|
32
|
+
"""
|
|
33
|
+
if isinstance(arr, list):
|
|
34
|
+
return 1 + self._calculate_rank(arr[0])
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
# --- Core geometric operations for Connections ---
|
|
38
|
+
|
|
39
|
+
def covariant_derivative(self, tensor_obj, verbose=False):
|
|
40
|
+
r"""
|
|
41
|
+
Computes the covariant derivative (\nabla_\mu) of a given tensor.
|
|
42
|
+
The derivative adds a new covariant index (lower 'l') at the beginning of the tensor.
|
|
43
|
+
Nabla_\mu T^{a}_{b} = \partial_\mu T^{a}_{b} + \Gamma^a_{\mu \sigma} T^\sigma_b - \Gamma^\sigma_{\mu b} T^a_\sigma
|
|
44
|
+
"""
|
|
45
|
+
import symengine as se
|
|
46
|
+
|
|
47
|
+
if verbose:
|
|
48
|
+
print(f"[{self.name}] Computing covariant derivative for tensor '{tensor_obj.name}'...")
|
|
49
|
+
|
|
50
|
+
dims = self.dims
|
|
51
|
+
syms = self.syms
|
|
52
|
+
rank = len(tensor_obj.config)
|
|
53
|
+
T_data = tensor_obj.get_raw_data()
|
|
54
|
+
Gamma = self._data # self is the connection, config 'ull': Gamma[up][down][down]
|
|
55
|
+
|
|
56
|
+
# Helper to extract the tensor component safely regardless of rank
|
|
57
|
+
def get_T(indices):
|
|
58
|
+
val = T_data
|
|
59
|
+
for idx in indices:
|
|
60
|
+
val = val[idx]
|
|
61
|
+
return val
|
|
62
|
+
|
|
63
|
+
# The covariant derivative ADDS a new covariant index at the front.
|
|
64
|
+
# e.g., T^\alpha_\beta (config "ul") -> \nabla_\mu T^\alpha_\beta (config "lul")
|
|
65
|
+
new_config = 'l' + tensor_obj.config
|
|
66
|
+
|
|
67
|
+
def build_cov_dir(current_indices):
|
|
68
|
+
# Base case: we have selected all coordinates for the new tensor
|
|
69
|
+
if len(current_indices) == rank + 1:
|
|
70
|
+
mu = current_indices[0] # The derivative index (\nabla_\mu)
|
|
71
|
+
T_indices = current_indices[1:] # The original tensor indices
|
|
72
|
+
|
|
73
|
+
# 1. Base term: Partial derivative \partial_\mu T^{...}
|
|
74
|
+
term = se.diff(get_T(T_indices), syms[mu])
|
|
75
|
+
|
|
76
|
+
# 2. Correction terms: loop over each index of the original tensor
|
|
77
|
+
for p in range(rank):
|
|
78
|
+
idx_type = tensor_obj.config[p]
|
|
79
|
+
current_idx_val = T_indices[p]
|
|
80
|
+
|
|
81
|
+
if idx_type == 'u':
|
|
82
|
+
# Add connection term for contravariant index
|
|
83
|
+
# + \sum_\sigma \Gamma^{current_idx_val}_{\mu \sigma} * T^{... \sigma ...}
|
|
84
|
+
for sigma in range(dims):
|
|
85
|
+
mod_indices = list(T_indices)
|
|
86
|
+
mod_indices[p] = sigma
|
|
87
|
+
term += Gamma[current_idx_val][mu][sigma] * get_T(mod_indices)
|
|
88
|
+
|
|
89
|
+
elif idx_type == 'l':
|
|
90
|
+
# Subtract connection term for covariant index
|
|
91
|
+
# - \sum_\sigma \Gamma^{\sigma}_{\mu current_idx_val} * T^{... \sigma ...}
|
|
92
|
+
for sigma in range(dims):
|
|
93
|
+
mod_indices = list(T_indices)
|
|
94
|
+
mod_indices[p] = sigma
|
|
95
|
+
term -= Gamma[sigma][mu][current_idx_val] * get_T(mod_indices)
|
|
96
|
+
|
|
97
|
+
return term
|
|
98
|
+
|
|
99
|
+
# Recursive case: dive deeper into the dimensions
|
|
100
|
+
else:
|
|
101
|
+
return [build_cov_dir(current_indices + [d]) for d in range(dims)]
|
|
102
|
+
|
|
103
|
+
cov_data = build_cov_dir([])
|
|
104
|
+
|
|
105
|
+
# Local import to avoid circular dependencies
|
|
106
|
+
from einsteinengine.symbolic.tensor import BaseRelativityTensor
|
|
107
|
+
|
|
108
|
+
final_name = f"CovDeriv_{tensor_obj.name}"
|
|
109
|
+
return BaseRelativityTensor(cov_data, syms, config=new_config, name=final_name, verbose=verbose)
|
|
110
|
+
|
|
111
|
+
|