gr-vis is a ray-tracing program for visualizing black holes and wormholes.
This synthetic image depicts the visual appearance of an imagined wormhole connecting the Milky Way to the young star cluster NGC 602.
Image credits: Milky Way panorama by ESO/S. Brunier. NGC 602 by the James Webb Space Telescope, ESA/Webb, NASA & CSA, P. Zeidler, E. Sabbi, A. Nota, M. Zamani (ESA/Webb), adapted here to wrap a celestial sphere. Both source images are used under CC BY 4.0.
Gravitational lensing is an astrophysical phenomenon where massive objects
bend or distort the trajectory of light rays traveling through outer space,
producing effects such as Einstein rings. Lensing is expected to be
particularly strong around black holes and wormholes. The Python program
gr-vis creates synthetic images of these highly relativistic situations as
they would appear to an astronaut or a full-sky camera. The project is inspired
by the 2014 film Interstellar. The visual appearance of both the wormhole and
the Gargantua black hole was rendered by realistic calculations (see James et
al. on the wormhole and on
Gargantua).
According to the general theory of relativity, light rays propagate through
spacetime along null geodesics. Local spacetime curvature
will cause those null geodesics to converge or diverge relative to their
counterparts in flat spacetime. To model gravitational lensing, gr-vis
numerically integrates the geodesic equation for light rays. A scene is defined
by three configuration files that specify the spacetime geometry, the camera's
position and viewpoint, and the distant vistas on one or two celestial spheres.
At least in its current version, gr-vis presumes that the geometry is static
and that the camera is at rest in the given coordinate system. Starting from
the metric tensor, gr-vis symbolically derives the affine connection. For
each camera pixel, the engine launches a light ray from the camera and
propagates it along a null geodesic until the ray reaches one or the other
celestial sphere. The color of the sphere at the ray's final arrival point
sets the color of the image pixel.
The driver file for this program is main.py. It first creates a
Communicator object, which parses the command-line options and handles
terminal output and logging. The options are then used to initialize
Geometry, Camera, and Vista objects, which use JSON configuration files
to define the physical situation, and an Engine object that carries out the
ray tracing. The Engine object establishes a
(partial) correspondence between the coordinates of the camera's local sky and
the coordinates labeling points on the distant vistas. This computation is
completed via multi-core numerical integration of the light rays' equation of
motion, propagating the light rays from the camera to the asymptotically flat
regions. Finally, the synthetic image is output to disk.
The gr-vis source code does not require modification in order to be applied
to new situations. The user specifies the physical situation via the JSON
configuration files (see docs/components.md).
gr-vis currently assumes that the geometry is static, spherically
symmetric, and asymptotically flat.
This program requires Python 3.10 or later.
conda create -n gr-vis -c conda-forge python=3.12 numpy sympy pillow tqdm matplotlib pyfiglet
gr-vis requires:
numpysympyPillow
The other dependencies are optional:
tqdmdraws a progress bar.pyfigletdraws the startup banner.matplotlibis needed only for embedding diagrams (--embed).einsteinpyoffers a second route to the Christoffel symbols. Without it,gr-viscomputes them directly with SymPy.
To generate the image shown above, run these steps from the repository root.
First, obtain the sky images. They are not currently included in this
repository, but the reader can easily obtain them by visiting the following
sites. Download the "Large JPEG" of each and save it into
config/vistas/img/:
- The Milky Way panorama (ESO),
saved as
eso0932a.jpg - NGC 602 (ESA/Webb), saved as
weic2425a.jpg
Second, prepare the Webb image to wrap a celestial sphere. This removes the seam where its left and right edges meet, and the streaks where its top and bottom rows converge at the poles:
python tools/make_seamless.py config/vistas/img/weic2425a.jpg config/vistas/img/weic2425a_seamless.jpg
Third, render the image:
python main.py -g config/geometries/wormhole_throat_short.json -c config/cameras/wormhole_mouth_wide.json -v config/vistas/milky_way_ngc602.json -s out/wormhole.png --parallel 8
Add --dry-run to check the configuration without tracing.
| Option | Meaning |
|---|---|
-g, --geometry |
JSON file specifying the spacetime metric. |
-c, --camera |
JSON file specifying the observer and its pixels. |
-v, --vista |
JSON file naming the celestial-sphere images. |
-s, --save |
Output image path. The log goes beside it. |
--resolution W H |
Override the camera's resolution. |
--parallel N |
Trace with N worker processes. |
--step |
RK4 affine-parameter step. Smaller is more accurate. |
--max-steps |
Step budget per ray. |
--sampling |
area (4 rays/pixel, bilinear) or point (1, nearest). |
--supersample, --interpolation |
Override the --sampling preset. |
--save-map |
Save the ray map as .npz for later recoloring. |
--save-tensors |
Dump the symbolic metric and Christoffel symbols. |
--embed |
Show an interactive 3D embedding diagram. |
--dry-run |
Configure, report, and exit before tracing. |
--blur, --show |
Cosmetic smoothing; display when saved. |
Run python main.py --help for the full list.
gr-vis/
|-- main.py driver
|-- src/
| |-- communicator.py command line, console output, run log
| |-- geometry.py metric -> Christoffels -> compiled functions
| |-- camera.py observer frame and per-pixel ray directions
| |-- vista.py celestial-sphere images and sampling
| |-- engine.py vectorized RK4 ray tracer
| |-- embed.py embedding diagrams
| |-- helpers.py shared utilities
| +-- constants.py numerical defaults and program metadata
|-- tools/
| |-- apply_map.py recolor a saved ray map with a new vista
| |-- embed_diagram.py draw an embedding diagram on its own
| |-- make_seamless.py prepare an ordinary picture to wrap a sphere
| +-- registration_sweep.py align a sky map to the coordinates
|-- config/
| |-- geometries/ *.json spacetimes
| |-- cameras/ *.json observer placements
| +-- vistas/ *.json celestial-sphere image sets
|-- example/ the render above
|-- docs/
| |-- algorithm.md the physics and how it becomes an algorithm
| +-- components.md every field of every configuration file
|-- pyproject.toml
|-- README.md
+-- LICENSE
gr-vis is a passion project intended for educational use. It is still under
active development.
The source code is released under the MIT License; see LICENSE.
Gravitational lensing:
- A. Einstein, "Lens-Like Action of a Star by the Deviation of Light in the Gravitational Field," Science 84, 506-507 (1936).
Wormhole spacetimes:
-
H. G. Ellis, "Ether Flow through a Drainhole: A Particle Model in General Relativity," J. Math. Phys. 14, 104-118 (1973).
-
M. S. Morris and K. S. Thorne, "Wormholes in spacetime and their use for interstellar travel: A tool for teaching general relativity," Am. J. Phys. 56, 395-412 (1988).
Ray-tracing programs used for the film Interstellar:
-
O. James, E. von Tunzelmann, P. Franklin, and K. S. Thorne, "Visualizing Interstellar's Wormhole," Am. J. Phys. 83, 486-499 (2015).
-
O. James, E. von Tunzelmann, P. Franklin, and K. S. Thorne, "Gravitational lensing by spinning black holes in astrophysics, and in the movie Interstellar," Class. Quantum Grav. 32, 065001 (2015).
