Metadata-Version: 2.4
Name: gwbpy
Version: 0.1.0a4
Summary: Gravitational-wave visualizations for Blender (alpha)
Author: Ellis Eisenberg
License-Expression: GPL-3.0-or-later
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Astronomy
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Dynamic: license-file

# gwbpy

Gravitational-wave and pulsar visualization for Blender.

**Status: alpha (0.1.0a4). Expect rough edges.** Pre-release on PyPI.

## What it does

- `animate_binary_physical()`: two bodies (black holes or neutron stars) that
  spiral together and merge, driven by SEOBNRv5HM effective-one-body dynamics,
  or that sit on a steady circular orbit (for continuous-wave sources).
- `animate_gw_polarization()`: a ring, or a tube of rings, of free test
  particles deformed by a gravitational wave. Linked to a circular binary, or
  standalone.
- `animate_pulsar()`: a spinning neutron star with lighthouse beams, optionally
  aimed at an observer.
- `animate_pta()`: several pulsars around one observer (optionally textured as
  the Earth), with pulses traveling along the lines of sight. With a circular
  binary in the scene, its gravitational wave shifts the pulse arrival times.
- `plot_residuals()`: one pulsar's timing residuals as a matplotlib image next
  to the PTA, facing the camera.
- `animate_spacetime()`: a rubber-sheet picture of a binary: a grid sheet with
  a dent under each body and ripples spreading out from it.
- `frame_scene()`, `camera_tour()`, `camera_orbit()`, `camera_targets()`: put
  the camera on any part of the scene, fly between parts, or circle them.
- `draw_pta_geometry()`: arrows from the observer to the pulsars and along the
  direction the wave travels, with optional x, y, z axes, angle arcs (theta,
  phi, zeta) and text labels, to recreate a PTA geometry diagram.
- `docs()`: print the full list of options of any function.
- `clear_scene()`: remove what gwbpy made (or everything) to rebuild a scene.

Each function documents its options: run `gwbpy.docs()` for the list, or
`gwbpy.docs("animate_pta")` and so on, in Blender's Python console. (Python's
built-in `help()` can freeze Blender on long output: it opens a pager that waits
for a keypress in the System Console window. If that happens, click that window
and press q. `docs()` avoids the problem.) The console also prints what it did,
including warnings, so keep Window > Toggle System Console open.

## Tested on

- Blender 5.0 on Windows, with Ubuntu 24.04 under WSL2. Installing into Blender's
  user modules folder with pip (see Install), then running the pure-Blender functions,
  an inspiral binary through the physics worker, the residual plot's matplotlib worker,
  and `setup()`.
- The physics-environment build script from scratch on Ubuntu 24.04 (about 10 minutes).

Not tested: macOS, other Linux distributions, Windows without WSL, and the build
script on a machine that lacks the system packages.

## What needs the physics environment

Inspiral binaries (`animate_binary_physical(..., inspiral=True)`, the default)
and `plot_residuals()`. Blender's Python cannot run these libraries, so gwbpy
builds a separate Python 3.11 environment at `~/.gwbpy/dynamics-env` (inside
WSL on Windows) with pyseobnr and lalsuite (which pyseobnr requires, and which
brings matplotlib). Calls run a small worker there, read the result back, and
keyframe it. Everything else (circular binaries, rings, pulsars, PTA scenes,
spacetime sheets and the camera) runs in Blender's own Python with numpy,
with no setup step.

## Install

gwbpy is a pre-release, so pip needs `--pre`.

1. Install it into Blender's user modules folder, once. In Blender's Scripting tab, run:

        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"])

   Then quit Blender completely and reopen it, so that it picks up the new folder.
   From then on `import gwbpy` works in every file, with no path to set.

   - `--no-deps` is deliberate: gwbpy needs numpy, which Blender already ships.
     Without it, pip would put a second numpy beside Blender's and could break it.
   - To update, quit Blender, delete the `gwbpy` and `gwbpy-*.dist-info` folders in
     that modules folder, and run the snippet again.
   - Each Blender version has its own modules folder, so repeat this for a new one.
   - Without internet access, put the path of a downloaded `.whl` file in place of
     `"gwbpy"` (and drop `--pre`).

2. For inspiral binaries and the residual plot only, install 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

3. Then build the physics environment, once, in Blender:

        import gwbpy
        gwbpy.setup()

   The first run builds from source and takes about 10 minutes. Blender may look
   frozen meanwhile; Window > Toggle System Console shows progress. If system
   packages are missing, setup stops and prints the exact command to run. Use
   `gwbpy.setup(force=True)` to rebuild.

To work on gwbpy itself, add the repository folder to Blender's path at the start of a
session instead of installing:

        import sys
        sys.path.insert(0, r"PATH_TO_REPOSITORY")
        import gwbpy

## Examples

A merging pair of black holes:

    gwbpy.animate_binary_physical(m1=30, m2=15, merge_frame=100, frames=200,
                                  f_lower=80.0, meters_per_unit=20000.0)

A neutron star and a black hole (m1 is the black hole for "BHNS"):

    gwbpy.animate_binary_physical(m1=5, m2=1.4, system="BHNS", f_lower=300,
                                  merge_frame=250, meters_per_unit=500.0)

A steady circular binary with two polarization rings around it:

    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 pulsar timing array whose pulses are shifted by that binary's wave, with a
residual plot (the observer is in the binary's orbital plane, so it sees the
binary edge-on):

    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:

    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)

## What is physics and what is not

