Showcase: Binary Black Hole Merger Lab, a real GR physics in a Shiny + Canvas app
I built an interactive simulation of binary black hole mergers entirely in
R/Shiny. The UI, controls, and layout are pure Shiny; the real-time rendering
and physics integration run in a JavaScript Canvas layer injected via
tags$script. No external JS framework, no htmlwidgets wrapper, just base
Shiny acting as the host for a self-contained Canvas application.
Live demo: https://kw1fpt-ahmet-duyar.shinyapps.io/Black_Hole_Collision_Observatory/
What it does
The simulation runs the full merger sequence with physically grounded models
rather than a scripted animation. Each phase is driven by its own governing
equations, and the transitions between them happen when the physics dictates
(e.g. the plunge begins when the orbital separation crosses the ISCO):
- Inspiral: TaylorT4 2.5PN orbital evolution (
dx/dt = 64η/5 · x⁵ · F(x)) - Plunge: numerical Kerr geodesics in horizon-penetrating Kerr-Schild coordinates
- Ringdown: quasi-normal mode fits (Berti, Cardoso & Will 2006)
- Spin-dependent Kerr ISCO (Bardeen-Press-Teukolsky 1972)
- Final spin and radiated mass from NR-calibrated fits (Rezzolla et al. 2008)
- Recoil kick of the remnant from the asymmetric mass/spin configuration
- A live validation panel that continuously compares the simulation's output
against the analytic GR values, so you can see the model staying consistent
with theory while it runs
It also overlays the real measured LIGO H1 strain of GW150914 (GWOSC open
data, whitened + 35–350 Hz bandpass per Abbott et al. 2016) so the synthetic
waveform can be visually checked against the actual detection.
Interactivity
The whole point of doing this in Shiny was to make it a tool you can poke at, but:
- Load real catalog events (GW150914, GW151226, GW170814, GW190412, GW190521)
or set masses and spins freely with sliders - Drag to orbit the camera, scroll to zoom
- Toggle physics/rendering layers independently: spacetime grid, GW ripples,
gravitational lensing of the star field, test masses, tidal probe, time-dilation
clocks, polarization ring, and the validation panel - "Skip to ISCO" to jump straight to the final moments before merger
- Export the full run to CSV (per-frame separation, GW frequency, strain, and
the state/energy/angular-momentum of every test mass)
Why R/Shiny for this?
It's a fair question, this is the kind of thing people usually reach for
three.js or a game engine to build. I wanted to see how far Shiny could go as
the delivery layer for something graphically demanding, and the answer is:
surprisingly far. Shiny handles the entire reactive UI, the parameter state,
and deployment, while the Canvas layer handles the per-frame work. For anyone
building scientific or educational visualizations in the R ecosystem, the
pattern is reusable: Shiny for structure and distribution, a thin Canvas/JS
layer for the heavy real-time rendering.
A few implementation notes that might interest others
- Everything is one
app.R. The CSS and ~1500 lines of JS are assembled as
R strings and injected, Shiny is essentially the delivery vehicle for a
self-contained Canvas app. - The spacetime grid had to be capped at ~96×96 vertices; an earlier 328×328
grid generated 200k+ path segments per frame and froze the main thread.
Clipping the grid to the viewport and bounding the vertex count fixed it. - The test masses use an RK4 integrator with a 1PN test-particle force term
(plain leapfrog loses its symplecticity once the force is velocity-dependent).
Periapsis precession matches6πM/(a(1-e²))to within a few percent. - Close encounters get 4× finer micro-steps, and the hot path is allocation-free
to keep the frame rate stable.
Limitations / honesty about the model
This is a physics-grounded visualization, not a numerical-relativity solver.
The inspiral is PN (accurate far from merger, approximate as it breaks down
near it), the plunge uses geodesic dynamics rather than a full two-body
spacetime, and the strain amplitudes are leading-order. The goal is to be
quantitatively reasonable and qualitatively faithful across the whole sequence
the validation panel exists precisely so the approximations are visible rather
than hidden.
Happy to answer questions about the physics or the R↔JS bridge. Feedback and
criticism both very welcome.
