Skip to main content
Pre-release

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) and plot_residuals also 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.

  1. 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"])
    
  2. Quit Blender completely and reopen it.

  3. Check the installation:

     import gwbpy
     print(gwbpy.__version__, gwbpy.__file__)
    

    The printed path should be inside the scripts/modules folder of the Blender user directory.

  4. 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-dev
    

    then build the physics environment from Blender:

     import gwbpy
     gwbpy.setup()
    

Notes:

  • --no-deps prevents pip from installing a second NumPy next to the one Blender ships.
  • To update, quit Blender, delete the gwbpy and gwbpy-*.dist-info folders 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 variable GWBPY_WSL_DISTRO to 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. Use gwbpy.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_frames frames, 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_orbits because 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_frame or use a higher f_lower.
  • The spacetime sheet stores one shape key per frame. The cost is printed first, and the function refuses above max_cost.
  • hide_edges works 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 gwbpy fails 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 press q, or use gwbpy.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 with observer_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

  1. 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.
  2. 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.
  3. LIGO Scientific Collaboration, Virgo Collaboration and KAGRA Collaboration, "LVK Algorithm Library - LALSuite," free software (GPL), doi:10.7935/GT1W-FZ16 (2018).
  4. 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.
  5. 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.
  6. 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.
  7. S. Detweiler, "Pulsar timing measurements and the search for gravitational waves," Astrophys. J. 234, 1100 (1979), doi:10.1086/157593.
  8. 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.
  9. M. Maggiore, Gravitational Waves, Volume 1: Theory and Experiments (Oxford University Press, 2007), ISBN 978-0-19-857074-5.
  10. M. Isi, "Parametrizing gravitational-wave polarizations," Class. Quantum Grav. 40, 203001 (2023), doi:10.1088/1361-6382/acf28c, arXiv:2208.03372.
  11. 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.
  12. 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.
  13. 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.
  14. 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):

  1. Citing pySEOBNR: https://waveforms.docs.ligo.org/software/pyseobnr/source/citing.html
  2. NASA Images and Media Usage Guidelines: https://www.nasa.gov/nasa-brand-center/images-and-media/
  3. 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)

Source distribution for gwbpy 0.1.0a5
File Size Uploaded Provenance
gwbpy-0.1.0a5.tar.gz 625.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for gwbpy 0.1.0a5
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page