Binary (inspiral=True)
- Orbital separation and phase come from the SEOBNRv5HM effective-one-body
  dynamics. Separation is the EOB radial coordinate, not a literal horizon
  position. Non-spinning only.
- Black holes are drawn at their Schwarzschild radius. Neutron stars are drawn
  at a fixed radius (`ns_radius_km`, default 12 km), and their glow is styling.
- For systems with neutron stars the orbit still comes from a binary-black-hole
  model, stopped at contact. Tides, disruption and matter effects are not
  modeled.
- Time is stretched; the slowdown factor is printed. If the data end slightly
  short of contact, a short schematic closing step finishes the merger; it is a
  visual stand-in, and the ringdown of the bodies is not modeled
  (`close_merger=False` removes it).
- The (2,2) mode of the strain is saved with the orbit and aligned with it (the
  worker prints the clock fit and phase agreement). Other modes are ignored.

Circular binary (inspiral=False)
- Newtonian Kepler orbit with no decay. A warning is printed below about 10 M.

Polarization ring
- Leading-order quadrupole radiation from a circular binary, transverse-traceless
  gauge, first order in the strain. The strain is greatly exaggerated (the
  console prints the factor); real strains are of order 1e-21.
- The phase offset between the ring and the orbit follows a hand derivation and
  has not been checked against a reference.
- The tube length and wave delay are a cartoon. Inspiral binaries are not
  supported yet.

Pulsar and PTA
- The lighthouse geometry is real; the rest is a cartoon. Spin periods, light
  travel times and distances are not to scale, the beams are opaque cones, and
  the signals are uniform stylized spikes (or spheres).
- With a circular binary in the scene, its wave shifts each pulse's arrival time
  using the standard timing formula (Earth term and pulsar term), derived by hand.
  The code is checked against a direct integration of the light travel time and
  against the Hellings-Downs curve for the Earth term. The pulsar term and the
  overall sign have not been checked against a published reference. The shifts
  are hugely exaggerated; their relative sizes follow the real geometry.
- The pulsars do not move. Positions of pulses in flight are a cartoon; the
  arrival times at the observer are the observable (see `plot_residuals`).
- A PTA listens at nanohertz to supermassive binaries; the circular binary on
  screen is a stand-in.

Spacetime sheet
- The ripples are the (2,2) strain of the binary in the orbital plane: analytic
  for a circular binary, from the model for an inspiral. They give a two-armed
  spiral that follows the chirp, with amplitude falling as 1/distance. The
  height is a picture of that pattern, not a physical displacement, and is
  exaggerated. The wave speed is a visual choice. The pattern is faded out
  inside the orbit.
- The dents are the rubber-sheet analogy, not spacetime curvature. Depth is
  proportional to mass, and on a flat sheet the spheres of a binary with
  different masses cannot all rest at the bottom of their dents (the console
  says by how much they hover).

Not modeled anywhere: spin, precession, eccentricity and higher modes.

## Known limits

- SEOBNRv5HM never starts closer than a separation of 10 M. A requested start
  frequency above about 2044 / (m1 + m2) Hz (masses in solar masses) is lowered
  to that value, and a note is printed, so every run has at least about 5 orbits.
- Systems with long inspirals (for example two neutron stars from tens of Hz) are
  refused above `max_orbits`, since they alias and create huge numbers of
  keyframes.
- If the console warns about 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 console prints the cost
  first, and the function refuses above `max_cost`. Large sheets over long clips
  are slow to build and to play.
- `hide_edges` works from the flat outline of the sheet, never zooms out, and
  cannot help for views that look toward the sheet's horizon (it says so).
- Colors, glow and images show in Material Preview or renders, not in the Solid
  viewport.

## Image credits

The Earth texture bundled with gwbpy (`gwbpy/textures/earth.jpg`, used with
`observer_texture="earth"`) is "Equirectangular Projected Earth for LARGEST"
(https://svs.gsfc.nasa.gov/3615/): NASA/Goddard Space Flight Center Scientific
Visualization Studio. The Blue Marble Next Generation data is courtesy of Reto
Stockli (NASA/GSFC) and NASA's Earth Observatory. The look was art-directed for
a film, so it is not a scientific rendering. This use does not imply NASA
endorsement. Check NASA's media usage guidelines before redistributing it
separately.

## Citing

pyseobnr asks that results made with it acknowledge the software and the model
papers:

- Mihaylov et al., "pySEOBNR: a software package for the next generation of
  effective-one-body multipolar waveform models", arXiv:2303.18203
- Pompili et al., "Laying the foundation of the effective-one-body waveform
  models SEOBNRv5", arXiv:2303.18039

For the polarization pictures, M. Isi, "Parametrizing gravitational wave
polarizations" (2022) and its animations at https://maxisi.github.io/gwpols/
(CC-BY-SA 4.0) are a good reference, and for the quadrupole formulas see
Maggiore, Gravitational Waves, Vol. 1. For pulsar timing, see Estabrook and
Wahlquist (1975), Detweiler (1979) and Hellings and Downs (1983).

Citations were copied or recalled by hand. Check them before use.

## License

gwbpy is free software, released under the GNU General Public License, version 3
or (at your option) any later version (GPL-3.0-or-later); see the file `LICENSE`.
This matches pyseobnr, which the physics environment installs and which is also
GPL-3.0-or-later.

The bundled Earth image is not covered by this license: it has its own credit and
terms (see the image credits above and `gwbpy/textures/CREDITS.txt`).
