LAL  7.5.0.1-bede9b2
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 
50  double welch_factor;
51 };
52 
53 
54 static 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 
106  const REAL8Sequence *s;
108  double *cached_kernel;
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 *);
118  void *kernel_data;
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 
169 LALREAL8SequenceInterp *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) {
203  if(!default_kernel_data) {
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 
233 void 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 
274 REAL8 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 
346 LALREAL8TimeSeriesInterp *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 
371 void 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 
396 REAL8 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 }
void XLALREAL8SequenceInterpDestroy(LALREAL8SequenceInterp *interp)
Free a LALREAL8SequenceInterp object.
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.
static void default_kernel(double *cached_kernel, int kernel_length, double residual, void *data)
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.
REAL8 XLALREAL8SequenceInterpEval(LALREAL8SequenceInterp *interp, double x, int bounds_check)
Evaluate a LALREAL8SequenceInterp at the real-valued index x.
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