Dynamics and remnant
Two products that are not a waveform, served by their own methods. Written
2026-09-02; the numbers were measured that day through the consumer route on
the released NRHJSur3dq8_AA_v3, NumPy backend, OMP_NUM_THREADS=1.
Orbital dynamics
dynamics = model.get_orbital_dynamics(mass_ratio, spin1z, spin2z)
One parameter point, geometric units, on the model's own orbital-phase grid. Measured at (4, 0.3, -0.2):
| key | what | measured |
|---|---|---|
"phi" |
the orbital-phase grid, radians, 4000 points | -113.650 to -0.024 |
"tau" |
the fitted clock, M, peak aligned | -3819.08 to -0.135 |
"omega" |
orbital frequency 1 / tau', a spectral derivative of the fitted clock |
0.01983 to 0.1754, positive and rising on every sample |
"x" |
the FITTED post-Newtonian variable | 0.0733 to 0.3133 |
"eps" |
-tau'' / tau', how far from adiabatic |
4.2e-3 to 0.158 |
"k" |
the scalar plunge coefficient | 0.24872 |
nrhjsurrogate/driver/aa_api.py:926-949. It costs 75 ms warm, almost all
of it the same predict_pieces call a waveform pays, and model.dynamics
is the superseded name, forwarding, measured bitwise. It also works on a
model loaded with backend="kokkos", because it is host-side arithmetic.
x is fitted, not derived, and it is load-bearing. The consistency
check |x - omega^(2/3)| / x reaches 2.5e-4 across the grid, which is the
size of the statement that x is its own variable. At serving time
v = sqrt(x) builds the post-Newtonian normaliser that the ratio route is
multiplied back by (aa_api.py:830-831, _predict_pieces_in_arm), so the paper
sentence calling it "only diagnostic" was refuted by the 2026-09-02
review. It is diagnostic for the timing only.
What this method does not return: J and E. Both are fitted variables
of the artifact (predict_pieces serves E, J, k, tau, x and the
seven modes, measured), both have stored variances reachable through
get_dynamics_error_estimate (keys E, J, tau, x, k), and neither
is in this dictionary. They are served by the waveform call instead: with
dynamics=True, result["dynamics"] is {Phi, J, E, omega, t}. On a native
call that is the native orbital-phase grid (4000 points); with f_low below
reach it is the extended domain (1354 points at f_low = 0.004), see
hybridisation.md. One key set for both, since 1.1;
in 1.0.0 the native call returned None under that key.
On the derived routes the clock is derived. timing_mode="aa-int" and
"aa-int-pn" obtain the clock from the stored action and energy through
the first law rather than from the fitted tau; they are reference models,
inspiral only, one point at a time (docs/USAGE.md section 5). The released
default route is the fitted clock, so for it the waveform is not derived
from J.
Remnant
mass_over_total, spin = model.get_remnant_properties_geometric(mass_ratio, spin1z, spin2z)
mass_solar, spin = model.get_remnant_properties(mass1, mass2, spin1z, spin2z)
Measured: at (4, 0.3, -0.2) M_f / M = 0.973603, chi_f = 0.624352, in
0.30 ms; for 50 + 12.5 solar masses M_f = 60.8502. Passing the bodies the
other way round, (12.5, 50, -0.2, 0.3), is bitwise the same, because the
physical method sorts the pair and each spin follows its body
(aa_api.py:2328-2346). The map is exchange-even by construction and that
is exact: rho(1/q, chi2, chi1) == rho(q, chi1, chi2) bitwise, measured.
Array inputs broadcast to (B,). At (1, 0, 0) it returns
(0.951588, 0.686433). remnant and remnant_physical are the superseded
names.
Where it comes from. The map ships INSIDE the merger-ringdown arm and is
the same object the ringdown detrend uses (self.mr.X.rho,
nrhjsurrogate/physics/spin_weighted_angular_harmonics.py:207 RemnantMap,
:457 remnant), so one artifact has exactly one source of remnant truth and
no sidecar file. Two Gaussian process maps on the same trend-plus-kernel
construction as the waveform fits, read off the released MR_v3: M_f
with a degree-4 trend, nugget 1e-9, fitted on 259 simulations
(SXS:BBH:1124 excluded as a catalogue outlier), and chi_f with a
degree-3 trend, nugget 1e-10, on all 260. A model must carry the arm to
answer, and on a released artifact
it always does, see artifacts-and-resolution.md.
Accuracy, 260-fold leave-one-out against the training catalogue, measured 2026-08-23:
| quantity | median abs error, all 260 | inside q <= 8, abs(chi) <= 0.8 (201) |
|---|---|---|
chi_f |
1.63e-5 | 1.44e-5 |
M_f / M |
4.48e-5 | 4.25e-5 |
RMS forms of the same errors, 1.1987e-4 on M_f (2.696e-4 if 1124 is kept)
and 5.306e-5 on chi_f, are what the error estimate carries as
remnant_sigma = (1.2e-4, 5.3e-5) (spin_weighted_angular_harmonics.py:802).
Against the shipped surfinBH fits, same catalogue, paired medians, ratio
above 1 meaning this map is more accurate: on chi_f 1.80x over
NRSur3dq8_RD on all 260 and 1.64x inside its own box; on M_f 1.21x on all
260 but 1.01x, parity, inside the box, the whole lead coming from the 59
simulations outside abs(chi) <= 0.8 where that fit extrapolates. About 23
percent of the catalogue coincides with the other fit's training points, and
on that subset it marginally wins chi_f (0.89x); the numbers above survive
the out-of-training cut. Do not claim more than parity on M_f.
Limits, next to the capability
- Aligned spins only, no kick.
spin1zandspin2zare z components and nothing else is modelled. - Outside the training hull the remnant map extrapolates silently. Only
a
chi_fthat leaves the tabulated range(-0.6, 0.9985)is clamped with anOutOfHullWarning(spin_weighted_angular_harmonics.py:53,:243-257). Measured:q = 8, chi = 0and evenq = 20, chi = 0.99return values with NO warning, whilemodel.in_hull(8, 0, 0)isFalse.in_hullis the authority; the remnant call is not. q = 8exactly is outside the hull by 1.51e-5 in log q, because the largest trained mass ratio is 7.999879;in_hull(7.9998, 0, 0)isTrue. Owner decision 2026-09-02: leave it and document it.- No public remnant gradient and no public remnant error envelope. The
paper's claim of both was refuted.
remnant_jacobian(:614) exists as an internal central difference withh = 1e-5for the merger-ringdown derivative chain, and the map's own predictive variance is not exposed; the error estimate uses the fixed RMS constants above. - The dynamics are scalar input, geometric units, inspiral only. There
is no batched dynamics method, no physical-units dynamics, and the grid
ends at the plunge handoff (
phi = -0.024), not at merger. get_dynamics_error_estimateis opt-in and separate, 4.9 ms against 109 ms for a waveform, because the product has no mode index and no merger-ringdown counterpart (model-error-estimate.md).- The adiabatic-angle class has neither method.
NRHJSurAdAexposesmodes,modes_grad,strain,strain_grad,time_derivativeandhull_mask, measured; its artifact holds/inspiraland/modesonly, noJand noE. The remnant map is inside the shared arm, so it exists on the AdA model asmodel.mr.X.rho, but no public method reads it there. - One docstring is stale, found while verifying this page.
get_remnant_properties_geometricsays it raises for a model loaded withmerger_ringdown_path=None; on the HDF5 routeNonemeans "whatever the artifact says" and the arm is resolved anyway (measured; the pickle route'sloadis the one whereNonemeans inspiral-only,nrhjsurrogate/driver/loader_arguments.py).
Where the code is
| what | where |
|---|---|
| dynamics method | nrhjsurrogate/driver/aa_api.py:926 |
| remnant methods | nrhjsurrogate/driver/aa_api.py:2300-2356 |
| the remnant map and its clamp | nrhjsurrogate/physics/spin_weighted_angular_harmonics.py:207, :457 |
| dynamics uncertainties | nrhjsurrogate/driver/aa_api.py:1123 |
| derived routes | nrhjsurrogate/driver/nrpn.py, driver/aaintpn.py |
| user documentation | docs/USAGE.md sections 5 and 11 |