LALPulsar  6.1.0.1-89842e6
SuperskyMetrics.c
Go to the documentation of this file.
1 //
2 // Copyright (C) 2014--2017 Karl Wette
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 #include <config.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <float.h>
25 #include <math.h>
26 
27 #include <gsl/gsl_math.h>
28 #include <gsl/gsl_blas.h>
29 #include <gsl/gsl_linalg.h>
30 #include <gsl/gsl_eigen.h>
31 #include <gsl/gsl_roots.h>
32 
33 #include <lal/SuperskyMetrics.h>
34 #include <lal/LALConstants.h>
35 #include <lal/LogPrintf.h>
36 #include <lal/MetricUtils.h>
37 #include <lal/ExtrapolatePulsarSpins.h>
38 
39 #include <lal/GSLHelpers.h>
40 
41 #ifdef __GNUC__
42 #define UNUSED __attribute__ ((unused))
43 #else
44 #define UNUSED
45 #endif
46 
47 // Square of a numbers
48 #define SQR(x) ((x)*(x))
49 
50 // Real part of square root of a number, i.e. zero if x < ~0
51 #define RE_SQRT(x) sqrt(GSL_MAX(DBL_EPSILON, (x)))
52 
53 // 2- and 3-dimensional vector dot products
54 #define DOT2(x,y) ((x)[0]*(y)[0] + (x)[1]*(y)[1])
55 #define DOT3(x,y) ((x)[0]*(y)[0] + (x)[1]*(y)[1] + (x)[2]*(y)[2])
56 
57 // Maximum number of sky offsets required
58 #define MAX_SKY_OFFSETS PULSAR_MAX_SPINS
59 
60 // FIXME: replace 'SMAX' with either 'nspins', 'nsky_offsets - 1', or something else...
61 #define SMAX nspins
62 
63 // Internal definition of reduced supersky metric coordinate transform data
65  UINT4 ndim; ///< Dimensions of the corresponding metric
66  LIGOTimeGPS ref_time; ///< Reference time of the metric
67  REAL8 fiducial_freq; ///< Fiducial frequency of metric
68  UINT4 nspins; ///< Number of spindown dimensions
69  REAL8 align_sky[3][3]; ///< Alignment transform of the supersky metric
70  UINT4 nsky_offsets; ///< Number of sky offsets
71  REAL8 sky_offsets[MAX_SKY_OFFSETS][3]; ///< Sky offsets of the supersky metric
72 };
73 
74 // Check reduced supersky coordinate metric and/or transform data
75 #define CHECK_RSSKY_METRIC_TRANSF(M, RT) \
76  ((M) != NULL && CHECK_RSSKY_TRANSF(RT) && (M)->size1 == (RT)->ndim && (M)->size2 == (RT)->ndim)
77 #define CHECK_RSSKY_TRANSF(RT) \
78  ((RT) != NULL && (RT)->ndim > 0 && (RT)->fiducial_freq > 0 && (RT)->nsky_offsets == 1 + (RT)->nspins)
79 
80 // Determine which dimension stores the reduced supersky frequency/spindown of order 's'
81 #define RSSKY_FKDOT_OFFSET(RT, S) (((S) == 0) ? ((RT)->SMAX) : ((size_t)((S) - 1)))
82 #define RSSKY_FKDOT_DIM(RT, S) (2 + RSSKY_FKDOT_OFFSET(RT, S))
83 
84 ///
85 /// Fiducial frequency at which to numerically calculate metrics, which
86 /// are then rescaled to user-requested frequency based on known scalings
87 ///
88 const double fiducial_calc_freq = 100.0;
89 
90 // Structures for callback functions
91 typedef struct tagSM_CallbackParam {
92  const SuperskyTransformData *rssky_transf; ///< Reduced supersky coordinate transform data
93  const SuperskyTransformData *rssky2_transf; ///< Other reduced supersky coordinate transform data
95 typedef struct tagSM_CallbackOut {
96  PulsarDopplerParams min_phys; ///< Minimum physical range
97  PulsarDopplerParams max_phys; ///< Maximum physical range
98  double min_rssky2_array[32]; ///< Minimum range of other reduced supersky coordinates
99  gsl_vector_view min_rssky2_view;
100  double max_rssky2_array[32]; ///< Maximum range of other reduced supersky coordinates
101  gsl_vector_view max_rssky2_view;
103 
104 ///
105 /// Call XLALComputeDopplerPhaseMetric() to compute the phase metric for a given coordinate system.
106 ///
107 static gsl_matrix *SM_ComputePhaseMetric(
108  const DopplerCoordinateSystem *coords, ///< [in] Coordinate system to compute metric for
109  const LIGOTimeGPS *ref_time, ///< [in] Reference time of the metric
110  const LIGOTimeGPS *start_time, ///< [in] Start time of the metric
111  const LIGOTimeGPS *end_time, ///< [in] End time of the metric
112  const MultiLALDetector *detectors, ///< [in] List of detectors to average metric over
113  const MultiNoiseFloor *detector_weights, ///< [in] Weights used to combine single-detector metrics (default: unit weights)
114  const DetectorMotionType detector_motion, ///< [in] Which detector motion to use
115  const EphemerisData *ephemerides ///< [in] Earth/Sun ephemerides
116 )
117 {
118 
119  // Check input
120  XLAL_CHECK_NULL( coords != NULL, XLAL_EFAULT );
121  XLAL_CHECK_NULL( ref_time != NULL, XLAL_EFAULT );
123  XLAL_CHECK_NULL( end_time != NULL, XLAL_EFAULT );
125  XLAL_CHECK_NULL( detectors->length > 0, XLAL_EINVAL );
126  XLAL_CHECK_NULL( detector_motion > 0, XLAL_EINVAL );
128 
129  // Supersky metric cannot (reliably) be computed for segment lengths <= ~24 hours
130  XLAL_CHECK_NULL( XLALGPSDiff( end_time, start_time ) >= 81000, XLAL_ERANGE, "Supersky metric cannot be computed for segment lengths <= ~24 hours" );
131 
132  // Create parameters struct for XLALComputeDopplerPhaseMetric()
134 
135  // Set coordinate system
136  par.coordSys = *coords;
137 
138  // Set detector motion type
139  par.detMotionType = detector_motion;
140 
141  // Set segment list
142  LALSegList segments;
144  LALSeg segment;
145  XLAL_CHECK_NULL( XLALSegSet( &segment, start_time, end_time, 0 ) == XLAL_SUCCESS, XLAL_EFUNC );
146  XLAL_CHECK_NULL( XLALSegListAppend( &segments, &segment ) == XLAL_SUCCESS, XLAL_EFUNC );
147  par.segmentList = segments;
148 
149  // Set detectors and detector weights
150  par.multiIFO = *detectors;
151  if ( detector_weights != NULL ) {
152  par.multiNoiseFloor = *detector_weights;
153  } else {
154  par.multiNoiseFloor.length = 0; // Indicates unit weights
155  }
156 
157  // Set reference time to segment mid-time, to improve stability of numerical calculation of metric
158  par.signalParams.Doppler.refTime = *start_time;
159  XLALGPSAdd( &par.signalParams.Doppler.refTime, 0.5 * XLALGPSDiff( end_time, start_time ) );
160 
161  // Set fiducial frequency
162  par.signalParams.Doppler.fkdot[0] = fiducial_calc_freq;
163 
164  // Do not project metric
165  par.projectCoord = -1;
166 
167  // Do not include sky-position-dependent Roemer delay in time variable
168  par.approxPhase = 1;
169 
170  // Call XLALComputeDopplerPhaseMetric() and check output
172  XLAL_CHECK_NULL( metric != NULL && metric->g_ij != NULL, XLAL_EFUNC, "XLALComputeDopplerPhaseMetric() failed" );
173 
174  // Extract metric, while transforming its reference time from segment mid-time to time specified by 'ref_time'
175  gsl_matrix *g_ij = NULL;
176  const REAL8 Dtau = XLALGPSDiff( ref_time, &par.signalParams.Doppler.refTime );
177  XLAL_CHECK_NULL( XLALChangeMetricReferenceTime( &g_ij, NULL, metric->g_ij, coords, Dtau ) == XLAL_SUCCESS, XLAL_EFUNC );
178 
179  // Cleanup
181  XLALSegListClear( &segments );
182 
183  return g_ij;
184 
185 }
186 
187 ///
188 /// Find a least-squares linear fit to the orbital X and Y metric elements using the frequency and
189 /// spindown metric elements. Outputs the intermediate \e fitted supersky metric, and updates the
190 /// reduced supersky metric coordinate transform data.
191 ///
193  gsl_matrix *fitted_ssky_metric, ///< [out] Fitted supersky metric
194  SuperskyTransformData *rssky_transf, ///< [in,out] Reduced supersky metric coordinate transform data
195  const gsl_matrix *ussky_metric, ///< [in] Unrestricted supersky metric
196  const gsl_matrix *orbital_metric, ///< [in] Orbital metric in ecliptic coordinates
197  const DopplerCoordinateSystem *ocoords, ///< [in] Coordinate system of orbital metric
198  const LIGOTimeGPS *start_time, ///< [in] Start time of the metrics
199  const LIGOTimeGPS *end_time ///< [in] End time of the metrics
200 )
201 {
202 
203  // Check input
204  XLAL_CHECK( fitted_ssky_metric != NULL, XLAL_EFAULT );
205  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
206  XLAL_CHECK( ussky_metric != NULL, XLAL_EFAULT );
207  XLAL_CHECK( orbital_metric != NULL, XLAL_EFAULT );
208  XLAL_CHECK( ocoords != NULL, XLAL_EFAULT );
209  XLAL_CHECK( start_time != NULL, XLAL_EFAULT );
210  XLAL_CHECK( end_time != NULL, XLAL_EFAULT );
211 
212  // Allocate memory
213  gsl_matrix *GAMAT( tmp, 3 + rssky_transf->SMAX, 3 + rssky_transf->SMAX );
214  gsl_vector *GAVEC( tmpv, 1 + rssky_transf->SMAX );
215 
216  // Compute mid-time of segment list
217  LIGOTimeGPS mid_time = *start_time;
218  XLALGPSAdd( &mid_time, 0.5 * XLALGPSDiff( end_time, start_time ) );
219 
220  // Internal copy of orbital metric, and various transforms performed on it
221  gsl_matrix *orb_metric = NULL, *mid_time_transf = NULL, *diag_norm_transf = NULL;
222 
223  // Transform reference time of orbital metric from reference time to segment list mid-time
224  const REAL8 Dtau = XLALGPSDiff( &mid_time, &rssky_transf->ref_time );
225  XLAL_CHECK( XLALChangeMetricReferenceTime( &orb_metric, &mid_time_transf, orbital_metric, ocoords, Dtau ) == XLAL_SUCCESS, XLAL_EFUNC );
226 
227  // Diagonally-normalize orbital metric
228  XLAL_CHECK( XLALDiagNormalizeMetric( &orb_metric, &diag_norm_transf, orb_metric ) == XLAL_SUCCESS, XLAL_EFUNC );
229 
230  // 'fitA' contains the frequency and spindown elements of the orbital metric, used for fitting
231  gsl_matrix *GAMAT( fitA, 3 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
232  {
233  gsl_matrix_view orb_metric_fspin = gsl_matrix_submatrix( orb_metric, 0, 2, 3 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
234  gsl_matrix_memcpy( fitA, &orb_metric_fspin.matrix );
235  }
236 
237  // Compute 'fitA^T * fitA'
238  gsl_matrix *GAMAT( fitAt_fitA, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
239  gsl_blas_dgemm( CblasTrans, CblasNoTrans, 1.0, fitA, fitA, 0.0, fitAt_fitA );
240 
241  // Find the singular value decomposition of 'fitA^T * fitA'
242  gsl_matrix *GAMAT( svd_U, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
243  gsl_matrix *GAMAT( svd_V, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
244  gsl_vector *GAVEC( svd_S, 1 + rssky_transf->SMAX );
245  gsl_matrix_memcpy( svd_U, fitAt_fitA );
246  XLAL_CHECK( gsl_linalg_SV_decomp( svd_U, svd_V, svd_S, tmpv ) == 0, XLAL_EFAILED );
247 
248  // The columns of 'fitc' contain the least-square fitting coefficients for the orbital X and Y metric elements:
249  // fitc(:,j) = inv(fitA^T * fitA) * fitA^T * orb_metric(:,j)
250  // The singular decomposition of fitA^T * fitA is used for the inverse
251  gsl_matrix *GAMAT( fitc, 1 + rssky_transf->SMAX, 2 );
252  for ( size_t j = 0; j < 2; ++j ) {
253  gsl_vector_view orb_metric_j = gsl_matrix_column( orb_metric, j );
254  gsl_vector_view fitc_j = gsl_matrix_column( fitc, j );
255  gsl_blas_dgemv( CblasTrans, 1.0, fitA, &orb_metric_j.vector, 0.0, tmpv );
256  XLAL_CHECK( gsl_linalg_SV_solve( svd_U, svd_V, svd_S, tmpv, &fitc_j.vector ) == 0, XLAL_EFAILED );
257  }
258 
259  // Construct the matrix 'subtract_orb', which subtracts the least-squares fit of
260  // the orbital X and Y metric elements from the orbital metric. Its layout is:
261  //
262  // #-------- sky --------#---f/s---#
263  // | | |
264  // sky identity matrix I | zeros |
265  // | | |
266  // |---------------------#---------#
267  // | | |
268  // f/s -fitc | I |
269  // | | |
270  // #---------------------#---------#
271  //
272  gsl_matrix *GAMAT( subtract_orb, 3 + rssky_transf->SMAX, 3 + rssky_transf->SMAX );
273  {
274  gsl_matrix_set_identity( subtract_orb );
275  gsl_matrix_view subtract_orb_fspin_sky = gsl_matrix_submatrix( subtract_orb, 2, 0, 1 + rssky_transf->SMAX, 2 );
276  gsl_matrix_memcpy( &subtract_orb_fspin_sky.matrix, fitc );
277  gsl_matrix_scale( &subtract_orb_fspin_sky.matrix, -1.0 );
278  }
279 
280  // Multiply 'subtract_orb' by the diagonal-normalization and reference time transforms,
281  // to obtain the matrix that substracts the fit from the unconstrained supersky metric
282  gsl_blas_dgemm( CblasNoTrans, CblasNoTrans, 1.0, diag_norm_transf, subtract_orb, 0.0, tmp );
283  gsl_blas_dgemm( CblasNoTrans, CblasNoTrans, 1.0, mid_time_transf, tmp, 0.0, subtract_orb );
284 
285  // Construct the matrix 'subtract_ussky', which subtracts the least-squares fit of the orbital X and Y metric
286  // elements from the *unconstrained* supersky metric, which is in *equatorial* coordinates. Its layout is:
287  //
288  // #------------------------------ sky -------------------------------#---f/s---#
289  // | | |
290  // sky identity matrix I | zeros |
291  // | | |
292  // |------------------------------------------------------------------#---------#
293  // | . . | |
294  // f/s sub_o(:,0) . sub_o(:,1)*LAL_COSIEARTH . sub_o(:,1)*LAL_SINIEARTH | I |
295  // | . . | |
296  // #------------------------------------------------------------------#---------#
297  //
298  // where 'sub_o' denotes 'subtract_orb'.
299  gsl_matrix *GAMAT( subtract_ussky, 4 + rssky_transf->SMAX, 4 + rssky_transf->SMAX );
300  {
301  gsl_matrix_set_identity( subtract_ussky );
302  gsl_matrix_view subtract_ussky_fspin_sky = gsl_matrix_submatrix( subtract_ussky, 3, 0, 1 + rssky_transf->SMAX, 3 );
303  gsl_matrix_view subtract_orb_fspin_sky = gsl_matrix_submatrix( subtract_orb, 2, 0, 1 + rssky_transf->SMAX, 2 );
304  {
305  gsl_vector_view subtract_ussky_fspin_sky_col = gsl_matrix_column( &subtract_ussky_fspin_sky.matrix, 0 );
306  gsl_vector_view subtract_orb_fspin_sky_col = gsl_matrix_column( &subtract_orb_fspin_sky.matrix, 0 );
307  gsl_vector_memcpy( &subtract_ussky_fspin_sky_col.vector, &subtract_orb_fspin_sky_col.vector );
308  }
309  {
310  gsl_vector_view subtract_ussky_fspin_sky_col = gsl_matrix_column( &subtract_ussky_fspin_sky.matrix, 1 );
311  gsl_vector_view subtract_orb_fspin_sky_col = gsl_matrix_column( &subtract_orb_fspin_sky.matrix, 1 );
312  gsl_vector_memcpy( &subtract_ussky_fspin_sky_col.vector, &subtract_orb_fspin_sky_col.vector );
313  gsl_vector_scale( &subtract_ussky_fspin_sky_col.vector, LAL_COSIEARTH );
314  }
315  {
316  gsl_vector_view subtract_ussky_fspin_sky_col = gsl_matrix_column( &subtract_ussky_fspin_sky.matrix, 2 );
317  gsl_vector_view subtract_orb_fspin_sky_col = gsl_matrix_column( &subtract_orb_fspin_sky.matrix, 1 );
318  gsl_vector_memcpy( &subtract_ussky_fspin_sky_col.vector, &subtract_orb_fspin_sky_col.vector );
319  gsl_vector_scale( &subtract_ussky_fspin_sky_col.vector, LAL_SINIEARTH );
320  }
321  }
322 
323  // Transform the unconstrained supersky metric to the intermediate fitted supersky metric
324  XLAL_CHECK( XLALTransformMetric( &fitted_ssky_metric, subtract_ussky, ussky_metric ) == XLAL_SUCCESS, XLAL_EFUNC );
325 
326  // Extract the sky offset vectors from 'subtract_ussky', and subtract them from the reduced supersky coordinate transform data
327  {
328  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
329  gsl_matrix_view subtract_ussky_fspin_sky = gsl_matrix_submatrix( subtract_ussky, 3, 0, 1 + rssky_transf->SMAX, 3 );
330  gsl_matrix_sub( &sky_offsets.matrix, &subtract_ussky_fspin_sky.matrix );
331  }
332 
333  // Cleanup
334  GFMAT( diag_norm_transf, fitA, fitAt_fitA, fitc, mid_time_transf, orb_metric, subtract_orb, subtract_ussky, svd_U, svd_V, tmp );
335  GFVEC( svd_S, tmpv );
336 
337  return XLAL_SUCCESS;
338 
339 }
340 
341 ///
342 /// Decouple the sky--sky and freq+spin--freq+spin blocks of the fitted supersky metric. Outputs the
343 /// intermediate \e decoupled supersky metric, and updates the reduced supersky metric coordinate
344 /// transform data.
345 ///
347  gsl_matrix *decoupled_ssky_metric, ///< [out] Decoupled supersky metric
348  SuperskyTransformData *rssky_transf, ///< [in,out] Reduced supersky metric coordinate transform data
349  const gsl_matrix *fitted_ssky_metric ///< [in] Fitted supersky metric
350 )
351 {
352 
353  // Check input
354  XLAL_CHECK( decoupled_ssky_metric != NULL, XLAL_EFAULT );
355  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
356  XLAL_CHECK( fitted_ssky_metric != NULL, XLAL_EFAULT );
357 
358  // Copy fitted metric to decoupled metric
359  gsl_matrix_memcpy( decoupled_ssky_metric, fitted_ssky_metric );
360 
361  // Create views of the sky--sky, freq+spin--freq+spin, and off-diagonal blocks
362  gsl_matrix_view sky_sky = gsl_matrix_submatrix( decoupled_ssky_metric, 0, 0, 3, 3 );
363  gsl_matrix_view sky_fspin = gsl_matrix_submatrix( decoupled_ssky_metric, 0, 3, 3, 1 + rssky_transf->SMAX );
364  gsl_matrix_view fspin_sky = gsl_matrix_submatrix( decoupled_ssky_metric, 3, 0, 1 + rssky_transf->SMAX, 3 );
365  gsl_matrix_view fspin_fspin = gsl_matrix_submatrix( decoupled_ssky_metric, 3, 3, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
366 
367  // Diagonal-normalise the freq+spin--freq+spin block
368  gsl_matrix *fspin_fspin_dnorm = NULL, *fspin_fspin_dnorm_transf = NULL;
369  XLAL_CHECK( XLALDiagNormalizeMetric( &fspin_fspin_dnorm, &fspin_fspin_dnorm_transf, &fspin_fspin.matrix ) == XLAL_SUCCESS, XLAL_EFUNC );
370 
371  // Invert the freq+spin--freq+spin block
372  gsl_matrix *GAMAT( fspin_fspin_dnorm_LU, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
373  gsl_matrix *GAMAT( fspin_fspin_dnorm_inv, 1 + rssky_transf->SMAX, 1 + rssky_transf->SMAX );
374  gsl_permutation *GAPERM( fspin_fspin_dnorm_LU_perm, 1 + rssky_transf->SMAX );
375  int fspin_fspin_dnorm_LU_sign = 0;
376  gsl_matrix_memcpy( fspin_fspin_dnorm_LU, fspin_fspin_dnorm );
377  XLAL_CHECK( gsl_linalg_LU_decomp( fspin_fspin_dnorm_LU, fspin_fspin_dnorm_LU_perm, &fspin_fspin_dnorm_LU_sign ) == 0, XLAL_EFAILED );
378  XLAL_CHECK( gsl_linalg_LU_invert( fspin_fspin_dnorm_LU, fspin_fspin_dnorm_LU_perm, fspin_fspin_dnorm_inv ) == 0, XLAL_EFAILED );
379 
380  // Compute the additional sky offsets required to decouple the sky--sky and frequency blocks:
381  // decouple_sky_offsets = fspin_fspin_dnorm_transf * inv(fspin_fspin_dnorm) * fspin_fspin_dnorm_transf * fspin_sky
382  // Uses fspin_sky as a temporary matrix, since it will be zeroed out anyway
383  gsl_matrix *GAMAT( decouple_sky_offsets, 1 + rssky_transf->SMAX, 3 );
384  gsl_blas_dtrmm( CblasLeft, CblasUpper, CblasNoTrans, CblasNonUnit, 1.0, fspin_fspin_dnorm_transf, &fspin_sky.matrix );
385  gsl_blas_dgemm( CblasNoTrans, CblasNoTrans, 1.0, fspin_fspin_dnorm_inv, &fspin_sky.matrix, 0.0, decouple_sky_offsets );
386  gsl_blas_dtrmm( CblasLeft, CblasUpper, CblasNoTrans, CblasNonUnit, 1.0, fspin_fspin_dnorm_transf, decouple_sky_offsets );
387 
388  // Add the additional sky offsets to the reduced supersky coordinate transform data
389  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
390  gsl_matrix_add( &sky_offsets.matrix, decouple_sky_offsets );
391 
392  // Apply the decoupling transform to the sky--sky block:
393  // sky_sky = sky_sky - sky_fspin * decouplp_sky_offsets
394  gsl_blas_dgemm( CblasNoTrans, CblasNoTrans, -1.0, &sky_fspin.matrix, decouple_sky_offsets, 1.0, &sky_sky.matrix );
395 
396  // Zero out the off-diagonal blocks
397  gsl_matrix_set_zero( &sky_fspin.matrix );
398  gsl_matrix_set_zero( &fspin_sky.matrix );
399 
400  // Cleanup
401  GFPERM( fspin_fspin_dnorm_LU_perm );
402  GFMAT( fspin_fspin_dnorm, fspin_fspin_dnorm_LU, fspin_fspin_dnorm_inv, fspin_fspin_dnorm_transf, decouple_sky_offsets );
403 
404  return XLAL_SUCCESS;
405 
406 }
407 
408 ///
409 /// Align the sky--sky block of the decoupled supersky metric with its eigenvalues by means of a
410 /// rotation. Outputs the intermediate \e aligned supersky metric, and updates the reduced supersky
411 /// metric coordinate transform data.
412 ///
414  gsl_matrix *aligned_ssky_metric, ///< [out] Aligned supersky metric
415  SuperskyTransformData *rssky_transf, ///< [in,out] Reduced supersky metric coordinate transform data
416  const gsl_matrix *decoupled_ssky_metric ///< [in] Decoupled supersky metric
417 )
418 {
419 
420  // Check input
421  XLAL_CHECK( aligned_ssky_metric != NULL, XLAL_EFAULT );
422  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
423  XLAL_CHECK( decoupled_ssky_metric != NULL, XLAL_EFAULT );
424 
425  // Allocate memory
426  gsl_matrix *GAMAT( tmp, 1 + rssky_transf->SMAX, 3 );
427 
428  // Copy decoupled metric to aligned metric
429  gsl_matrix_memcpy( aligned_ssky_metric, decoupled_ssky_metric );
430 
431  // Compute the eigenvalues/vectors of the sky--sky block
432  gsl_vector *GAVEC( sky_eval, 3 );
433  gsl_matrix *GAMAT( sky_evec, 3, 3 );
434  gsl_eigen_symmv_workspace *GALLOC( wksp, gsl_eigen_symmv_alloc( 3 ) );
435  gsl_matrix_view sky_sky = gsl_matrix_submatrix( aligned_ssky_metric, 0, 0, 3, 3 );
436  XLAL_CHECK( gsl_eigen_symmv( &sky_sky.matrix, sky_eval, sky_evec, wksp ) == 0, XLAL_EFAILED );
437 
438  // Sort the eigenvalues/vectors by descending absolute eigenvalue
439  XLAL_CHECK( gsl_eigen_symmv_sort( sky_eval, sky_evec, GSL_EIGEN_SORT_ABS_DESC ) == 0, XLAL_EFAILED );
440 
441  // Set the sky--sky block to the diagonal matrix of eigenvalues
442  gsl_matrix_set_zero( &sky_sky.matrix );
443  gsl_vector_view sky_sky_diag = gsl_matrix_diagonal( &sky_sky.matrix );
444  gsl_vector_memcpy( &sky_sky_diag.vector, sky_eval );
445 
446  // Ensure that the matrix of eigenvalues has a positive diagonal; this and
447  // the determinant constraints ensures fully constraints the eigenvector signs
448  for ( size_t j = 0; j < 3; ++j ) {
449  gsl_vector_view col = gsl_matrix_column( sky_evec, j );
450  if ( gsl_vector_get( &col.vector, j ) < 0.0 ) {
451  gsl_vector_scale( &col.vector, -1.0 );
452  }
453  }
454 
455  // Store the alignment transform in the reduced supersky coordinate transform data
456  gsl_matrix_view align_sky = gsl_matrix_view_array( &rssky_transf->align_sky[0][0], 3, 3 );
457  gsl_matrix_transpose_memcpy( &align_sky.matrix, sky_evec );
458 
459  // Ensure that the alignment transform has a positive determinant,
460  // to ensure that that it represents a rotation
461  gsl_permutation *GAPERM( LU_perm, 3 );
462  int LU_sign = 0;
463  XLAL_CHECK( gsl_linalg_LU_decomp( sky_evec, LU_perm, &LU_sign ) == 0, XLAL_EFAILED );
464  if ( gsl_linalg_LU_det( sky_evec, LU_sign ) < 0.0 ) {
465  gsl_vector_view col = gsl_matrix_column( &align_sky.matrix, 2 );
466  gsl_vector_scale( &col.vector, -1.0 );
467  }
468 
469  // Multiply the sky offsets by the alignment transform to transform to aligned sky coordinates:
470  // aligned_sky_off = sky_offsets * alignsky^T;
471  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
472  gsl_matrix_memcpy( tmp, &sky_offsets.matrix );
473  gsl_blas_dgemm( CblasNoTrans, CblasTrans, 1.0, tmp, &align_sky.matrix, 0.0, &sky_offsets.matrix );
474 
475  // Cleanup
476  gsl_eigen_symmv_free( wksp );
477  GFMAT( sky_evec, tmp );
478  GFPERM( LU_perm );
479  GFVEC( sky_eval );
480 
481  return XLAL_SUCCESS;
482 
483 }
484 
485 ///
486 /// Compute the reduced supersky metric
487 ///
489  gsl_matrix **rssky_metric, ///< [out] Reduced supersky metric
490  SuperskyTransformData **rssky_transf, ///< [out] Reduced supersky metric coordinate transform data
491  const size_t spindowns, ///< [in] Number of frequency+spindown coordinates
492  const gsl_matrix *ussky_metric, ///< [in] Unrestricted supersky metric
493  const DopplerCoordinateSystem *ucoords, ///< [in] Coordinate system of unrestricted supersky metric
494  const gsl_matrix *orbital_metric, ///< [in] Orbital metric in ecliptic coordinates
495  const DopplerCoordinateSystem *ocoords, ///< [in] Coordinate system of orbital metric
496  const LIGOTimeGPS *ref_time, ///< [in] Reference time of the metrics
497  const LIGOTimeGPS *start_time, ///< [in] Start time of the metrics
498  const LIGOTimeGPS *end_time ///< [in] End time of the metrics
499 )
500 {
501 
502  // Check input
503  XLAL_CHECK( rssky_metric != NULL && *rssky_metric == NULL, XLAL_EFAULT );
504  XLAL_CHECK( rssky_transf != NULL && *rssky_transf == NULL, XLAL_EFAULT );
505  XLAL_CHECK( ussky_metric != NULL, XLAL_EFAULT );
506  XLAL_CHECK( ussky_metric->size1 == ussky_metric->size2, XLAL_ESIZE );
507  XLAL_CHECK( ucoords != NULL, XLAL_EFAULT );
508  XLAL_CHECK( orbital_metric != NULL, XLAL_EFAULT );
509  XLAL_CHECK( orbital_metric->size1 == orbital_metric->size2, XLAL_ESIZE );
510  XLAL_CHECK( ocoords != NULL, XLAL_EFAULT );
511  XLAL_CHECK( ref_time != NULL, XLAL_EFAULT );
512  XLAL_CHECK( start_time != NULL, XLAL_EFAULT );
513  XLAL_CHECK( end_time != NULL, XLAL_EFAULT );
514 
515  // Allocate memory
516  GAMAT( *rssky_metric, orbital_metric->size1, orbital_metric->size1 );
517  *rssky_transf = XLALCalloc( 1, sizeof( **rssky_transf ) );
518  XLAL_CHECK( *rssky_transf != NULL, XLAL_ENOMEM );
519  gsl_matrix *GAMAT( assky_metric, 1 + orbital_metric->size1, 1 + orbital_metric->size1 );
520 
521  // Set coordinate transform data
522  ( *rssky_transf )->ndim = ( *rssky_metric )->size1;
523  ( *rssky_transf )->ref_time = *ref_time;
524  ( *rssky_transf )->fiducial_freq = fiducial_calc_freq;
525  ( *rssky_transf )->nspins = spindowns;
526  ( *rssky_transf )->nsky_offsets = 1 + spindowns;
527 
528  // Compute the aligned supersky metric from the unrestricted supersky metric and the orbital metric
529  XLAL_CHECK( SM_ComputeFittedSuperskyMetric( assky_metric, *rssky_transf, ussky_metric, orbital_metric, ocoords, start_time, end_time ) == XLAL_SUCCESS, XLAL_EFUNC );
530  XLAL_CHECK( SM_ComputeDecoupledSuperskyMetric( assky_metric, *rssky_transf, assky_metric ) == XLAL_SUCCESS, XLAL_EFUNC );
531  XLAL_CHECK( SM_ComputeAlignedSuperskyMetric( assky_metric, *rssky_transf, assky_metric ) == XLAL_SUCCESS, XLAL_EFUNC );
532 
533  {
534  // Move the row/column of the aligned supersky metric corresponding to the frequency to the last row/column
535  const int ifreq = XLALFindDopplerCoordinateInSystem( ucoords, DOPPLERCOORD_FREQ );
536  XLAL_CHECK( 3 <= ifreq, XLAL_EFAILED ); // 'ucoords' has 3 sky positions
537  for ( size_t i = ifreq; i + 1 < assky_metric->size1; ++i ) {
538  gsl_matrix_swap_rows( assky_metric, i, i + 1 );
539  gsl_matrix_swap_columns( assky_metric, i, i + 1 );
540  }
541 
542  // Move the row of the coordinate transform data corresponding to the frequency to the last row
543  const int isky_offset_freq = ifreq - 3; // 'ucoords' has 3 sky positions
544  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &( *rssky_transf )->sky_offsets[0][0], ( *rssky_transf )->nsky_offsets, 3 );
545  for ( size_t i = isky_offset_freq; i + 1 < sky_offsets.matrix.size1; ++i ) {
546  gsl_matrix_swap_rows( &sky_offsets.matrix, i, i + 1 );
547  }
548 
549  // Move the row/column of the aligned supersky metric corresponding to the 'n_c' sky coordinate to the last row/column, so it can be easily dropped
550  const int inc = XLALFindDopplerCoordinateInSystem( ucoords, DOPPLERCOORD_N3Z_EQU );
551  XLAL_CHECK( 0 <= inc && inc < ifreq, XLAL_EFAILED );
552  for ( size_t i = inc; i + 1 < assky_metric->size1; ++i ) {
553  gsl_matrix_swap_rows( assky_metric, i, i + 1 );
554  gsl_matrix_swap_columns( assky_metric, i, i + 1 );
555  }
556  }
557 
558  // Copy all but the last row/column to the aligned supersky metric to the reduced supersky metric, dropping 'n_c'
559  gsl_matrix_view extract_rssky_metric = gsl_matrix_submatrix( assky_metric, 0, 0, ( *rssky_metric )->size1, ( *rssky_metric )->size1 );
560  gsl_matrix_memcpy( *rssky_metric, &extract_rssky_metric.matrix );
561 
562  // Ensure reduced supersky metric is symmetric
563  for ( size_t i = 0; i < ( *rssky_metric )->size1; ++i ) {
564  for ( size_t j = i + 1; j < ( *rssky_metric )->size1; ++j ) {
565  const double gij = gsl_matrix_get( *rssky_metric, i, j );
566  const double gji = gsl_matrix_get( *rssky_metric, j, i );
567  const double g = 0.5 * ( gij + gji );
568  gsl_matrix_set( *rssky_metric, i, j, g );
569  gsl_matrix_set( *rssky_metric, j, i, g );
570  }
571  }
572 
573  // Ensure reduced supersky metric is positive definite
574  for ( size_t s = 1; s <= ( *rssky_metric )->size1; ++s ) {
575  gsl_matrix_view rssky_metric_s = gsl_matrix_submatrix( *rssky_metric, 0, 0, s, s );
576  const double det_s = XLALMetricDeterminant( &rssky_metric_s.matrix );
577  XLAL_CHECK( det_s > 0, XLAL_EFAILED, "Reduced supersky metric is not positive definite (s=%zu, det_s=%0.3e)", s, det_s );
578  }
579 
580  // Cleanup
581  GFMAT( assky_metric );
582 
583  return XLAL_SUCCESS;
584 
585 }
586 
588  const SuperskyMetricType type,
589  const size_t spindowns,
590  const LIGOTimeGPS *ref_time,
591  const LALSegList *segments,
592  const double fiducial_freq,
593  const MultiLALDetector *detectors,
594  const MultiNoiseFloor *detector_weights,
595  const DetectorMotionType detector_motion,
597 )
598 {
599 
600  // Check input
602  XLAL_CHECK_NULL( spindowns <= 4, XLAL_EINVAL );
603  XLAL_CHECK_NULL( ref_time != NULL, XLAL_EFAULT );
604  XLAL_CHECK_NULL( segments != NULL, XLAL_EFAULT );
606  XLAL_CHECK_NULL( segments->length > 0, XLAL_EINVAL );
607  XLAL_CHECK_NULL( fiducial_freq > 0, XLAL_EINVAL );
609  XLAL_CHECK_NULL( detectors->length > 0, XLAL_EINVAL );
610  XLAL_CHECK_NULL( detector_motion > 0, XLAL_EINVAL );
612 
613  // Build coordinate system for the unrestricted supersky metric and orbital metric
616  {
617  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_N3X_EQU;
618  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_N3Y_EQU;
619  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_N3Z_EQU;
620  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_N3OX_ECL;
621  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_N3OY_ECL;
622  }
623  {
624  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_FREQ;
625  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_FREQ;
626  }
627  if ( spindowns >= 1 ) {
628  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_F1DOT;
629  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_F1DOT;
630  }
631  if ( spindowns >= 2 ) {
632  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_F2DOT;
633  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_F2DOT;
634  }
635  if ( spindowns >= 3 ) {
636  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_F3DOT;
637  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_F3DOT;
638  }
639  if ( spindowns >= 4 ) {
640  ucoords.coordIDs[ucoords.dim++] = DOPPLERCOORD_F4DOT;
641  ocoords.coordIDs[ocoords.dim++] = DOPPLERCOORD_F4DOT;
642  }
643 
644  // Allocate memory for output struct
645  SuperskyMetrics *metrics = XLALCalloc( 1, sizeof( *metrics ) );
646  XLAL_CHECK_NULL( metrics != NULL, XLAL_ENOMEM );
647  metrics->num_segments = segments->length;
648 
649  // Allocate memory for arrays of coherent metrics
650  metrics->coh_rssky_metric = XLALCalloc( metrics->num_segments, sizeof( *metrics->coh_rssky_metric ) );
651  XLAL_CHECK_NULL( metrics->coh_rssky_metric != NULL, XLAL_ENOMEM );
652  metrics->coh_rssky_transf = XLALCalloc( metrics->num_segments, sizeof( *metrics->coh_rssky_transf ) );
653  XLAL_CHECK_NULL( metrics->coh_rssky_transf != NULL, XLAL_ENOMEM );
654 
655  // Allocate memory for averaged metrics
656  gsl_matrix *GAMAT_NULL( ussky_metric_avg, 4 + spindowns, 4 + spindowns );
657  gsl_matrix *GAMAT_NULL( orbital_metric_avg, 3 + spindowns, 3 + spindowns );
658 
659  // Compute the coherent supersky metrics for each segment
660  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
661  const LIGOTimeGPS *start_time_seg = &segments->segs[n].start;
662  const LIGOTimeGPS *end_time_seg = &segments->segs[n].end;
663 
664  // Compute the unrestricted supersky metric
665  gsl_matrix *ussky_metric_seg = SM_ComputePhaseMetric( &ucoords, ref_time, start_time_seg, end_time_seg, detectors, detector_weights, detector_motion, ephemerides );
666  XLAL_CHECK_NULL( ussky_metric_seg != NULL, XLAL_EFUNC );
667  gsl_matrix_add( ussky_metric_avg, ussky_metric_seg );
668 
669  // Compute the orbital metric in ecliptic coordinates
670  gsl_matrix *orbital_metric_seg = SM_ComputePhaseMetric( &ocoords, ref_time, start_time_seg, end_time_seg, detectors, detector_weights, detector_motion, ephemerides );
671  XLAL_CHECK_NULL( orbital_metric_seg != NULL, XLAL_EFUNC );
672  gsl_matrix_add( orbital_metric_avg, orbital_metric_seg );
673 
674  // Compute the coherent reduced supersky metric
675  XLAL_CHECK_NULL( SM_ComputeReducedSuperskyMetric( &metrics->coh_rssky_metric[n], &metrics->coh_rssky_transf[n], spindowns, ussky_metric_seg, &ucoords, orbital_metric_seg, &ocoords, ref_time, start_time_seg, end_time_seg ) == XLAL_SUCCESS, XLAL_EFUNC );
676  LogPrintf( LOG_DEBUG, "Computed coherent reduced supersky metric for segment %zu/%zu\n", n, metrics->num_segments );
677 
678  // Cleanup
679  GFMAT( ussky_metric_seg, orbital_metric_seg );
680 
681  }
682 
683  // Normalise averaged metrics by number of segments
684  gsl_matrix_scale( ussky_metric_avg, 1.0 / metrics->num_segments );
685  gsl_matrix_scale( orbital_metric_avg, 1.0 / metrics->num_segments );
686 
687  // Compute the semicoherent supersky metric for all segments
688  const LIGOTimeGPS *start_time_avg = &segments->segs[0].start;
689  const LIGOTimeGPS *end_time_avg = &segments->segs[segments->length - 1].end;
690  XLAL_CHECK_NULL( SM_ComputeReducedSuperskyMetric( &metrics->semi_rssky_metric, &metrics->semi_rssky_transf, spindowns, ussky_metric_avg, &ucoords, orbital_metric_avg, &ocoords, ref_time, start_time_avg, end_time_avg ) == XLAL_SUCCESS, XLAL_EFUNC );
691  LogPrintf( LOG_DEBUG, "Computed semicoherent reduced supersky metric for %zu segments\n", metrics->num_segments );
692 
693  // Rescale metrics to input fiducial frequency
694  XLALScaleSuperskyMetricsFiducialFreq( metrics, fiducial_freq );
695 
696  // Cleanup
697  GFMAT( ussky_metric_avg, orbital_metric_avg );
698 
699  return metrics;
700 
701 }
702 
704  const SuperskyMetrics *metrics
705 )
706 {
707 
708  // Check input
709  XLAL_CHECK_NULL( metrics != NULL, XLAL_EFAULT );
710 
711  // Allocate memory for copied struct
712  SuperskyMetrics *copy_metrics = XLALCalloc( 1, sizeof( *copy_metrics ) );
713  XLAL_CHECK_NULL( copy_metrics != NULL, XLAL_ENOMEM );
714  copy_metrics->num_segments = metrics->num_segments;
715 
716  // Allocate memory for copies of arrays of coherent metrics
717  copy_metrics->coh_rssky_metric = XLALCalloc( copy_metrics->num_segments, sizeof( *copy_metrics->coh_rssky_metric ) );
718  XLAL_CHECK_NULL( copy_metrics->coh_rssky_metric != NULL, XLAL_ENOMEM );
719  copy_metrics->coh_rssky_transf = XLALCalloc( copy_metrics->num_segments, sizeof( *copy_metrics->coh_rssky_transf ) );
720  XLAL_CHECK_NULL( copy_metrics->coh_rssky_transf != NULL, XLAL_ENOMEM );
721 
722  // Copy coherent metrics and transform data
723  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
724  GAMAT_NULL( copy_metrics->coh_rssky_metric[n], metrics->coh_rssky_metric[n]->size1, metrics->coh_rssky_metric[n]->size2 );
725  gsl_matrix_memcpy( copy_metrics->coh_rssky_metric[n], metrics->coh_rssky_metric[n] );
726  copy_metrics->coh_rssky_transf[n] = XLALCalloc( 1, sizeof( *copy_metrics->coh_rssky_transf[n] ) );
727  XLAL_CHECK_NULL( copy_metrics->coh_rssky_transf[n] != NULL, XLAL_ENOMEM );
728  memcpy( copy_metrics->coh_rssky_transf[n], metrics->coh_rssky_transf[n], sizeof( *copy_metrics->coh_rssky_transf[n] ) );
729  }
730 
731  // Copy semocherent metrics and transform data
732  GAMAT_NULL( copy_metrics->semi_rssky_metric, metrics->semi_rssky_metric->size1, metrics->semi_rssky_metric->size2 );
733  gsl_matrix_memcpy( copy_metrics->semi_rssky_metric, metrics->semi_rssky_metric );
734  copy_metrics->semi_rssky_transf = XLALCalloc( 1, sizeof( *copy_metrics->semi_rssky_transf ) );
735  XLAL_CHECK_NULL( copy_metrics->semi_rssky_transf != NULL, XLAL_ENOMEM );
736  memcpy( copy_metrics->semi_rssky_transf, metrics->semi_rssky_transf, sizeof( *copy_metrics->semi_rssky_transf ) );
737 
738  return copy_metrics;
739 
740 }
741 
743  SuperskyMetrics *metrics
744 )
745 {
746  if ( metrics != NULL ) {
747  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
748  GFMAT( metrics->coh_rssky_metric[n] );
749  XLALFree( metrics->coh_rssky_transf[n] );
750  }
751  XLALFree( metrics->coh_rssky_metric );
752  XLALFree( metrics->coh_rssky_transf );
753  GFMAT( metrics->semi_rssky_metric );
754  XLALFree( metrics->semi_rssky_transf );
755  XLALFree( metrics );
756  }
757 }
758 
759 ///
760 /// Initialise a FITS table for writing/reading a table of SuperskyTransformData entries
761 ///
763  FITSFile *file
764 )
765 {
766  XLAL_CHECK( file != NULL, XLAL_EFAULT );
767  XLAL_FITS_TABLE_COLUMN_BEGIN( SuperskyTransformData );
775  return XLAL_SUCCESS;
776 }
777 
779  FITSFile *file,
780  const SuperskyMetrics *metrics
781 )
782 {
783 
784  // Check input
785  XLAL_CHECK( file != NULL, XLAL_EFAULT );
786  XLAL_CHECK( metrics != NULL, XLAL_EFAULT );
787 
788  // Write coherent metrics to a FITS array
789  {
790  size_t dims[3] = {
791  metrics->coh_rssky_metric[0]->size1,
792  metrics->coh_rssky_metric[0]->size2,
793  metrics->num_segments
794  };
795  XLAL_CHECK( XLALFITSArrayOpenWrite( file, "coh_rssky_metric", 3, dims, "coherent supersky metrics" ) == XLAL_SUCCESS, XLAL_EFUNC );
796  size_t idx[3];
797  for ( idx[2] = 0; idx[2] < dims[2]; ++idx[2] ) {
799  }
800  }
801 
802  // Write coherent metric transform data to a FITS table
803  {
804  XLAL_CHECK( XLALFITSTableOpenWrite( file, "coh_rssky_transf", "coherent supersky metric transform data" ) == XLAL_SUCCESS, XLAL_EFUNC );
806  for ( size_t i = 0; i < metrics->num_segments; ++i ) {
808  }
809  }
810 
811  // Write semicoherent metric to a FITS array
812  {
813  XLAL_CHECK( XLALFITSArrayOpenWrite2( file, "semi_rssky_metric", metrics->semi_rssky_metric->size1, metrics->semi_rssky_metric->size2, "semicoherent supersky metric" ) == XLAL_SUCCESS, XLAL_EFUNC );
815  }
816 
817  // Write semicoherent metric transform data to a FITS table
818  {
819  XLAL_CHECK( XLALFITSTableOpenWrite( file, "semi_rssky_transf", "semicoherent supersky metric transform data" ) == XLAL_SUCCESS, XLAL_EFUNC );
822  }
823 
824  return XLAL_SUCCESS;
825 
826 }
827 
829  FITSFile *file,
830  SuperskyMetrics **metrics
831 )
832 {
833 
834  // Check input
835  XLAL_CHECK( file != NULL, XLAL_EFAULT );
836  XLAL_CHECK( metrics != NULL && *metrics == NULL, XLAL_EFAULT );
837 
838  // Allocate memory
839  *metrics = XLALCalloc( 1, sizeof( **metrics ) );
840  XLAL_CHECK( *metrics != NULL, XLAL_ENOMEM );
841 
842  // Read coherent metrics from a FITS array
843  {
844  size_t ndim, dims[FFIO_MAX];
845  XLAL_CHECK( XLALFITSArrayOpenRead( file, "coh_rssky_metric", &ndim, dims ) == XLAL_SUCCESS, XLAL_EFUNC );
846  XLAL_CHECK( ndim == 3, XLAL_EIO );
847  ( *metrics )->num_segments = dims[2];
848  ( *metrics )->coh_rssky_metric = XLALCalloc( ( *metrics )->num_segments, sizeof( *( *metrics )->coh_rssky_metric ) );
849  XLAL_CHECK( ( *metrics )->coh_rssky_metric != NULL, XLAL_ENOMEM );
850  size_t idx[3];
851  for ( idx[2] = 0; idx[2] < dims[2]; ++idx[2] ) {
852  XLAL_CHECK( XLALFITSArrayReadGSLMatrix( file, idx, &( *metrics )->coh_rssky_metric[idx[2]] ) == XLAL_SUCCESS, XLAL_EFUNC );
853  }
854  }
855 
856  // Read coherent metric transform data from a FITS table
857  {
858  UINT8 nrows = 0;
859  XLAL_CHECK( XLALFITSTableOpenRead( file, "coh_rssky_transf", &nrows ) == XLAL_SUCCESS, XLAL_EFUNC );
860  XLAL_CHECK( nrows == ( *metrics )->num_segments, XLAL_EIO );
862  ( *metrics )->coh_rssky_transf = XLALCalloc( ( *metrics )->num_segments, sizeof( *( *metrics )->coh_rssky_transf ) );
863  XLAL_CHECK( ( *metrics )->coh_rssky_transf != NULL, XLAL_ENOMEM );
864  for ( size_t i = 0; i < ( *metrics )->num_segments; ++i ) {
865  ( *metrics )->coh_rssky_transf[i] = XLALCalloc( 1, sizeof( *( *metrics )->coh_rssky_transf[i] ) );
866  XLAL_CHECK( ( *metrics )->coh_rssky_transf[i] != NULL, XLAL_ENOMEM );
867  XLAL_CHECK( XLALFITSTableReadRow( file, ( *metrics )->coh_rssky_transf[i], &nrows ) == XLAL_SUCCESS, XLAL_EFUNC );
868  }
869  }
870 
871  // Read semicoherent metric from a FITS array
872  {
873  XLAL_CHECK( XLALFITSArrayOpenRead2( file, "semi_rssky_metric", NULL, NULL ) == XLAL_SUCCESS, XLAL_EFUNC );
874  XLAL_CHECK( XLALFITSArrayReadGSLMatrix( file, NULL, &( *metrics )->semi_rssky_metric ) == XLAL_SUCCESS, XLAL_EFUNC );
875  }
876 
877  // Read semicoherent metric transform data from a FITS table
878  {
879  UINT8 nrows = 0;
880  XLAL_CHECK( XLALFITSTableOpenRead( file, "semi_rssky_transf", &nrows ) == XLAL_SUCCESS, XLAL_EFUNC );
881  XLAL_CHECK( nrows == 1, XLAL_EIO );
883  ( *metrics )->semi_rssky_transf = XLALCalloc( 1, sizeof( *( *metrics )->semi_rssky_transf ) );
884  XLAL_CHECK( ( *metrics )->semi_rssky_transf != NULL, XLAL_ENOMEM );
885  XLAL_CHECK( XLALFITSTableReadRow( file, ( *metrics )->semi_rssky_transf, &nrows ) == XLAL_SUCCESS, XLAL_EFUNC );
886  }
887 
888  return XLAL_SUCCESS;
889 
890 }
891 
893  const SuperskyMetrics *metrics,
894  size_t *spindowns
895 )
896 {
897 
898  // Check input
899  XLAL_CHECK( metrics != NULL, XLAL_EFAULT );
900  XLAL_CHECK( metrics->num_segments > 0, XLAL_EINVAL );
901  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
903  }
905 
906  // Return dimensions
907  if ( spindowns != NULL ) {
908  *spindowns = metrics->semi_rssky_transf->nspins;
909  }
910 
911  return XLAL_SUCCESS;
912 
913 }
914 
915 ///
916 /// Scale a given supersky metric and its coordinate transform data to a new fiducial frequency.
917 ///
919  gsl_matrix *rssky_metric, ///< [in] Reduced supersky metric
920  SuperskyTransformData *rssky_transf, ///< [in] Reduced supersky metric coordinate transform data
921  const double new_fiducial_freq ///< [in] New fiducial frequency
922 )
923 {
924 
925  // Check input
926  XLAL_CHECK( CHECK_RSSKY_METRIC_TRANSF( rssky_metric, rssky_transf ), XLAL_EINVAL );
927  XLAL_CHECK( new_fiducial_freq > 0, XLAL_EINVAL );
928 
929  // Rescale metrics to 'new_fiducial_freq' based on known scalings
930  const double fiducial_scale = new_fiducial_freq / rssky_transf->fiducial_freq;
931  gsl_matrix_view sky_sky = gsl_matrix_submatrix( rssky_metric, 0, 0, 2, 2 );
932  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
933  gsl_matrix_scale( &sky_sky.matrix, SQR( fiducial_scale ) );
934  gsl_matrix_scale( &sky_offsets.matrix, fiducial_scale );
935 
936  // Set new fiducial frequency
937  rssky_transf->fiducial_freq = new_fiducial_freq;
938 
939  return XLAL_SUCCESS;
940 
941 }
942 
944  SuperskyMetrics *metrics,
945  const double new_fiducial_freq
946 )
947 {
948 
949  // Check input
950  XLAL_CHECK( metrics != NULL, XLAL_EFAULT );
951  XLAL_CHECK( metrics->num_segments > 0, XLAL_EINVAL );
952  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
954  }
956  XLAL_CHECK( new_fiducial_freq > 0, XLAL_EINVAL );
957 
958  // Rescale all metrics to 'new_fiducial_freq'
959  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
961  }
963 
964  return XLAL_SUCCESS;
965 
966 }
967 
969  SuperskyMetrics *metrics,
970  const double coh_max_mismatch,
971  const double semi_max_mismatch
972 )
973 {
974 
975  // Check input
976  XLAL_CHECK( metrics != NULL, XLAL_EFAULT );
977  XLAL_CHECK( metrics->num_segments > 0, XLAL_EINVAL );
978  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
980  }
982  XLAL_CHECK( coh_max_mismatch > 0, XLAL_EINVAL );
983  XLAL_CHECK( semi_max_mismatch > 0, XLAL_EINVAL );
984 
985  // Find the maximum, over both semicoherent and coherent metrics, of 'g_{ff} / mu',
986  // where 'g_{ff}' is the frequency-frequency metric element, and mu is the mismatch
987  const size_t ifreq = RSSKY_FKDOT_DIM( metrics->semi_rssky_transf, 0 );
988  double max_gff_d_mu = gsl_matrix_get( metrics->semi_rssky_metric, ifreq, ifreq ) / semi_max_mismatch;
989  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
990  const double coh_gff_d_mu = gsl_matrix_get( metrics->coh_rssky_metric[n], ifreq, ifreq ) / coh_max_mismatch;
991  if ( max_gff_d_mu < coh_gff_d_mu ) {
992  max_gff_d_mu = coh_gff_d_mu;
993  }
994  }
995 
996  // Rescale rows 'g_{f*}' and columns 'g_{*f}' of both semicoherent and coherent metrics by
997  // 'sqrt( max{ g_{ff} / mu } / ( g_{ff} / mu ) )'; this scales the frequency coordinate such
998  // that 'g_{ff}' will give the same frequency spacing for all metrics, while also scaling the
999  // off-diagonal frequency terms (e.g. frequency-spindown correlations) correctly.
1000  {
1001  const double semi_gff_d_mu = gsl_matrix_get( metrics->semi_rssky_metric, ifreq, ifreq ) / semi_max_mismatch;
1002  const double scale = sqrt( max_gff_d_mu / semi_gff_d_mu );
1003  XLAL_CHECK( scale >= 1, XLAL_EFAILED );
1004  {
1005  gsl_vector_view rssky_metric_f_row = gsl_matrix_row( metrics->semi_rssky_metric, ifreq );
1006  gsl_vector_scale( &rssky_metric_f_row.vector, scale );
1007  } {
1008  gsl_vector_view rssky_metric_f_col = gsl_matrix_column( metrics->semi_rssky_metric, ifreq );
1009  gsl_vector_scale( &rssky_metric_f_col.vector, scale );
1010  }
1011  }
1012  for ( size_t n = 0; n < metrics->num_segments; ++n ) {
1013  const double coh_gff_d_mu = gsl_matrix_get( metrics->coh_rssky_metric[n], ifreq, ifreq ) / coh_max_mismatch;
1014  const double scale = sqrt( max_gff_d_mu / coh_gff_d_mu );
1015  XLAL_CHECK( scale >= 1, XLAL_EFAILED );
1016  {
1017  gsl_vector_view rssky_metric_f_row = gsl_matrix_row( metrics->coh_rssky_metric[n], ifreq );
1018  gsl_vector_scale( &rssky_metric_f_row.vector, scale );
1019  } {
1020  gsl_vector_view rssky_metric_f_col = gsl_matrix_column( metrics->coh_rssky_metric[n], ifreq );
1021  gsl_vector_scale( &rssky_metric_f_col.vector, scale );
1022  }
1023  }
1024 
1025  return XLAL_SUCCESS;
1026 
1027 }
1028 
1030  PulsarDopplerParams *out_phys,
1031  const SuperskyTransformData *rssky_transf
1032 )
1033 {
1034 
1035  // Check input
1036  XLAL_CHECK( out_phys != NULL, XLAL_EFAULT );
1037  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EFAULT );
1038 
1039  // Set output physical point reference time to that of of coordinate transform data
1040  out_phys->refTime = rssky_transf->ref_time;
1041 
1042  return XLAL_SUCCESS;
1043 
1044 }
1045 
1046 /**
1047  * Convert from 2-dimensional reduced supersky coordinates to 3-dimensional aligned sky
1048  * coordinates.
1049  *
1050  * The 2-dimensional reduced supersky coordinates \c rss = (\c A, \c B) encode the two
1051  * hemispheres of the sky as two neighbouring unit disks. The conversion to 3-dimensional
1052  * aligned sky coordinates is illustrated in the following diagram:
1053  *
1054  \verbatim
1055  | as[1] = __________________________________________
1056  | B = 1_| _____ _____ |
1057  | | .-' '-. .-' '-. |
1058  | | .' '. .' '. |
1059  | | / \ / \ |
1060  | | ; ; ; |
1061  | 0-| | | | |
1062  | | ; ; ; |
1063  | | \ / \ / |
1064  | | '. .' '. .' |
1065  | _| '-._____.-' '-._____.-' |
1066  | -1 |__.________.________.________.________.__|
1067  | ' ' ' ' '
1068  | A = -2 -1 0 1 2
1069  | as[0] = 1 0 -1 0 1
1070  | as[2] = 0 -1 0 1 0
1071  \endverbatim
1072  *
1073  * Points outside the unit disks are moved radially onto their boundaries.
1074  */
1076  double as[3], ///< [out] 3-dimensional aligned sky coordinates
1077  const gsl_vector *rss, ///< [in] 2-dimensional reduced supersky coordinates
1078  const double hemi ///< [in] Supersky coordinate hemisphere; only sign is used
1079 )
1080 {
1081  const double A = GSL_SIGN( hemi ) * gsl_vector_get( rss, 0 ) - 1;
1082  const double B = gsl_vector_get( rss, 1 );
1083  const double R = sqrt( SQR( A ) + SQR( B ) );
1084  const double Rmax = GSL_MAX( 1.0, R );
1085  as[0] = A / Rmax;
1086  as[1] = B / Rmax;
1087  as[2] = GSL_SIGN( hemi ) * RE_SQRT( 1.0 - DOT2( as, as ) );
1088 }
1089 
1090 ///
1091 /// Convert from 3-dimensional aligned sky coordinates to 2-dimensional reduced supersky
1092 /// coordinates.
1093 ///
1095  gsl_vector *rss, ///< [out] 2-dimensional reduced supersky coordinates
1096  const double as[3] ///< [in] 3-dimensional aligned sky coordinates
1097 )
1098 {
1099  const double r = sqrt( DOT3( as, as ) );
1100  const double A = GSL_SIGN( as[2] ) * ( ( as[0] / r ) + 1.0 );
1101  const double B = as[1] / r;
1102  gsl_vector_set( rss, 0, A );
1103  gsl_vector_set( rss, 1, B );
1104 }
1105 
1107  gsl_vector *out_rssky,
1108  const PulsarDopplerParams *in_phys,
1109  const SuperskyTransformData *rssky_transf
1110 )
1111 {
1112 
1113  // Check input
1114  XLAL_CHECK( out_rssky != NULL, XLAL_EFAULT );
1115  XLAL_CHECK( in_phys != NULL, XLAL_EFAULT );
1116  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EFAULT );
1117  XLAL_CHECK( out_rssky->size == rssky_transf->ndim, XLAL_ESIZE );
1118 
1119  // Transform input physical point to reference time of coordinate transform data
1120  PulsarDopplerParams in_phys_ref = *in_phys;
1121  {
1122  const REAL8 dtau = XLALGPSDiff( &rssky_transf->ref_time, &in_phys_ref.refTime );
1123  XLAL_CHECK( XLALExtrapolatePulsarSpins( in_phys_ref.fkdot, in_phys_ref.fkdot, dtau ) == XLAL_SUCCESS, XLAL_EFUNC );
1124  }
1125 
1126  // Create array for intermediate coordinates
1127  double intm[4 + rssky_transf->SMAX];
1128  gsl_vector_view intm_sky2 = gsl_vector_view_array( &intm[0], 2 );
1129  gsl_vector_view intm_sky3 = gsl_vector_view_array( &intm[0], 3 );
1130  gsl_vector_view intm_fspin = gsl_vector_view_array( &intm[3], 1 + rssky_transf->SMAX );
1131 
1132  // Convert right ascension and declination to equatorial coordinates
1133  {
1134  const double cos_Delta = cos( in_phys_ref.Delta );
1135  intm[0] = cos( in_phys_ref.Alpha ) * cos_Delta;
1136  intm[1] = sin( in_phys_ref.Alpha ) * cos_Delta;
1137  intm[2] = sin( in_phys_ref.Delta );
1138  }
1139 
1140  // Copy frequency/spindowns to intermediate array; frequency goes last
1141  intm[3 + rssky_transf->SMAX] = in_phys_ref.fkdot[0];
1142  for ( size_t s = 1; s <= rssky_transf->SMAX; ++s ) {
1143  intm[2 + s] = in_phys_ref.fkdot[s];
1144  }
1145 
1146  // Apply the alignment transform to the supersky position to produced the aligned sky position:
1147  // asky = align_sky * ssky
1148  double asky[3];
1149  gsl_vector_view asky_v = gsl_vector_view_array( asky, 3 );
1150  gsl_matrix_const_view align_sky = gsl_matrix_const_view_array( &rssky_transf->align_sky[0][0], 3, 3 );
1151  gsl_blas_dgemv( CblasNoTrans, 1.0, &align_sky.matrix, &intm_sky3.vector, 0.0, &asky_v.vector );
1152 
1153  // Add the inner product of the sky offsets with the aligned sky position
1154  // to the supersky spins and frequency to get the reduced supersky quantities:
1155  // rssky_fspin[i] = ussky_fspin[i] + dot(sky_offsets[i], asky)
1156  gsl_matrix_const_view sky_offsets = gsl_matrix_const_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
1157  gsl_blas_dgemv( CblasNoTrans, 1.0, &sky_offsets.matrix, &asky_v.vector, 1.0, &intm_fspin.vector );
1158 
1159  // Convert from 3-dimensional aligned sky coordinates to 2-dimensional reduced supersky coordinates
1160  SM_AlignedToReduced( &intm_sky3.vector, asky );
1161 
1162  // Copy intermediate array to output point
1163  {
1164  gsl_vector_view out_sky2 = gsl_vector_subvector( out_rssky, 0, 2 );
1165  gsl_vector_view out_fspin = gsl_vector_subvector( out_rssky, 2, 1 + rssky_transf->SMAX );
1166  gsl_vector_memcpy( &out_sky2.vector, &intm_sky2.vector );
1167  gsl_vector_memcpy( &out_fspin.vector, &intm_fspin.vector );
1168  }
1169 
1170  return XLAL_SUCCESS;
1171 
1172 }
1173 
1175  PulsarDopplerParams *out_phys,
1176  const gsl_vector *in_rssky,
1177  const gsl_vector *ref_rssky,
1178  const SuperskyTransformData *rssky_transf
1179 )
1180 {
1181 
1182  // Check input
1183  XLAL_CHECK( out_phys != NULL, XLAL_EFAULT );
1184  XLAL_CHECK( in_rssky != NULL, XLAL_EFAULT );
1185  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EFAULT );
1186  XLAL_CHECK( in_rssky->size == rssky_transf->ndim, XLAL_ESIZE );
1187  XLAL_CHECK( ref_rssky == NULL || in_rssky->size == rssky_transf->ndim, XLAL_ESIZE );
1188 
1189  // Set output physical point reference time to that of of coordinate transform data
1190  out_phys->refTime = rssky_transf->ref_time;
1191 
1192  // Create array for intermediate coordinates
1193  double intm[4 + rssky_transf->SMAX];
1194  gsl_vector_view intm_sky2 = gsl_vector_view_array( &intm[0], 2 );
1195  gsl_vector_view intm_sky3 = gsl_vector_view_array( &intm[0], 3 );
1196  gsl_vector_view intm_fspin = gsl_vector_view_array( &intm[3], 1 + rssky_transf->SMAX );
1197 
1198  // Copy input point to intermediate array
1199  {
1200  gsl_vector_const_view in_sky2 = gsl_vector_const_subvector( in_rssky, 0, 2 );
1201  gsl_vector_const_view in_fspin = gsl_vector_const_subvector( in_rssky, 2, 1 + rssky_transf->SMAX );
1202  gsl_vector_memcpy( &intm_sky2.vector, &in_sky2.vector );
1203  gsl_vector_memcpy( &intm_fspin.vector, &in_fspin.vector );
1204  }
1205 
1206  // Convert from 2-dimensional reduced supersky coordinates to 3-dimensional aligned sky coordinates
1207  const double A = gsl_vector_get( ref_rssky != NULL ? ref_rssky : in_rssky, 0 );
1208  double asky[3];
1209  SM_ReducedToAligned( asky, &intm_sky3.vector, A );
1210  gsl_vector_view asky_v = gsl_vector_view_array( asky, 3 );
1211 
1212  // Subtract the inner product of the sky offsets with the aligned sky position
1213  // from the reduced supersky spins and frequency to get the supersky quantities:
1214  // ussky_fspin[i] = rssky_fspin[i] - dot(sky_offsets[i], asky)
1215  gsl_matrix_const_view sky_offsets = gsl_matrix_const_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
1216  gsl_blas_dgemv( CblasNoTrans, -1.0, &sky_offsets.matrix, &asky_v.vector, 1.0, &intm_fspin.vector );
1217 
1218  // Apply the inverse alignment transform to the aligned sky position to produced the supersky position:
1219  // ssky = align_sky^T * asky
1220  gsl_matrix_const_view align_sky = gsl_matrix_const_view_array( &rssky_transf->align_sky[0][0], 3, 3 );
1221  gsl_blas_dgemv( CblasTrans, 1.0, &align_sky.matrix, &asky_v.vector, 0.0, &intm_sky3.vector );
1222 
1223  // Copy frequency/spindowns to output physical point; frequency goes first
1224  out_phys->fkdot[0] = intm[3 + rssky_transf->SMAX];
1225  for ( size_t s = 1; s <= rssky_transf->SMAX; ++s ) {
1226  out_phys->fkdot[s] = intm[2 + s];
1227  }
1228 
1229  // Convert supersky position in equatorial coordinates to right ascension and declination
1230  out_phys->Alpha = atan2( intm[1], intm[0] );
1231  out_phys->Delta = atan2( intm[2], sqrt( SQR( intm[0] ) + SQR( intm[1] ) ) );
1232  XLALNormalizeSkyPosition( &out_phys->Alpha, &out_phys->Delta );
1233 
1234  return XLAL_SUCCESS;
1235 
1236 }
1237 
1239  gsl_vector *out_rssky,
1240  const SuperskyTransformData *out_rssky_transf,
1241  const gsl_vector *in_rssky,
1242  const gsl_vector *ref_rssky,
1243  const SuperskyTransformData *in_rssky_transf
1244 )
1245 {
1246 
1247  // Check input
1248  XLAL_CHECK( out_rssky != NULL, XLAL_EFAULT );
1249  XLAL_CHECK( CHECK_RSSKY_TRANSF( out_rssky_transf ), XLAL_EFAULT );
1250  XLAL_CHECK( in_rssky != NULL, XLAL_EFAULT );
1251  XLAL_CHECK( CHECK_RSSKY_TRANSF( in_rssky_transf ), XLAL_EFAULT );
1252 
1253  // Convert input reduced supersky point to physical coordinates
1255  XLAL_CHECK( XLALConvertSuperskyToPhysicalPoint( &phys, in_rssky, ref_rssky, in_rssky_transf ) == XLAL_SUCCESS, XLAL_EINVAL );
1256 
1257  // Convert physical point to output reduced supersky coordinates
1258  XLAL_CHECK( XLALConvertPhysicalToSuperskyPoint( out_rssky, &phys, out_rssky_transf ) == XLAL_SUCCESS, XLAL_EINVAL );
1259 
1260  return XLAL_SUCCESS;
1261 
1262 }
1263 
1265  gsl_matrix **out_rssky,
1266  const gsl_matrix *in_phys,
1267  const SuperskyTransformData *rssky_transf
1268 )
1269 {
1270 
1271  // Check input
1272  XLAL_CHECK( out_rssky != NULL, XLAL_EFAULT );
1273  XLAL_CHECK( in_phys != NULL, XLAL_EFAULT );
1274  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EFAULT );
1275  XLAL_CHECK( in_phys->size1 == rssky_transf->ndim, XLAL_ESIZE );
1276 
1277  // Resize or allocate output matrix, if required
1278  if ( *out_rssky != NULL ) {
1279  if ( ( *out_rssky )->size1 != in_phys->size1 || ( *out_rssky )->size2 != in_phys->size2 ) {
1280  GFMAT( *out_rssky );
1281  *out_rssky = NULL;
1282  }
1283  }
1284  if ( *out_rssky == NULL ) {
1285  GAMAT( *out_rssky, in_phys->size1, in_phys->size2 );
1286  }
1287 
1288  // Loop over all input points
1289  for ( size_t j = 0; j < in_phys->size2; ++j ) {
1290 
1291  // Fill PulsarDopplerParams struct from input point
1292  PulsarDopplerParams XLAL_INIT_DECL( in_phys_j );
1293  in_phys_j.refTime = rssky_transf->ref_time;
1294  in_phys_j.Alpha = gsl_matrix_get( in_phys, 0, j );
1295  in_phys_j.Delta = gsl_matrix_get( in_phys, 1, j );
1296  for ( size_t s = 0; s <= rssky_transf->SMAX; ++s ) {
1297  in_phys_j.fkdot[s] = gsl_matrix_get( in_phys, 2 + s, j );
1298  }
1299 
1300  // Convert point from physical to supersky coordinates
1301  gsl_vector_view out_rssky_j = gsl_matrix_column( *out_rssky, j );
1302  XLAL_CHECK( XLALConvertPhysicalToSuperskyPoint( &out_rssky_j.vector, &in_phys_j, rssky_transf ) == XLAL_SUCCESS, XLAL_EFUNC );
1303 
1304  }
1305 
1306  return XLAL_SUCCESS;
1307 
1308 }
1309 
1311  gsl_matrix **out_phys,
1312  const gsl_matrix *in_rssky,
1313  const SuperskyTransformData *rssky_transf
1314 )
1315 {
1316 
1317  // Check input
1318  XLAL_CHECK( out_phys != NULL, XLAL_EFAULT );
1319  XLAL_CHECK( in_rssky != NULL, XLAL_EFAULT );
1320  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EFAULT );
1321  XLAL_CHECK( in_rssky->size1 == rssky_transf->ndim, XLAL_ESIZE );
1322 
1323  // Resize or allocate output matrix, if required
1324  if ( *out_phys != NULL ) {
1325  if ( ( *out_phys )->size1 != in_rssky->size1 || ( *out_phys )->size2 != in_rssky->size2 ) {
1326  GFMAT( *out_phys );
1327  *out_phys = NULL;
1328  }
1329  }
1330  if ( *out_phys == NULL ) {
1331  GAMAT( *out_phys, in_rssky->size1, in_rssky->size2 );
1332  }
1333 
1334  // Loop over all input points
1335  for ( size_t j = 0; j < in_rssky->size2; ++j ) {
1336 
1337  // Convert point from supersky to physical coordinates
1338  gsl_vector_const_view in_rssky_j = gsl_matrix_const_column( in_rssky, j );
1339  PulsarDopplerParams XLAL_INIT_DECL( out_phys_j );
1340  XLAL_CHECK( XLALConvertSuperskyToPhysicalPoint( &out_phys_j, &in_rssky_j.vector, NULL, rssky_transf ) == XLAL_SUCCESS, XLAL_EFUNC );
1341 
1342  // Fill output point from PulsarDopplerParams struct
1343  gsl_matrix_set( *out_phys, 0, j, out_phys_j.Alpha );
1344  gsl_matrix_set( *out_phys, 1, j, out_phys_j.Delta );
1345  for ( size_t s = 0; s <= rssky_transf->SMAX; ++s ) {
1346  gsl_matrix_set( *out_phys, 2 + s, j, out_phys_j.fkdot[s] );
1347  }
1348 
1349  }
1350 
1351  return XLAL_SUCCESS;
1352 
1353 }
1354 
1355 static void SkyBoundCache(
1356  const size_t dim UNUSED,
1357  const gsl_vector *point,
1358  gsl_vector *cache
1359 )
1360 {
1361 
1362  // Convert from 2-dimensional reduced supersky coordinates to 3-dimensional aligned sky coordinates
1363  const double A = gsl_vector_get( point, 0 );
1364  double as[3];
1365  SM_ReducedToAligned( as, point, A );
1366 
1367  // Store aligned sky position in cache
1368  for ( size_t i = 0; i < 3; ++i ) {
1369  gsl_vector_set( cache, i, as[i] );
1370  }
1371 
1372 }
1373 
1374 typedef struct {
1375  double max_A;
1376  int type;
1377  double r;
1378  double na0;
1379  bool altroot;
1380  double angle0;
1381  double C;
1382  double S;
1383  double Z;
1385 typedef struct {
1386  double min_A;
1387  double min_A_bound;
1388  double max_A;
1389  double max_A_bound;
1392 
1393 static double PhysicalSkyBound(
1394  const void *data,
1395  const size_t dim UNUSED,
1396  const gsl_matrix *cache UNUSED,
1397  const gsl_vector *point
1398 )
1399 {
1400 
1401  // Get bounds data
1402  const PhysicalSkyBoundData *psbd = ( const PhysicalSkyBoundData * ) data;
1403 
1404  // Decode the reduced supersky coordinates to get
1405  // na = as[0] = Q_na . n
1406  const double A = gsl_vector_get( point, 0 );
1407  const double rssky[2] = { A, 0 };
1408  gsl_vector_const_view rssky_view = gsl_vector_const_view_array( rssky, 2 );
1409  double as[3];
1410  SM_ReducedToAligned( as, &rssky_view.vector, A );
1411  const double na = as[0];
1412 
1413  // Absolute limiting bound on 'nb = +/- sqrt(1 - na^2)'
1414  const double limit = RE_SQRT( 1 - SQR( na ) );
1415 
1416  // If 'A' is outside range '(min_A, max_A)', set bound to 'min_A_bound' or 'max_A_bound'
1417  double bound = GSL_NAN;
1418  if ( A <= psbd->min_A ) {
1419  bound = psbd->min_A_bound;
1420  } else if ( A >= psbd->max_A ) {
1421  bound = psbd->max_A_bound;
1422  } else {
1423 
1424  // Loop over bound pieces to find which one currently applies, based on 'max_A'
1425  for ( size_t i = 0; i < XLAL_NUM_ELEM( psbd->pieces ); ++i ) {
1426  const PhysicalSkyBoundPiece p = psbd->pieces[i];
1427  if ( A <= p.max_A ) {
1428 
1429  if ( p.type != 0 ) {
1430 
1431  // Set bound 'nb = +/- sqrt(1 - na^2)'
1432  bound = p.type * limit;
1433 
1434  } else {
1435 
1436  // Set bound 'nb' to either a constant right ascension or constant declination bound,
1437  // depending on bounds data set by XLALSetSuperskyPhysicalSkyBounds()
1438  const double c = ( na - p.na0 ) / p.r;
1439  double angle = asin( GSL_MAX( -1, GSL_MIN( c, 1 ) ) );
1440  if ( p.altroot ) {
1441  angle = LAL_PI - angle;
1442  }
1443  angle -= p.angle0;
1444  bound = p.C * cos( angle ) + p.S * sin( angle ) + p.Z;
1445 
1446  }
1447 
1448  break;
1449 
1450  }
1451  }
1452 
1453  }
1454 
1455  return GSL_MAX( -limit, GSL_MIN( bound, limit ) );
1456 
1457 }
1458 
1460  LatticeTiling *tiling,
1461  gsl_matrix *rssky_metric,
1462  SuperskyTransformData *rssky_transf,
1463  const double alpha1,
1464  const double alpha2,
1465  const double delta1,
1466  const double delta2
1467 )
1468 {
1469 
1470  // Check input
1471  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
1472  XLAL_CHECK( CHECK_RSSKY_METRIC_TRANSF( rssky_metric, rssky_transf ), XLAL_EINVAL );
1473  XLAL_CHECK( gsl_matrix_get( rssky_metric, 0, 1 ) == 0, XLAL_EINVAL );
1474  XLAL_CHECK( gsl_matrix_get( rssky_metric, 1, 0 ) == 0, XLAL_EINVAL );
1475 
1476  // Decompose coordinate transform data
1477  gsl_matrix_view align_sky = gsl_matrix_view_array( &rssky_transf->align_sky[0][0], 3, 3 );
1478  gsl_matrix_view sky_offsets = gsl_matrix_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
1479 
1480  // Check sky bound ranges
1481  XLAL_CHECK( ( fabs( alpha1 - alpha2 ) > 0 ) == ( fabs( delta1 - delta2 ) > 0 ), XLAL_EINVAL,
1482  "|alpha1 - alpha2| and |delta1 - delta2| must either be both zero, or both nonzero" );
1483  if ( fabs( alpha1 - alpha2 ) >= LAL_TWOPI ) {
1484  XLAL_CHECK( fabs( delta1 ) >= LAL_PI_2 && fabs( delta2 ) >= LAL_PI_2, XLAL_EINVAL,
1485  "If |alpha1 - alpha2| covers the whole sky, then |delta1| == |delta2| == PI/2 is required" );
1486  } else {
1487  XLAL_CHECK( fabs( alpha1 - alpha2 ) <= LAL_PI, XLAL_EINVAL,
1488  "If |alpha1 - alpha2| does not cover the whole sky, then |alpha1 - alpha2| <= PI is required" );
1489  XLAL_CHECK( -LAL_PI_2 <= delta1 && delta1 <= LAL_PI_2, XLAL_EINVAL,
1490  "If |alpha1 - alpha2| does not cover the whole sky, then -PI/2 <= delta1 <= PI/2 is required" );
1491  XLAL_CHECK( -LAL_PI_2 <= delta2 && delta2 <= LAL_PI_2, XLAL_EINVAL,
1492  "If |alpha1 - alpha2| does not cover the whole sky, then -PI/2 <= delta2 <= PI/2 is required" );
1493  }
1494 
1495  // Set parameter-space bound names
1496  XLAL_CHECK( XLALSetLatticeTilingBoundName( tiling, 0, "sskyA" ) == XLAL_SUCCESS, XLAL_EFUNC );
1497  XLAL_CHECK( XLALSetLatticeTilingBoundName( tiling, 1, "sskyB" ) == XLAL_SUCCESS, XLAL_EFUNC );
1498 
1499  // If parameter space is a single point:
1500  if ( alpha1 == alpha2 && delta1 == delta2 ) {
1501 
1502  // Convert physical point to reduced supersky coordinates A and B
1503  double rssky_point[rssky_metric->size1];
1504  gsl_vector_view rssky_point_view = gsl_vector_view_array( rssky_point, rssky_metric->size1 );
1505  PulsarDopplerParams XLAL_INIT_DECL( phys_point );
1506  phys_point.Alpha = alpha1;
1507  phys_point.Delta = delta1;
1508  XLAL_CHECK( XLALConvertPhysicalToSuperskyPoint( &rssky_point_view.vector, &phys_point, rssky_transf ) == XLAL_SUCCESS, XLAL_EFUNC );
1509 
1510  // Set the parameter-space bounds on reduced supersky sky coordinates A and B
1511  for ( size_t dim = 0; dim < 2; ++dim ) {
1512  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, dim, rssky_point[dim], rssky_point[dim] ) == XLAL_SUCCESS, XLAL_EFUNC );
1513  }
1515 
1516  return XLAL_SUCCESS;
1517 
1518  }
1519 
1520  // Initialise bounds data
1521  PhysicalSkyBoundData XLAL_INIT_DECL( data_lower );
1522  PhysicalSkyBoundData XLAL_INIT_DECL( data_upper );
1523  for ( size_t i = 0; i < XLAL_NUM_ELEM( data_lower.pieces ); ++i ) {
1524  data_lower.pieces[i].max_A = data_upper.pieces[i].max_A = GSL_NEGINF;
1525  }
1526 
1527  // Special bounds data representing the lower/upper circular bounds on reduced supersky coordinate B
1528  const PhysicalSkyBoundPiece lower_circular = { .type = -1 };
1529  const PhysicalSkyBoundPiece upper_circular = { .type = 1 };
1530 
1531  // If parameter space is the entire sky:
1532  if ( fabs( alpha1 - alpha2 ) >= LAL_TWOPI ) {
1533 
1534  // Set bounds data to lower/upper circular bounds
1535  data_lower.min_A = data_upper.min_A = -2;
1536  data_lower.min_A_bound = data_upper.min_A_bound = 0;
1537  data_lower.max_A = data_upper.max_A = 2;
1538  data_lower.max_A_bound = data_upper.max_A_bound = 0;
1539  data_lower.pieces[0] = lower_circular;
1540  data_lower.pieces[0].max_A = GSL_POSINF;
1541  data_upper.pieces[0] = upper_circular;
1542  data_upper.pieces[0].max_A = GSL_POSINF;
1543 
1544  // Set the parameter-space bounds on reduced supersky sky coordinates A and B
1545  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, 0, data_lower.min_A, data_lower.max_A ) == XLAL_SUCCESS, XLAL_EFUNC );
1546  XLAL_CHECK( XLALSetLatticeTilingBound( tiling, 1, PhysicalSkyBound, sizeof( data_lower ), &data_lower, &data_upper ) == XLAL_SUCCESS, XLAL_EFUNC );
1548 
1549  return XLAL_SUCCESS;
1550 
1551  }
1552 
1553  // Determine the right-handed angle of rotation 'phi' in the reduced supersky
1554  // plane 'nc = 0' to apply to the matrix
1555  // Q^T = [Q_na; Q_nb; Q_nc] = rssky_transf[0:2, 0:2]
1556  // such that the physical sky point 'alpha = min(alpha1,alpha2), delta = 0' is
1557  // mapped to the reduced supersky point 'A = B = 0'. This will transform the
1558  // physical sky region '[alpha1,alpha2] x [delta1,delta2]' such that moving from
1559  // 'delta1' to 'delta2' will run through the 'A' coordinate, and moving from
1560  // 'alpha1' to 'alpha2' will roughly run through the 'B' coordinate:
1561  // __________________________________________
1562  // B = 1_| _____ _____ |
1563  // | .-' '-. .-' '-. |
1564  // | .' '. .' '. |
1565  // | / \ / \ |
1566  // | ; ; ; | \.
1567  // 0-| | | | | |---> ~direction of delta
1568  // | ; ; ; | <__/
1569  // | \ / \ / | ~direction of alpha
1570  // | '. .' '. .' |
1571  // _| '-._____.-' '-._____.-' |
1572  // -1 |__.________.________.________.________.__|
1573  // ' ' ' ' '
1574  // A = -2 -1 0 1 2
1575  // where '#' indicates the area being tiled.
1576  double phi = 0;
1577  {
1578  const double alpha = GSL_MIN( alpha1, alpha2 );
1579  const double cos_alpha = cos( alpha );
1580  const double sin_alpha = sin( alpha );
1581  const double Q_na_0 = gsl_matrix_get( &align_sky.matrix, 0, 0 );
1582  const double Q_na_1 = gsl_matrix_get( &align_sky.matrix, 0, 1 );
1583  const double Q_nb_0 = gsl_matrix_get( &align_sky.matrix, 1, 0 );
1584  const double Q_nb_1 = gsl_matrix_get( &align_sky.matrix, 1, 1 );
1585  const double na = Q_na_0 * cos_alpha + Q_na_1 * sin_alpha;
1586  const double nb = Q_nb_0 * cos_alpha + Q_nb_1 * sin_alpha;
1587  phi = atan2( -nb, na );
1588  const double na_rot = na * cos( phi ) + nb * sin( phi );
1589  if ( na_rot < 0 ) {
1590  phi -= LAL_PI;
1591  } else if ( na_rot > 0 ) {
1592  phi += LAL_PI;
1593  }
1594  }
1595  const double cos_phi = cos( phi );
1596  const double sin_phi = sin( phi );
1597 
1598  // Apply the right-handed rotation matrix
1599  // R = [cos(phi), -sin(phi), 0; sin(phi), cos(phi), 0; 0, 0, 1]
1600  // to the reduced supersky coordinate transform data
1601  // rssky_transf = [Q^T; Delta^s]
1602  // where 'Q' is the sky alignment matrix and 'Delta^s' are the sky offset vectors.
1603  // The correct transformation to apply is:
1604  // Q^T ==> R * Q^T, Delta^s ==> Delta^s . R^T
1605  for ( size_t j = 0; j < 3; ++j ) {
1606  const double Q_na_j = gsl_matrix_get( &align_sky.matrix, 0, j );
1607  const double Q_nb_j = gsl_matrix_get( &align_sky.matrix, 1, j );
1608  const double Q_na_j_rot = Q_na_j * cos_phi - Q_nb_j * sin_phi;
1609  const double Q_nb_j_rot = Q_nb_j * cos_phi + Q_na_j * sin_phi;
1610  gsl_matrix_set( &align_sky.matrix, 0, j, Q_na_j_rot );
1611  gsl_matrix_set( &align_sky.matrix, 1, j, Q_nb_j_rot );
1612  }
1613  for ( size_t i = 0; i < sky_offsets.matrix.size1; ++i ) {
1614  const double Delta_0 = gsl_matrix_get( &sky_offsets.matrix, i, 0 );
1615  const double Delta_1 = gsl_matrix_get( &sky_offsets.matrix, i, 1 );
1616  const double Delta_0_rot = Delta_0 * cos_phi - Delta_1 * sin_phi;
1617  const double Delta_1_rot = Delta_1 * cos_phi + Delta_0 * sin_phi;
1618  gsl_matrix_set( &sky_offsets.matrix, i, 0, Delta_0_rot );
1619  gsl_matrix_set( &sky_offsets.matrix, i, 1, Delta_1_rot );
1620  }
1621 
1622  // Apply 'R' to the sky-sky block of the reduced supersky metric
1623  // g_nn = [g_na_na, 0; 0, g_nb_nb]
1624  // to compensate for the changes to the coordinate transform data.
1625  // The correct transformation to apply is:
1626  // Lambda ==> R * Lambda * R^T
1627  // where 'Lambda' re-introduces the supressed 'nc' coordinate dimension
1628  // Lambda = [g_na_na, 0, 0; 0, g_nb_nb, 0; 0, 0, 0]
1629  // but since 'R' is a rotation in the 'nc = 0' plane, the metric in
1630  // this dimension need not be known, and thus can be assumed to be zero.
1631  {
1632  const double g_na_na = gsl_matrix_get( rssky_metric, 0, 0 );
1633  const double g_nb_nb = gsl_matrix_get( rssky_metric, 1, 1 );
1634  const double g_na_na_rot = g_na_na * SQR( cos_phi ) + g_nb_nb * SQR( sin_phi );
1635  const double g_na_nb_rot = ( g_na_na - g_nb_nb ) * cos_phi * sin_phi;
1636  const double g_nb_nb_rot = g_na_na * SQR( sin_phi ) + g_nb_nb * SQR( cos_phi );
1637  gsl_matrix_set( rssky_metric, 0, 0, g_na_na_rot );
1638  gsl_matrix_set( rssky_metric, 0, 1, g_na_nb_rot );
1639  gsl_matrix_set( rssky_metric, 1, 0, g_na_nb_rot );
1640  gsl_matrix_set( rssky_metric, 1, 1, g_nb_nb_rot );
1641  }
1642 
1643  // Get components of the vectors 'Q_na', 'Q_nb', and 'Q_nc' from coordinate transform data
1644  const double Q_na[3] = { gsl_matrix_get( &align_sky.matrix, 0, 0 ), gsl_matrix_get( &align_sky.matrix, 0, 1 ), gsl_matrix_get( &align_sky.matrix, 0, 2 ) };
1645  const double Q_nb[3] = { gsl_matrix_get( &align_sky.matrix, 1, 0 ), gsl_matrix_get( &align_sky.matrix, 1, 1 ), gsl_matrix_get( &align_sky.matrix, 1, 2 ) };
1646  const double Q_nc[3] = { gsl_matrix_get( &align_sky.matrix, 2, 0 ), gsl_matrix_get( &align_sky.matrix, 2, 1 ), gsl_matrix_get( &align_sky.matrix, 2, 2 ) };
1647 
1648  // Determine the minimum and maximum right ascension and declination
1649  const double alphas[2] = { GSL_MIN( alpha1, alpha2 ), GSL_MAX( alpha1, alpha2 ) };
1650  const double deltas[2] = { GSL_MIN( delta1, delta2 ), GSL_MAX( delta1, delta2 ) };
1651 
1652  // Create bound data for declination bounds, at constant minimum/maximum right ascension:
1653  // Given known 'na' from previous bound, and known minimum/maximum 'alpha', solve
1654  // na = ( Q_na[0]*cos(alpha) + Q_na[1]*sin(alpha) )*cos(delta) + Q_na[2]*sin(delta)
1655  // for
1656  // delta = asin( ( na - na0 ) / r ) - angle0
1657  // and compute
1658  // nb = C*cos(delta) + S*sin(delta) + Z
1659  PhysicalSkyBoundPiece const_alpha[2];
1660  for ( size_t i = 0; i < 2; ++i ) {
1661  XLAL_INIT_MEM( const_alpha[i] );
1662  const_alpha[i].type = 0;
1663  const double cos_alpha = cos( alphas[i] );
1664  const double sin_alpha = sin( alphas[i] );
1665  const double x = Q_na[2];
1666  const double y = Q_na[0] * cos_alpha + Q_na[1] * sin_alpha;
1667  const_alpha[i].na0 = 0;
1668  const_alpha[i].r = sqrt( SQR( x ) + SQR( y ) );
1669  const_alpha[i].angle0 = atan2( y, x );
1670  const_alpha[i].C = Q_nb[0] * cos_alpha + Q_nb[1] * sin_alpha;
1671  const_alpha[i].S = Q_nb[2];
1672  const_alpha[i].Z = 0;
1673  }
1674 
1675  // Create bound data for right ascension bounds, at constant minimum/maximum declination:
1676  // Given known 'na' from previous bound, and known minimum/maximum 'delta', solve
1677  // na = ( Q_na[0]*cos(alpha) + Q_na[1]*sin(alpha) )*cos(delta) + Q_na[2]*sin(delta)
1678  // for
1679  // alpha = asin( ( na - na0 ) / r ) - angle0
1680  // and compute
1681  // nb = C*cos(alpha) + S*sin(alpha) + Z
1682  PhysicalSkyBoundPiece const_delta[2];
1683  for ( size_t j = 0; j < 2; ++j ) {
1684  XLAL_INIT_MEM( const_delta[j] );
1685  const_delta[j].type = 0;
1686  const double cos_delta = cos( deltas[j] );
1687  const double sin_delta = sin( deltas[j] );
1688  const double x = Q_na[1] * cos_delta;
1689  const double y = Q_na[0] * cos_delta;
1690  const_delta[j].na0 = Q_na[2] * sin_delta;
1691  const_delta[j].r = sqrt( SQR( x ) + SQR( y ) );
1692  const_delta[j].angle0 = atan2( y, x );
1693  const_delta[j].C = Q_nb[0] * cos_delta;
1694  const_delta[j].S = Q_nb[1] * cos_delta;
1695  const_delta[j].Z = Q_nb[2] * sin_delta;
1696  }
1697 
1698  // Determine corner points in reduced supersky coordinate A of the region
1699  // '[min(alpha),max(alpha)] x [min(delta),max(delta)]'
1700  double corner_A[2][2], corner_B[2][2];
1701  for ( size_t i = 0; i < 2; ++i ) {
1702  for ( size_t j = 0; j < 2; ++j ) {
1703  double rssky_point[rssky_metric->size1];
1704  gsl_vector_view rssky_point_view = gsl_vector_view_array( rssky_point, rssky_metric->size1 );
1705  PulsarDopplerParams XLAL_INIT_DECL( phys_point );
1706  phys_point.Alpha = alphas[i];
1707  phys_point.Delta = deltas[j];
1708  XLAL_CHECK( XLALConvertPhysicalToSuperskyPoint( &rssky_point_view.vector, &phys_point, rssky_transf ) == XLAL_SUCCESS, XLAL_EFUNC );
1709  corner_A[i][j] = rssky_point[0];
1710  corner_B[i][j] = rssky_point[1];
1711  }
1712  }
1713 
1714  // Use corner points to classify parameter space into different shapes and set bounds data
1715  data_lower.min_A = data_upper.min_A = GSL_NEGINF;
1716  data_lower.max_A = data_upper.max_A = GSL_POSINF;
1717  if ( corner_A[1][0] < 0 && corner_A[1][1] <= 0 ) {
1718 
1719  if ( corner_A[1][1] < corner_A[1][0] ) {
1720 
1721  // __________________________________________ Lower bound(s) on B:
1722  // B = 1_| _____ _____ | 0 = right ascension bound at max(delta) until max_A
1723  // | .-' '-. .-' '-. |
1724  // | .' '. .' '. | Upper bound(s) on B:
1725  // | / \ / \ | 0 = declination bound at max(alpha) until corner_A[1][0]
1726  // | ; __2_ ; ; | 1 = right ascension bound at min(delta) until corner_A[0][0]
1727  // 0-| | /####| | | | 2 = declination bound at min(alpha) until max_A
1728  // | ; _1-#####; ; ; |
1729  // | \ 0/#######/ / \ / |
1730  // | '. /###0##.' .' '. .' |
1731  // _| '-._____.-' '-._____.-' |
1732  // -1 |__.________.________.________.________.__|
1733  // ' ' ' ' '
1734  // A = -2 -1 0 1 2
1735  data_lower.min_A = data_upper.min_A = corner_A[1][1];
1736  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[1][1];
1737  data_lower.max_A = data_upper.max_A = corner_A[0][1];
1738  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[0][1];
1739  data_lower.pieces[0] = const_delta[1];
1740  data_lower.pieces[0].max_A = GSL_POSINF;
1741  data_upper.pieces[0] = const_alpha[1];
1742  data_upper.pieces[0].max_A = corner_A[1][0];
1743  data_upper.pieces[1] = const_delta[0];
1744  data_upper.pieces[1].max_A = corner_A[0][0];
1745  data_upper.pieces[2] = const_alpha[0];
1746  data_upper.pieces[2].max_A = GSL_POSINF;
1747  data_upper.pieces[2].altroot = true;
1748 
1749  } else {
1750 
1751  // __________________________________________ Lower bound(s) on B:
1752  // B = 1_| _____ _____ | 0 = declination bound at max(alpha) until corner_A[1][1]
1753  // | .-' '-. .-' '-. | 1 = right ascension bound at max(delta) until max_A
1754  // | .' '. .' '. |
1755  // | / \ / \ | Upper bound(s) on B:
1756  // | ; __1_ ; ; | 0 = right ascension bound at min(delta) until corner_A[0][0]
1757  // 0-| | 0/####| | | | 1 = declination bound at min(alpha) until max_A
1758  // | ; -#####; ; ; |
1759  // | \ 0\####/ / \ / |
1760  // | '. \1.' .' '. .' |
1761  // _| '-._____.-' '-._____.-' |
1762  // -1 |__.________.________.________.________.__|
1763  // ' ' ' ' '
1764  // A = -2 -1 0 1 2
1765  data_lower.min_A = data_upper.min_A = corner_A[1][0];
1766  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[1][0];
1767  data_lower.max_A = data_upper.max_A = corner_A[0][1];
1768  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[0][1];
1769  data_lower.pieces[0] = const_alpha[1];
1770  data_lower.pieces[0].max_A = corner_A[1][1];
1771  data_lower.pieces[0].altroot = true;
1772  data_lower.pieces[1] = const_delta[1];
1773  data_lower.pieces[1].max_A = GSL_POSINF;
1774  data_upper.pieces[0] = const_delta[0];
1775  data_upper.pieces[0].max_A = corner_A[0][0];
1776  data_upper.pieces[1] = const_alpha[0];
1777  data_upper.pieces[1].max_A = GSL_POSINF;
1778  data_upper.pieces[1].altroot = true;
1779 
1780  }
1781 
1782  } else if ( 0 <= corner_A[1][0] && 0 < corner_A[1][1] ) {
1783 
1784  if ( corner_A[1][1] < corner_A[1][0] ) {
1785 
1786  // __________________________________________ Lower bound(s) on B:
1787  // B = 1_| _____ _____ | 0 = right ascension bound at min(delta) until max_A
1788  // | .-' '-. .-' '-. |
1789  // | .' '. .' '. | Upper bound(s) on B:
1790  // | / \ / \ | 0 = declination bound at min(alpha) until corner_A[0][1]
1791  // | ; ; _0__ ; | 1 = right ascension bound at max(delta) until corner_A[1][1x]
1792  // 0-| | | |####\ | | 2 = declination bound at max(alpha) until max_A
1793  // | ; ; ;#####-1_ ; |
1794  // | \ / \ \#######\2 / |
1795  // | '. .' '. '.##0###\ .' |
1796  // _| '-._____.-' '-._____.-' |
1797  // -1 |__.________.________.________.________.__|
1798  // ' ' ' ' '
1799  // A = -2 -1 0 1 2
1800  data_lower.min_A = data_upper.min_A = corner_A[0][0];
1801  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[0][0];
1802  data_lower.max_A = data_upper.max_A = corner_A[1][0];
1803  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[1][0];
1804  data_lower.pieces[0] = const_delta[0];
1805  data_lower.pieces[0].max_A = GSL_POSINF;
1806  data_upper.pieces[0] = const_alpha[0];
1807  data_upper.pieces[0].max_A = corner_A[0][1];
1808  data_upper.pieces[1] = const_delta[1];
1809  data_upper.pieces[1].max_A = corner_A[1][1];
1810  data_upper.pieces[2] = const_alpha[1];
1811  data_upper.pieces[2].max_A = GSL_POSINF;
1812  data_upper.pieces[2].altroot = true;
1813 
1814  } else {
1815 
1816  // __________________________________________ Lower bound(s) on B:
1817  // B = 1_| _____ _____ | 0 = right ascension bound at min(delta) until corner_A[1][0]
1818  // | .-' '-. .-' '-. | 1 = declination bound at max(alpha) until max_A
1819  // | .' '. .' '. |
1820  // | / \ / \ | Upper bound(s) on B:
1821  // | ; ; _0__ ; | 0 = declination bound at min(alpha) until corner_A[0][1]
1822  // 0-| | | |####\1 | | 1 = right ascension bound at max(delta) until max_A
1823  // | ; ; ;#####- ; |
1824  // | \ / \ \####/1 / |
1825  // | '. .' '. \0.' .' |
1826  // _| '-._____.-' '-._____.-' |
1827  // -1 |__.________.________.________.________.__|
1828  // ' ' ' ' '
1829  // A = -2 -1 0 1 2
1830  data_lower.min_A = data_upper.min_A = corner_A[0][0];
1831  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[0][0];
1832  data_lower.max_A = data_upper.max_A = corner_A[1][1];
1833  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[1][1];
1834  data_lower.pieces[0] = const_delta[0];
1835  data_lower.pieces[0].max_A = corner_A[1][0];
1836  data_lower.pieces[1] = const_alpha[1];
1837  data_lower.pieces[1].max_A = GSL_POSINF;
1838  data_upper.pieces[0] = const_alpha[0];
1839  data_upper.pieces[0].max_A = corner_A[0][1];
1840  data_upper.pieces[1] = const_delta[1];
1841  data_upper.pieces[1].max_A = GSL_POSINF;
1842 
1843  }
1844 
1845  } else {
1846 
1847  // This parameter space straddles both reduced supersky hemispheres
1848  // Find the value of 'na' where this occurs at max(alpha) by solving
1849  // nc = ( Q_nc[0]*cos(max(alpha)) + Q_nc[1]*sin(max(alpha)) )*cos(delta) + Q_nc[2]*sin(delta)
1850  // for delta, then computing
1851  // na = ( Q_na[0]*cos(alpha) + Q_na[1]*sin(alpha) )*cos(delta) + Q_na[2]*sin(delta)
1852  const double cos_alpha_split = cos( alphas[1] );
1853  const double sin_alpha_split = sin( alphas[1] );
1854  const double delta_split = -1 * atan2( Q_nc[0] * cos_alpha_split + Q_nc[1] * sin_alpha_split, Q_nc[2] );
1855  const double na_split = ( Q_na[0] * cos_alpha_split + Q_na[1] * sin_alpha_split ) * cos( delta_split ) + Q_na[2] * sin( delta_split );
1856  const double split_A[2] = { -1 - na_split, 1 + na_split };
1857  const double split_B = -1 * RE_SQRT( 1 - SQR( na_split ) );
1858 
1859  if ( split_A[0] < corner_A[1][0] ) {
1860  if ( corner_A[1][1] < split_A[1] ) {
1861 
1862  // __________________________________________ Lower bound(s) on B:
1863  // B = 1_| _____ _____ | 0 = lower circular bound until max_A
1864  // | .-' '-. .-' '-. |
1865  // | .' '. .' '. | Upper bound(s) on B:
1866  // | / \ / \ | 0 = declination bound at max(alpha) until corner_A[1][0]
1867  // | ; __2__;___3___ ; | 1 = right ascension bound at min(delta) until corner_A[0][0]
1868  // 0-| | /#####|#######\ | | 2 = declination bound at min(alpha) until A = 0
1869  // | ; ___1-######;########-4_ ; | 3 = declination bound at min(alpha) until corner_A[0][1]
1870  // | \ /##########/ \##########\ / | 4 = right ascension bound at max(delta) until corner_A[1][1]
1871  // | '.0/#########.' '.#########\5.' | 5 = declination bound at max(alpha) until max_A
1872  // _| '-.##0##.-' '-.##0##.-' |
1873  // -1 |__.________.________.________.________.__|
1874  // ' ' ' ' '
1875  // A = -2 -1 0 1 2
1876  data_lower.min_A = data_upper.min_A = split_A[0];
1877  data_lower.min_A_bound = data_upper.min_A_bound = split_B;
1878  data_lower.max_A = data_upper.max_A = split_A[1];
1879  data_lower.max_A_bound = data_upper.max_A_bound = split_B;
1880  data_lower.pieces[0] = lower_circular;
1881  data_lower.pieces[0].max_A = GSL_POSINF;
1882  data_upper.pieces[0] = const_alpha[1];
1883  data_upper.pieces[0].max_A = corner_A[1][0];
1884  data_upper.pieces[1] = const_delta[0];
1885  data_upper.pieces[1].max_A = corner_A[0][0];
1886  data_upper.pieces[2] = const_alpha[0];
1887  data_upper.pieces[2].max_A = 0;
1888  data_upper.pieces[2].altroot = true;
1889  data_upper.pieces[3] = const_alpha[0];
1890  data_upper.pieces[3].max_A = corner_A[0][1];
1891  data_upper.pieces[4] = const_delta[1];
1892  data_upper.pieces[4].max_A = corner_A[1][1];
1893  data_upper.pieces[5] = const_alpha[1];
1894  data_upper.pieces[5].max_A = GSL_POSINF;
1895  data_upper.pieces[5].altroot = true;
1896 
1897  } else {
1898 
1899  // __________________________________________ Lower bound(s) on B:
1900  // B = 1_| _____ _____ | 0 = lower circular bound until split_A[1]
1901  // | .-' '-. .-' '-. | 1 = declination bound at max(alpha) until max_A
1902  // | .' '. .' '. |
1903  // | / \ / \ | Upper bound(s) on B:
1904  // | ; __2__;___3___ ; | 0 = declination bound at max(alpha) until corner_A[1][0]
1905  // 0-| | /#####|#######\ | | 1 = right ascension bound at min(delta) until corner_A[0][0]
1906  // | ; ___1-######;########-4_ ; | 2 = declination bound at min(alpha) until A = 0
1907  // | \ /##########/ \##########/ / | 3 = declination bound at min(alpha) until corner_A[0][1]
1908  // | '.0/#########.' '.#######/1 .' | 4 = right ascension bound at max(delta) until max_A
1909  // _| '-.##0##.-' '-.#0#/_.-' |
1910  // -1 |__.________.________.________.________.__|
1911  // ' ' ' ' '
1912  // A = -2 -1 0 1 2
1913  data_lower.min_A = data_upper.min_A = split_A[0];
1914  data_lower.min_A_bound = data_upper.min_A_bound = split_B;
1915  data_lower.max_A = data_upper.max_A = corner_A[1][1];
1916  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[1][1];
1917  data_lower.pieces[0] = lower_circular;
1918  data_lower.pieces[0].max_A = split_A[1];
1919  data_lower.pieces[1] = const_alpha[1];
1920  data_lower.pieces[1].max_A = GSL_POSINF;
1921  data_upper.pieces[0] = const_alpha[1];
1922  data_upper.pieces[0].max_A = corner_A[1][0];
1923  data_upper.pieces[1] = const_delta[0];
1924  data_upper.pieces[1].max_A = corner_A[0][0];
1925  data_upper.pieces[2] = const_alpha[0];
1926  data_upper.pieces[2].max_A = 0;
1927  data_upper.pieces[2].altroot = true;
1928  data_upper.pieces[3] = const_alpha[0];
1929  data_upper.pieces[3].max_A = corner_A[0][1];
1930  data_upper.pieces[4] = const_delta[1];
1931  data_upper.pieces[4].max_A = GSL_POSINF;
1932 
1933  }
1934 
1935  } else {
1936  if ( corner_A[1][1] < split_A[1] ) {
1937 
1938  // __________________________________________ Lower bound(s) on B:
1939  // B = 1_| _____ _____ | 0 = declination bound at max(alpha) until split_A[0]
1940  // | .-' '-. .-' '-. | 1 = lower circular bound until split_A[1]
1941  // | .' '. .' '. |
1942  // | / \ / \ | Upper bound(s) on B:
1943  // | ; __1__;___2___ ; | 0 = right ascension bound at min(delta) until corner_A[0][0]
1944  // 0-| | /#####|#######\ | | 1 = declination bound at min(alpha) until A = 0
1945  // | ; ___0-######;########-3_ ; | 2 = declination bound at min(alpha) until corner_A[0][1]
1946  // | \ \##########/ \##########\ / | 3 = right ascension bound at max(delta) until corner_A[1][1]
1947  // | '. 0\#######.' '.#########\4.' | 4 = declination bound at max(alpha) until max_A
1948  // _| '-._'.#1.-' '-.##1##.-' |
1949  // -1 |__.________.________.________.________.__|
1950  // ' ' ' ' '
1951  // A = -2 -1 0 1 2
1952  data_lower.min_A = data_upper.min_A = corner_A[1][0];
1953  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[1][0];
1954  data_lower.max_A = data_upper.max_A = split_A[1];
1955  data_lower.max_A_bound = data_upper.max_A_bound = split_B;
1956  data_lower.pieces[0] = const_alpha[1];
1957  data_lower.pieces[0].max_A = split_A[0];
1958  data_lower.pieces[0].altroot = true;
1959  data_lower.pieces[1] = lower_circular;
1960  data_lower.pieces[1].max_A = GSL_POSINF;
1961  data_upper.pieces[0] = const_delta[0];
1962  data_upper.pieces[0].max_A = corner_A[0][0];
1963  data_upper.pieces[1] = const_alpha[0];
1964  data_upper.pieces[1].max_A = 0;
1965  data_upper.pieces[1].altroot = true;
1966  data_upper.pieces[2] = const_alpha[0];
1967  data_upper.pieces[2].max_A = corner_A[0][1];
1968  data_upper.pieces[3] = const_delta[1];
1969  data_upper.pieces[3].max_A = corner_A[1][1];
1970  data_upper.pieces[4] = const_alpha[1];
1971  data_upper.pieces[4].max_A = GSL_POSINF;
1972  data_upper.pieces[4].altroot = true;
1973 
1974  } else {
1975 
1976  // __________________________________________ Lower bound(s) on B:
1977  // B = 1_| _____ _____ | 0 = declination bound at max(alpha) until split_A[0]
1978  // | .-' '-. .-' '-. | 1 = lower circular bound until split_A[1]
1979  // | .' '. .' '. | 2 = declination bound at max(alpha) until max_A
1980  // | / \ / \ |
1981  // | ; __1__;___2___ ; | Upper bound(s) on B:
1982  // 0-| | /#####|#######\ | | 0 = right ascension bound at min(delta) until corner_A[0][0]
1983  // | ; ___0-######;########-3_ ; | 1 = declination bound at min(alpha) until A = 0
1984  // | \ \##########/ \##########/ / | 2 = declination bound at min(alpha) until corner_A[0][1]
1985  // | '. 0\#######.' '.#######/2 .' | 3 = right ascension bound at max(delta) until max_A
1986  // _| '-._'.#1.-' '-.#1.'_.-' |
1987  // -1 |__.________.________.________.________.__|
1988  // ' ' ' ' '
1989  // A = -2 -1 0 1 2
1990  data_lower.min_A = data_upper.min_A = corner_A[1][0];
1991  data_lower.min_A_bound = data_upper.min_A_bound = corner_B[1][0];
1992  data_lower.max_A = data_upper.max_A = corner_A[1][1];
1993  data_lower.max_A_bound = data_upper.max_A_bound = corner_B[1][1];
1994  data_lower.pieces[0] = const_alpha[1];
1995  data_lower.pieces[0].max_A = split_A[0];
1996  data_lower.pieces[0].altroot = true;
1997  data_lower.pieces[1] = lower_circular;
1998  data_lower.pieces[1].max_A = split_A[1];
1999  data_lower.pieces[2] = const_alpha[1];
2000  data_lower.pieces[2].max_A = GSL_POSINF;
2001  data_upper.pieces[0] = const_delta[0];
2002  data_upper.pieces[0].max_A = corner_A[0][0];
2003  data_upper.pieces[1] = const_alpha[0];
2004  data_upper.pieces[1].max_A = 0;
2005  data_upper.pieces[1].altroot = true;
2006  data_upper.pieces[2] = const_alpha[0];
2007  data_upper.pieces[2].max_A = corner_A[0][1];
2008  data_upper.pieces[3] = const_delta[1];
2009  data_upper.pieces[3].max_A = GSL_POSINF;
2010 
2011  }
2012  }
2013 
2014  }
2015 
2016  // Set the parameter-space bounds on reduced supersky sky coordinates A and B
2017  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, 0, data_lower.min_A, data_lower.max_A ) == XLAL_SUCCESS, XLAL_EFUNC );
2018  XLAL_CHECK( XLALSetLatticeTilingBound( tiling, 1, PhysicalSkyBound, sizeof( data_lower ), &data_lower, &data_upper ) == XLAL_SUCCESS, XLAL_EFUNC );
2020 
2021  // Set the parameter-space origin on reduced supersky sky coordinates A and B
2024 
2025  return XLAL_SUCCESS;
2026 
2027 }
2028 
2029 static double ConstantBoundB(
2030  const void *data,
2031  const size_t dim UNUSED,
2032  const gsl_matrix *cache UNUSED,
2033  const gsl_vector *point
2034 )
2035 {
2036 
2037  // Get bounds data
2038  const double bound = *( ( const double * ) data );
2039 
2040  // Decode the reduced supersky coordinates to get
2041  // na = as[0] = Q_na . n
2042  const double A = gsl_vector_get( point, 0 );
2043  const double rssky[2] = { A, 0 };
2044  gsl_vector_const_view rssky_view = gsl_vector_const_view_array( rssky, 2 );
2045  double as[3];
2046  SM_ReducedToAligned( as, &rssky_view.vector, A );
2047  const double na = as[0];
2048 
2049  // Absolute limiting bound on 'nb = +/- sqrt(1 - na^2)'
2050  const double limit = RE_SQRT( 1 - SQR( na ) );
2051 
2052  return GSL_MAX( -limit, GSL_MIN( bound, limit ) );
2053 
2054 }
2055 
2057  double A1,
2058  void *params
2059 )
2060 {
2061 
2062  // Get parameters
2063  const double target_area = ( ( double * ) params )[0];
2064 
2065  // Compute area of unit disk to the left of 'A1'
2066  const double area = LAL_PI_2 + A1 * RE_SQRT( 1 - SQR( A1 ) ) + asin( A1 );
2067 
2068  return area - target_area;
2069 
2070 }
2071 
2073  double B1,
2074  void *params
2075 )
2076 {
2077 
2078  // Get parameters
2079  const double target_area = ( ( double * ) params )[0];
2080  double A0 = ( ( double * ) params )[1];
2081  double A1 = ( ( double * ) params )[2];
2082 
2083  // Compute area of unit disk between 'A0' and 'A1'
2084  const double max_area = A1 * RE_SQRT( 1 - SQR( A1 ) ) - A0 * RE_SQRT( 1 - SQR( A0 ) ) + asin( A1 ) - asin( A0 );
2085 
2086  // Work out where '-|B1| = const' line intersects unit disk boundary
2087  const double Ai = RE_SQRT( 1 - SQR( B1 ) );
2088 
2089  // Restrict range '[A0, A1]' if '-|B1| = const' line intersects unit disk boundary within it
2090  if ( A0 < -Ai ) {
2091  A0 = -Ai;
2092  }
2093  if ( A1 > Ai ) {
2094  A1 = Ai;
2095  }
2096 
2097  // Compute area of unit disk between 'A0' and 'A1' and below '-|B1|'
2098  double area = -fabs( B1 ) * ( A1 - A0 ) + 0.5 * ( A1 * RE_SQRT( 1 - SQR( A1 ) ) - A0 * RE_SQRT( 1 - SQR( A0 ) ) + asin( A1 ) - asin( A0 ) );
2099 
2100  // For positive B, substract 'area' from 'max_area'
2101  if ( B1 > 0 ) {
2102  area = max_area - area;
2103  }
2104 
2105  return area - target_area;
2106 
2107 }
2108 
2110  LatticeTiling *tiling,
2111  const gsl_matrix *rssky_metric,
2112  const double max_mismatch,
2113  const UINT4 patch_count,
2114  const UINT4 patch_index
2115 )
2116 {
2117 
2118  // Check input
2119  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2120  XLAL_CHECK( rssky_metric->size1 == rssky_metric->size2, XLAL_EINVAL );
2121  XLAL_CHECK( rssky_metric->size1 >= 2, XLAL_EINVAL );
2122  XLAL_CHECK( gsl_matrix_get( rssky_metric, 0, 1 ) == 0, XLAL_EINVAL );
2123  XLAL_CHECK( gsl_matrix_get( rssky_metric, 1, 0 ) == 0, XLAL_EINVAL );
2124  XLAL_CHECK( max_mismatch > 0, XLAL_EINVAL );
2125  XLAL_CHECK( patch_count > 0, XLAL_EINVAL );
2126  XLAL_CHECK( patch_count == 1 || patch_count % 2 == 0, XLAL_EINVAL, "'patch_count' must be either one or even" );
2127  XLAL_CHECK( patch_index < patch_count, XLAL_EINVAL );
2128 
2129  // Set parameter-space bound names
2130  XLAL_CHECK( XLALSetLatticeTilingBoundName( tiling, 0, "sskyA" ) == XLAL_SUCCESS, XLAL_EFUNC );
2131  XLAL_CHECK( XLALSetLatticeTilingBoundName( tiling, 1, "sskyB" ) == XLAL_SUCCESS, XLAL_EFUNC );
2132 
2133  // Parameter-space bounds on reduced supersky sky coordinates A and B
2134  double A_bound[2] = {GSL_NAN, GSL_NAN};
2135  double B_bound[2] = {-1, 1};
2136 
2137  // Start with default padding on parameter-space bounds on coordinates A and B
2138  double A_lower_bbox_pad = -1, A_upper_bbox_pad = -1;
2139  double B_lower_bbox_pad = -1, B_upper_bbox_pad = -1;
2140 
2141  // Handle special cases of 1 and 2 patches
2142  if ( patch_count <= 2 ) {
2143  A_bound[0] = -2 + 4 * ( ( double ) patch_index ) / ( ( double ) patch_count );
2144  A_bound[1] = A_bound[0] + 4 / ( ( double ) patch_count );
2145  } else {
2146 
2147  // Number of patches per hemisphere for odd number of patches
2148  const UINT4 hemi_patch_count = patch_count / 2;
2149 
2150  // Index of patch within hemisphere; increases to 'hemi_patch_count' then decreases to zero
2151  const UINT4 hemi_patch_index = patch_index < hemi_patch_count ? patch_index : patch_count - patch_index - 1;
2152 
2153  // Minimum number of patch divisions in supersky coordinate A within one hemisphere
2154  // - Prevents patches from being too squashed in either A or B, which lead to overcoverage (i.e. the
2155  // sum of points over all sky patches being much larger than the number of points in the whole sky)
2156  const UINT4 min_A_count = ceil( sqrt( hemi_patch_count ) );
2157 
2158  // Maximum extent of metric ellipse bounding box in supersky coordinate B
2159  const double max_B_bbox = 2 * sqrt( max_mismatch / gsl_matrix_get( rssky_metric, 1, 1 ) );
2160 
2161  // Target number of patch divisions in supersky coordinate B
2162  const double target_B_count = 1.0 / ( 1.0 * max_B_bbox );
2163 
2164  // Number of patch divisions in supersky coordinate A within one hemisphere
2165  const UINT4 A_count = GSL_MIN( min_A_count, ceil( hemi_patch_count / target_B_count ) );
2166 
2167  // Minimum number of patch divisions in supersky coordinate B
2168  const UINT4 min_B_count = hemi_patch_count / A_count;
2169 
2170  // Excess number of patches, which must be added on to get 'hemi_patch_count'
2171  INT4 patch_excess = hemi_patch_count - A_count * min_B_count;
2172  XLAL_CHECK( patch_excess >= 0, XLAL_EFAILED );
2173 
2174  // Initialise number of patch divisions in 'B'; if there are excess patches, add an extra patch
2175  UINT4 B_count = min_B_count;
2176  if ( patch_excess > 0 ) {
2177  ++B_count;
2178  }
2179 
2180  // Calculate range of indices in 'A', and number of patch divisions and index in 'B'.
2181  // - The divisions in 'A' are set in proportion to the range of 'A_index', i.e. the number of
2182  // divisions in 'B' for that range of 'A_index'. This is so that, if 'patch_excess' is
2183  // not zero, and therefore the number of divisions in 'B' is not constant, patch areas should
2184  // still be equal. Example:
2185  // hemi_patch_count=7 hemi_patch_index=0 | A_index=0--3 B_count=3 B_index=0
2186  // hemi_patch_count=7 hemi_patch_index=1 | A_index=0--3 B_count=3 B_index=1
2187  // hemi_patch_count=7 hemi_patch_index=2 | A_index=0--3 B_count=3 B_index=2
2188  // hemi_patch_count=7 hemi_patch_index=3 | A_index=3--5 B_count=2 B_index=0
2189  // hemi_patch_count=7 hemi_patch_index=4 | A_index=3--5 B_count=2 B_index=1
2190  // hemi_patch_count=7 hemi_patch_index=5 | A_index=5--7 B_count=2 B_index=0
2191  // hemi_patch_count=7 hemi_patch_index=6 | A_index=5--7 B_count=2 B_index=1
2192  UINT4 A_index[2] = {0, B_count};
2193  UINT4 B_index = hemi_patch_index;
2194  while ( B_index >= B_count ) {
2195 
2196  // Decrease index in 'B'; we are done when 'B_index' < 'B_count'
2197  B_index -= B_count;
2198 
2199  // Decrease number of excess patches; if zero, subtract extra patch from patch divisions in 'B'
2200  --patch_excess;
2201  if ( patch_excess == 0 ) {
2202  --B_count;
2203  }
2204 
2205  // Store the current last 'A' index in 'A_index[0]', and increase
2206  // 'A_index[1]' by the current number of patch divisions in 'B'
2207  A_index[0] = A_index[1];
2208  A_index[1] += B_count;
2209 
2210  }
2211 
2212  // Decide which patches to add padding to (-1 denotes default padding)
2213  A_lower_bbox_pad = ( A_index[0] == 0 && patch_index < hemi_patch_count ) ? -1 : 0;
2214  A_upper_bbox_pad = ( A_index[0] == 0 && patch_index >= hemi_patch_count ) ? -1 : 0;
2215  B_lower_bbox_pad = ( B_index == 0 ) ? -1 : 0;
2216  B_upper_bbox_pad = ( B_index + 1 == B_count ) ? -1 : 0;
2217 
2218  // Allocate a GSL root solver
2219  gsl_root_fsolver *fs = gsl_root_fsolver_alloc( gsl_root_fsolver_brent );
2220  XLAL_CHECK( fs != NULL, XLAL_ENOMEM );
2221 
2222  // Find bounds on 'A' corresponding to the computed indexes
2223  for ( size_t i = 0; i < 2; ++i ) {
2224  const UINT4 A_index_i = A_index[i];
2225 
2226  // Handle boundaries as special cases
2227  if ( A_index_i == 0 ) {
2228  A_bound[i] = -1;
2229  } else if ( A_index_i == hemi_patch_count ) {
2230  A_bound[i] = 1;
2231  } else {
2232 
2233  // Calculate the target area of unit disk
2234  const double target_area = LAL_PI * ( ( double ) A_index_i ) / ( ( ( double ) patch_count ) / 2 );
2235 
2236  // Set up GSL root solver
2237  double params[] = { target_area };
2238  gsl_function F = { .function = EqualAreaSkyBoundSolverA, .params = params };
2239  double A_lower = -1, A_upper = 1;
2240  XLAL_CHECK( gsl_root_fsolver_set( fs, &F, A_lower, A_upper ) == 0, XLAL_EFAILED );
2241 
2242  // Try to find root
2243  int status = 0, iter = 0;
2244  do {
2245  XLAL_CHECK( gsl_root_fsolver_iterate( fs ) == 0, XLAL_EFAILED );
2246  A_lower = gsl_root_fsolver_x_lower( fs );
2247  A_upper = gsl_root_fsolver_x_upper( fs );
2248  status = gsl_root_test_interval( A_lower, A_upper, 1e-5, 1e-5 );
2249  } while ( status == GSL_CONTINUE && ++iter < 1000 );
2250  XLAL_CHECK( status == GSL_SUCCESS, XLAL_EMAXITER, "Could not find bound for A_index[%zu]=%i; best guess [%g, %g]", i, A_index_i, A_lower, A_upper );
2251 
2252  // Store bound
2253  A_bound[i] = gsl_root_fsolver_root( fs );
2254 
2255  }
2256 
2257  }
2258 
2259  // Find bounds on 'B' corresponding to the computed indexes
2260  for ( size_t i = 0; i < 2; ++i ) {
2261  const UINT4 B_index_i = B_index + i;
2262 
2263  // Maximum possible value of 'B' within the region bound by 'A_bound'
2264  // - For a single patch in 'A', 'B' must span the entire unit disk
2265  double B_max = ( A_count == 1 ) ? 1 : RE_SQRT( 1 - GSL_MIN( SQR( A_bound[0] ), SQR( A_bound[1] ) ) );
2266 
2267  // Handle boundaries as special cases
2268  if ( B_index_i == 0 ) {
2269  B_bound[i] = -B_max;
2270  } else if ( B_index_i == B_count ) {
2271  B_bound[i] = B_max;
2272  } else {
2273 
2274  // Calculate the target area of unit disk
2275  const double A0 = A_bound[0];
2276  const double A1 = A_bound[1];
2277  const double target_area = ( A1 * RE_SQRT( 1 - SQR( A1 ) ) - A0 * RE_SQRT( 1 - SQR( A0 ) ) + asin( A1 ) - asin( A0 ) ) * ( ( double ) B_index_i ) / ( ( double ) B_count );
2278 
2279  // Set up GSL root solver
2280  double params[] = { target_area, A0, A1 };
2281  gsl_function F = { .function = EqualAreaSkyBoundSolverB, .params = params };
2282  if ( EqualAreaSkyBoundSolverB( -B_max, params ) * EqualAreaSkyBoundSolverB( B_max, params ) > 0 ) {
2283  // [-B_max, B_max] does not bracket root, so use B_max = 1
2284  B_max = 1;
2285  }
2286  double B_lower = -B_max, B_upper = B_max;
2287  XLAL_CHECK( gsl_root_fsolver_set( fs, &F, B_lower, B_upper ) == 0, XLAL_EFAILED );
2288 
2289  // Try to find root
2290  int status = 0, iter = 0;
2291  do {
2292  XLAL_CHECK( gsl_root_fsolver_iterate( fs ) == 0, XLAL_EFAILED );
2293  B_lower = gsl_root_fsolver_x_lower( fs );
2294  B_upper = gsl_root_fsolver_x_upper( fs );
2295  status = gsl_root_test_interval( B_lower, B_upper, 1e-5, 1e-5 );
2296  } while ( status == GSL_CONTINUE && ++iter < 1000 );
2297  XLAL_CHECK( status == GSL_SUCCESS, XLAL_EMAXITER, "Could not find bound for B_index[%zu]=%i; best guess [%g, %g]", i, B_index_i, B_lower, B_upper );
2298 
2299  // Store bound
2300  B_bound[i] = gsl_root_fsolver_root( fs );
2301 
2302  }
2303 
2304  }
2305 
2306  // Restrict range 'A' if 'B = const' bounds intersect unit disk boundary within it
2307  // - Only start to do this when there are 3 patches in 'B' direction
2308  // - Do not do this for the middle 'B' patch which straddles 'B = 0'
2309  if ( B_count >= 3 && B_index != ( B_count - 1 ) / 2 ) {
2310  const double Ai = RE_SQRT( 1 - GSL_MIN( SQR( B_bound[0] ), SQR( B_bound[1] ) ) );
2311  if ( A_bound[0] < -Ai ) {
2312  A_bound[0] = -Ai;
2313  }
2314  if ( A_bound[1] > Ai ) {
2315  A_bound[1] = Ai;
2316  }
2317  }
2318 
2319  // Shift bounds on 'A' into the correct hemisphere, and put in correct order
2320  if ( patch_index < hemi_patch_count ) { // This patch is in the left hemisphere
2321  A_bound[0] = A_bound[0] - 1;
2322  A_bound[1] = A_bound[1] - 1;
2323  } else { // This patch is in the left hemisphere
2324  A_bound[0] = -A_bound[0] + 1;
2325  A_bound[1] = -A_bound[1] + 1;
2326  }
2327 
2328  // Cleanup
2329  gsl_root_fsolver_free( fs );
2330 
2331  }
2332 
2333  // Set the parameter-space bounds on reduced supersky sky coordinates A and B
2334  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, 0, A_bound[0], A_bound[1] ) == XLAL_SUCCESS, XLAL_EFUNC );
2335  XLAL_CHECK( XLALSetLatticeTilingBound( tiling, 1, ConstantBoundB, sizeof( B_bound[0] ), &B_bound[0], &B_bound[1] ) == XLAL_SUCCESS, XLAL_EFUNC );
2337 
2338  // Set the parameter-space origin on reduced supersky sky coordinates A and B
2341 
2342  // Set the parameter-space padding control flags for reduced supersky sky coordinates A and B (-1 denotes default padding)
2343  XLAL_CHECK( XLALSetLatticeTilingPadding( tiling, 0, A_lower_bbox_pad, A_upper_bbox_pad, -1, -1, -1 ) == XLAL_SUCCESS, XLAL_EFUNC );
2344  XLAL_CHECK( XLALSetLatticeTilingPadding( tiling, 1, B_lower_bbox_pad, B_upper_bbox_pad, -1, -1, -1 ) == XLAL_SUCCESS, XLAL_EFUNC );
2345 
2346  return XLAL_SUCCESS;
2347 
2348 }
2349 
2350 static double PhysicalSpinBound(
2351  const void *data,
2352  const size_t dim UNUSED,
2353  const gsl_matrix *cache,
2354  const gsl_vector *point UNUSED
2355 )
2356 {
2357 
2358  // Get bounds data
2359  const double *sky_offsets = ( ( const double * ) data );
2360  double bound = ( ( const double * ) data )[3];
2361 
2362  // Add the inner product of the sky offsets with the aligned sky
2363  // position to the physical bound to get the reduced supersky bound
2364  for ( size_t i = 0; i < 3; ++i ) {
2365  bound += sky_offsets[i] * gsl_matrix_get( cache, 1, i );
2366  }
2367 
2368  return bound;
2369 
2370 }
2371 
2373  LatticeTiling *tiling,
2374  const SuperskyTransformData *rssky_transf,
2375  const size_t s,
2376  const double bound1,
2377  const double bound2
2378 )
2379 {
2380 
2381  // Check input
2382  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2383  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
2384  XLAL_CHECK( isfinite( bound1 ), XLAL_EINVAL );
2385  XLAL_CHECK( isfinite( bound2 ), XLAL_EINVAL );
2386  XLAL_CHECK( s <= rssky_transf->SMAX, XLAL_ESIZE );
2387 
2388  // Decompose coordinate transform data
2389  gsl_matrix_const_view sky_offsets = gsl_matrix_const_view_array( &rssky_transf->sky_offsets[0][0], rssky_transf->nsky_offsets, 3 );
2390 
2391  // Set parameter-space bound name
2392  XLAL_CHECK( XLALSetLatticeTilingBoundName( tiling, RSSKY_FKDOT_DIM( rssky_transf, s ), "nu%zudot", s ) == XLAL_SUCCESS, XLAL_EFUNC );
2393 
2394  // Copy the sky offset vector to bounds data
2395  double data_lower[4], data_upper[4];
2396  for ( size_t j = 0; j < 3; ++j ) {
2397  data_lower[j] = data_upper[j] = gsl_matrix_get( &sky_offsets.matrix, RSSKY_FKDOT_OFFSET( rssky_transf, s ), j );
2398  }
2399  data_lower[3] = GSL_MIN( bound1, bound2 );
2400  data_upper[3] = GSL_MAX( bound1, bound2 );
2401 
2402  // Set the parameter-space bound on physical frequency/spindown coordinate
2403  XLAL_CHECK( XLALSetLatticeTilingBound( tiling, RSSKY_FKDOT_DIM( rssky_transf, s ), PhysicalSpinBound, sizeof( data_lower ), &data_lower, &data_upper ) == XLAL_SUCCESS, XLAL_EFUNC );
2404 
2405  // Supersky metric requires searching over spindown if also searching over sky
2406  const int tiled_sskyA = XLALIsTiledLatticeTilingDimension( tiling, 0 );
2407  XLAL_CHECK( tiled_sskyA != XLAL_FAILURE, XLAL_EFUNC );
2408  const int tiled_sskyB = XLALIsTiledLatticeTilingDimension( tiling, 1 );
2409  XLAL_CHECK( tiled_sskyB != XLAL_FAILURE, XLAL_EFUNC );
2410  const int tiled_fkdot = XLALIsTiledLatticeTilingDimension( tiling, RSSKY_FKDOT_DIM( rssky_transf, s ) );
2411  XLAL_CHECK( tiled_fkdot != XLAL_FAILURE, XLAL_EFUNC );
2412  XLAL_CHECK( !( tiled_sskyA || tiled_sskyB ) || tiled_fkdot, XLAL_EINVAL, "Must search over %zu-order spindown if also searching over sky", s );
2413 
2414  return XLAL_SUCCESS;
2415 
2416 }
2417 
2419  LatticeTiling *tiling,
2420  const SuperskyTransformData *rssky_transf,
2421  const size_t s,
2422  const bool padding
2423 )
2424 {
2425 
2426  // Check input
2427  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2428  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
2429  XLAL_CHECK( s <= rssky_transf->SMAX, XLAL_ESIZE );
2430 
2431  // Set padding (-1 denotes default padding)
2432  const double bbox_pad = padding ? -1 : 0;
2433  XLAL_CHECK( XLALSetLatticeTilingPadding( tiling, RSSKY_FKDOT_DIM( rssky_transf, s ), bbox_pad, bbox_pad, -1, -1, -1 ) == XLAL_SUCCESS, XLAL_EFUNC );
2434 
2435  return XLAL_SUCCESS;
2436 
2437 }
2438 
2440  const bool first_call,
2441  const LatticeTiling *tiling,
2442  const LatticeTilingIterator *itr,
2443  const gsl_vector *point,
2444  const size_t changed_i,
2445  const void *param,
2446  void *out
2447 )
2448 {
2449 
2450  // Only care about changes in sky position
2451  if ( 2 <= changed_i ) {
2452  return XLAL_SUCCESS;
2453  }
2454 
2455  // Get callback data
2456  const SM_CallbackParam *cparam = ( ( const SM_CallbackParam * ) param );
2457  SM_CallbackOut *cout = ( ( SM_CallbackOut * ) out );
2458 
2459  // Initialise translation data
2460  if ( first_call ) {
2461  XLAL_INIT_MEM( cout->min_phys );
2462  XLAL_INIT_MEM( cout->max_phys );
2463  cout->min_phys.refTime = cparam->rssky_transf->ref_time;
2464  cout->max_phys.refTime = cparam->rssky_transf->ref_time;
2465  cout->min_phys.Alpha = GSL_POSINF;
2466  cout->max_phys.Alpha = GSL_NEGINF;
2467  cout->min_phys.Delta = GSL_POSINF;
2468  cout->max_phys.Delta = GSL_NEGINF;
2469  for ( size_t s = 0; s <= cparam->rssky_transf->SMAX; ++s ) {
2470  cout->min_phys.fkdot[s] = GSL_POSINF;
2471  cout->max_phys.fkdot[s] = GSL_NEGINF;
2472  }
2473  }
2474 
2475  // Convert point from reduced supersky coordinates to physical coordinates
2478 
2479  // Store minimum/maximum values of physical sky position
2480  cout->min_phys.Alpha = GSL_MIN( cout->min_phys.Alpha, phys.Alpha );
2481  cout->max_phys.Alpha = GSL_MAX( cout->max_phys.Alpha, phys.Alpha );
2482  cout->min_phys.Delta = GSL_MIN( cout->min_phys.Delta, phys.Delta );
2483  cout->max_phys.Delta = GSL_MAX( cout->max_phys.Delta, phys.Delta );
2484 
2485  for ( size_t s = 0; s <= cparam->rssky_transf->SMAX; ++s ) {
2486 
2487  // Get indexes of left/right-most point in current frequency block
2488  INT4 left = 0, right = 0;
2490 
2491  // Get step size
2492  const double step = XLALLatticeTilingStepSize( tiling, RSSKY_FKDOT_DIM( cparam->rssky_transf, s ) );
2493 
2494  // Store minimum/maximum values of physical frequency and spindowns
2495  if ( XLALIsTiledLatticeTilingDimension( tiling, RSSKY_FKDOT_DIM( cparam->rssky_transf, s ) ) ) {
2496  cout->min_phys.fkdot[s] = GSL_MIN( cout->min_phys.fkdot[s], phys.fkdot[s] + step * ( left - 1 ) );
2497  cout->max_phys.fkdot[s] = GSL_MAX( cout->max_phys.fkdot[s], phys.fkdot[s] + step * ( right + 1 ) );
2498  } else {
2499  cout->min_phys.fkdot[s] = cout->max_phys.fkdot[s] = phys.fkdot[s];
2500  }
2501 
2502  }
2503 
2504  return XLAL_SUCCESS;
2505 
2506 }
2507 
2509  LatticeTiling *tiling,
2510  const SuperskyTransformData *rssky_transf,
2511  const PulsarDopplerParams **min_phys,
2512  const PulsarDopplerParams **max_phys
2513 )
2514 {
2515 
2516  // Check input
2517  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2518  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
2519  XLAL_CHECK( min_phys != NULL, XLAL_EFAULT );
2520  XLAL_CHECK( max_phys != NULL, XLAL_EFAULT );
2521 
2522  // Register callback function
2523  const SM_CallbackParam param = {
2524  .rssky_transf = rssky_transf,
2525  };
2526  const SM_CallbackOut *out = XLALRegisterLatticeTilingCallback( tiling, SM_LatticePhysicalRangeCallback, sizeof( param ), &param, sizeof( *out ) );
2527  XLAL_CHECK( out != NULL, XLAL_EFUNC );
2528 
2529  // Set output parameters
2530  *min_phys = &out->min_phys;
2531  *max_phys = &out->max_phys;
2532 
2533  return XLAL_SUCCESS;
2534 
2535 }
2536 
2538  const bool first_call,
2539  const LatticeTiling *tiling,
2540  const LatticeTilingIterator *itr,
2541  const gsl_vector *point,
2542  const size_t changed_i,
2543  const void *param,
2544  void *out
2545 )
2546 {
2547 
2548  // Only care about changes in sky position
2549  if ( 2 <= changed_i ) {
2550  return XLAL_SUCCESS;
2551  }
2552 
2553  // Get callback data
2554  const SM_CallbackParam *cparam = ( ( const SM_CallbackParam * ) param );
2555  SM_CallbackOut *cout = ( ( SM_CallbackOut * ) out );
2556 
2557  // Initialise translation data
2558  if ( first_call ) {
2559  cout->min_rssky2_view = gsl_vector_view_array( cout->min_rssky2_array, cparam->rssky2_transf->ndim );
2560  cout->max_rssky2_view = gsl_vector_view_array( cout->max_rssky2_array, cparam->rssky2_transf->ndim );
2561  gsl_vector_set_all( &cout->min_rssky2_view.vector, GSL_POSINF );
2562  gsl_vector_set_all( &cout->max_rssky2_view.vector, GSL_NEGINF );
2563  }
2564 
2565  // Convert point from reduced supersky coordinates to other reduced supersky coordinates
2566  double rssky2_array[cparam->rssky_transf->ndim];
2567  gsl_vector_view rssky2_view = gsl_vector_view_array( rssky2_array, cparam->rssky2_transf->ndim );
2568  XLAL_CHECK( XLALConvertSuperskyToSuperskyPoint( &rssky2_view.vector, cparam->rssky2_transf, point, point, cparam->rssky_transf ) == XLAL_SUCCESS, XLAL_EFUNC );
2569 
2570  // Store minimum/maximum values of other reduced supersky sky coordinates
2571  for ( size_t i = 0; i < 2; ++i ) {
2572  cout->min_rssky2_array[i] = GSL_MIN( cout->min_rssky2_array[i], rssky2_array[i] );
2573  cout->max_rssky2_array[i] = GSL_MAX( cout->max_rssky2_array[i], rssky2_array[i] );
2574  }
2575 
2576  for ( size_t i = 2; i < cparam->rssky2_transf->ndim; ++i ) {
2577 
2578  // Get indexes of left/right-most point in current frequency block
2579  INT4 left = 0, right = 0;
2580  XLAL_CHECK( XLALCurrentLatticeTilingBlock( itr, i, &left, &right ) == XLAL_SUCCESS, XLAL_EFUNC );
2581 
2582  // Get step size
2583  const double step = XLALLatticeTilingStepSize( tiling, i );
2584 
2585  // Store minimum/maximum values of other reduced supersky frequency and spindown coordinates
2586  if ( XLALIsTiledLatticeTilingDimension( tiling, i ) ) {
2587  cout->min_rssky2_array[i] = GSL_MIN( cout->min_rssky2_array[i], rssky2_array[i] + step * ( left - 1 ) );
2588  cout->max_rssky2_array[i] = GSL_MAX( cout->max_rssky2_array[i], rssky2_array[i] + step * ( right + 1 ) );
2589  } else {
2590  cout->min_rssky2_array[i] = cout->max_rssky2_array[i] = rssky2_array[i];
2591  }
2592 
2593  }
2594 
2595  return XLAL_SUCCESS;
2596 
2597 }
2598 
2600  LatticeTiling *tiling,
2601  const SuperskyTransformData *rssky_transf,
2602  const SuperskyTransformData *rssky2_transf,
2603  const gsl_vector **min_rssky2,
2604  const gsl_vector **max_rssky2
2605 )
2606 {
2607 
2608  // Check input
2609  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2610  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky_transf ), XLAL_EINVAL );
2611  XLAL_CHECK( CHECK_RSSKY_TRANSF( rssky2_transf ), XLAL_EINVAL );
2612  XLAL_CHECK( min_rssky2 != NULL, XLAL_EFAULT );
2613  XLAL_CHECK( max_rssky2 != NULL, XLAL_EFAULT );
2614 
2615  // Register callback function
2616  const SM_CallbackParam param = {
2617  .rssky_transf = rssky_transf,
2618  .rssky2_transf = rssky2_transf,
2619  };
2620  const SM_CallbackOut *out = XLALRegisterLatticeTilingCallback( tiling, SM_LatticeSuperskyRangeCallback, sizeof( param ), &param, sizeof( *out ) );
2621  XLAL_CHECK( out != NULL, XLAL_EFUNC );
2622  XLAL_CHECK( rssky2_transf->ndim <= XLAL_NUM_ELEM( out->min_rssky2_array ), XLAL_EFAILED );
2623  XLAL_CHECK( rssky2_transf->ndim <= XLAL_NUM_ELEM( out->max_rssky2_array ), XLAL_EFAILED );
2624 
2625  // Set output parameters
2626  *min_rssky2 = &out->min_rssky2_view.vector;
2627  *max_rssky2 = &out->max_rssky2_view.vector;
2628 
2629  return XLAL_SUCCESS;
2630 
2631 }
2632 
2634  LatticeTiling *tiling,
2635  const gsl_vector *min_rssky,
2636  const gsl_vector *max_rssky
2637 )
2638 {
2639 
2640  // Check input
2641  XLAL_CHECK( tiling != NULL, XLAL_EFAULT );
2642  const size_t n = XLALTotalLatticeTilingDimensions( tiling );
2643  XLAL_CHECK( min_rssky != NULL, XLAL_EFAULT );
2644  XLAL_CHECK( min_rssky->size == n, XLAL_EINVAL );
2645  XLAL_CHECK( max_rssky != NULL, XLAL_EFAULT );
2646  XLAL_CHECK( max_rssky->size == n, XLAL_EINVAL );
2647 
2648  // Set the parameter-space bounds on reduced supersky sky coordinates A and B
2649  double A_bound[2] = { gsl_vector_get( min_rssky, 0 ), gsl_vector_get( max_rssky, 0 ) };
2650  double B_bound[2] = { gsl_vector_get( min_rssky, 1 ), gsl_vector_get( max_rssky, 1 ) };
2651  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, 0, A_bound[0], A_bound[1] ) == XLAL_SUCCESS, XLAL_EFUNC );
2652  XLAL_CHECK( XLALSetLatticeTilingBound( tiling, 1, ConstantBoundB, sizeof( B_bound[0] ), &B_bound[0], &B_bound[1] ) == XLAL_SUCCESS, XLAL_EFUNC );
2654 
2655  // Set the parameter-space bounds on all other coordinates
2656  for ( size_t j = 2; j < n; ++j ) {
2657  XLAL_CHECK( XLALSetLatticeTilingConstantBound( tiling, j, gsl_vector_get( min_rssky, j ), gsl_vector_get( max_rssky, j ) ) == XLAL_SUCCESS, XLAL_EFUNC );
2658  }
2659 
2660  return XLAL_SUCCESS;
2661 
2662 }
2663 
2664 // Local Variables:
2665 // c-file-style: "linux"
2666 // c-basic-offset: 2
2667 // End:
int XLALFITSArrayOpenWrite(FITSFile UNUSED *file, const CHAR UNUSED *name, const size_t UNUSED ndim, const size_t UNUSED dims[], const CHAR UNUSED *comment)
Definition: FITSFileIO.c:1587
int XLALFITSTableWriteRow(FITSFile UNUSED *file, const void UNUSED *record)
Definition: FITSFileIO.c:2550
int XLALFITSTableReadRow(FITSFile UNUSED *file, void UNUSED *record, UINT8 UNUSED *rem_nrows)
Definition: FITSFileIO.c:2621
int XLALFITSArrayReadGSLMatrix(FITSFile UNUSED *file, const size_t UNUSED idx[], gsl_matrix UNUSED **elems)
Definition: FITSFileIO.c:2118
int XLALFITSArrayOpenRead2(FITSFile UNUSED *file, const CHAR UNUSED *name, size_t UNUSED *dim0, size_t UNUSED *dim1)
Definition: FITSFileIO.c:1723
int XLALFITSArrayOpenRead(FITSFile UNUSED *file, const CHAR UNUSED *name, size_t UNUSED *ndim, size_t UNUSED dims[])
Definition: FITSFileIO.c:1634
int XLALFITSTableOpenWrite(FITSFile UNUSED *file, const CHAR UNUSED *name, const CHAR UNUSED *comment)
Definition: FITSFileIO.c:2162
int XLALFITSArrayOpenWrite2(FITSFile UNUSED *file, const CHAR UNUSED *name, const size_t UNUSED dim0, const size_t UNUSED dim1, const CHAR UNUSED *comment)
Definition: FITSFileIO.c:1710
int XLALFITSArrayWriteGSLMatrix(FITSFile UNUSED *file, const size_t UNUSED idx[], const gsl_matrix UNUSED *elems)
Definition: FITSFileIO.c:2070
int XLALFITSTableOpenRead(FITSFile UNUSED *file, const CHAR UNUSED *name, UINT8 UNUSED *nrows)
Definition: FITSFileIO.c:2198
int j
#define c
int XLALSetLatticeTilingBoundName(LatticeTiling *tiling, const size_t dim, const char *fmt,...)
UINT2 A
Definition: SFTnaming.c:46
UINT2 B
Definition: SFTnaming.c:47
#define MAX_SKY_OFFSETS
static int SM_LatticePhysicalRangeCallback(const bool first_call, const LatticeTiling *tiling, const LatticeTilingIterator *itr, const gsl_vector *point, const size_t changed_i, const void *param, void *out)
static void SkyBoundCache(const size_t dim UNUSED, const gsl_vector *point, gsl_vector *cache)
static double PhysicalSkyBound(const void *data, const size_t dim UNUSED, const gsl_matrix *cache UNUSED, const gsl_vector *point)
#define DOT3(x, y)
static double PhysicalSpinBound(const void *data, const size_t dim UNUSED, const gsl_matrix *cache, const gsl_vector *point UNUSED)
#define SMAX
#define RSSKY_FKDOT_DIM(RT, S)
const double fiducial_calc_freq
Fiducial frequency at which to numerically calculate metrics, which are then rescaled to user-request...
static int SM_ComputeReducedSuperskyMetric(gsl_matrix **rssky_metric, SuperskyTransformData **rssky_transf, const size_t spindowns, const gsl_matrix *ussky_metric, const DopplerCoordinateSystem *ucoords, const gsl_matrix *orbital_metric, const DopplerCoordinateSystem *ocoords, const LIGOTimeGPS *ref_time, const LIGOTimeGPS *start_time, const LIGOTimeGPS *end_time)
Compute the reduced supersky metric.
#define CHECK_RSSKY_TRANSF(RT)
static int SM_ComputeFittedSuperskyMetric(gsl_matrix *fitted_ssky_metric, SuperskyTransformData *rssky_transf, const gsl_matrix *ussky_metric, const gsl_matrix *orbital_metric, const DopplerCoordinateSystem *ocoords, const LIGOTimeGPS *start_time, const LIGOTimeGPS *end_time)
Find a least-squares linear fit to the orbital X and Y metric elements using the frequency and spindo...
#define CHECK_RSSKY_METRIC_TRANSF(M, RT)
static double ConstantBoundB(const void *data, const size_t dim UNUSED, const gsl_matrix *cache UNUSED, const gsl_vector *point)
static double EqualAreaSkyBoundSolverA(double A1, void *params)
static void SM_ReducedToAligned(double as[3], const gsl_vector *rss, const double hemi)
Convert from 2-dimensional reduced supersky coordinates to 3-dimensional aligned sky coordinates.
#define RE_SQRT(x)
#define RSSKY_FKDOT_OFFSET(RT, S)
static double EqualAreaSkyBoundSolverB(double B1, void *params)
#define SQR(x)
static int SM_ScaleSuperskyMetricFiducialFreq(gsl_matrix *rssky_metric, SuperskyTransformData *rssky_transf, const double new_fiducial_freq)
Scale a given supersky metric and its coordinate transform data to a new fiducial frequency.
static int SM_ComputeAlignedSuperskyMetric(gsl_matrix *aligned_ssky_metric, SuperskyTransformData *rssky_transf, const gsl_matrix *decoupled_ssky_metric)
Align the sky–sky block of the decoupled supersky metric with its eigenvalues by means of a rotation.
static int fits_table_init_SuperskyTransformData(FITSFile *file)
Initialise a FITS table for writing/reading a table of SuperskyTransformData entries.
static int SM_LatticeSuperskyRangeCallback(const bool first_call, const LatticeTiling *tiling, const LatticeTilingIterator *itr, const gsl_vector *point, const size_t changed_i, const void *param, void *out)
static int SM_ComputeDecoupledSuperskyMetric(gsl_matrix *decoupled_ssky_metric, SuperskyTransformData *rssky_transf, const gsl_matrix *fitted_ssky_metric)
Decouple the sky–sky and freq+spin–freq+spin blocks of the fitted supersky metric.
static void SM_AlignedToReduced(gsl_vector *rss, const double as[3])
Convert from 3-dimensional aligned sky coordinates to 2-dimensional reduced supersky coordinates.
static gsl_matrix * SM_ComputePhaseMetric(const DopplerCoordinateSystem *coords, const LIGOTimeGPS *ref_time, const LIGOTimeGPS *start_time, const LIGOTimeGPS *end_time, const MultiLALDetector *detectors, const MultiNoiseFloor *detector_weights, const DetectorMotionType detector_motion, const EphemerisData *ephemerides)
Call XLALComputeDopplerPhaseMetric() to compute the phase metric for a given coordinate system.
#define DOT2(x, y)
const double scale
multiplicative scaling factor of the coordinate
int s
double e
int XLALExtrapolatePulsarSpins(PulsarSpins fkdot1, const PulsarSpins fkdot0, REAL8 dtau)
Extrapolate the Pulsar spin-parameters (fkdot0) from the initial reference-epoch to the new referen...
#define XLAL_FITS_TABLE_COLUMN_BEGIN(record_type)
Definition: FITSFileIO.h:239
#define FFIO_MAX
Maximum possible number of FITS array dimensions, and FITS table columns.
Definition: FITSFileIO.h:49
#define XLAL_FITS_TABLE_COLUMN_ADD(file, type, field)
Definition: FITSFileIO.h:243
struct tagFITSFile FITSFile
Representation of a FITS file.
Definition: FITSFileIO.h:54
#define XLAL_FITS_TABLE_COLUMN_ADD_ARRAY2(file, type, field)
Definition: FITSFileIO.h:255
#define LAL_PI_2
#define LAL_COSIEARTH
#define LAL_PI
#define LAL_TWOPI
#define LAL_SINIEARTH
#define XLAL_NUM_ELEM(x)
#define XLAL_INIT_MEM(x)
uint64_t UINT8
double REAL8
#define XLAL_INIT_DECL(var,...)
uint32_t UINT4
int32_t INT4
void * XLALCalloc(size_t m, size_t n)
void XLALFree(void *p)
int XLALIsTiledLatticeTilingDimension(const LatticeTiling *tiling, const size_t dim)
Return >0 if a lattice tiling dimension is tiled (i.e.
int XLALSetLatticeTilingBound(LatticeTiling *tiling, const size_t dim, const LatticeTilingBound func, const size_t data_len, const void *data_lower, const void *data_upper)
Set a parameter-space bound on a dimension of the lattice tiling.
size_t XLALTotalLatticeTilingDimensions(const LatticeTiling *tiling)
Return the total number of dimensions of the lattice tiling.
int XLALSetLatticeTilingConstantBound(LatticeTiling *tiling, const size_t dim, const double bound1, const double bound2)
Set a constant lattice tiling parameter-space bound, given by the minimum and maximum of the two supp...
REAL8 XLALLatticeTilingStepSize(const LatticeTiling *tiling, const size_t dim)
Return the step size of the lattice tiling in a given dimension, or 0 for non-tiled dimensions.
int XLALSetLatticeTilingBoundCacheFunction(LatticeTiling *tiling, const size_t dim, const LatticeTilingBoundCache func)
Set bound cache function for a lattice tiling parameter-space dimension.
int XLALCurrentLatticeTilingBlock(const LatticeTilingIterator *itr, const size_t dim, INT4 *left, INT4 *right)
Return indexes of the left-most and right-most points in the current block of points in the given dim...
const void * XLALRegisterLatticeTilingCallback(LatticeTiling *tiling, const LatticeTilingCallback func, const size_t param_len, const void *param, const size_t out_len)
Register a callback function which can be used to compute properties of a lattice tiling.
int XLALSetLatticeTilingOrigin(LatticeTiling *tiling, const size_t dim, const double origin)
Set the physical parameter-space origin of the lattice tiling in the given dimension.
int XLALSetLatticeTilingPadding(LatticeTiling *tiling, const size_t dim, const double lower_bbox_pad, const double upper_bbox_pad, const int lower_intp_pad, const int upper_intp_pad, const int find_bound_extrema)
Control the padding of lattice tiling parameter-space bounds in the given dimension.
void LogPrintf(LogLevel_t, const char *format,...) _LAL_GCC_PRINTF_FORMAT_(2
LOG_DEBUG
int XLALChangeMetricReferenceTime(gsl_matrix **p_gpr_ij, gsl_matrix **p_transform, const gsl_matrix *g_ij, const DopplerCoordinateSystem *coordSys, const double Dtau)
Compute the transform which changes the metric reference time .
Definition: MetricUtils.c:578
int XLALDiagNormalizeMetric(gsl_matrix **p_gpr_ij, gsl_matrix **p_transform, const gsl_matrix *g_ij)
Diagonally-normalize a metric .
Definition: MetricUtils.c:391
double XLALMetricDeterminant(const gsl_matrix *g_ij)
Compute the determinant of a metric .
Definition: MetricUtils.c:98
int XLALTransformMetric(gsl_matrix **p_gpr_ij, const gsl_matrix *transform, const gsl_matrix *g_ij)
Apply a transform to a metric such that , or equivalently .
Definition: MetricUtils.c:286
static const INT4 r
int XLALSegListInit(LALSegList *seglist)
int XLALSegListClear(LALSegList *seglist)
int XLALSegListIsInitialized(const LALSegList *seglist)
int XLALSegListAppend(LALSegList *seglist, const LALSeg *seg)
int XLALSegSet(LALSeg *seg, const LIGOTimeGPS *start, const LIGOTimeGPS *end, const INT4 id)
void XLALNormalizeSkyPosition(double *restrict longitude, double *restrict latitude)
int XLALConvertSuperskyToPhysicalPoints(gsl_matrix **out_phys, const gsl_matrix *in_rssky, const SuperskyTransformData *rssky_transf)
Convert a set of points from supersky to physical coordinates.
int XLALSetSuperskyPhysicalSkyBounds(LatticeTiling *tiling, gsl_matrix *rssky_metric, SuperskyTransformData *rssky_transf, const double alpha1, const double alpha2, const double delta1, const double delta2)
Set parameter-space bounds on the physical sky position for a lattice tiling using the reduced super...
void XLALDestroySuperskyMetrics(SuperskyMetrics *metrics)
Destroy a SuperskyMetrics struct.
int XLALSetSuperskyEqualAreaSkyBounds(LatticeTiling *tiling, const gsl_matrix *rssky_metric, const double max_mismatch, const UINT4 patch_count, const UINT4 patch_index)
Divide the reduced supersky parameter space into patch_count equal-area patches, and set parameter-sp...
int XLALConvertPhysicalToSuperskyPoints(gsl_matrix **out_rssky, const gsl_matrix *in_phys, const SuperskyTransformData *rssky_transf)
Convert a set of points from physical to supersky coordinates.
int XLALSuperskyMetricsDimensions(const SuperskyMetrics *metrics, size_t *spindowns)
Return dimensions of the supersky metrics.
int XLALEqualizeReducedSuperskyMetricsFreqSpacing(SuperskyMetrics *metrics, const double coh_max_mismatch, const double semi_max_mismatch)
Project and rescale the reduced supersky metrics in the frequency dimension, such that all reduced su...
SuperskyMetrics * XLALCopySuperskyMetrics(const SuperskyMetrics *metrics)
Copy a SuperskyMetrics struct.
SuperskyMetricType
Type of supersky metric to compute.
int XLALScaleSuperskyMetricsFiducialFreq(SuperskyMetrics *metrics, const double new_fiducial_freq)
Scale all supersky metrics and their coordinate transform data to a new fiducial frequency.
int XLALSetPhysicalPointSuperskyRefTime(PulsarDopplerParams *out_phys, const SuperskyTransformData *rssky_transf)
Set the reference time of a physical point to that of the reduced supersky coordinates.
int XLALConvertPhysicalToSuperskyPoint(gsl_vector *out_rssky, const PulsarDopplerParams *in_phys, const SuperskyTransformData *rssky_transf)
Convert a point from physical to supersky coordinates.
int XLALConvertSuperskyToPhysicalPoint(PulsarDopplerParams *out_phys, const gsl_vector *in_rssky, const gsl_vector *ref_rssky, const SuperskyTransformData *rssky_transf)
Convert a point from supersky to physical coordinates.
int XLALFITSWriteSuperskyMetrics(FITSFile *file, const SuperskyMetrics *metrics)
Write a SuperskyMetrics struct to a FITS file.
int XLALSetSuperskyRangeBounds(LatticeTiling *tiling, const gsl_vector *min_rssky, const gsl_vector *max_rssky)
Set parameter-space bounds on an entire lattice tiling given minimum and maximum ranges in reduced su...
int XLALRegisterSuperskyLatticePhysicalRangeCallback(LatticeTiling *tiling, const SuperskyTransformData *rssky_transf, const PulsarDopplerParams **min_phys, const PulsarDopplerParams **max_phys)
Register a lattice tiling callback function which computes the physical range covered by a reduced su...
int XLALConvertSuperskyToSuperskyPoint(gsl_vector *out_rssky, const SuperskyTransformData *out_rssky_transf, const gsl_vector *in_rssky, const gsl_vector *ref_rssky, const SuperskyTransformData *in_rssky_transf)
Convert a point between supersky coordinates.
int XLALSetSuperskyPhysicalSpinBoundPadding(LatticeTiling *tiling, const SuperskyTransformData *rssky_transf, const size_t s, const bool padding)
Set parameter-space bound padding on the physical frequency/spindowns for a lattice tiling using the...
int XLALSetSuperskyPhysicalSpinBound(LatticeTiling *tiling, const SuperskyTransformData *rssky_transf, const size_t s, const double bound1, const double bound2)
Set parameter-space bounds on the physical frequency/spindowns for a lattice tiling using the reduce...
SuperskyMetrics * XLALComputeSuperskyMetrics(const SuperskyMetricType type, const size_t spindowns, const LIGOTimeGPS *ref_time, const LALSegList *segments, const double fiducial_freq, const MultiLALDetector *detectors, const MultiNoiseFloor *detector_weights, const DetectorMotionType detector_motion, const EphemerisData *ephemerides)
Compute the supersky metrics, which are returned in a SuperskyMetrics struct.
int XLALRegisterSuperskyLatticeSuperskyRangeCallback(LatticeTiling *tiling, const SuperskyTransformData *rssky_transf, const SuperskyTransformData *rssky2_transf, const gsl_vector **min_rssky2, const gsl_vector **max_rssky2)
Register a lattice tiling callback function which computes the range covered by a reduced supersky la...
int XLALFITSReadSuperskyMetrics(FITSFile *file, SuperskyMetrics **metrics)
Read a SuperskyMetrics struct from a FITS file.
@ MAX_METRIC_TYPE
DetectorMotionType
Bitfield of different types of detector-motion to use in order to compute the Doppler-metric.
int XLALFindDopplerCoordinateInSystem(const DopplerCoordinateSystem *coordSys, const DopplerCoordinateID coordID)
Given a coordinate ID 'coordID', return its dimension within the given coordinate system 'coordSys',...
void XLALDestroyDopplerPhaseMetric(DopplerPhaseMetric *metric)
Free a DopplerPhaseMetric structure.
DopplerPhaseMetric * XLALComputeDopplerPhaseMetric(const DopplerMetricParams *metricParams, const EphemerisData *edat)
Calculate an approximate "phase-metric" with the specified parameters.
@ DOPPLERCOORD_N3X_EQU
X component of unconstrained super-sky position in equatorial coordinates [Units: none].
@ DOPPLERCOORD_N3OY_ECL
Y orbit-component of unconstrained super-sky position in ecliptic coordinates [Units: none].
@ DOPPLERCOORD_N3Z_EQU
Z component of unconstrained super-sky position in equatorial coordinates [Units: none].
@ DOPPLERCOORD_F2DOT
Second spindown [Units: Hz/s^2].
@ DOPPLERCOORD_N3Y_EQU
Y component of unconstrained super-sky position in equatorial coordinates [Units: none].
@ DOPPLERCOORD_F1DOT
First spindown [Units: Hz/s].
@ DOPPLERCOORD_F4DOT
Fourth spindown [Units: Hz/s^4].
@ DOPPLERCOORD_F3DOT
Third spindown [Units: Hz/s^3].
@ DOPPLERCOORD_N3OX_ECL
X orbit-component of unconstrained super-sky position in ecliptic coordinates [Units: none].
@ DOPPLERCOORD_FREQ
Frequency [Units: Hz].
#define XLAL_CHECK(assertion,...)
#define XLAL_CHECK_NULL(assertion,...)
XLAL_ENOMEM
XLAL_SUCCESS
XLAL_EFAULT
XLAL_ERANGE
XLAL_EFUNC
XLAL_EMAXITER
XLAL_ESIZE
XLAL_EIO
XLAL_EINVAL
XLAL_EFAILED
XLAL_FAILURE
LIGOTimeGPS * XLALGPSAdd(LIGOTimeGPS *epoch, REAL8 dt)
REAL8 XLALGPSDiff(const LIGOTimeGPS *t1, const LIGOTimeGPS *t0)
list y
start_time
n
out
LALDetector detectors[LAL_NUM_DETECTORS]
double alpha
type describing a Doppler coordinate system: lists the number of dimensions and the symbolic names of...
meta-info specifying a Doppler-metric
Struct to hold the output of XLALComputeDopplerPhaseMetric(), including meta-info on the number of di...
gsl_matrix * g_ij
symmetric matrix holding the phase-metric, averaged over segments
This structure contains all information about the center-of-mass positions of the Earth and Sun,...
LIGOTimeGPS end
LIGOTimeGPS start
UINT4 length
LALSeg * segs
array of detectors definitions 'LALDetector'
array of detector-specific 'noise floors' (ie PSD values), assumed constant over the frequency-band o...
PhysicalSkyBoundPiece pieces[6]
Type containing the 'Doppler-parameters' affecting the time-evolution of the phase.
PulsarSpins fkdot
Intrinsic spins: [Freq, f1dot, f2dot, ...
REAL8 Delta
Sky position: DEC (latitude) in equatorial coords and radians.
LIGOTimeGPS refTime
Reference time of pulsar parameters (in SSB!)
REAL8 Alpha
Sky position: RA (longitude) in equatorial coords and radians.
PulsarDopplerParams min_phys
Minimum physical range.
gsl_vector_view max_rssky2_view
double min_rssky2_array[32]
Minimum range of other reduced supersky coordinates.
double max_rssky2_array[32]
Maximum range of other reduced supersky coordinates.
gsl_vector_view min_rssky2_view
PulsarDopplerParams max_phys
Maximum physical range.
const SuperskyTransformData * rssky_transf
Reduced supersky coordinate transform data.
const SuperskyTransformData * rssky2_transf
Other reduced supersky coordinate transform data.
Computed supersky metrics, returned by XLALComputeSuperskyMetrics().
gsl_matrix * semi_rssky_metric
Semicoherent reduced supersky metric (2-dimensional sky)
gsl_matrix ** coh_rssky_metric
Coherent reduced supersky metric (2-dimensional sky) for each segment.
size_t num_segments
Number of segments.
SuperskyTransformData * semi_rssky_transf
Semicoherent reduced supersky metric coordinate transform data.
SuperskyTransformData ** coh_rssky_transf
Coherent reduced supersky metric coordinate transform data for each segment.
Supersky metric coordinate transform data.
UINT4 nspins
Number of spindown dimensions.
UINT4 ndim
Dimensions of the corresponding metric.
UINT4 nsky_offsets
Number of sky offsets.
REAL8 fiducial_freq
Fiducial frequency of metric.
REAL8 sky_offsets[MAX_SKY_OFFSETS][3]
Sky offsets of the supersky metric.
REAL8 align_sky[3][3]
Alignment transform of the supersky metric.
LIGOTimeGPS ref_time
Reference time of the metric.