Resampling kernel
The band-limited resampling kernel: a Kaiser-windowed sinc.
Shifting a uniformly sampled, band-limited signal by a fraction of a sample needs its values between samples. The exact interpolant is the sinc series; truncating it to a finite window is the only approximation, and its error falls steeply with the number of taps. That makes accuracy a tunable rather than a fixed property of the method, unlike a cubic, whose error is fixed at O(h^4).
The gwmock packages resample with this one kernel -- gwmock-signal when it projects onto a detector and places a signal on the output lattice, gwmock when it injects a chunk that sits off a segment's grid -- so they agree on the same operation. The specification (tap count, window shape, weight formula) is defined here once.
Choosing the tap count
The defaults were set by measured convergence against an interpolation-free direct-Fourier reference, not from a design formula. A Kaiser window is used because its stopband is controlled by a single parameter, so tap count and attenuation can be traded explicitly.
No windowed-sinc kernel resamples accurately for content arbitrarily close to Nyquist: the default is accurate
to 0.8 x Nyquist and fails above it (about 0.4 of a tone's amplitude at 0.98 x Nyquist). A series with content up
to its Nyquist frequency is therefore oversampled first with :func:oversample_fourier, which puts the
whole of its band at or below half the kernel's Nyquist frequency -- exactly, when the series is a whole
signal tapered to zero at both ends.
kaiser_window_chebyshev(beta, tolerance=_WINDOW_FIT_TOLERANCE)
¶
Cache-normalising wrapper; see :func:_kaiser_window_chebyshev for the fit itself.
float() before the cache, on a reviewer's finding: 32 and 32.0 are distinct keys to
lru_cache but the same Kaiser window, so an int caller silently paid for a second fit and
filled a slot. numpy scalars have the same problem.
Source code in src/gwmock_data/resampling.py
298 299 300 301 302 303 304 305 | |
oversample_fourier(samples, factor=OVERSAMPLING_FACTOR)
¶
Return the band-limited interpolant of samples on a grid factor times finer.
Sample k of samples is sample factor * k of the result, which has factor * (n - 1) + 1 samples,
so a position p on the input's grid is the position factor * p on the result's.
Computed in the frequency domain: the input, padded with zeros to twice its length, is transformed, and its spectrum extended with zeros up to the finer grid's Nyquist frequency, so nothing is added or removed below the input's own. A component at exactly the input's Nyquist frequency has no unique interpolant (its samples are those of a cosine at that frequency whatever its phase); it is split evenly between the positive and the negative frequency, which keeps the result real and reproduces the input's samples.
What this computes is the band-limited interpolant of the padded series repeated with period 2 n. For a
series that falls smoothly to zero at both ends, such as a whole generated waveform, the repetitions
contribute nothing and it is the band-limited interpolant of the series taken as zero outside its samples,
accurate to roundoff up to the input's Nyquist frequency. For one that ends abruptly it is neither: the
abrupt ends ring across the whole result -- 8e-5 of the signal mid-series for an abruptly ending tone at
0.995 x Nyquist of 8192 samples, and more towards its ends -- where the windowed-sinc kernel alone is affected only within half its taps of them. Use it only on
whole, tapered series. LAL's injection (XLALSimAddInjectionREAL8TimeSeries) shifts a series by a fraction
of a sample the same way, in the frequency domain over the whole zero-padded series.
Peak memory is about 110 bytes per input sample at a factor of 2 (6.1 GB at 5.2e7 samples, a 1.8-hour series at 8192 Hz), most of it the transforms' own working arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples
|
ndarray
|
Uniformly sampled real series, shape |
required |
factor
|
int
|
How many times finer the result's grid is; a positive integer. |
OVERSAMPLING_FACTOR
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The interpolated series, shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gwmock_data/resampling.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
resample_uniform_sinc(samples, index, *, fraction=None, taps=DEFAULT_SINC_TAPS, beta=DEFAULT_KAISER_BETA)
¶
Resample a uniformly sampled series at fractional positions (NumPy).
Evaluates the Kaiser-windowed sinc series. Positions outside
[0, len(samples) - 1] return zero, matching the fill_value=0.0 behaviour of
the interpolation this replaces.
Weights are normalised to sum to one, which enforces unit gain at DC and removes the amplitude bias the window would otherwise introduce near the ends of the series.
A position given as one float64 keeps about 16 significant digits, so its fractional part is
resolved only to index * 2.2e-16 of a sample: about 1e-9 of a sample at ten million samples,
which the kernel turns into an error of that much time times the signal's slope. A caller whose
positions are a large whole number plus a small offset should pass them as such: index the
integer part and fraction the offset. The two are never added in floating point, so the
offset keeps its own precision whatever the size of index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples
|
ndarray
|
Uniformly sampled series, shape |
required |
index
|
ndarray
|
Sample positions to evaluate at, any shape: fractional, or, with |
required |
fraction
|
ndarray | float | None
|
Offset added to each position of |
None
|
taps
|
int
|
Number of kernel taps. |
DEFAULT_SINC_TAPS
|
beta
|
float
|
Kaiser window shape parameter. |
DEFAULT_KAISER_BETA
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Interpolated values, the shape of |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Source code in src/gwmock_data/resampling.py
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
validate_kernel(taps, beta)
¶
Validate a kernel specification and return it normalised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
taps
|
int
|
Number of kernel taps; must be an odd integer >= 3. |
required |
beta
|
float
|
Kaiser window shape parameter; must be finite, non-negative and small enough
that its transition band fits in |
required |
Returns:
| Type | Description |
|---|---|
tuple[int, float]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/gwmock_data/resampling.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |