gravityp 0.1.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.
- gravityp-0.1.0/LICENSE +21 -0
- gravityp-0.1.0/PKG-INFO +138 -0
- gravityp-0.1.0/README.md +117 -0
- gravityp-0.1.0/pyproject.toml +36 -0
- gravityp-0.1.0/setup.cfg +4 -0
- gravityp-0.1.0/src/gravityp/__init__.py +46 -0
- gravityp-0.1.0/src/gravityp/emission_models.py +45 -0
- gravityp-0.1.0/src/gravityp/geodesic_integration.py +192 -0
- gravityp-0.1.0/src/gravityp/plots.py +293 -0
- gravityp-0.1.0/src/gravityp/ray_tracing.py +53 -0
- gravityp-0.1.0/src/gravityp/rings.py +217 -0
- gravityp-0.1.0/src/gravityp/shadows.py +38 -0
- gravityp-0.1.0/src/gravityp/transfer_functions.py +44 -0
- gravityp-0.1.0/src/gravityp/utils.py +15 -0
- gravityp-0.1.0/src/gravityp.egg-info/PKG-INFO +138 -0
- gravityp-0.1.0/src/gravityp.egg-info/SOURCES.txt +17 -0
- gravityp-0.1.0/src/gravityp.egg-info/dependency_links.txt +1 -0
- gravityp-0.1.0/src/gravityp.egg-info/requires.txt +7 -0
- gravityp-0.1.0/src/gravityp.egg-info/top_level.txt +1 -0
gravityp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Álvaro Salazar Cuadros
|
|
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.
|
gravityp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gravityp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GRAVITYp is a Python library to study the optical appearance of spherically symmetric compact objects in General Relativity. It stands for Geodesic RAys and Visualization of IntensiTY profiles.
|
|
5
|
+
Author-email: Alvaro Salazar-Cuadros <a.salazar@uva.es>, Diego Saez-Chillon Gomez <diego.saez@uva.es>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/alvaroslzar/GRAVITYp
|
|
8
|
+
Project-URL: Issues, https://github.com/alvaroslzar/GRAVITYp/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy>=1.26
|
|
15
|
+
Requires-Dist: scipy>=1.11
|
|
16
|
+
Requires-Dist: matplotlib>=3.8
|
|
17
|
+
Provides-Extra: notebooks
|
|
18
|
+
Requires-Dist: jupyter>=1.0; extra == "notebooks"
|
|
19
|
+
Requires-Dist: ipykernel>=6.0; extra == "notebooks"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# GRAVITYp
|
|
23
|
+
|
|
24
|
+
**GRAVITYp** is a Python package for studying the optical appearance of spherically symmetric compact objects in General Relativity.
|
|
25
|
+
It stands for **G**eodesic **RA**ys and **V**isualization of **I**ntensi**TY** **p**rofiles.
|
|
26
|
+
|
|
27
|
+
## About the project
|
|
28
|
+
|
|
29
|
+
This is the Python implementation of **GRAVITYp**, which is originally a numerical code in Wolfram language developed by G.J. Olmo, D. Rubiera García, J.L. Rosa, D. Sáez-Chillón and collaborators [arXiv:2307.06778v2](https://arxiv.org/abs/2307.06778v2).
|
|
30
|
+
It was developed to study the optical appearance or *shadow* cast by black holes, wormholes and different kinds of exotic compact objects, inspired by the work of S.E. Gralla et al. [arXiv:1906.00873](https://arxiv.org/abs/1906.00873).
|
|
31
|
+
|
|
32
|
+
Then, Á. Salazar Cuadros preferred to work on Python seeking faster execution times for simulations and a language better suited for Version Control Systems (VCS), and that's how the Python implementation was born.
|
|
33
|
+
The goal of this open source version is to provide open access to anyone interested in using the package or learning how simulated black hole shadow images are generated.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
You can clone `GRAVITYp` locally in editable mode for research and development:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone git@github.com:alvaroslzar/GRAVITYp.git # SSH
|
|
41
|
+
git clone https://github.com/alvaroslzar/GRAVITYp.git # HTTPS
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then, install it in your virtual environment
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd GRAVITYp
|
|
48
|
+
pip install -e .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Quick example
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import numpy as np
|
|
57
|
+
from gravityp import make_geodesics_plot
|
|
58
|
+
|
|
59
|
+
def g_rr(r: float, dummy) -> float:
|
|
60
|
+
"""Radial function g^{rr} of the Schwarzschild metric in units of M=1"""
|
|
61
|
+
return 1 - 2./r
|
|
62
|
+
|
|
63
|
+
def g_thth(r: float, dummy) -> float:
|
|
64
|
+
"""Areal radius squared g^{theta theta} of the Schwarzschild in units of M=1"""
|
|
65
|
+
return r**2
|
|
66
|
+
|
|
67
|
+
# Dictionary codifying Schwarzschild metric and required params
|
|
68
|
+
Schwarzschild_kwargs = {
|
|
69
|
+
'r_phs': [3.,], # Photon sphere at r=3M
|
|
70
|
+
'inner_edge': 2., # Inner edge of disk at horizon r=2M
|
|
71
|
+
'radial_fun': g_rr, # Above defined radial function
|
|
72
|
+
'radial_params': None, # g_rr has no additional params
|
|
73
|
+
'areal': g_thth, # Above defined areal radius squared
|
|
74
|
+
'areal_params': None, # g_thth has no additional params
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Array of impact parameters from 0M to 10M
|
|
78
|
+
impact_parameters = np.arange(0,10,0.2)
|
|
79
|
+
make_geodesics_plot(impact_parameters, figsize=(4,4), **Schwarzschild_kwargs)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The output image looks like this:
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
For a more detailed explanation, check the Jupyter notebooks in the [Examples](Examples/) folder for details.
|
|
87
|
+
|
|
88
|
+
### Intensity profiles and black hole shadows
|
|
89
|
+
|
|
90
|
+
The primary use is to obtain the observed intensity profile $I_\mathrm{ob}(b)$ as a function of the impact parameter $b$ given some predefined emission profile $I_\mathrm{em}(r)$.
|
|
91
|
+
The emitted light is due to the matter in the accretion disk, which is assumed to be both geometrically and optically thin.
|
|
92
|
+
Moreover, the orientation is assumed to be face-on with respect to the accretion disk, so the optical appearance has have rotational symmetry.
|
|
93
|
+
|
|
94
|
+
In the following images, the emission profile of the accretion disk has been chosen to peak at the event horizon $r=2M$, and the optical appearance corresponds to a Schwarzschild black hole.ç
|
|
95
|
+
The code properly reproduces the predicted light rings up to two intersections with the accretion disk, as well as the central brightness depression or *shadow* characteristic of compact objects such as black holes.
|
|
96
|
+
|
|
97
|
+

|
|
98
|
+

|
|
99
|
+

|
|
100
|
+
|
|
101
|
+
### Further references
|
|
102
|
+
|
|
103
|
+
Watch Prof. D. Rubiera García's [talk](https://www.youtube.com/live/q0a4RXdxk4o?si=njtj2yOmCwim25ix) for an overview of the main concepts involved in the ray-tracing method and analysis of intensity profiles.
|
|
104
|
+
|
|
105
|
+
For an introductory discussion of the inner workings of the Wolfram Mathematica code, watch Prof. G.J. Olmo's [tutorial](https://www.youtube.com/live/f5-s2gVd5xE?si=xCtJxFQikmkLehdU); for a more user-friendly application, watch Dr. J.L. Rosa's [talk](https://www.youtube.com/live/9k8qMq9V814?si=MtXYMg0exd_6_37t).
|
|
106
|
+
|
|
107
|
+
Finally, for a more advanced topic regarding compact objects, watch Prof. D. Sáez-Chillón's [contribution](https://www.youtube.com/watch?v=8pR8kE_ABzQ).
|
|
108
|
+
|
|
109
|
+
## Citation
|
|
110
|
+
|
|
111
|
+
The use of **GRAVITYp** in scientific publications must be properly acknowledged.
|
|
112
|
+
Please cite the following:
|
|
113
|
+
|
|
114
|
+
**BibTeX**
|
|
115
|
+
```
|
|
116
|
+
@article{Nojiri:2026tjn,
|
|
117
|
+
author = "Nojiri, Shin'ichi and Odintsov, Sergei D. and S{\'a}ez-Chill{\'o}n G{\'o}mez, Diego and Cuadros, {\'A}lvaro Salazar",
|
|
118
|
+
title = "{Horizon singularity, energy conditions and shadows in time-dependent and spherically symmetric spacetime}",
|
|
119
|
+
eprint = "2608.15740",
|
|
120
|
+
archivePrefix = "arXiv",
|
|
121
|
+
primaryClass = "gr-qc",
|
|
122
|
+
reportNumber = "KEK-TH-2861, KEK-Cosmo-0429",
|
|
123
|
+
month = "8",
|
|
124
|
+
year = "2026"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Publications using GRAVITYp
|
|
129
|
+
|
|
130
|
+
Let us know if you use **GRAVITYp** in your publication, and we will add it to the [list of publications](/docs/PUBLICATIONS.md)!
|
|
131
|
+
|
|
132
|
+
## Author
|
|
133
|
+
|
|
134
|
+
The author and mantainer of the Python code is Álvaro Salazar Cuadros.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
The software is licensed under the [MIT license](LICENSE).
|
gravityp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# GRAVITYp
|
|
2
|
+
|
|
3
|
+
**GRAVITYp** is a Python package for studying the optical appearance of spherically symmetric compact objects in General Relativity.
|
|
4
|
+
It stands for **G**eodesic **RA**ys and **V**isualization of **I**ntensi**TY** **p**rofiles.
|
|
5
|
+
|
|
6
|
+
## About the project
|
|
7
|
+
|
|
8
|
+
This is the Python implementation of **GRAVITYp**, which is originally a numerical code in Wolfram language developed by G.J. Olmo, D. Rubiera García, J.L. Rosa, D. Sáez-Chillón and collaborators [arXiv:2307.06778v2](https://arxiv.org/abs/2307.06778v2).
|
|
9
|
+
It was developed to study the optical appearance or *shadow* cast by black holes, wormholes and different kinds of exotic compact objects, inspired by the work of S.E. Gralla et al. [arXiv:1906.00873](https://arxiv.org/abs/1906.00873).
|
|
10
|
+
|
|
11
|
+
Then, Á. Salazar Cuadros preferred to work on Python seeking faster execution times for simulations and a language better suited for Version Control Systems (VCS), and that's how the Python implementation was born.
|
|
12
|
+
The goal of this open source version is to provide open access to anyone interested in using the package or learning how simulated black hole shadow images are generated.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
You can clone `GRAVITYp` locally in editable mode for research and development:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone git@github.com:alvaroslzar/GRAVITYp.git # SSH
|
|
20
|
+
git clone https://github.com/alvaroslzar/GRAVITYp.git # HTTPS
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then, install it in your virtual environment
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd GRAVITYp
|
|
27
|
+
pip install -e .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Quick example
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import numpy as np
|
|
36
|
+
from gravityp import make_geodesics_plot
|
|
37
|
+
|
|
38
|
+
def g_rr(r: float, dummy) -> float:
|
|
39
|
+
"""Radial function g^{rr} of the Schwarzschild metric in units of M=1"""
|
|
40
|
+
return 1 - 2./r
|
|
41
|
+
|
|
42
|
+
def g_thth(r: float, dummy) -> float:
|
|
43
|
+
"""Areal radius squared g^{theta theta} of the Schwarzschild in units of M=1"""
|
|
44
|
+
return r**2
|
|
45
|
+
|
|
46
|
+
# Dictionary codifying Schwarzschild metric and required params
|
|
47
|
+
Schwarzschild_kwargs = {
|
|
48
|
+
'r_phs': [3.,], # Photon sphere at r=3M
|
|
49
|
+
'inner_edge': 2., # Inner edge of disk at horizon r=2M
|
|
50
|
+
'radial_fun': g_rr, # Above defined radial function
|
|
51
|
+
'radial_params': None, # g_rr has no additional params
|
|
52
|
+
'areal': g_thth, # Above defined areal radius squared
|
|
53
|
+
'areal_params': None, # g_thth has no additional params
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Array of impact parameters from 0M to 10M
|
|
57
|
+
impact_parameters = np.arange(0,10,0.2)
|
|
58
|
+
make_geodesics_plot(impact_parameters, figsize=(4,4), **Schwarzschild_kwargs)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The output image looks like this:
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
For a more detailed explanation, check the Jupyter notebooks in the [Examples](Examples/) folder for details.
|
|
66
|
+
|
|
67
|
+
### Intensity profiles and black hole shadows
|
|
68
|
+
|
|
69
|
+
The primary use is to obtain the observed intensity profile $I_\mathrm{ob}(b)$ as a function of the impact parameter $b$ given some predefined emission profile $I_\mathrm{em}(r)$.
|
|
70
|
+
The emitted light is due to the matter in the accretion disk, which is assumed to be both geometrically and optically thin.
|
|
71
|
+
Moreover, the orientation is assumed to be face-on with respect to the accretion disk, so the optical appearance has have rotational symmetry.
|
|
72
|
+
|
|
73
|
+
In the following images, the emission profile of the accretion disk has been chosen to peak at the event horizon $r=2M$, and the optical appearance corresponds to a Schwarzschild black hole.ç
|
|
74
|
+
The code properly reproduces the predicted light rings up to two intersections with the accretion disk, as well as the central brightness depression or *shadow* characteristic of compact objects such as black holes.
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+

|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
### Further references
|
|
81
|
+
|
|
82
|
+
Watch Prof. D. Rubiera García's [talk](https://www.youtube.com/live/q0a4RXdxk4o?si=njtj2yOmCwim25ix) for an overview of the main concepts involved in the ray-tracing method and analysis of intensity profiles.
|
|
83
|
+
|
|
84
|
+
For an introductory discussion of the inner workings of the Wolfram Mathematica code, watch Prof. G.J. Olmo's [tutorial](https://www.youtube.com/live/f5-s2gVd5xE?si=xCtJxFQikmkLehdU); for a more user-friendly application, watch Dr. J.L. Rosa's [talk](https://www.youtube.com/live/9k8qMq9V814?si=MtXYMg0exd_6_37t).
|
|
85
|
+
|
|
86
|
+
Finally, for a more advanced topic regarding compact objects, watch Prof. D. Sáez-Chillón's [contribution](https://www.youtube.com/watch?v=8pR8kE_ABzQ).
|
|
87
|
+
|
|
88
|
+
## Citation
|
|
89
|
+
|
|
90
|
+
The use of **GRAVITYp** in scientific publications must be properly acknowledged.
|
|
91
|
+
Please cite the following:
|
|
92
|
+
|
|
93
|
+
**BibTeX**
|
|
94
|
+
```
|
|
95
|
+
@article{Nojiri:2026tjn,
|
|
96
|
+
author = "Nojiri, Shin'ichi and Odintsov, Sergei D. and S{\'a}ez-Chill{\'o}n G{\'o}mez, Diego and Cuadros, {\'A}lvaro Salazar",
|
|
97
|
+
title = "{Horizon singularity, energy conditions and shadows in time-dependent and spherically symmetric spacetime}",
|
|
98
|
+
eprint = "2608.15740",
|
|
99
|
+
archivePrefix = "arXiv",
|
|
100
|
+
primaryClass = "gr-qc",
|
|
101
|
+
reportNumber = "KEK-TH-2861, KEK-Cosmo-0429",
|
|
102
|
+
month = "8",
|
|
103
|
+
year = "2026"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Publications using GRAVITYp
|
|
108
|
+
|
|
109
|
+
Let us know if you use **GRAVITYp** in your publication, and we will add it to the [list of publications](/docs/PUBLICATIONS.md)!
|
|
110
|
+
|
|
111
|
+
## Author
|
|
112
|
+
|
|
113
|
+
The author and mantainer of the Python code is Álvaro Salazar Cuadros.
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
The software is licensed under the [MIT license](LICENSE).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gravityp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Alvaro Salazar-Cuadros", email="a.salazar@uva.es" },
|
|
10
|
+
{ name="Diego Saez-Chillon Gomez", email="diego.saez@uva.es" },
|
|
11
|
+
|
|
12
|
+
]
|
|
13
|
+
description = "GRAVITYp is a Python library to study the optical appearance of spherically symmetric compact objects in General Relativity. It stands for Geodesic RAys and Visualization of IntensiTY profiles."
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.12"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"numpy>=1.26",
|
|
18
|
+
"scipy>=1.11",
|
|
19
|
+
"matplotlib>=3.8",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
]
|
|
25
|
+
license = "MIT"
|
|
26
|
+
license-files = ["LICEN[CS]E*"]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
notebooks = [
|
|
30
|
+
"jupyter>=1.0",
|
|
31
|
+
"ipykernel>=6.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/alvaroslzar/GRAVITYp"
|
|
36
|
+
Issues = "https://github.com/alvaroslzar/GRAVITYp/issues"
|
gravityp-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GRAVITYp: Python package to study the optical appearance of spherically symmetric
|
|
3
|
+
compact objects in General Relativity.
|
|
4
|
+
|
|
5
|
+
Source: https://github.com/alvaroslzar/GRAVITYp
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .utils import *
|
|
9
|
+
from .geodesic_integration import *
|
|
10
|
+
from .ray_tracing import *
|
|
11
|
+
from .rings import *
|
|
12
|
+
from .transfer_functions import *
|
|
13
|
+
from .emission_models import *
|
|
14
|
+
from .shadows import *
|
|
15
|
+
from .plots import *
|
|
16
|
+
|
|
17
|
+
import email.utils
|
|
18
|
+
from importlib.metadata import metadata, PackageNotFoundError
|
|
19
|
+
|
|
20
|
+
__name__ = "gravityp"
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
_meta = metadata(__name__)
|
|
24
|
+
__version__ = _meta.get("Version", "0.1.0")
|
|
25
|
+
|
|
26
|
+
# setuptools packs pyproject.toml authors into the 'Author-email' header
|
|
27
|
+
_author_email_header = _meta.get("Author-email")
|
|
28
|
+
|
|
29
|
+
if _author_email_header:
|
|
30
|
+
# Parse header into [("Name", "email@domain.com"), ...]
|
|
31
|
+
_parsed_contacts = email.utils.getaddresses([_author_email_header])
|
|
32
|
+
|
|
33
|
+
_names = [name for name, _ in _parsed_contacts if name]
|
|
34
|
+
_emails = [addr for _, addr in _parsed_contacts if addr]
|
|
35
|
+
|
|
36
|
+
# Format as strings so help(gravityp) displays them cleanly
|
|
37
|
+
__author__ = ", ".join(_names) if _names else _meta.get("Author")
|
|
38
|
+
__email__ = ", ".join(_emails) if _emails else None
|
|
39
|
+
else:
|
|
40
|
+
__author__ = _meta.get("Author")
|
|
41
|
+
__email__ = None
|
|
42
|
+
|
|
43
|
+
except PackageNotFoundError:
|
|
44
|
+
__version__ = "0.1.0"
|
|
45
|
+
__author__ = "Gonzalo J. Olmo, Diego Rubiera-Garcia, João Luís Rosa, Diego Sáez-Chillón Gómez, Álvaro Salazar-Cuadros"
|
|
46
|
+
__email__ = None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""This module contains some predefined emission profiles
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import scipy.optimize as optimize
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"intensity_at_ISCO",
|
|
10
|
+
"intensity_at_photon_ring",
|
|
11
|
+
"intensity_at_inner_edge",
|
|
12
|
+
"standard_Unbound",
|
|
13
|
+
"normalized_Standard_Unbound",
|
|
14
|
+
"exponential",
|
|
15
|
+
"uniform"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def intensity_at_ISCO(r, r_ISCO):
|
|
20
|
+
func = lambda x: (1./(x-(r_ISCO-1))**2) / (1./(r_ISCO-(r_ISCO-1))**2)
|
|
21
|
+
return np.piecewise(r, [r>=r_ISCO,r<r_ISCO] , [func,0] )
|
|
22
|
+
|
|
23
|
+
def intensity_at_photon_ring(r, r_ph):
|
|
24
|
+
func = lambda x: (1./(x-(r_ph-1))**3) / (1./(r_ph-(r_ph-1))**3)
|
|
25
|
+
return np.piecewise(r, [r>=r_ph,r<r_ph] , [func,0] )
|
|
26
|
+
|
|
27
|
+
def intensity_at_inner_edge(r, inner_edge):
|
|
28
|
+
func = lambda x: ( np.pi/2-np.arctan(x-5) ) / ( np.pi/2-np.arctan(inner_edge-5) )
|
|
29
|
+
return np.piecewise(r, [r>=inner_edge,r<inner_edge] , [func,0] )
|
|
30
|
+
|
|
31
|
+
def standard_Unbound(r, mu: float=8., sigma: float=2., gamma: float=2.):
|
|
32
|
+
return ( np.exp(-0.5*(gamma+np.arcsinh((r-mu)/sigma))**2) /
|
|
33
|
+
np.sqrt((r-mu)**2+sigma**2) )
|
|
34
|
+
|
|
35
|
+
def normalized_Standard_Unbound(r, mu: float=8., sigma: float=2., gamma: float=2.):
|
|
36
|
+
# FIXME: for some values of params, it doesn't find the minimum
|
|
37
|
+
neg_fun = lambda r: -standard_Unbound(r, mu, sigma, gamma)
|
|
38
|
+
minimum = optimize.minimize(neg_fun, 0).fun
|
|
39
|
+
return neg_fun(r)/minimum
|
|
40
|
+
|
|
41
|
+
def exponential(r):
|
|
42
|
+
return np.exp(-r)
|
|
43
|
+
|
|
44
|
+
def uniform(r):
|
|
45
|
+
return np.ones(shape=r.shape)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""This module contains all the functions needed for the geodesic integration
|
|
2
|
+
routine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import scipy.integrate as integrate
|
|
7
|
+
import scipy.optimize as optimize
|
|
8
|
+
from bisect import bisect_right
|
|
9
|
+
|
|
10
|
+
from .utils import to_tuple
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"potential",
|
|
15
|
+
"get_increasing_maxima",
|
|
16
|
+
"find_interval",
|
|
17
|
+
"find_rmin",
|
|
18
|
+
"geodesic_in",
|
|
19
|
+
"geodesic_out",
|
|
20
|
+
"compute_outgoing",
|
|
21
|
+
"compute_geodesic",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
DEFAULT_NPOINTS = 100000 # ODE integration resolution
|
|
25
|
+
DEFAULT_R0 = 1000.0 # Asymptotic boundary for initial conditions
|
|
26
|
+
MINIMUM_R = 1e-6 # Minimum value of r for integration
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def potential(r, radial_fun, radial_params, areal, areal_params):
|
|
30
|
+
"""
|
|
31
|
+
Effective potential of a spherically symmetric metric of the form:
|
|
32
|
+
|
|
33
|
+
ds^2 = - radial_fun(r; radial_params)dt^2 + 1/radial_fun(r; radial_params)dr^2 + areal(r; areal_params)dOmega^2
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
r (float): radial coordinate of the metric
|
|
37
|
+
radial_fun (callable): -gtt metric function. Its first argument must be r
|
|
38
|
+
radial_params (float | tuple): additional parameters for radial_fun
|
|
39
|
+
areal (callable): areal radius squared. Its first argument must be r
|
|
40
|
+
areal_params (float | tuple): additional parameters for areal
|
|
41
|
+
"""
|
|
42
|
+
return radial_fun(r,*to_tuple(radial_params)) / areal(r,*to_tuple(areal_params))
|
|
43
|
+
|
|
44
|
+
def get_increasing_maxima(r_phs, potential):
|
|
45
|
+
"""Returns increasing local maxima of the potential with their photon sphere values"""
|
|
46
|
+
if len(r_phs) >= 1: # There has to be at least 1 local maximum
|
|
47
|
+
r_phs = np.array(r_phs)
|
|
48
|
+
r_phs = np.sort(r_phs)[::-1] # descending order of
|
|
49
|
+
photon_sph = r_phs[0].reshape(1,)
|
|
50
|
+
local_maxima = potential(photon_sph)
|
|
51
|
+
for r_ph in r_phs:
|
|
52
|
+
maximum = potential(r_ph)
|
|
53
|
+
if maximum > local_maxima.max(): # We take increasing local maxima
|
|
54
|
+
local_maxima = np.append(local_maxima, maximum)
|
|
55
|
+
photon_sph = np.append(photon_sph, r_ph)
|
|
56
|
+
return photon_sph[::-1], local_maxima[::-1] # Swap to ascending order of photon_spheres
|
|
57
|
+
else:
|
|
58
|
+
return np.array([]), np.array([])
|
|
59
|
+
|
|
60
|
+
def find_interval(inverse_b2, photon_sph, local_maxima, lower_bound=1e-4, upper_bound=100):
|
|
61
|
+
"""Returns the bounds (r1,r2) where the rmin is found for a given 1/b^2"""
|
|
62
|
+
# NOTE: photon_sph and local_maxima have to be ordered by get_increasing_maxima
|
|
63
|
+
if len(photon_sph)==0 and len(local_maxima)==0: # Case 1: there is no local maximum
|
|
64
|
+
return (lower_bound, upper_bound)
|
|
65
|
+
if inverse_b2 >= local_maxima[0]: # Case 2: 1/b^2 higher than all local maxima
|
|
66
|
+
return (lower_bound, photon_sph[0])
|
|
67
|
+
if inverse_b2 <= local_maxima[-1]: # Case 3: 1/b^2 lower than all local maxima
|
|
68
|
+
return (photon_sph[-1], upper_bound)
|
|
69
|
+
|
|
70
|
+
# Case 4: there are more than 1 local maximum and 1/b^2 lies between two of them
|
|
71
|
+
# We negate both the array elements and 1/b^2 so the logic mirrors an increasing array
|
|
72
|
+
idx = bisect_right(local_maxima, -inverse_b2, key=lambda x: -x)
|
|
73
|
+
return (photon_sph[idx - 1], photon_sph[idx])
|
|
74
|
+
|
|
75
|
+
def find_rmin(b, r_phs, inner_edge, radial_fun, radial_params, areal, areal_params):
|
|
76
|
+
"""
|
|
77
|
+
Finds the minimum radius of the trajectory using Brent's method.
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
b (float): impact parameter of the photon trajectory
|
|
81
|
+
r_phs (array): array with the position of photon spheres (maxima) of the potential
|
|
82
|
+
inner_edge (float): value of the r coordinate of the inner edge (event horizon, throat, etc.)
|
|
83
|
+
potential (callable): effective potential
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
i) If b > bmin, then rmin is found by solving V(r)-1/b^2 = 0
|
|
87
|
+
ii) If b < bmin, then rmin = inner_edge.
|
|
88
|
+
"""
|
|
89
|
+
pot = lambda r: potential(r, radial_fun, radial_params, areal, areal_params) # V(r)
|
|
90
|
+
photon_sph, local_maxima = get_increasing_maxima(r_phs, pot)
|
|
91
|
+
bounds = find_interval(1/b**2, photon_sph, local_maxima) # Interval for Brent's method
|
|
92
|
+
fun = lambda r: pot(r)-1/b**2
|
|
93
|
+
if fun(bounds[0])*fun(bounds[1]) >= 0: # No root if there is no sign change
|
|
94
|
+
root = 0
|
|
95
|
+
else:
|
|
96
|
+
root, results = optimize.brentq(fun, *bounds, full_output=True)
|
|
97
|
+
return max( max(root, inner_edge) , MINIMUM_R )
|
|
98
|
+
|
|
99
|
+
def geodesic_in(phi, r, b, radial_fun, radial_params, areal, areal_params):
|
|
100
|
+
"""Expression for dphi/dr in the incoming null geodesic equation"""
|
|
101
|
+
areal_params = to_tuple(areal_params)
|
|
102
|
+
radial_params = to_tuple(radial_params)
|
|
103
|
+
return -( b / np.sqrt(areal(r,*areal_params)) )/np.sqrt( areal(r,*areal_params) - b**2*radial_fun(r,*radial_params) )
|
|
104
|
+
|
|
105
|
+
def geodesic_out(phi, r, b, radial_fun, radial_params, areal, areal_params):
|
|
106
|
+
"""Expression for dphi/dr in the outgoing null geodesic equation"""
|
|
107
|
+
areal_params = to_tuple(areal_params)
|
|
108
|
+
radial_params = to_tuple(radial_params)
|
|
109
|
+
return +( b / np.sqrt(areal(r,*areal_params)) )/np.sqrt( areal(r,*areal_params) - b**2*radial_fun(r,*radial_params) )
|
|
110
|
+
|
|
111
|
+
def compute_outgoing(r_phs, inner_edge, rmin) -> bool:
|
|
112
|
+
"""Returns a boolean that tells whether to compute outgoing geodesic after reaching rmin"""
|
|
113
|
+
compute = False
|
|
114
|
+
if (len(r_phs)>0): # There is at least 1 photon sphere
|
|
115
|
+
innermost_photon_sphere = np.array(r_phs).min()
|
|
116
|
+
if (innermost_photon_sphere < MINIMUM_R):
|
|
117
|
+
if (rmin > MINIMUM_R):
|
|
118
|
+
compute = True
|
|
119
|
+
# if (rmin > inner_edge) and (innermost_photon_sphere > 0): # Probamos con inner_edge mejor
|
|
120
|
+
else:
|
|
121
|
+
# print('Hola')
|
|
122
|
+
if (rmin > inner_edge) and (rmin > MINIMUM_R): # Probamos con inner_edge mejor
|
|
123
|
+
compute = True
|
|
124
|
+
else: # There is no photon sphere
|
|
125
|
+
compute = True
|
|
126
|
+
return compute
|
|
127
|
+
|
|
128
|
+
def compute_geodesic(b, r_phs, inner_edge, radial_fun, radial_params, areal, areal_params,
|
|
129
|
+
Npoints=DEFAULT_NPOINTS, r0=DEFAULT_R0):
|
|
130
|
+
"""
|
|
131
|
+
Computes the null geodesic trajectory in Schwarzschild spacetime.
|
|
132
|
+
|
|
133
|
+
Integrates the geodesic equations to trace light ray paths. For incoming rays,
|
|
134
|
+
integrates from r0 down to rmin. For rays escaping to infinity (rmin > 3),
|
|
135
|
+
also integrates the outgoing trajectory from rmin back to r0.
|
|
136
|
+
|
|
137
|
+
Parameters:
|
|
138
|
+
b (float): Impact parameter in units of M=1
|
|
139
|
+
Npoints (int): Number of radial points for integration. Default: DEFAULT_NPOINTS
|
|
140
|
+
r0 (float): Starting radius. Default: DEFAULT_R0
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
tuple[np.ndarray, np.ndarray]: (rr, phi) where rr is the radial coordinate
|
|
144
|
+
array and phi is the azimuthal angle array along the geodesic
|
|
145
|
+
"""
|
|
146
|
+
rmin = find_rmin(b, r_phs, inner_edge, radial_fun, radial_params, areal, areal_params)
|
|
147
|
+
rr = np.geomspace(r0, rmin, Npoints) # We must ensure rmin > 0 in find_rmin for np.geomspace
|
|
148
|
+
|
|
149
|
+
phi0 = b/rr[0] # good approximation for r0>>rSch and better than phi0=0
|
|
150
|
+
phi = integrate.odeint(
|
|
151
|
+
geodesic_in, phi0, rr, (b,radial_fun,radial_params,areal,areal_params)
|
|
152
|
+
).reshape(-1,)
|
|
153
|
+
phi = phi[~np.isnan(phi)]
|
|
154
|
+
rr = rr[:len(phi)]
|
|
155
|
+
|
|
156
|
+
# NOTE: does not work properly with high r0 and low Npoints for outgoing geodesics
|
|
157
|
+
# FIXME: si len(r_phs)=0, solo integra incoming.
|
|
158
|
+
if compute_outgoing(r_phs, inner_edge, rmin):
|
|
159
|
+
rout = rr[::-1]
|
|
160
|
+
phi0 = 2*phi[-1]-phi[-2] # Linear approx. for next angle. Required for strict monotonicity!!
|
|
161
|
+
phi_out = integrate.odeint(
|
|
162
|
+
geodesic_out, phi0, rout, (b,radial_fun,radial_params,areal,areal_params)
|
|
163
|
+
).reshape(-1,)
|
|
164
|
+
rr = np.concatenate([rr,rout])
|
|
165
|
+
phi = np.concatenate([phi,phi_out])
|
|
166
|
+
|
|
167
|
+
return (rr,phi)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__=="__main__":
|
|
171
|
+
|
|
172
|
+
def g_rr(r: float, dummy) -> float:
|
|
173
|
+
"""Radial function g^{rr} of the Schwarzschild metric in units of M=1"""
|
|
174
|
+
return 1 - 2./r
|
|
175
|
+
|
|
176
|
+
def g_thth(r: float, dummy) -> float:
|
|
177
|
+
"""Areal radius squared g^{theta theta} of the Schwarzschild in units of M=1"""
|
|
178
|
+
return r**2
|
|
179
|
+
|
|
180
|
+
# Dictionary codifying Schwarzschild metric and required params
|
|
181
|
+
Schwarzschild_kwargs = {
|
|
182
|
+
'r_phs': [3.,], # Photon sphere at r=3M
|
|
183
|
+
'inner_edge': 2., # Inner edge of disk at horizon r=2M
|
|
184
|
+
'radial_fun': g_rr, # Above defined radial function
|
|
185
|
+
'radial_params': None, # g_rr has no additional params
|
|
186
|
+
'areal': g_thth, # Above defined areal radius squared
|
|
187
|
+
'areal_params': None, # g_thth has no additional params
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
rr, phi = compute_geodesic(3, **Schwarzschild_kwargs)
|
|
192
|
+
print(rr, phi)
|