Loading [MathJax]/extensions/TeX/AMSsymbols.js
LAL 7.7.0.1-5e288d3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
TimeSeriesInterp.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2014,2015 Kipp Cannon
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA
18 */
19
20
21#include <math.h>
22
23
24#include <lal/Date.h>
25#include <lal/LALDatatypes.h>
26#include <lal/LALMalloc.h>
27#include <lal/TimeSeriesInterp.h>
28#include <lal/Window.h>
29#include <lal/XLALError.h>
30
31
32/**
33 * Default kernel function. A Welch-windowed sinc interpolating kernel is
34 * used. See
35 *
36 * Smith, Julius O. Digital Audio Resampling Home Page Center for Computer
37 * Research in Music and Acoustics (CCRMA), Stanford University,
38 * 2014-01-10. Web published at
39 * http://www-ccrma.stanford.edu/~jos/resample/.
40 *
41 * for more information, but note that that reference uses a Kaiser window
42 * for the sinc kernel's envelope whereas we use a Welch window here. The
43 * Welch window (inverted parabola) is chosen because it yields results
44 * similar in accuracy to the Lanczos window but is much less costly to
45 * compute.
46 */
47
48
51};
52
53
54static void default_kernel(double *cached_kernel, int kernel_length, double residual, void *data)
55{
56 /* kernel is Welch-windowed sinc function. the sinc component
57 * takes the form
58 *
59 * x = pi (i - x);
60 * kern = sin(x) / x
61 *
62 * we don't check for 0/0 because that can only occur if x is an
63 * integer, which is trapped by no-op path above. note that the
64 * argument of sin(x) increases by pi each iteration, so we just
65 * need to compute its value for the first iteration then flip sign
66 * for each subsequent iteration. for numerical reasons, it's
67 * better to compute sin(x) from residual rather than from (start -
68 * x), i.e. what it's argument should be for the first iteration,
69 * so we also have to figure out how many factors of -1 to apply to
70 * get its sign right for the first iteration.
71 */
72
73 double welch_factor = ((struct default_kernel_data *) data)->welch_factor;
74 double *stop = cached_kernel + kernel_length;
75 /* put a factor of welch_factor in this. see below */
76 double sinx_over_pi = sin(LAL_PI * residual) / LAL_PI * welch_factor;
77 int i;
78 if(kernel_length & 2)
79 sinx_over_pi = -sinx_over_pi;
80 for(i = -(kernel_length - 1) / 2; cached_kernel < stop; i++, sinx_over_pi = -sinx_over_pi) {
81 double y = welch_factor * (i + residual);
82 if(fabs(y) < 1.)
83 /* the window is
84 *
85 * sinx_over_pi / i * (1. - y * y)
86 *
87 * but by putting an extra factor of welch_factor
88 * into sinx_over_pi we can replace i with y,
89 * and then move the factor of 1/y into the
90 * parentheses to reduce the total number of
91 * arithmetic operations in the loop
92 */
93 *cached_kernel++ = sinx_over_pi * (1. / y - y);
94 else
95 *cached_kernel++ = 0.;
96 }
97}
98
99
100/**
101 * Opaque LALREAL8SequenceInterp instance structure.
102 */
103
104
109 double residual;
110 /* samples. the length of the kernel sets the bandwidth of the
111 * interpolator: the longer the kernel, the closer to an ideal
112 * interpolator it becomes. we tie the interval at which the
113 * kernel is regenerated to this in a heuristic way to hide the
114 * sub-sample residual quantization in the filter's roll-off. */
116 /* calling-code supplied kernel generator */
117 void (*kernel)(double *, int, double, void *);
119};
120
121
122/**
123 * Create a new REAL8Sequence interpolator associated with the given
124 * REAL8Sequence object. The kernel_length parameter sets the length of
125 * the interpolating kernel in samples. Use
126 * XLALREAL8SequenceInterpDestroy() to free.
127 *
128 * The optional kernel() function has the signature
129 *
130 * void (*kernel)(double *kern, int length, double residual, void *data);
131 *
132 * The optional kernel_data parameter will be passed to kernel() as the
133 * final argument. It is an error to provide a non-NULL kernel_data
134 * pointer without supplying a kernel() function. If both are NULL, an
135 * internal default Welch-windowed sinc kernel function will be used. If
136 * supplied, the kernel() function is responsible for computing the
137 * time-domain interpolation kernel. The kernel must be exactly length
138 * samples long and be placed in the kernel array, which is guaranteed to
139 * be non-NULL and large enough to contain length samples. length is
140 * guaranteed to be odd, and not less than 3.
141 *
142 * residual is the interval from the nearest available sample of source
143 * data to the co-ordinate at which the user has requested the interpolated
144 * function be evaluated. The middle sample of the kernel will be the
145 * weight applied to that sample of source data. A purely sinc() kernel
146 * function would be of the form
147 *
148 * static voic kernel(double *kern, int length, double res, void *ignored)
149 * {
150 * int i;
151 *
152 * for(i = 0; i < length; i++) {
153 * double x = i - (length - 1) / 2.;
154 * kern[i] = x ? sin(PI * x + res) / (PI * x + res) : 1.;
155 * }
156 * }
157 *
158 * Notes:
159 *
160 * The LALREAL8SequenceInterp object is opaque. Calling code should not
161 * attempt to inspect nor manipulate its contents directly.
162 *
163 * The REAL8Sequence object with which the LALREAL8SequenceInterp is
164 * associated must remain valid as long as the interpolator exists. Do not
165 * free the REAL8Sequence before freeing the LALREAL8SequenceInterp.
166 */
167
168
169LALREAL8SequenceInterp *XLALREAL8SequenceInterpCreate(const REAL8Sequence *s, int kernel_length, void (*kernel)(double *, int, double, void *), void *kernel_data)
170{
171 LALREAL8SequenceInterp *interp;
172 double *cached_kernel;
173
174 if(kernel_length < 3)
176 if(!kernel && kernel_data) {
177 /* FIXME: print error message */
179 }
180 /* interpolator induces phase shifts unless this is odd */
181 kernel_length -= (~kernel_length) & 1;
182
183 interp = XLALMalloc(sizeof(*interp));
184 cached_kernel = XLALMalloc(kernel_length * sizeof(*cached_kernel));
185 if(!interp || !cached_kernel) {
186 XLALFree(interp);
187 XLALFree(cached_kernel);
189 }
190
191 interp->s = s;
192 interp->kernel_length = kernel_length;
193 interp->cached_kernel = cached_kernel;
194 /* >= 1 --> impossible. forces kernel init on first eval */
195 interp->residual = 2.;
196 /* set no-op threshold. the kernel is recomputed when the residual
197 * changes by this much */
198 interp->noop_threshold = 1. / (4 * interp->kernel_length);
199
200 /* install interpolator, using default if needed */
201 if(!kernel) {
204 XLALFree(interp);
205 XLALFree(cached_kernel);
207 }
208
209 default_kernel_data->welch_factor = 1.0 / ((kernel_length - 1.) / 2. + 1.);
210
211 kernel = default_kernel;
212 kernel_data = default_kernel_data;
213 } else if(kernel == default_kernel) {
214 /* not allowed because destroy method checks for the
215 * default kernel to decide if it must free the kernel_data
216 * memory */
217 XLALFree(interp);
218 XLALFree(cached_kernel);
220 }
221 interp->kernel = kernel;
222 interp->kernel_data = kernel_data;
223
224 return interp;
225}
226
227
228/**
229 * Free a LALREAL8SequenceInterp object. NULL is no-op.
230 */
231
232
233void XLALREAL8SequenceInterpDestroy(LALREAL8SequenceInterp *interp)
234{
235 if(interp) {
236 XLALFree(interp->cached_kernel);
237 /* unref the REAL8Sequence. place-holder in case this code
238 * is ported to a language where this matters */
239 interp->s = NULL;
240 /* only free if we allocated it ourselves */
241 if(interp->kernel == default_kernel)
242 XLALFree(interp->kernel_data);
243 /* unref kernel and kernel_data. place-holder in case this
244 * code is ported to a language where this matters */
245 interp->kernel = NULL;
246 interp->kernel_data = NULL;
247 }
248 XLALFree(interp);
249}
250
251
252/**
253 * Evaluate a LALREAL8SequenceInterp at the real-valued index x. The data
254 * beyond the domain of the input sequence are assumed to be 0 when
255 * computing results near (or beyond) the boundaries. An XLAL_EDOM domain
256 * error is raised if x is not finite. If bounds_check is non-zero then an
257 * XLAL_EDOM domain error is also raised if x is not in [0, length) where
258 * length is the sample count of the sequence to which the interpolator is
259 * attached.
260 *
261 * Be aware that for performance reasons the interpolating kernel is cached
262 * and only recomputed if the error estimated to arise from failing to
263 * recompute it exceeds the error estimated to arise from using a finite
264 * interpolating kernel. Therefore, if a function is interpolated at very
265 * high resolution with a short kernel the result will consist of intervals
266 * of constant values in a stair-step pattern. The stair steps should be a
267 * small contribution to the interpolation error but numerical
268 * differentiation of the result is likely to be unsatisfactory. In that
269 * case, consider interpolating the derivative or use a longer kernel to
270 * force more frequent kernel updates.
271 */
272
273
274REAL8 XLALREAL8SequenceInterpEval(LALREAL8SequenceInterp *interp, double x, int bounds_check)
275{
276 const REAL8 *data = interp->s->data;
277 double *cached_kernel = interp->cached_kernel;
278 double *stop = cached_kernel + interp->kernel_length;
279 /* split the real-valued sample index into integer and fractional
280 * parts. the fractional part (residual) is the offset in samples
281 * from where we want to evaluate the function to where we know its
282 * value. the interpolating kernel depends only on this quantity.
283 * when we compute a kernel, we record the value of this quantity,
284 * and only recompute the kernel if this quantity differs from the
285 * one for which the kernel was computed by more than the no-op
286 * threshold */
287 int start = lround(x);
288 double residual = start - x;
289 REAL8 val;
290
291 if(!isfinite(x) || (bounds_check && (x < 0 || x >= interp->s->length)))
293
294 /* special no-op case for default kernel */
295 if(fabs(residual) < interp->noop_threshold && interp->kernel == default_kernel)
296 return 0 <= start && start < (int) interp->s->length ? data[start] : 0.0;
297
298 /* need new kernel? */
299 if(fabs(residual - interp->residual) >= interp->noop_threshold) {
300 interp->kernel(cached_kernel, interp->kernel_length, residual, interp->kernel_data);
301 interp->residual = residual;
302 }
303
304 /* inner product of kernel and samples */
305 start -= (interp->kernel_length - 1) / 2;
306 if(start + interp->kernel_length > (signed) interp->s->length)
307 stop -= start + interp->kernel_length - interp->s->length;
308 if(start < 0)
309 cached_kernel -= start;
310 else
311 data += start;
312 for(val = 0.0; cached_kernel < stop;)
313 val += *cached_kernel++ * *data++;
314
315 return val;
316}
317
318
321 LALREAL8SequenceInterp *seqinterp;
322};
323
324
325/**
326 * Create a new REAL8TimeSeries interpolator associated with the given
327 * REAL8TimeSeries object. The kernel_length parameter sets the length of
328 * the interpolating kernel in samples. Use
329 * XLALREAL8TimeSeriesInterpDestroy() to free.
330 *
331 * See XLALREAL8SequenceInterpCreate() for the definition of the kernel and
332 * kernel_data parameters. These are optional; set both to NULL to use
333 * the internal default interpolation kernel.
334 *
335 * Notes:
336 *
337 * The LALREAL8TimeSeriesInterp object is opaque. Calling code should not
338 * attempt to inspect nor manipulate its contents directly.
339 *
340 * The REAL8TimeSeries object with which the LALREAL8TimeSeriesInterp is
341 * associated must remain valid as long as the interpolator exists. Do not
342 * free the REAL8TimeSeries before freeing the LALREAL8TimeSeriesInterp.
343 */
344
345
346LALREAL8TimeSeriesInterp *XLALREAL8TimeSeriesInterpCreate(const REAL8TimeSeries *series, int kernel_length, void (*kernel)(double *, int, double, void *), void *kernel_data)
347{
348 LALREAL8TimeSeriesInterp *interp;
349 LALREAL8SequenceInterp *seqinterp;
350
351 interp = XLALMalloc(sizeof(*interp));
352 seqinterp = XLALREAL8SequenceInterpCreate(series->data, kernel_length, kernel, kernel_data);
353 if(!interp || !seqinterp) {
354 XLALFree(interp);
357 }
358
359 interp->series = series;
360 interp->seqinterp = seqinterp;
361
362 return interp;
363}
364
365
366/**
367 * Free a LALREAL8TimeSeriesInterp object. NULL is no-op.
368 */
369
370
371void XLALREAL8TimeSeriesInterpDestroy(LALREAL8TimeSeriesInterp *interp)
372{
373 if(interp) {
374 XLALREAL8SequenceInterpDestroy(interp->seqinterp);
375 /* unref the REAL8TimeSeries. place-holder in case this
376 * code is ported to a language where this matters */
377 interp->series = NULL;
378 }
379 XLALFree(interp);
380}
381
382
383/**
384 * Evaluate a LALREAL8TimeSeriesInterp at the LIGOTimeGPS t. Raises a
385 * XLAL_EDOM domain error if t is not in [epoch, epoch + length * deltaT)
386 * where epoch, length, and deltaT are the start time, sample count, and
387 * sample period, respectively, of the time series to which the
388 * interpolator is attached.
389 *
390 * See XLALREAL8SequenceInterpEval() for information about the
391 * interpolation kernel that is used and performance enhancements that can
392 * give rise to numerical artifacts.
393 */
394
395
396REAL8 XLALREAL8TimeSeriesInterpEval(LALREAL8TimeSeriesInterp *interp, const LIGOTimeGPS *t, int bounds_check)
397{
398 return XLALREAL8SequenceInterpEval(interp->seqinterp, XLALGPSDiff(t, &interp->series->epoch) / interp->series->deltaT, bounds_check);
399}
LALREAL8SequenceInterp * XLALREAL8SequenceInterpCreate(const REAL8Sequence *s, int kernel_length, void(*kernel)(double *, int, double, void *), void *kernel_data)
Create a new REAL8Sequence interpolator associated with the given REAL8Sequence object.
void XLALREAL8SequenceInterpDestroy(LALREAL8SequenceInterp *interp)
Free a LALREAL8SequenceInterp object.
static void default_kernel(double *cached_kernel, int kernel_length, double residual, void *data)
REAL8 XLALREAL8SequenceInterpEval(LALREAL8SequenceInterp *interp, double x, int bounds_check)
Evaluate a LALREAL8SequenceInterp at the real-valued index x.
LALREAL8TimeSeriesInterp * XLALREAL8TimeSeriesInterpCreate(const REAL8TimeSeries *series, int kernel_length, void(*kernel)(double *, int, double, void *), void *kernel_data)
Create a new REAL8TimeSeries interpolator associated with the given REAL8TimeSeries object.
void XLALREAL8TimeSeriesInterpDestroy(LALREAL8TimeSeriesInterp *interp)
Free a LALREAL8TimeSeriesInterp object.
REAL8 XLALREAL8TimeSeriesInterpEval(LALREAL8TimeSeriesInterp *interp, const LIGOTimeGPS *t, int bounds_check)
Evaluate a LALREAL8TimeSeriesInterp at the LIGOTimeGPS t.
#define LAL_PI
Archimedes's constant, pi.
Definition: LALConstants.h:179
double REAL8
Double precision real floating-point number (8 bytes).
#define XLALMalloc(n)
Definition: LALMalloc.h:44
#define XLALFree(p)
Definition: LALMalloc.h:47
#define XLAL_ERROR_REAL8(...)
Macro to invoke a failure from a XLAL routine returning a REAL8.
Definition: XLALError.h:752
#define XLAL_ERROR_NULL(...)
Macro to invoke a failure from a XLAL routine returning a pointer.
Definition: XLALError.h:713
@ XLAL_EFUNC
Internal function call failed bit: "or" this with existing error number.
Definition: XLALError.h:462
@ XLAL_EDOM
Input domain error.
Definition: XLALError.h:410
@ XLAL_EINVAL
Invalid argument.
Definition: XLALError.h:409
REAL8 XLALGPSDiff(const LIGOTimeGPS *t1, const LIGOTimeGPS *t0)
Difference between two GPS times as double.
Definition: XLALTime.c:157
Epoch relative to GPS epoch, see LIGOTimeGPS type for more details.
Definition: LALDatatypes.h:458
Time series of REAL8 data, see DATATYPE-TimeSeries types for more details.
Definition: LALDatatypes.h:580
REAL8Sequence * data
The sequence of sampled data.
Definition: LALDatatypes.h:586
Vector of type REAL8, see DATATYPE-Vector types for more details.
Definition: LALDatatypes.h:154
Default kernel function.
Opaque LALREAL8SequenceInterp instance structure.
void(* kernel)(double *, int, double, void *)
const REAL8Sequence * s
Opaque LALREAL8TimeSeriesInterp structure.
const REAL8TimeSeries * series
LALREAL8SequenceInterp * seqinterp