This repository implements a numerical and symbolic analysis of a charged, dark matter embedded,
traversable wormhole, following the model of Hossain and Rahaman (2025), arXiv:2503.16111. The
package (src/) constructs the metric, evaluates energy conditions, integrates null geodesics, and
computes photon sphere and shadow properties for static and rotating configurations, optionally in
the presence of a plasma medium. Three executable scripts and a derivation notebook reproduce the
figures collected in plots/.
The codebase is organized as a small research package rather than a general purpose relativity library. Each module implements one piece of the physical model described in the source paper, and the top level scripts assemble these pieces into complete analysis pipelines.
Note on the correction below: the New Astronomy reference badge above points to the article's
correct DOI, 10.1016/j.newast.2023.102183. The original docs/references.bib and an earlier
version of this README carried a typo, 10.1016/j.newast.2024.102183, which does not resolve; the
in text citation in Section 10 has been corrected accordingly.
- Scientific Background
- Repository Architecture
- Module Documentation
- Executable Scripts
- Derivation Notebook
- Generated Figures
- Testing
- Configuration
- Installation
- References
The wormhole is embedded in a galactic dark matter halo whose energy density is modeled, as
implemented in src/dark_matter.py, as an exponentially decaying profile (Eq. 9 of the reference
paper):
where Sofue2013 (Milky Way rotation curve), which anchors the
choice of an exponential dark matter density as an empirically reasonable galactic halo model.
The wormhole geometry is a Morris-Thorne type traversable wormhole (MorrisThorne1988) generalized
to include an electric charge src/metric.py
(ChargedWormholeMetric) is (Eq. 3):
so that
The diagonal inverse metric components (inverse_metric) are simply the reciprocals of these terms.
The shape function src/shape_function.py, Eq. 11):
with config/params.yaml: wormhole.C1). The charge modifies this
into an effective shape function (Eq. 12):
which enters ShapeFunction supports both a NumPy evaluated form (b, used for
plotting and root finding) and a SymPy symbolic form (b_symbolic, b_eff_symbolic), the latter
used by the derivation notebook. Analytic derivatives b_prime(r) and b_eff_prime(r) are also
implemented in closed form (numeric only; they raise if passed a symbolic input).
ShapeFunction.find_throat() locates the wormhole throat radius scipy.optimize.root_scalar, bracketed search in [r_min, r_max], default [0.1, 10.0]) of the
standard throat condition
ShapeFunction.check_flaring_out(r) evaluates the Morris-Thorne flare out condition
which must hold at the throat for the geometry to represent a traversable wormhole rather than a horizon.
src/energy_conditions.py (EnergyConditions) evaluates the stress energy components implied by
the Einstein equations for this metric (Eqs. 4 to 8), given a ShapeFunction instance.
Total energy density (Eq. 4):
Dark matter energy density ShapeFunction.dm.density(r) (Eq. 9/10).
Electromagnetic energy density (Eq. 8):
A radial-pressure related quantity
check_NEC(r) reports whether the null energy condition holds radially, get_all(r) bundles
src/metric_rotating.py (RotatingChargedWormholeMetric) extends the static metric with a Teo type
rotation (Teo1998), parametrized by a spin parameter
the off diagonal term is
and the rotated azimuthal term is
The inverse metric is computed from the
src/geodesics.py (NullGeodesic) and src/geodesics_rotating.py (RotatingNullGeodesic)
implement a Hamiltonian formulation of null geodesic motion,
with an added refractive term scipy.integrate.solve_ivp (RK45, rtol=1e-8, atol=1e-10). As implemented, the
momentum components geodesic_equations; that is, every momentum component, including
RotatingNullGeodesic.find_photon_sphere() locates the equatorial photon sphere radius by
maximizing an effective potential
via scipy.optimize.minimize_scalar on
src/plasma.py (PlasmaProfile) supplies a plasma frequency squared function
profile_type:
profile_type |
|
|---|---|
homogeneous |
constant density_param
|
longitudinal |
density_param |
radial |
density_param |
spherical |
density_param |
| other |
Only the homogeneous case is treated specially by the shadow classes below
(through an explicit profile_type == 'homogeneous' check); the other
profiles feed only into the geodesic Hamiltonian.
Three shadow classes exist, sharing a common pattern. WormholeShadow (src/shadow.py) and
PlasmaShadow (src/shadow_plasma.py, functionally near identical to WormholeShadow but
structured to take a plasma profile explicitly) compute static wormhole shadows.
RotatingWormholeShadow (src/shadow_rotating.py) computes shadows for the Teo type rotating
metric.
In all three, the photon sphere radius
in the static case, or the rotating analogue described in Section 1.6, via
scipy.optimize.minimize_scalar.
generate_shadow and generate_shadow_image render the shadow as a binary pixel image on an
shadow_boundary (static classes) computes celestial coordinates via shadow_boundary (RotatingWormholeShadow) instead uses a Bardeen style
parametrization,
with
The dependency graph below traces every module from its configuration source through to the figures it ultimately feeds.
flowchart TD
CFG[config/params.yaml] --> DM[DarkMatterProfile]
CFG --> SF[ShapeFunction]
DM --> SF
SF --> MET[ChargedWormholeMetric]
SF --> METROT[RotatingChargedWormholeMetric]
SF --> EC[EnergyConditions]
MET --> GEO[NullGeodesic]
METROT --> GEOROT[RotatingNullGeodesic]
PL[PlasmaProfile] --> GEOROT
MET --> SHW[WormholeShadow and PlasmaShadow]
PL --> SHW
METROT --> SHWROT[RotatingWormholeShadow]
PL --> SHWROT
MET --> VIS[visualization.py]
EC --> VIS
SF --> VIS
SHW --> EXTVIS[extended_visualization.py]
SHWROT --> EXTVIS
VIS --> PLOTS[plots directory]
EXTVIS --> PLOTS
SF --> ADV[final_plots.py]
EC --> ADV
MET --> ADV
ADV --> PLOTSADV[plots/advanced directory]
The three entry point scripts, run_analysis.py, run_extended_analysis.py, and final_plots.py,
each drive a subset of this pipeline, described in Section 4.
dark_matter.py, class DarkMatterProfile. Implements the exponential halo density rho_s, r_s directly, or a config_path from which they are read via
yaml.safe_load. Consumed by ShapeFunction (which instantiates its own internal
DarkMatterProfile from the same rho_s, r_s) and directly by EnergyConditions.rho_0 and by
final_plots.py's shape function figure.
shape_function.py, class ShapeFunction. Implements DarkMatterProfile (instantiated internally
as self.dm). Consumed by ChargedWormholeMetric, RotatingChargedWormholeMetric, and
EnergyConditions.
metric.py, class ChargedWormholeMetric. Implements the static charged wormhole line element
(Eq. 3) and its diagonal inverse. Takes a ShapeFunction instance at construction and reads NullGeodesic, WormholeShadow, PlasmaShadow, and the
visualization/analysis scripts.
metric_rotating.py, class RotatingChargedWormholeMetric. Extends the static metric with Teo type
frame dragging parametrized by spin RotatingNullGeodesic and
RotatingWormholeShadow.
energy_conditions.py, class EnergyConditions. Implements the density and pressure expressions
(Eqs. 4 to 8) and the null energy condition check (Section 1.4). Depends on ShapeFunction (for
visualization.plot_energy_conditions,
plot_generator.plot_energy_conditions_comparison, and
final_plots.plot_energy_conditions_advanced / plot_comprehensive_summary.
geodesics.py, class NullGeodesic. Static metric Hamiltonian null geodesic integrator
(Section 1.6). Depends on ChargedWormholeMetric. Instantiated internally by WormholeShadow but
its integrate/hamiltonian methods are not otherwise called elsewhere in the analysis scripts.
geodesics_rotating.py, class RotatingNullGeodesic. Rotating metric analogue including a plasma
refractive term in the Hamiltonian, plus find_photon_sphere (Section 1.6). Depends on
RotatingChargedWormholeMetric and, optionally, PlasmaProfile.
plasma.py, class PlasmaProfile. Plasma frequency squared profiles selectable by name
(Section 1.7). Consumed by the geodesic and shadow classes wherever a plasma medium is modeled.
shadow.py, class WormholeShadow. Static wormhole photon sphere finder, shadow boundary curve
(shadow_boundary, structurally degenerate as noted in Section 1.8), and pixel disk shadow image
(generate_shadow). Depends on ChargedWormholeMetric, optionally PlasmaProfile; internally
constructs a NullGeodesic that is unused by its own methods. Driven by run_extended_analysis.py.
shadow_rotating.py, class RotatingWormholeShadow. Rotating wormhole analogue: effective potential
and photon sphere including the frame dragging cross term, a Bardeen style
celestial_coordinates/shadow_boundary pair that is the one implementation whose boundary curve is
actually populated with nonzero points, and a pixel disk generate_shadow_image. Depends on
RotatingChargedWormholeMetric, optionally PlasmaProfile. Driven by run_extended_analysis.py.
shadow_plasma.py, class PlasmaShadow. A static wormhole shadow class parallel to
WormholeShadow, requiring a PlasmaProfile at construction; its shadow_boundary has the same
src/__init__.py.
visualization.py. Three baseline plotting functions used by run_analysis.py:
plot_shape_functions plots plot_energy_conditions renders a two by two panel of plot_metric_components plots
extended_visualization.py. Plotting helpers driven by run_extended_analysis.py:
plot_plasma_effect_series and plot_rotation_effect_series render grids of pixel disk shadow
images labeled by plasma density plot_kerr_vs_wormhole_comparison places a
schematic Kerr shadow (see run_extended_analysis.create_kerr_shadow, Section 4) side by side with
a computed wormhole shadow image; plot_shadow_boundary_comparison overlays multiple
plot_eht_constraints fills caller supplied polygonal allowed
regions, labeled for example M87* or Sgr A*, on an
plot_generator.py. A more elaborate, largely parallel set of plotting functions: four panel shape
function and energy condition comparisons, a deflection angle comparison against a Schwarzschild
reference impact_params, deflection_data, R_values, and so on) as
arguments; they are not called from any of the three top level scripts and are not currently wired
into a generation pipeline in this repository.
Loads config/params.yaml, builds DarkMatterProfile, ShapeFunction, ChargedWormholeMetric,
and EnergyConditions from the configured parameters, and evaluates them on
n_points samples. It then:
- Calls
plot_shape_functions, savingplots/shape_functions/shape_analysis.png, and prints the throat radius and$b_{\text{eff}}'(r_0)$ iffind_throat()succeeds. - Calls
plot_energy_conditions, savingplots/energy_conditions/energy_conditions.png. - Calls
plot_metric_components, savingplots/metric_components.png.
The script also creates a larger set of output directories
(plots/shadows/{static,rotating,plasma}, plots/comparisons/{kerr_vs_wormhole, plasma_comparisons, parameter_space, observational}) in anticipation of the shadow and comparison
figures produced by run_extended_analysis.py, though it does not itself populate them.
Loads the same configuration and constructs a static metric (ChargedWormholeMetric), then:
- Static shadow:
WormholeShadow(metric_static).generate_shadow(200)producesplots/shadows/static/static_vacuum_shadow.png. - Plasma shadows: homogeneous plasma shadows for
$\rho \in {0, 0.3, 0.5, 0.7}$ , combined viaplot_plasma_effect_seriesintoplots/comparisons/plasma_comparisons/plasma_effect_series.png. - Rotating shadows:
RotatingChargedWormholeMetric(sf, a)shadows for$a \in {0, 0.3, 0.6, 0.9}$ , combined viaplot_rotation_effect_seriesintoplots/comparisons/parameter_space/rotation_effect_series.png. - Kerr comparison: a schematic Kerr black hole shadow is synthesized by
create_kerr_shadow(a=0.9), a simple circular disk of radius$r_{\text{shadow}} = 6(1-0.3a)$ pixels, not a geodesic based Kerr shadow computation, compared against the$a=0.9$ wormhole shadow image from step 3 viaplot_kerr_vs_wormhole_comparisonintoplots/comparisons/kerr_vs_wormhole/kerr_comparison.png. - Shadow boundaries: static vacuum, static plasma (
$\rho=0.5$ ), and rotating ($a=0.9$ ) boundary curves via each shadow class'sshadow_boundary(), combined viaplot_shadow_boundary_comparisonintoplots/comparisons/shadow_boundaries/boundary_comparison.png. - Parameter space: a
$20 \times 20$ grid over spin$a \in [0, 0.9]$ and plasma density$\rho \in [0, 0.7]$ ; at each grid point,RotatingWormholeShadow(metric_rot, plasma).shadow_boundary()is evaluated and its root mean square radius$\sqrt{\langle \alpha^2 + \beta^2 \rangle}$ is recorded as$Z[i,j]$ , rendered as a 3D surface intoplots/comparisons/parameter_space/3d_parameter_space.png. - EHT constraints: schematic, hand specified polygonal allowed regions labeled M87* and Sgr A* on
the
$(a,\rho)$ plane, viaplot_eht_constraintsintoplots/comparisons/observational/eht_constraints.png. These polygons are illustrative inputs defined directly in the script and are not derived from an observational constraint calculation within the repository.
A standalone advanced publication plots script. It loads config/params.yaml and constructs
ShapeFunction and EnergyConditions as in run_analysis.py, then produces seven figures in
plots/advanced/, falling into two categories.
Figures computed from the repository's physics classes:
-
plot_shape_function_advanced: six panels covering$b(r)$ ,$b_{\text{eff}}(r)$ , the flare out ratio$b_{\text{eff}}(r)/r$ ,$b_{\text{eff}}'(r)$ , the dark matter density$\rho_w(r)$ (viaDarkMatterProfile), and a text summary of the parameters and throat properties. -
plot_energy_conditions_advanced: six panels covering$\rho(r)$ , NEC radial, NEC tangential, SEC ($\rho + P_r + 2P_t$ ), a pressure comparison ($P_r$ ,$P_t$ ), and a text summary of which conditions are satisfied or violated. -
plot_comprehensive_summary: six panels combining$b_{\text{eff}}(r)$ , the three energy condition combinations, the pressures, the metric components$g_{tt}$ /$g_{rr}$ (viaChargedWormholeMetric), the flare out ratio, and a text summary table.
Figures using illustrative or schematic representative formulas, not computed from the repository's shadow or geodesic classes:
-
plot_shadow_radius_advanced: shadow radius versus spin and versus plasma density curves for Kerr, vacuum, plasma, and rotating cases, all defined by hand picked polynomial formulas in$a$ and$\rho$ (for example $R_{\text{vacuum}} = 5.2(1 + 0.3a + 0.05a^2)$), together with a table of representative M87*/Sgr A* observational values ($11 \pm 1.5,M$ ,$9.5 \pm 1.4,M$ ). -
plot_deflection_advanced: deflection angle curves of the schematic form$\frac{4}{b}\left(1 + \frac{\alpha}{b}\right)$ compared against the Schwarzschild reference$\frac{4}{b}$ , for representative values of a parameter$\alpha$ ; these are illustrative curve shapes, independent of the model's actual$\rho_s$ ,$r_s$ ,$C_1$ ,$Q$ parameters. -
plot_eht_advanced: a parameter space polygon plot, a bar chart comparison of representative shadow radii against M87*/Sgr A* observational bands, and a contour plot of a hand specified formula$R(a,\rho) = \frac{5.2}{\sqrt{1-\rho+0.01}}\left(1 + 0.3a + 0.05a^2\right)$ . -
plot_shadow_boundary_comparison_advanced: circular boundary curves using the same representative radius formulas as above, rather than the geodesic derivedshadow_boundary()methods inshadow.py/shadow_rotating.py.
These schematic figures illustrate expected qualitative trends and frame the model against real EHT
results, but the numeric values they display are not outputs of the metric, geodesic, or shadow
computations implemented elsewhere in src/.
The notebook loads config/params.yaml and instantiates DarkMatterProfile, ShapeFunction, and
ChargedWormholeMetric exactly as run_analysis.py does. It then re-derives the shape function
symbolically with SymPy, r, theta = sp.symbols(...),
and prints the corresponding symbolic config/params.yaml at the time the notebook was last executed,
and a plt.subplots(1, 2, ...), reproducing the visualization.plot_metric_components) is present with its rendered output
embedded in the notebook. notebooks/plots/shadow_analysis_improved.png and
shadow_radius_corrected.png are referenced image assets stored alongside the notebook but are not
generated by any cell shown in the notebook as provided; they are reproduced in Section 6.4 below.
Three parallel sets of figures exist, corresponding to the three generation scripts described in
Section 4. In each set, the shape function, energy condition, and comprehensive summary panels are
grounded in the repository's ShapeFunction, EnergyConditions, and ChargedWormholeMetric
computations, while the shadow radius, deflection angle, and EHT constraint panels produced by
final_plots.py use illustrative representative formulas rather than the repository's own
geodesic/shadow classes (shadow.py, shadow_rotating.py), as detailed in Section 4.
| Figure | Description |
|---|---|
![]() |
01_shape_function.png |
![]() |
02_energy_conditions.png |
![]() |
03_shadow_comparison.png |
![]() |
04_deflection_angle.png |
![]() |
05_eht_constraints.png |
| Figure | Description |
|---|---|
![]() |
01_shape_function_enhanced.png |
![]() |
02_energy_conditions_enhanced.png |
![]() |
03_shadow_radius_analysis.png |
![]() |
04_comprehensive_comparison.png |
![]() |
05_eht_constraints_enhanced.png |
| Figure | Description |
|---|---|
![]() |
shadow_analysis_improved.png |
![]() |
shadow_radius_corrected.png |
As noted in Section 5, these two images are stored alongside 01_metric_derivation.ipynb but are
not generated by any cell shown in the notebook as provided.
TestMetric (pytest) constructs a ShapeFunction/ChargedWormholeMetric pair with
and that metric_tensor(r, theta=pi/4) contains all four expected keys, with
test_metric_tensor).
Run with:
pytest tests/No tests currently cover EnergyConditions, the geodesic integrators, the plasma profiles, the
rotating metric, or the shadow classes.
dark_matter:
rho_s: 0.01 # central dark matter density (working value)
r_s: 1.0 # dark matter halo scale radius
wormhole:
Q: 0.0 # electric charge (default: uncharged limit)
C1: 1.0 # integration constant in b(r)
analysis:
r_min: 0.5
r_max: 10.0
n_points: 1000
r_throat: 1.0rho_s, r_s, C1, and Q are consumed by ShapeFunction/DarkMatterProfile, directly, or via
each class's optional config_path argument. r_min, r_max, and n_points are read by
run_analysis.py to build the radial sampling grid used for the shape function, energy condition,
and metric component plots. analysis.r_throat is present in the configuration file but is not read
by any of the scripts or modules examined in this repository; the throat location is instead computed
numerically via ShapeFunction.find_throat().
pip install -r requirements.txtrequirements.txt specifies numpy, scipy, sympy, matplotlib, pandas, plotly, seaborn,
pyyaml, jupyter, tqdm, pytest, and imageio. Of these, the modules examined in this
repository import numpy, scipy (optimize, integrate), sympy, matplotlib, yaml, and
tqdm directly; pytest runs the test suite; jupyter is required to execute the derivation
notebook. pandas, plotly, seaborn, and imageio are declared as dependencies but are not
imported by any source file examined here (matplotlib's seaborn-v0_8-paper/seaborn-v0_8
styles are used via plt.style.use, which does not require importing the seaborn package itself).
Gravitational Lensing Due to Charged Galactic Wormhole (2025). Modules: metric.py,
shape_function.py, energy_conditions.py.
Full citation: M. K. Hossain, F. Rahaman, Int. J. Geom. Methods Mod. Phys. 22, 2550151 (2025),
arXiv:2503.16111 [gr-qc]. It proposes a charged galactic
wormhole metric built on an exponential dark matter density profile of the Sofue (2013) type, and
analyzes the resulting spacetime, embedding surface, and light deflection. This is the primary
paper implemented here: the metric (Eq. 3, metric.py), the shape function (Eqs. 11 to 12,
shape_function.py), and the energy conditions (Eqs. 4 to 8, energy_conditions.py) are direct
translations of its equations, as documented in Section 1 above.
Shadows of Lorentzian Traversable Wormholes (2021). Modules: shadow.py, shadow_rotating.py.
Full citation: F. Rahaman, Ksh. N. Singh, R. Shaikh, T. Manna, S. Aktar, Class. Quantum Grav. 38,
215007 (2021), arXiv:2108.09930 [gr-qc]. It investigates the
shadows cast by rotating traversable wormholes, studying how wormhole parameters affect photon
orbits and the shadow's shape and size. It provides the underlying methodology for the photon
sphere and shadow boundary computations in shadow.py and shadow_rotating.py (Section 1.8),
applied here to the charged galactic wormhole metric of Hossain and Rahaman (2025) instead of the
vacuum rotating wormhole treated in the original paper.
Dark Matter Supporting Traversable Wormholes in the Galactic Halo (2024). Modules: dark_matter.py,
config/params.yaml.
Full citation: S. Sarkar, N. Sarkar, S. Aktar, M. Sarkar, F. Rahaman, A. K. Yadav, New Astronomy
109, 102183 (2024), doi:10.1016/j.newast.2023.102183.
It studies static wormholes embedded in the Milky Way's galactic halo using the Einasto dark matter
density profile, analyzing the properties and viability of dark matter supported wormholes. It
grounds the concept of a dark matter supported wormhole in a realistic galactic context, motivating
the exponential halo density dark_matter.py and
parametrized in config/params.yaml. Note that this repository implements the exponential profile
of Eq. 9 in Hossain and Rahaman (2025) rather than the Einasto profile used in Sarkar et al. (2024).
Sofue, Y. (2013), Rotation curve of the Milky Way, Publ. Astron. Soc. Japan 65, S5. The
observational Milky Way rotation curve work that motivates the exponential galactic dark matter
density profile used in dark_matter.py (see also Sarkar et al. 2024 above).
Morris, M. S. and Thorne, K. S. (1988), Wormholes in spacetime and their use for interstellar
travel, Am. J. Phys. 56(5), 395 to 412. The foundational traversable wormhole framework underlying
the throat condition shape_function.py.
Teo, E. (1998), Rotating traversable wormholes, Phys. Rev. D 58, 024014. The basis for the Teo type
rotating metric extension implemented in metric_rotating.py.
This repository extends the charged galactic wormhole model of Hossain and Rahaman (2025) by adding:
- Rotation: Generalizing the static metric to a Teo-type rotating wormhole (
metric_rotating.py) - Plasma: Adding homogeneous plasma environments (
plasma.py,shadow_plasma.py) - Shadows: Computing photon sphere shadows for the charged wormhole (
shadow.py,shadow_rotating.py) - Observational Constraints: Comparing shadows with EHT M87*/Sgr A* data (
final_plots.py)
The original 2025 paper analyzed lensing and energy conditions. This work adds shadow analysis, plasma effects, and rotation to make the model observationally testable.
| Finding | Value | Status |
|---|---|---|
| Throat radius | r0 = 0.5048 | Found |
| Flaring-out condition | b_eff'(r0) = 0.0367 < 1 | Passed |
| Null Energy Condition | rho + P_r < 0 at throat | Violated (Exotic matter) |
| Shadow radius (static) | R ~ 5.2 M | Within EHT range |
| Shadow radius (rotating) | R ~ 6.8 M | Within EHT range |
| Shadow radius (plasma) | R ~ 8.5 M | Within EHT range |
| Spin effect | Shadow grows with spin | Observed |
| Plasma effect | Shadow grows with plasma density | Observed |
The charged galactic wormhole is a valid traversable wormhole with observable shadows distinguishable from Kerr black holes.
| Property | Kerr BH | Charged Wormhole (This Work) |
|---|---|---|
| Shadow Radius | R ~ 5.5 M | R ~ 5.2 - 8.5 M |
| NEC | Satisfied | Violated (Exotic matter) |
| Plasma Effect | Weak | Strong |
| EHT Compatibility | Marginal | Consistent |
- The wormhole throat exists at r0 = 0.5048 with the flaring-out condition satisfied (b_eff'(r0) = 0.0367 < 1).
- The Null Energy Condition is violated at the throat, confirming the presence of exotic matter required for traversability.
- Shadows grow with both spin and plasma density, providing clear observational signatures.
- Shadow radii (R ~ 5.2 - 8.5 M) are consistent with EHT observations of M87* and Sgr A*.
- The wormhole shadow is distinguishable from Kerr black hole shadows, making it observationally testable.
If you use this code or results in your research, please cite:
-
Hossain, M. K. & Rahaman, F. (2025). Gravitational lensing due to charged galactic wormhole. Int. J. Geom. Methods Mod. Phys. 22, 2550151. [arXiv:2503.16111]
-
Rahaman, F. et al. (2021). Shadows of Lorentzian traversable wormholes. Class. Quantum Grav. 38, 215007. [arXiv:2108.09930]
-
Sarkar, S. et al. (2024). Dark matter supporting traversable wormholes in the Galactic halo. New Astron. 109, 102183.
-
This repository: https://github.com/Soyebsoyeb/charged-galactic-wormhole-shadow-analysis


















