LALPulsar  6.1.0.1-c9a8ef6
GenerateHyperbolicSpinOrbitCW.c
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2007 Teviet Creighton
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with with program; see the file COPYING. If not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA
18 */
19 
20 
21 #include <lal/LALStdio.h>
22 #include <lal/LALStdlib.h>
23 #include <lal/LALConstants.h>
24 #include <lal/Units.h>
25 #include <lal/AVFactories.h>
26 #include <lal/SeqFactories.h>
27 #include <lal/PulsarSimulateCoherentGW.h>
28 #include <lal/GenerateSpinOrbitCW.h>
29 
30 /**
31  * \author Creighton, T. D.
32  *
33  * \brief Computes a continuous waveform with frequency drift and Doppler
34  * modulation from a hyperbolic orbital trajectory.
35  *
36  * This function computes a quaiperiodic waveform using the spindown and
37  * orbital parameters in <tt>*params</tt>, storing the result in
38  * <tt>*output</tt>.
39  *
40  * In the <tt>*params</tt> structure, the routine uses all the "input"
41  * fields specified in \ref GenerateSpinOrbitCW_h, and sets all of the
42  * "output" fields. If <tt>params->f</tt>=\c NULL, no spindown
43  * modulation is performed. If <tt>params->oneMinusEcc</tt> \f$ \not<0 \f$ (a
44  * non-hyperbolic orbit), or if
45  * <tt>params->rPeriNorm</tt> \f$ \times \f$ <tt>params->angularSpeed</tt> \f$ \geq1 \f$
46  * (faster-than-light speed at periapsis), an error is returned.
47  *
48  * In the <tt>*output</tt> structure, the field <tt>output->h</tt> is
49  * ignored, but all other pointer fields must be set to \c NULL. The
50  * function will create and allocate space for <tt>output->a</tt>,
51  * <tt>output->f</tt>, and <tt>output->phi</tt> as necessary. The
52  * <tt>output->shift</tt> field will remain set to \c NULL.
53  *
54  * ### Algorithm ###
55  *
56  * For hyperbolic orbits, we combine \eqref{eq_spinorbit-tr},
57  * \eqref{eq_spinorbit-t}, and \eqref{eq_spinorbit-upsilon} to get \f$ t_r \f$
58  * directly as a function of \f$ E \f$ :
59  * \f{eqnarray}{
60  * \label{eq_tr-e3}
61  * t_r = t_p & + & \left(\frac{r_p \sin i}{c}\right)\sin\omega\\
62  * & + & \frac{1}{n} \left( -E +
63  * \left[v_p(e-1)\cos\omega + e\right]\sinh E
64  * - \left[v_p\sqrt{\frac{e-1}{e+1}}\sin\omega\right]
65  * [\cosh E - 1]\right) \;,
66  * \f}
67  * where \f$ v_p=r_p\dot{\upsilon}_p\sin i/c \f$ is a normalized velocity at
68  * periapsis and \f$ n=\dot{\upsilon}_p\sqrt{(1-e)^3/(1+e)} \f$ is a normalized
69  * angular speed for the orbit (the hyperbolic analogue of the mean
70  * angular speed for closed orbits). For simplicity we write this as:
71  * \f{equation}{
72  * \label{eq_tr-e4}
73  * t_r = T_p + \frac{1}{n}\left( E + A\sinh E + B[\cosh E - 1] \right) \;,
74  * \f}
75  *
76  * \anchor inject_hanomaly
77  * \image html inject_hanomaly.png "Function to be inverted to find eccentric anomaly"
78  *
79  * where \f$ T_p \f$ is the \e observed time of periapsis passage. Thus
80  * the key numerical procedure in this routine is to invert the
81  * expression \f$ x=E+A\sinh E+B(\cosh E - 1) \f$ to get \f$ E(x) \f$ . This function
82  * is sketched to the right (solid line), along with an approximation
83  * used for making an initial guess (dotted line), as described later.
84  *
85  * We note that \f$ A^2-B^2<1 \f$ , although it approaches 1 when
86  * \f$ e\rightarrow1 \f$ , or when \f$ v_p\rightarrow1 \f$ and either \f$ e=0 \f$ or
87  * \f$ \omega=\pi \f$ . Except in this limit, Newton-Raphson methods will
88  * converge rapidly for any initial guess. In this limit, though, the
89  * slope \f$ dx/dE \f$ approaches zero at \f$ E=0 \f$ , and an initial guess or
90  * iteration landing near this point will send the next iteration off to
91  * unacceptably large or small values. A hybrid root-finding strategy is
92  * used to deal with this, and with the exponential behaviour of \f$ x \f$ at
93  * large \f$ E \f$ .
94  *
95  * First, we compute \f$ x=x_{\pm1} \f$ at \f$ E=\pm1 \f$ . If the desired \f$ x \f$ lies
96  * in this range, we use a straightforward Newton-Raphson root finder,
97  * with the constraint that all guesses of \f$ E \f$ are restricted to the
98  * domain \f$ [-1,1] \f$ . This guarantees that the scheme will eventually find
99  * itself on a uniformly-convergent trajectory.
100  *
101  * Second, for \f$ E \f$ outside of this range, \f$ x \f$ is dominated by the
102  * exponential terms: \f$ x\approx\frac{1}{2}(A+B)\exp(E) \f$ for \f$ E\gg1 \f$ , and
103  * \f$ x\approx-\frac{1}{2}(A-B)\exp(-E) \f$ for \f$ E\ll-1 \f$ . We therefore do an
104  * \e approximate Newton-Raphson iteration on the function \f$ \ln|x| \f$ ,
105  * where the approximation is that we take \f$ d\ln|x|/d|E|\approx1 \f$ . This
106  * involves computing an extra logarithm inside the loop, but gives very
107  * rapid convergence to high precision, since \f$ \ln|x| \f$ is very nearly
108  * linear in these regions.
109  *
110  * At the start of the algorithm, we use an initial guess of
111  * \f$ E=-\ln[-2(x-x_{-1})/(A-B)-\exp(1)] \f$ for \f$ x<x_{-1} \f$ , \f$ E=x/x_{-1} \f$ for
112  * \f$ x_{-1}\leq x\leq0 \f$ , \f$ E=x/x_{+1} \f$ for \f$ 0\leq x\leq x_{+1} \f$ , or
113  * \f$ E=\ln[2(x-x_{+1})/(A+B)-\exp(1)] \f$ for \f$ x>x_{+1} \f$ . We refine this
114  * guess until we get agreement to within 0.01 parts in part in
115  * \f$ N_\mathrm{cyc} \f$ (where \f$ N_\mathrm{cyc} \f$ is the larger of the number
116  * of wave cycles in a time \f$ 2\pi/n \f$ , or the number of wave cycles in the
117  * entire waveform being generated), or one part in \f$ 10^{15} \f$ (an order
118  * of magnitude off the best precision possible with \c REAL8
119  * numbers). The latter case indicates that \c REAL8 precision may
120  * fail to give accurate phasing, and one should consider modeling the
121  * orbit as a set of Taylor frequency coefficients \'{a} la
122  * <tt>LALGenerateTaylorCW()</tt>. On subsequent timesteps, we use the
123  * previous timestep as an initial guess, which is good so long as the
124  * timesteps are much smaller than \f$ 1/n \f$ .
125  *
126  * Once a value of \f$ E \f$ is found for a given timestep in the output
127  * series, we compute the system time \f$ t \f$ via \eqref{eq_spinorbit-t},
128  * and use it to determine the wave phase and (non-Doppler-shifted)
129  * frequency via \eqref{eq_taylorcw-freq}
130  * and \eqref{eq_taylorcw-phi}. The Doppler shift on the frequency is
131  * then computed using \eqref{eq_spinorbit-upsilon}
132  * and \eqref{eq_orbit-rdot}. We use \f$ \upsilon \f$ as an intermediate in
133  * the Doppler shift calculations, since expressing \f$ \dot{R} \f$ directly in
134  * terms of \f$ E \f$ results in expression of the form \f$ (e-1)/(e\cosh E-1) \f$ ,
135  * which are difficult to simplify and face precision losses when
136  * \f$ E\sim0 \f$ and \f$ e\rightarrow1 \f$ . By contrast, solving for \f$ \upsilon \f$ is
137  * numerically stable provided that the system <tt>atan2()</tt> function is
138  * well-designed.
139  *
140  * This routine does not account for relativistic timing variations, and
141  * issues warnings or errors based on the criterea of
142  * \eqref{eq_relativistic-orbit} in \ref LALGenerateEllipticSpinOrbitCW().
143  */
144 void
148 {
149  UINT4 n, i; /* number of and index over samples */
150  UINT4 nSpin = 0, j; /* number of and index over spindown terms */
151  REAL8 t, dt, tPow; /* time, interval, and t raised to a power */
152  REAL8 phi0, f0, twopif0; /* initial phase, frequency, and 2*pi*f0 */
153  REAL8 f, fPrev; /* current and previous values of frequency */
154  REAL4 df = 0.0; /* maximum difference between f and fPrev */
155  REAL8 phi; /* current value of phase */
156  REAL8 vDotAvg; /* nomalized orbital angular speed */
157  REAL8 vp; /* projected speed at periapsis */
158  REAL8 upsilon, argument; /* true anomaly, and argument of periapsis */
159  REAL8 eCosOmega; /* eccentricity * cosine of argument */
160  REAL8 tPeriObs; /* time of observed periapsis */
161  REAL8 spinOff; /* spin epoch - orbit epoch */
162  REAL8 x; /* observed ``mean anomaly'' */
163  REAL8 xPlus, xMinus; /* limits where exponentials dominate */
164  REAL8 dx, dxMax; /* current and target errors in x */
165  REAL8 a, b; /* constants in equation for x */
166  REAL8 ecc; /* orbital eccentricity */
167  REAL8 eccMinusOne, eccPlusOne; /* ecc - 1 and ecc + 1 */
168  REAL8 e; /* hyperbolic anomaly */
169  REAL8 sinhe, coshe; /* sinh of e, and cosh of e minus 1 */
170  REAL8 *fSpin = NULL; /* pointer to Taylor coefficients */
171  REAL4 *fData; /* pointer to frequency data */
172  REAL8 *phiData; /* pointer to phase data */
173 
174  INITSTATUS( stat );
176 
177  /* Make sure parameter and output structures exist. */
179  GENERATESPINORBITCWH_MSGENUL );
181  GENERATESPINORBITCWH_MSGENUL );
182 
183  /* Make sure output fields don't exist. */
185  GENERATESPINORBITCWH_MSGEOUT );
187  GENERATESPINORBITCWH_MSGEOUT );
189  GENERATESPINORBITCWH_MSGEOUT );
191  GENERATESPINORBITCWH_MSGEOUT );
192 
193  /* If Taylor coeficients are specified, make sure they exist. */
194  if ( params->f ) {
196  GENERATESPINORBITCWH_MSGENUL );
197  nSpin = params->f->length;
198  fSpin = params->f->data;
199  }
200 
201  /* Set up some constants (to avoid repeated calculation or
202  dereferencing), and make sure they have acceptable values. */
203  eccMinusOne = -params->oneMinusEcc;
204  ecc = 1.0 + eccMinusOne;
205  eccPlusOne = 2.0 + eccMinusOne;
206  if ( eccMinusOne <= 0.0 ) {
208  GENERATESPINORBITCWH_MSGEECC );
209  }
210  vp = params->rPeriNorm * params->angularSpeed;
211  vDotAvg = params->angularSpeed
212  * sqrt( eccMinusOne * eccMinusOne * eccMinusOne / eccPlusOne );
213  n = params->length;
214  dt = params->deltaT;
215  f0 = fPrev = params->f0;
216  if ( vp >= 1.0 ) {
218  GENERATESPINORBITCWH_MSGEFTL );
219  }
220  if ( vp <= 0.0 || dt <= 0.0 || f0 <= 0.0 || vDotAvg <= 0.0 ||
221  n == 0 ) {
223  GENERATESPINORBITCWH_MSGESGN );
224  }
225 
226  /* Set up some other constants. */
227  twopif0 = f0 * LAL_TWOPI;
228  phi0 = params->phi0;
229  argument = params->omega;
230  a = vp * eccMinusOne * cos( argument ) + ecc;
231  b = -vp * sqrt( eccMinusOne / eccPlusOne ) * sin( argument );
232  eCosOmega = ecc * cos( argument );
233  if ( n * dt * vDotAvg > LAL_TWOPI ) {
234  dxMax = 0.01 / ( f0 * n * dt );
235  } else {
236  dxMax = 0.01 / ( f0 * LAL_TWOPI / vDotAvg );
237  }
238  if ( dxMax < 1.0e-15 ) {
239  dxMax = 1.0e-15;
240  LALWarning( stat, "REAL8 arithmetic may not have sufficient"
241  " precision for this orbit" );
242  }
243  if ( lalDebugLevel & LALWARNING ) {
244  REAL8 tau = n * dt;
245  if ( tau > LAL_TWOPI / vDotAvg ) {
246  tau = LAL_TWOPI / vDotAvg;
247  }
248  if ( f0 * tau * vp * vp * ecc / eccPlusOne > 0.25 )
249  LALWarning( stat, "Orbit may have significant relativistic"
250  " effects that are not included" );
251  }
252 
253  /* Compute offset between time series epoch and observed periapsis,
254  and betweem true periapsis and spindown reference epoch. */
255  tPeriObs = ( REAL8 )( params->orbitEpoch.gpsSeconds -
257  tPeriObs += 1.0e-9 * ( REAL8 )( params->orbitEpoch.gpsNanoSeconds -
259  tPeriObs += params->rPeriNorm * sin( params->omega );
260  spinOff = ( REAL8 )( params->orbitEpoch.gpsSeconds -
261  params->spinEpoch.gpsSeconds );
262  spinOff += 1.0e-9 * ( REAL8 )( params->orbitEpoch.gpsNanoSeconds -
263  params->spinEpoch.gpsNanoSeconds );
264 
265  /* Determine bounds of hybrid root-finding algorithm, and initial
266  guess for e. */
267  xMinus = 1.0 + a * sinh( -1.0 ) + b * cosh( -1.0 ) - b;
268  xPlus = -1.0 + a * sinh( 1.0 ) + b * cosh( 1.0 ) - b;
269  x = -vDotAvg * tPeriObs;
270  if ( x < xMinus ) {
271  e = -log( -2.0 * ( x - xMinus ) / ( a - b ) - exp( 1.0 ) );
272  } else if ( x <= 0 ) {
273  e = x / xMinus;
274  } else if ( x <= xPlus ) {
275  e = x / xPlus;
276  } else {
277  e = log( 2.0 * ( x - xPlus ) / ( a + b ) - exp( 1.0 ) );
278  }
279  sinhe = sinh( e );
280  coshe = cosh( e ) - 1.0;
281 
282  /* Allocate output structures. */
283  if ( ( output->a = ( REAL4TimeVectorSeries * )
284  LALMalloc( sizeof( REAL4TimeVectorSeries ) ) ) == NULL ) {
286  GENERATESPINORBITCWH_MSGEMEM );
287  }
288  memset( output->a, 0, sizeof( REAL4TimeVectorSeries ) );
289  if ( ( output->f = ( REAL4TimeSeries * )
290  LALMalloc( sizeof( REAL4TimeSeries ) ) ) == NULL ) {
291  LALFree( output->a );
292  output->a = NULL;
294  GENERATESPINORBITCWH_MSGEMEM );
295  }
296  memset( output->f, 0, sizeof( REAL4TimeSeries ) );
297  if ( ( output->phi = ( REAL8TimeSeries * )
298  LALMalloc( sizeof( REAL8TimeSeries ) ) ) == NULL ) {
299  LALFree( output->a );
300  output->a = NULL;
301  LALFree( output->f );
302  output->f = NULL;
304  GENERATESPINORBITCWH_MSGEMEM );
305  }
306  memset( output->phi, 0, sizeof( REAL8TimeSeries ) );
307 
308  /* Set output structure metadata fields. */
309  output->position = params->position;
310  output->psi = params->psi;
311  output->a->epoch = output->f->epoch = output->phi->epoch
312  = params->epoch;
313  output->a->deltaT = n * params->deltaT;
314  output->f->deltaT = output->phi->deltaT = params->deltaT;
315  output->a->sampleUnits = lalStrainUnit;
316  output->f->sampleUnits = lalHertzUnit;
317  output->phi->sampleUnits = lalDimensionlessUnit;
318  snprintf( output->a->name, LALNameLength, "CW amplitudes" );
319  snprintf( output->f->name, LALNameLength, "CW frequency" );
320  snprintf( output->phi->name, LALNameLength, "CW phase" );
321 
322  /* Allocate phase and frequency arrays. */
323  LALSCreateVector( stat->statusPtr, &( output->f->data ), n );
324  BEGINFAIL( stat ) {
325  LALFree( output->a );
326  output->a = NULL;
327  LALFree( output->f );
328  output->f = NULL;
329  LALFree( output->phi );
330  output->phi = NULL;
331  }
332  ENDFAIL( stat );
333  LALDCreateVector( stat->statusPtr, &( output->phi->data ), n );
334  BEGINFAIL( stat ) {
335  TRY( LALSDestroyVector( stat->statusPtr, &( output->f->data ) ),
336  stat );
337  LALFree( output->a );
338  output->a = NULL;
339  LALFree( output->f );
340  output->f = NULL;
341  LALFree( output->phi );
342  output->phi = NULL;
343  }
344  ENDFAIL( stat );
345 
346  /* Allocate and fill amplitude array. */
347  {
348  CreateVectorSequenceIn in; /* input to create output->a */
349  in.length = 2;
350  in.vectorLength = 2;
351  LALSCreateVectorSequence( stat->statusPtr, &( output->a->data ), &in );
352  BEGINFAIL( stat ) {
353  TRY( LALSDestroyVector( stat->statusPtr, &( output->f->data ) ),
354  stat );
355  TRY( LALDDestroyVector( stat->statusPtr, &( output->phi->data ) ),
356  stat );
357  LALFree( output->a );
358  output->a = NULL;
359  LALFree( output->f );
360  output->f = NULL;
361  LALFree( output->phi );
362  output->phi = NULL;
363  }
364  ENDFAIL( stat );
365  output->a->data->data[0] = output->a->data->data[2] = params->aPlus;
366  output->a->data->data[1] = output->a->data->data[3] = params->aCross;
367  }
368 
369  /* Fill frequency and phase arrays. */
370  fData = output->f->data->data;
371  phiData = output->phi->data->data;
372  for ( i = 0; i < n; i++ ) {
373 
374  x = vDotAvg * ( i * dt - tPeriObs );
375 
376  /* Use approximate Newton-Raphson method on ln|x| if |x| > 1. */
377  if ( x < xMinus ) {
378  x = log( -x );
379  while ( fabs( dx = log( e - a * sinhe - b * coshe ) - x ) > dxMax ) {
380  e += dx;
381  sinhe = sinh( e );
382  coshe = cosh( e ) - 1.0;
383  }
384  } else if ( x > xPlus ) {
385  x = log( x );
386  while ( fabs( dx = log( -e + a * sinhe + b * coshe ) - x ) > dxMax ) {
387  e -= dx;
388  sinhe = sinh( e );
389  coshe = cosh( e ) - 1.0;
390  }
391  }
392 
393  /* Use ordinary Newton-Raphson method on x if |x| <= 1. */
394  else {
395  while ( fabs( dx = -e + a * sinhe + b * coshe - x ) > dxMax ) {
396  e -= dx / ( -1.0 + a * coshe + a + b * sinhe );
397  if ( e < -1.0 ) {
398  e = -1.0;
399  } else if ( e > 1.0 ) {
400  e = 1.0;
401  }
402  sinhe = sinh( e );
403  coshe = cosh( e ) - 1.0;
404  }
405  }
406 
407  /* Compute source emission time, phase, and frequency. */
408  phi = t = tPow =
409  ( ecc * sinhe - e ) / vDotAvg + spinOff;
410  f = 1.0;
411  for ( j = 0; j < nSpin; j++ ) {
412  f += fSpin[j] * tPow;
413  phi += fSpin[j] * ( tPow *= t ) / ( j + 2.0 );
414  }
415 
416  /* Appy frequency Doppler shift. */
417  upsilon = 2.0 * atan2( sqrt( eccPlusOne * coshe ),
418  sqrt( eccMinusOne * ( coshe + 2.0 ) ) );
419  f *= f0 / ( 1.0 + vp * ( cos( argument + upsilon ) + eCosOmega )
420  / eccPlusOne );
421  phi *= twopif0;
422  if ( fabs( f - fPrev ) > df ) {
423  df = fabs( f - fPrev );
424  }
425  *( fData++ ) = fPrev = f;
426  *( phiData++ ) = phi + phi0;
427  }
428 
429  /* Set output field and return. */
430  params->dfdt = df * dt;
432  RETURN( stat );
433 }
double cosh(double)
int j
#define LALMalloc(n)
#define LALFree(p)
#define ABORT(statusptr, code, mesg)
#define ENDFAIL(statusptr)
#define TRY(func, statusptr)
#define ATTATCHSTATUSPTR(statusptr)
#define ASSERT(assertion, statusptr, code, mesg)
#define DETATCHSTATUSPTR(statusptr)
#define INITSTATUS(statusptr)
#define RETURN(statusptr)
#define BEGINFAIL(statusptr)
double e
void LALSCreateVectorSequence(LALStatus *status, REAL4VectorSequence **vectorSequence, CreateVectorSequenceIn *vSeqParams)
#define GENERATESPINORBITCWH_EECC
Eccentricity out of range.
void LALGenerateHyperbolicSpinOrbitCW(LALStatus *stat, PulsarCoherentGW *output, SpinOrbitCWParamStruc *params)
Computes a continuous waveform with frequency drift and Doppler modulation from a hyperbolic orbital ...
#define GENERATESPINORBITCWH_ESGN
Sign error: positive parameter expected.
#define GENERATESPINORBITCWH_EMEM
Out of memory.
#define GENERATESPINORBITCWH_ENUL
Unexpected null pointer in arguments.
#define GENERATESPINORBITCWH_EOUT
Output field a, f, phi, or shift already exists.
#define GENERATESPINORBITCWH_EFTL
Periapsis motion is faster than light.
#define LAL_TWOPI
double REAL8
uint32_t UINT4
float REAL4
LALNameLength
#define lalDebugLevel
LALWARNING
int LALWarning(LALStatus *status, const char *warning)
static const INT4 a
const LALUnit lalStrainUnit
const LALUnit lalHertzUnit
const LALUnit lalDimensionlessUnit
void LALDCreateVector(LALStatus *, REAL8Vector **, UINT4)
void LALDDestroyVector(LALStatus *, REAL8Vector **)
void LALSDestroyVector(LALStatus *, REAL4Vector **)
void LALSCreateVector(LALStatus *, REAL4Vector **, UINT4)
n
INT4 gpsNanoSeconds
This structure stores a representation of a plane gravitational wave propagating from a particular po...
This structure stores the parameters for constructing a gravitational waveform with both a Taylor-pol...
LIGOTimeGPS epoch
double psi
double df