~20 years in my headCoherent harmonic summing is not a new idea.PRESTO’s accelsearch has a “coherent power”. Nice et al. 1995 used phases to check candidates.I recently added fourier_fold.py to PRESTO for the same reason.Plus I wanted to learn how to use LLMs!
06 / Parameter space
Which pulsars do we want?
FFT searches struggle with slow pulsars and narrow duty cycles, especially in red noise
MeerKAT TPA: ~13% of 1176 pulsars are below 1% FWHM duty, ~2% below 0.5%
So 60 harmonics (a 120-bin profile) covers most of the population
But we still want to be very sensitive to the fastest MSPs (5–30% duty, up to 716 Hz)
Posselt et al. 2021, MNRAS 508, 4249 (supplementary W50 table)
07 / The idea
What is coherent harmonic summing?
01Grab the harmonicsInterpolate the exact complex amplitudes of H harmonics, phases and all, from one long FFT.
02Build a profileStack them in a vector and take an inverse real FFT: a pulse profile of 2H bins.
03Test for pulsar-nessMatch-filter the profile with a bank of boxcars at every width and phase.
It is folding — done in the Fourier domain, at every trial frequency.
08 / Step 01
Fourier interpolation
Ar complex amplitude at any frequency r = fTAk FFT amplitudes at integer bins k[r] nearest integer to rm bins used (16): loses ~1.3% of the power
e.g. Ransom, Eikenberry & Middleditch 2002, AJ 124, 1788
Coefficients factor as A(Δr)/(Δr−j): a real weight per bin
Trial grid is rational, so only 2H offsets ever occur: tabulate the weights once
32 trials at a time in SIMD lanes, bins read from cache. Accurate to ~10−7
Interpolated power around a 1.158 Hz pulsar
09 / Step 02+
Harmonic decimation
Every kth harmonic of r is the harmonic stack of a signal at k r
Speed: one short inverse FFT, no new interpolation. k = 1…6 gives 120…20 bins, and 0.1–125 Hz fundamentals reach 750 Hz
Sensitivity: it is the harmonic-sum ladder. A broad pulse is best seen in a shallow fold; one depth alone is ~8% worse
Higher harmonics nearly for free!
A pulsar at 3× the trial fundamental: the k = 3 fold finds it
10 / The search
Making this into a search
01Step over frequenciesFourier frequency trials r, spaced 1/(2H): the FFA’s own rule.
02Examine profilesInterpolate H harmonics once, then fold at r, 2r, … 6r.
03Compute S/NBest boxcar over width and phase, against one threshold.
The paper’s pseudocode. bin/toy_coherent_search.jl implements it line by line
11 / Implementation
CoherentSearch.jl: why Julia?
Compiles to native code: C/Fortran speed, readable source, works well with Python
SIMD, threads, and GPUs (CUDA.jl) all built in
One-command install. No compiler, no system libraries
And I wanted to learn it a bit!
validationPinned to PythonEverything is referenced to coherent_search, the Python/NumPy version I wrote: same amplitudes, profiles and statistic to ~10−16.
Who is doing the coding?All of the Julia was written by ClaudeThe majority by some version of Opus 5, under my direction, against that Python reference.
github.com/scottransom/CoherentSearch.jl
12 / Nutshell
So how does it do?
76%Sensitiveof injected pulsars found, vs 64% for riptide (recommended) and 42% for accelsearch
1.4–2.2×Fastfaster than riptide on one core. 27× on 48 cores; GPUs up to 9× a 20-core workstation
6.75Red-noise resilientour matched threshold, 6.70–6.75 out to a 50 Hz knee. rseek C (whitened): 7.55–7.65. riptide unwhitened: up to 250