This release is a pre-release and may not be stable for production use.
gwbpy
Gravitational-wave and pulsar visualization for Blender.
gwbpy builds parameterized, animated scenes in Blender for talks, posters and teaching: compact binaries, gravitational-wave polarization, pulsars, pulsar timing arrays (PTAs), and a rubber-sheet picture of spacetime. Orbits of inspiralling binaries come from the SEOBNRv5HM effective-one-body model [1, 2].
Development status: alpha. Function names and parameters may change between releases. Read "Physical model" and "Validation" before using a figure in a publication.
Functions
| Function | Purpose |
|---|---|
animate_binary_physical |
Two black holes or neutron stars on an inspiral and merger (SEOBNRv5HM) or on a circular orbit |
animate_gw_polarization |
Ring or tube of free test particles deformed by a gravitational wave; standalone or linked to a circular binary |
animate_pulsar |
Spinning neutron star with beams and an optional observer marker |
animate_pta |
Pulsars around an observer, with pulses along the lines of sight; with a circular binary in the scene, its wave shifts the pulse arrival times |
plot_residuals |
Timing residuals of one pulsar as a Matplotlib image in the scene |
animate_spacetime |
Rubber-sheet grid with a dent under each mass and ripples from the binary |
draw_pta_geometry |
Arrows, axes, angle arcs and labels for a PTA geometry diagram |
frame_scene, camera_tour, camera_orbit, camera_targets |
Camera framing and motion |
clear_scene |
Remove the objects gwbpy created |
docs |
Print the parameters of any function |
Requirements
- Blender 5.0 (tested). gwbpy runs in Blender's bundled Python (3.11.13 in the tested build) and uses Blender's NumPy. It has no other Python dependency inside Blender.
- Inspiral binaries (the default mode of
animate_binary_physical) andplot_residualsalso need a separate Python 3.11 environment containing pyseobnr, lalsuite and Matplotlib.gwbpy.setup()builds it on Linux, or inside WSL 2 when Blender runs on Windows. Tested with Ubuntu 24.04, pyseobnr 0.3.7 and lalsuite 7.26.16. - System packages for that build (Debian/Ubuntu names):
python3-venv,libgsl-dev,swig,build-essential,libfftw3-dev,libhdf5-dev.setup()checks for python3, python3-venv, gsl-config, swig and gcc. FFTW3 and HDF5 are listed among the system dependencies on pyseobnr's PyPI page;setup()does not check for them.
Circular binaries, polarization rings, pulsars, PTA scenes, spacetime sheets and the camera functions need none of the physics environment.
Installation
gwbpy is currently released as a pre-release, so pip needs --pre.
-
In Blender's Scripting tab, run once:
import sys, subprocess, bpy mods = bpy.utils.user_resource('SCRIPTS', path="modules", create=True) subprocess.run([sys.executable, "-m", "pip", "install", "--no-deps", "--pre", "--target", mods, "gwbpy"]) -
Quit Blender completely and reopen it.
-
Check the installation:
import gwbpy print(gwbpy.__version__, gwbpy.__file__)The printed path should be inside the
scripts/modulesfolder of the Blender user directory. -
Only for inspiral binaries and
plot_residuals: install the system packages once, in a Linux or WSL terminal,sudo apt update && sudo apt install -y python3-venv libgsl-dev swig build-essential libfftw3-dev libhdf5-devthen build the physics environment from Blender:
import gwbpy gwbpy.setup()
Notes:
--no-depsprevents pip from installing a second NumPy next to the one Blender ships.- To update, quit Blender, delete the
gwbpyandgwbpy-*.dist-infofolders from that modules folder, and run step 1 again. - Each Blender version has its own modules folder; repeat step 1 for a new version.
- Offline: replace
"gwbpy"in step 1 with the path of a downloaded wheel and drop--pre. setup()creates~/.gwbpy/dynamics-env(inside the default WSL distribution on Windows; set the environment variableGWBPY_WSL_DISTROto choose another). It installs uv, which obtains a Python 3.11 interpreter if none is present, and builds pygsl-lite, a pyseobnr dependency, from source without build isolation against setuptools older than 70, because its build script fails under current setuptools. The first run took about 11 minutes on the tested system, and is skipped when a working environment exists. Usegwbpy.setup(force=True)to rebuild. If system packages are missing, it stops and prints the command to run.
To work on gwbpy itself, add a clone of the repository to Blender's path at the start of a session instead of installing it:
import sys
sys.path.insert(0, r"PATH_TO_REPOSITORY")
import gwbpy
Quick start
A circular binary (no physics environment needed):
import gwbpy
gwbpy.clear_scene()
gwbpy.animate_binary_physical(m1=30, m2=30, inspiral=False,
initial_separation_km=2000, period_frames=100,
frames=200, meters_per_unit=300000.0)
gwbpy.frame_scene("binary", view="iso", margin=2.0)
Set the 3D viewport to Material Preview (colors and glow are not shown in Solid mode) and press Space to play.
Examples
A merging pair of black holes (needs the physics environment):
import gwbpy
gwbpy.animate_binary_physical(m1=30, m2=15, merge_frame=100, frames=200,
f_lower=80.0, meters_per_unit=20000.0)
A black hole and a neutron star (m1 is the black hole for "BHNS"):
import gwbpy
gwbpy.animate_binary_physical(m1=5, m2=1.4, system="BHNS", f_lower=300,
merge_frame=250, meters_per_unit=500.0)
A circular binary with two polarization rings, face-on and edge-on:
import gwbpy
gwbpy.animate_binary_physical(m1=30, m2=30, inspiral=False,
initial_separation_km=2000, period_frames=60,
frames=240, meters_per_unit=300000.0)
gwbpy.animate_gw_polarization(h=0.25, inclination_deg=0, name="face_on")
gwbpy.animate_gw_polarization(h=0.25, inclination_deg=90, name="edge_on")
A PTA whose pulse arrival times are shifted by the binary's wave, with a residual plot (needs the physics environment for the plot only). The observer lies in the binary's orbital plane:
import gwbpy
gwbpy.animate_binary_physical(m1=30, m2=30, inspiral=False,
initial_separation_km=2000, period_frames=100,
frames=400, meters_per_unit=300000.0,
body_scale=3.0)
res = gwbpy.animate_pta(observer_position=(35, 0, 0), distance=8,
period_frames=10, gw_shift_frames=4,
travel_frames=38, pulsar_radius=0.4,
observer_size=1.0, observer_texture="earth")
gwbpy.plot_residuals(res, pulsar=0)
A rubber-sheet picture of an inspiral, with the camera kept inside the sheet:
import gwbpy
gwbpy.animate_binary_physical(m1=30, m2=15, merge_frame=200, frames=260,
f_lower=40.0, meters_per_unit=20000.0)
gwbpy.animate_spacetime(size=80, resolution=100, dent_depth=14.0,
dent_width=3.0, ripple_height=2.0)
gwbpy.frame_scene("binary", view="iso", margin=1.5, hide_edges=True)
A camera move from the binary to the PTA:
gwbpy.camera_tour([("binary", 1, "iso"), ("pta", 200, "iso")],
transition=80, margin=1.5)
Documentation
Run gwbpy.docs() for a list of functions and gwbpy.docs("animate_pta") for the
signature and every parameter of one function. Each function also prints what it did,
including warnings, to Blender's console; on Windows, open Window > Toggle System
Console to see it.
Physical model
Each visualization combines a model of the physics with presentation choices. The model and its approximations are listed here; the presentation choices are listed last.
Inspiral binaries (inspiral=True). The separation r and orbital phase of the two
bodies are the effective-one-body radial coordinate and orbital phase of SEOBNRv5HM [2],
generated with pySEOBNR [1] for non-spinning bodies. r is a coordinate quantity, not a
horizon position. Each body sits at its Newtonian centre-of-mass fraction of r. Black
holes are drawn as spheres at their Schwarzschild radius, 2GM/c². Neutron stars are drawn
at a fixed radius (ns_radius_km, default 12 km), which is a parameter and not a
prediction. For systems containing neutron stars the orbit is still the binary black hole
model, stopped when the spheres touch; tides, disruption and matter effects are not
modelled. Real data are used until the spheres touch or the dynamics end, whichever comes first.
An optional closing step (close_merger, on by default) then reduces the separation
linearly to zero at the last measured radial rate (lasting at least one frame and at most
max_close_frames frames, so faster than measured if capped) while the orbital angle
advances at the last measured rate; the spheres then stay merged for the rest of the
clip. It is an animation device and not physics. The
(2,2) mode of the strain is generated by the same call, aligned in time with the orbit by
a fit (the fitted offset and the agreement of the phases are printed on every run), and
stored for the polarization ring and the spacetime sheet. Other modes are not used.
Circular binaries (inspiral=False). Newtonian Kepler orbit with constant
separation. A warning is printed below a separation of 10 M (M = G(m1+m2)/c²), where
this approximation is poor.
Polarization ring. For a circular binary at inclination ι to the line of sight, the strain is h₊ = −A (1 + cos²ι)/2 · cos 2Φ and h× = −A cos ι · sin 2Φ, with Φ the orbital phase. The ratio of the two amplitudes, 2 cos ι/(1 + cos²ι), agrees with Eqs. (32)-(33) of [10], and the polarization state (circular along the orbital axis, linear in the orbital plane, elliptical in between) agrees with the description in [10]. Eq. (32a) of [10] has the opposite overall sign if the phases are identified; the phase conventions have not been matched, and gwbpy's sign was set by the implementation. Test particles are displaced by δx = (h₊x + h×y)/2 and δy = (h×x − h₊y)/2 in the transverse-traceless gauge to first order in h. See [9] for a textbook treatment of gravitational-wave generation.
Pulsar timing. The arrival time of a pulse is delayed by
delay(t) = [G(t) − G(t − τ)] / [2 (1 + Ω̂·p̂)],
G(t) = ∫ p̂ⁱ p̂ʲ h_ij dt, τ = L (1 + Ω̂·p̂) / c,
where p̂ points from the observer to the pulsar, L is the distance, and Ω̂ is the direction of propagation of the wave. The first term is the wave at the observer and the second is the wave at the pulsar. The expression has the structure of the standard redshift of [6, 7] (the factor 1/(1 + Ω̂·p̂), an observer term, a pulsar term, and a retardation L(1 + Ω̂·p̂)/c), written for example as Eqs. (8) and (15) of [8]. gwbpy's sign convention: a positive strain along the line of sight lengthens the light travel time, and a positive delay means a later arrival. In the long-wavelength limit Eq. (8) of [8], with the redshift as defined there, has the opposite overall sign; this has not been reconciled. The pulsars do not move; the effect is a change of arrival times.
Spacetime sheet. The ripples are twice the plus polarization of the (2,2) strain seen in the orbital plane (analytic for a circular binary, from the model for an inspiral), evaluated at the retarded time t − ρ/c_vis, with amplitude falling as 1/√(1 + (ρ/ρ_core)²) and faded inside the orbit. The dents are the rubber-sheet analogy and are not spacetime curvature. Depth is proportional to mass; on a flat sheet the spheres of a binary with unequal masses cannot all rest at the bottom of their dents, and the console reports how far they hover.
Presentation choices (not physics).
- The strain displayed by the polarization ring and the sheet is greatly exaggerated; the console prints the factor relative to 10⁻²¹, the peak strain of GW150914 [11].
- The timing shifts displayed in PTA scenes are far larger than real residuals.
- Spin periods, light travel times and distances in pulsar and PTA scenes are not to scale, and pulses in flight are drawn with a uniform spike or sphere.
- The speed of the ripples on the sheet and the heights of the ripples and dents are chosen for legibility.
- Real time is stretched to the chosen frame range; the stretch factor is printed.
- Glows, colors and sphere sizes are styling. The beams of a pulsar are opaque cones, and
its marker brightens whenever a beam points at the observer; the flash lasts at least
flash_framesframes, longer than the real pulse.
Validation
Modules with non-trivial geometry provide a self_test() function (pure Python, runs
outside Blender): gwwave, spacetime, camera, annotate, plotimage, scene and
reference. For example:
from gwbpy import gwwave
gwwave.self_test()
What the tests establish:
- The timing delay agrees with a direct numerical integration of the light travel time along the path for 36 random geometries (largest difference 5 × 10⁻⁹ frames).
- The observer-term response, averaged over wave directions and polarizations, reproduces the Hellings-Downs curve [5], Γ(x) = 1/2 + (3/2) x ln x − x/4 with x = (1 − cos ζ)/2 (the same form as Eq. (160) of [8]): largest deviation 0.0010 for pulsar separations of 20°, 45°, 82°, 120° and 180° (4 × 10⁵ random directions).
- A pulsar in the direction of the source has no timing shift; a steady strain track reproduces the circular-binary strain; face-on strain has equal plus and cross amplitudes, and edge-on strain is plus only at half the face-on amplitude.
- For inspiral binaries, the orbital phase agrees with half the (2,2) strain phase to 0.003 rad early in the inspiral and 0.016 rad late (the worker prints this on every run, measured up to 20 M before the end of the dynamics).
What has not been done:
- No comparison with numerical-relativity simulations or with other waveform codes beyond the published validation of SEOBNRv5HM [2].
- No comparison with independent PTA software.
- The overall sign of the timing delay has not been reconciled with Eq. (8) of [8], and the sign and phase conventions of the polarization ring have not been matched to a reference (see Physical model).
- Rendering has been tested on Blender 5.0 on Windows with WSL 2 only. macOS, other Linux distributions, Windows without WSL, and a build on a machine lacking the system packages have not been tested.
Limitations
- SEOBNRv5HM does not start closer than a separation of 10 M. A requested start frequency above 2044/(m1 + m2) Hz (masses in solar masses) is lowered to that value and a note is printed, so every inspiral has at least about five orbits.
- Systems with long inspirals, for example two neutron stars from tens of hertz, are
refused above
max_orbitsbecause they alias and need very many keyframes. - If the console warns of more than half a turn per frame, the motion will look aliased.
Raise
merge_frameor use a higherf_lower. - The spacetime sheet stores one shape key per frame. The cost is printed first, and the
function refuses above
max_cost. hide_edgesworks from the flat outline of the sheet, never zooms out, and cannot help for views toward the horizon of the sheet (it says so).- Only non-spinning, quasi-circular binaries are modelled. Spin, precession, eccentricity and higher modes are not.
- Colors, glow and images appear in Material Preview and in renders, not in Solid mode.
Troubleshooting
import gwbpyfails after installation: quit Blender completely and reopen it.- Python's built-in
help()can freeze Blender on long output because it opens a pager that waits for a keypress in the System Console window. Click that window and pressq, or usegwbpy.docs(). setup()prints the command to run when system packages are missing.
AI assistance
gwbpy was developed by its author with assistance from Claude, a large language model from Anthropic. The author conceived the project, specified its features and scientific content, selected the physics models and libraries, made the design decisions, wrote and adapted the scripts that build the example scenes, and tested the package in Blender. Claude wrote most of the package source code, the unit tests, the build scripts and the documentation text, and proposed the implementation of the physical expressions listed under Physical model; these are checked by the unit tests described under Validation and have not been compared with independent implementations. The author is responsible for the package.
Citing
If you use gwbpy, please cite it:
E. Eisenberg, gwbpy: gravitational-wave and pulsar visualization for Blender
(2026), https://pypi.org/project/gwbpy/
Inspiral binaries use pySEOBNR and SEOBNRv5HM, whose authors ask that results derived from them cite the software and the model [1, 2, 15]. Their environment also uses LALSuite, whose authors ask for citation of the suite and, for the Python interface, SWIGLAL [3, 4].
Acknowledgments and third-party material
- Software used: pySEOBNR [1] and SEOBNRv5HM [2]; LALSuite [3, 4]; NumPy [12]; SciPy [13]; Matplotlib [14]; Blender [17].
- The polarization pictures follow the conventions of [10]; the animations at https://maxisi.github.io/gwpols/ are by Max Isi and are licensed CC-BY-SA 4.0. They are not reused in gwbpy.
- The Earth texture bundled with gwbpy (
gwbpy/textures/earth.jpg, used withobserver_texture="earth") is "Equirectangular Projected Earth for LARGEST" (NASA Scientific Visualization Studio, https://svs.gsfc.nasa.gov/3615/): NASA/Goddard Space Flight Center Scientific Visualization Studio; the Blue Marble Next Generation data are courtesy of Reto Stockli (NASA/GSFC) and NASA's Earth Observatory. Its look was art-directed for a film and is not a scientific rendering. NASA states that content of this kind, including texture maps, is generally not subject to copyright in the United States, may be used for educational or informational purposes, and should be acknowledged [16]. This use does not imply NASA endorsement.
License
gwbpy is released under the GNU General Public License, version 3 or (at your option) any
later version (GPL-3.0-or-later); see LICENSE. pyseobnr is GPL-3.0-or-later and LALSuite
is GPL-2.0-or-later. Blender's license requires published Python scripts that use its API
to be shared under a GPL-compatible license [17]. The Earth image has the terms described
above and is not covered by this license.
References
- D. P. Mihaylov, S. Ossokine, A. Buonanno, H. Estellés, L. Pompili, M. Pürrer and A. Ramos-Buades, "pySEOBNR: a software package for the next generation of effective-one-body multipolar waveform models," SoftwareX 30, 102080 (2025), doi:10.1016/j.softx.2025.102080, arXiv:2303.18203.
- L. Pompili, A. Buonanno, H. Estellés, M. Khalil, M. van de Meent, D. P. Mihaylov, S. Ossokine, M. Pürrer, A. Ramos-Buades, et al., "Laying the foundation of the effective-one-body waveform models SEOBNRv5: improved accuracy and efficiency for spinning non-precessing binary black holes," Phys. Rev. D 108, 124035 (2023), doi:10.1103/PhysRevD.108.124035, arXiv:2303.18039.
- LIGO Scientific Collaboration, Virgo Collaboration and KAGRA Collaboration, "LVK Algorithm Library - LALSuite," free software (GPL), doi:10.7935/GT1W-FZ16 (2018).
- K. Wette, "SWIGLAL: Python and Octave interfaces to the LALSuite gravitational-wave data analysis libraries," SoftwareX 12, 100634 (2020), doi:10.1016/j.softx.2020.100634.
- R. W. Hellings and G. S. Downs, "Upper limits on the isotropic gravitational radiation background from pulsar timing analysis," Astrophys. J. 265, L39 (1983), doi:10.1086/183954.
- F. B. Estabrook and H. D. Wahlquist, "Response of Doppler spacecraft tracking to gravitational radiation," Gen. Relativ. Gravit. 6, 439-447 (1975), doi:10.1007/bf00762449.
- S. Detweiler, "Pulsar timing measurements and the search for gravitational waves," Astrophys. J. 234, 1100 (1979), doi:10.1086/157593.
- M. Anholm, S. Ballmer, J. D. E. Creighton, L. R. Price and X. Siemens, "Optimal strategies for gravitational wave stochastic background searches in pulsar timing data," Phys. Rev. D 79, 084030 (2009), doi:10.1103/PhysRevD.79.084030, arXiv:0809.0701.
- M. Maggiore, Gravitational Waves, Volume 1: Theory and Experiments (Oxford University Press, 2007), ISBN 978-0-19-857074-5.
- M. Isi, "Parametrizing gravitational-wave polarizations," Class. Quantum Grav. 40, 203001 (2023), doi:10.1088/1361-6382/acf28c, arXiv:2208.03372.
- LIGO Scientific Collaboration and Virgo Collaboration (B. P. Abbott et al.), "Observation of gravitational waves from a binary black hole merger," Phys. Rev. Lett. 116, 061102 (2016), doi:10.1103/PhysRevLett.116.061102, arXiv:1602.03837.
- C. R. Harris, K. J. Millman, S. J. van der Walt, R. Gommers, P. Virtanen, et al., "Array programming with NumPy," Nature 585, 357-362 (2020), doi:10.1038/s41586-020-2649-2.
- P. Virtanen, R. Gommers, T. E. Oliphant, M. Haberland, T. Reddy, et al., "SciPy 1.0: fundamental algorithms for scientific computing in Python," Nature Methods 17, 261-272 (2020), doi:10.1038/s41592-019-0686-2.
- J. D. Hunter, "Matplotlib: a 2D graphics environment," Comput. Sci. Eng. 9, 90-95 (2007), doi:10.1109/MCSE.2007.55.
Web resources (accessed October 2026):
- Citing pySEOBNR: https://waveforms.docs.ligo.org/software/pyseobnr/source/citing.html
- NASA Images and Media Usage Guidelines: https://www.nasa.gov/nasa-brand-center/images-and-media/
- Blender license: https://www.blender.org/about/license/
Metadata
Release files for gwbpy 0.1.0a5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| gwbpy-0.1.0a5.tar.gz | 625.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gwbpy-0.1.0a5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.3 MB
Release files / gwbpy-0.1.0a5.tar.gz
| Download URL | gwbpy-0.1.0a5.tar.gz |
|---|---|
| Size | 625.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7941a10f20a38e439cadb52ee9badd343a6c4fbcb36701b19ba6831e31d0456e
|
|
BLAKE2b-256 checksum How to use checksums |
5ef999bf573a46c7656a3f2a46313e6f565c3fb1200d80d5176202d7331a16e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / gwbpy-0.1.0a5-py3-none-any.whl
| Download URL | gwbpy-0.1.0a5-py3-none-any.whl |
|---|---|
| Size | 625.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
16ec2a118d957e10b84698ce6294557b7021d3933936155c5f46110b2442dfa9
|
|
BLAKE2b-256 checksum How to use checksums |
ad16e9e38fdfd2a80acab84cc6b09b110137504ae94a7764cf5806f80020873e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for gwbpy 0.1.0a5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.