Hybridisation: starting below the artifact's reach
The artifact carries a finite stretch of inspiral. A start frequency below
it is served by extending the inspiral with a post-Newtonian description
joined onto the artifact's own content. Written 2026-09-02; every number on
this page was measured that day through the consumer route on the released
NRHJSur3dq8_AA_v3 unless it says otherwise.
The knob and its three states
f_low on the geometric methods (dimensionless M f of the (2, 2) mode) and
f_min on the physical ones (Hz) select where the waveform starts. None or
exactly 0 is the full span; in reach it trims; below reach it extends.
hybridization is the policy:
| state | in reach | below reach | f_low absent |
|---|---|---|---|
"extend_if_below_reach" (default) |
trims, bit for bit | extends | full span |
"never" |
trims | raises, naming the point's own start | full span |
"require" |
raises: the arm would not engage | extends | raises: no target |
Every cell was exercised. The trim at f_low = 0.01 returned 4453 samples
that are a bitwise slice of the untrimmed call. "never" at f_low = 0.004
raised f_low=0.004 is below this point's native start (M f = 0.006311) and hybridization='never' guarantees NR-only content. "require" at
f_low = 0.01 raised the PN extension arm would not engage there, and with
no f_low raised there is no target frequency, the PN extension arm cannot extend to 0 Hz. Both keywords at once, or an unknown state, raise. The old
hybridize="auto"/False/True spellings map one for one and warn with
FutureWarning (nrhjsurrogate/driver/hybridization_states.py).
Only timing_mode="aa" honours the policy. The derived routes carry their
own extension gate: timing_mode="aa-int" with f_low = 0.004 extended on
its own to 55379 samples, and with hybridization="never" raised
hybridization is honoured on timing_mode='aa' only
(nrhjsurrogate/driver/aa_api.py:1450-1455 and :1498-1505).
Where the reach is
The reach is decided in the action, by one function every caller shares:
physics.reach.reaches compares the 4PN angular momentum at the requested
frequency against the artifact's J at its first break, with a relative
margin of 1e-12 tolerant toward the native side
(nrhjsurrogate/physics/reach.py:211, REACH_REL_MARGIN). It used to be
decided twice, from 1/tau' at the edge and from the x variable, and the
two disagreed by up to 4.6e-4, so an f_low in that band raised instead of
returning a waveform (nrhjsurrogate/physics/action_extend.py:1414-1424).
The artifact holds about 18.07 orbits of numerical-relativity content at every point, because the inspiral is fitted on the common phase domain of its 260 training simulations and the shortest one sets it. Measured (2, 2) start frequencies:
(mass_ratio, spin1z, spin2z) |
native start M f |
at 20 solar masses | at 100 solar masses |
|---|---|---|---|
| (1, 0, 0) | 0.004941 | 50.2 Hz | 10.0 Hz |
| (1, 0.8, 0.8) | 0.005756 | 58.4 Hz | 11.7 Hz |
| (4, 0.3, -0.2) | 0.006311 | 64.1 Hz | 12.8 Hz |
| (8, -0.5, 0.6) | 0.006393 | 64.9 Hz | 13.0 Hz |
| (2.5, 0.61, 0.13) | 0.006018 | 61.1 Hz | 12.2 Hz |
So a 20 Hz analysis of a 60 solar mass system is served natively, and one at
20 solar masses draws its first two thirds from the extension. Each mode
(l, m) reaches its own content at m/2 times the (2, 2) start, so an
analysis that begins exactly at the (2, 2) reach still takes its (3, 3),
(4, 4) and (5, 5) from the extension until the (2, 2) frequency has grown by
that factor (measured 2026-08-20, memory seam-depth-nr-length-requirement).
What the extension does, from the code
nrhjsurrogate/physics/action_extend.py:extend_action (line 1391), reached
through driver/reconstruct.py:370-414:
- The dynamics. The fitted action
J(Phi)is continued below the edge along the 4PN trajectory, positioned by value continuity ofJat an anchorDEFAULT_ANCHOR_ORBITS = 4.0orbits INSIDE the artifact (physics/pn_extend.py:75), not at the edge. Anchoring at the edge measured 5.5 rad of secular drift against 0.1 rad four orbits in (action_extend.py:240-248). The orbital frequency, the energy and the clock derive from the first law along that trajectory; the report calls thisanchor = 'J-action'and at (4, 0.3, -0.2) the anchor sits atPhi = -90.92against the edge-113.65. - The modes. Below the edge each mode is
R_lm * h^PN_lm, the fitted ratio route taken to its post-Newtonian limit.R_lmstarts from the artifact's edge ratio, taken as the median over a window one to five orbits interior (r_edge = 'median-window'), and decays to exactly 1 on a per-mode power law invfitted over the artifact's own span (NRHJ_PN_RATIO_POWERLAW, default on,action_extend.py:48-104). The fitted exponents at this point are 10.8 on (2, 2) and 4.6 to 6.4 on the others; on (2, 1) the guard declined and the 4-orbit cosine roll served instead (ratio_decay_p = Nonein the report). - The closure.
Eis a FITTED variable of the artifact, served into the join asclosure_mode = 'measured-E', not derived fromJ. The acceptance diagnostic is the first-law closure|dE/dJ / omega - 1|: 7.2e-7 at most on the extension and 3.1e-4 median, 6.9e-4 maximum on the numerical-relativity side of the seam.
Every quantity in that list is in result["hybrid_report"], a dictionary of
scalars: Phi_start, Phi_edge, Phi_anchor, orbits_added, t_added,
f22_start, f22_edge, roll_orbits, per-mode edge_step_rel, R_edge
and ratio_decay_p, the J and E edge steps and the closures. At
f_low = 0.004 the extension added 24.25 orbits and 10035.5 M; at 0.002,
125.57 orbits and 87971 M. With dynamics=True the result also carries
result["dynamics"] = {Phi, J, E, omega, t} over the whole extended domain
(1354 points at 0.004). "hybrid_report" is None when the extension did
not engage; "dynamics" is then the native-span dynamics under the same key
names (since 1.1), see dynamics-and-remnant.md.
What it does to the content you already had
The extension is not a prepend, and the numbers below are the size of that
statement. Evaluated on the native grid by passing times=, the extended
call at f_low = 0.004 differs from the native call on 15256 of 15775
samples:
| where | |ext - native| / peak, (2, 2) |
| --- | --- |
| first 2.7 orbits after the edge, the roll window | up to 2.0e-4 (8.1e-4 on (4, 4)) |
| the rest of the inspiral, to -20 M | 1e-8 to 7e-7, median 1e-9 |
| the join window, -20 M to -5 M | 2.6e-7 |
| from -5.1 M through ringdown | 0, bitwise |
The roll re-shapes the ratio in the window where R_lm is taken to 1; the
rest of the inspiral is re-sampled through the extension's own phase grid
(PN_EXTENSION_N_PER_ORBIT, action_extend.py:313) rather than evaluated
on the spectral route the native call uses; and everything past the end of
the inspiral to merger-ringdown join is untouched. This is what
"never" guarantees you against: not only the PN content below the edge but
the re-sampling above it.
Two different f_low values are not bitwise on their overlap either. On a
shared grid, 0.002 against 0.0015 differ by 2.6e-5 of peak on (2, 2),
2.1e-5 on (3, 3) and 1.0e-4 on (4, 4). The paper review measured 1.37e-5 for
a different pair and refuted the paper's "bitwise across starting
frequencies" claim. Only a repeated identical call is bitwise, which was
also measured.
Note also that the extended grid is np.arange from the new start, so it
contains none of the native sample times; a like-for-like comparison needs
times=.
Accuracy against longer numerical relativity, recorded elsewhere
Measured 2026-08-20 on four training simulations (so in sample, and the floor
is optimistic; only the rise above it is the seam's cost). Per-mode agreement
of the artifact with its PN normaliser at the seam, median |R_lm - 1| over
260: (2, 2) 2.7e-3, (2, 1) 6.7e-3, (3, 3) 5.7e-3, (3, 2) 1.0e-2, (4, 3)
2.2e-2, (4, 4) 4.2e-2, (5, 5) 7.3e-2. The (2, 2) arrives converged and its
residual is phase; the corner modes are 2 to 7 percent off and the roll forces
that to zero. Detector verdict at mismatch below D / (2 rho^2), D = 4:
with aLIGO at 20 solar masses from 20 Hz, an analysis at signal-to-noise 25
needs about 13 to 14 more orbits of numerical relativity than the artifact
carries; at 100 the count is 46 to 56. The practical rule from the collapse
onto f_seam / f_low is to start at about twice the seam frequency. This
behaviour is shown in the hybridisation figures accompanying the model paper.
Cross-machine reproducibility of a DEEP extension is bounded at about 1e-5
relative by libm's exp, through the conditioning of the anchor
contraction; the native span is far less sensitive (ACCURACY.md section 2).
Cost, measured
NumPy backend, OMP_NUM_THREADS=1, one waveform, best of three: native
86 ms; f_low = 0.004 186 ms (55881 samples); 0.002 355 ms (367642);
0.0015 657 ms (796583). On the kokkos backend the same f_low = 0.004 call
returns the same 55881 samples, agreeing with NumPy to 2.0e-10 of peak on
(2, 2); the extension pack is built and synthesised in the compiled layer
(nrhjsurrogate/execution/batched_evaluator.py module docstring). The
compiled route's cost model on the v1-era build was 29.4 ms fixed plus
3.03 us per manufactured sample, with the fixed term dominating any
extension shallower than about 10 Hz at 60 solar masses. An earlier
per-stage cost ledger was superseded by its own correction, and the
whole-call model is what survives.
Limits, next to the capability
- No derivatives below reach on
timing_mode="aa".with_derivatives=Truewith a below-reachf_lowraisesNotImplementedErrornaminghybrid_gradients.hybrid_modes_grad, the scalar chain that does carry them. The batched hybridised derivatives are inspiral-only by explicit pin, see parameter-derivatives.md. - The error estimate does not know about the extension.
get_waveform_error_estimatetakes nof_lowand reports over the native span only (model-error-estimate.md). "never"guarantees numerical-relativity based content, not bitwise equality with a shorter request: an in-reach trim is bitwise, a below-reach request raises.- Four predict-time environment flags move the served extension.
NRHJ_PN_RATIO_POWERLAW=0restores the fixed 4-orbit cosine roll (7 percent worse on (4, 4) over the whole PN segment, measured at (6.5, 0.3, -0.1),action_extend.py:48-104);NRHJ_SMOOTH_R_EDGE=1takes the seam ratio from a fit evaluated at the seam (3 to 12x smaller seam step on six modes, not clearly a win on (4, 3); default off because it moves values,action_extend.py:105-125);NRHJ_SEAM_ANCHOR_OMEGA=1(:267) andNRHJ_SEAM_FIRST_LAW=1(:403) move the seam anchor and are default off. Every number on this page is at the defaults, and a number taken under any other setting must say so. - Batched below-reach requests on the kokkos backend are served by a
row loop over the scalar arm (
modes_hybrid_batch), so they do not enjoy the batched value evaluator's throughput. - The physical-units batch (
physical_batch) honours the same three states (nrhjsurrogate/driver/aa_batch.pymodule docstring).
Where the code is
| what | where |
|---|---|
| named states and the legacy spellings | nrhjsurrogate/driver/hybridization_states.py |
| the reach test | nrhjsurrogate/physics/reach.py:211 |
| the extension | nrhjsurrogate/physics/action_extend.py:1391, geometry at :1263 |
| dispatch and the refusals | nrhjsurrogate/driver/aa_api.py:1324-1713, driver/reconstruct.py:370-414 |
| compiled extension pack | nrhjsurrogate/execution/batched_evaluator.py, cpp/pack.cpp |
| user documentation | docs/USAGE.md section 4; examples/usage/03_hybridisation.py |