| 1 | /*
|
|---|
| 2 | * Copyright (c) 2010-2015 Centre National de la Recherche Scientifique.
|
|---|
| 3 | * written by Nathanael Schaeffer (CNRS, ISTerre, Grenoble, France).
|
|---|
| 4 | *
|
|---|
| 5 | * nathanael.schaeffer@ujf-grenoble.fr
|
|---|
| 6 | *
|
|---|
| 7 | * This software is governed by the CeCILL license under French law and
|
|---|
| 8 | * abiding by the rules of distribution of free software. You can use,
|
|---|
| 9 | * modify and/or redistribute the software under the terms of the CeCILL
|
|---|
| 10 | * license as circulated by CEA, CNRS and INRIA at the following URL
|
|---|
| 11 | * "http://www.cecill.info".
|
|---|
| 12 | *
|
|---|
| 13 | * The fact that you are presently reading this means that you have had
|
|---|
| 14 | * knowledge of the CeCILL license and that you accept its terms.
|
|---|
| 15 | *
|
|---|
| 16 | */
|
|---|
| 17 |
|
|---|
| 18 | /** \internal \file sht_legendre.c
|
|---|
| 19 | \brief Compute legendre polynomials and associated functions.
|
|---|
| 20 |
|
|---|
| 21 | - Normalization of the associated functions for various spherical harmonics conventions are possible, with or without Condon-Shortley phase.
|
|---|
| 22 | - When computing the derivatives (with respect to colatitude theta), there are no singularities.
|
|---|
| 23 | - written by Nathanael Schaeffer / CNRS, with some ideas and code from the GSL 1.13 and Numerical Recipies.
|
|---|
| 24 |
|
|---|
| 25 | The associated legendre functions are computed using the following recurrence formula :
|
|---|
| 26 | \f[ Y_l^m(x) = a_l^m \, x \ Y_{l-1}^m(x) + b_l^m \ Y_{l-2}^m(x) \f]
|
|---|
| 27 | with starting values :
|
|---|
| 28 | \f[ Y_m^m(x) = a_m^m \ (1-x^2)^{m/2} \f]
|
|---|
| 29 | and
|
|---|
| 30 | \f[ Y_{m+1}^m(x) = a_{m+1}^m \ Y_m^m(x) \f]
|
|---|
| 31 | where \f$ a_l^m \f$ and \f$ b_l^m \f$ are coefficients that depend on the normalization, and which are
|
|---|
| 32 | precomputed once for all by \ref legendre_precomp.
|
|---|
| 33 | */
|
|---|
| 34 |
|
|---|
| 35 | // index in alm array for given im.
|
|---|
| 36 | #define alm_im(shtns, im) (shtns->alm + (im)*(2*shtns->lmax - ((im)-1)*shtns->mres))
|
|---|
| 37 |
|
|---|
| 38 | #if SHT_VERBOSE > 1
|
|---|
| 39 | #define LEG_RANGE_CHECK
|
|---|
| 40 | #endif
|
|---|
| 41 |
|
|---|
| 42 | #ifndef HAVE_LONG_DOUBLE_WIDER
|
|---|
| 43 | #define long_double_caps 0
|
|---|
| 44 | typedef double real;
|
|---|
| 45 | #define SQRT sqrt
|
|---|
| 46 | #define COS cos
|
|---|
| 47 | #define SIN sin
|
|---|
| 48 | #define FABS fabs
|
|---|
| 49 | #define legendre_sphPlm_hp legendre_sphPlm
|
|---|
| 50 | #define legendre_sphPlm_array_hp legendre_sphPlm_array
|
|---|
| 51 | #define legendre_sphPlm_deriv_array_hp legendre_sphPlm_deriv_array
|
|---|
| 52 | #else
|
|---|
| 53 | int long_double_caps = 0;
|
|---|
| 54 | typedef long double real;
|
|---|
| 55 | #define SQRT sqrtl
|
|---|
| 56 | #define COS cosl
|
|---|
| 57 | #define SIN sinl
|
|---|
| 58 | #define FABS fabsl
|
|---|
| 59 |
|
|---|
| 60 | // scale factor applied for LMAX larger than SHT_L_RESCALE. Allows accurate transforms up to l=2700 with 64 bit double precision.
|
|---|
| 61 | #define SHT_LEG_SCALEF 1.1018032079253110206e-280
|
|---|
| 62 | #define SHT_L_RESCALE 1536
|
|---|
| 63 |
|
|---|
| 64 | // returns 1 for large exponent only => useless.
|
|---|
| 65 | // returns 2 for extended precision only => ok to improve gauss points.
|
|---|
| 66 | // returns 3 for extended precision and large exponent => ok to improve legendre recurrence.
|
|---|
| 67 | static int test_long_double()
|
|---|
| 68 | {
|
|---|
| 69 | volatile real tt; // volatile avoids optimizations.
|
|---|
| 70 | int p = 0;
|
|---|
| 71 |
|
|---|
| 72 | tt = 1.0e-1000L; tt *= tt;
|
|---|
| 73 | if (tt > 0) p |= 1; // bit 0 set for large exponent
|
|---|
| 74 | tt = 1.0; tt += 1.e-18;
|
|---|
| 75 | if (tt > 1.0) p |= 2; // bit 1 set for extended precision
|
|---|
| 76 | long_double_caps = p;
|
|---|
| 77 | return p;
|
|---|
| 78 | }
|
|---|
| 79 |
|
|---|
| 80 | /// \internal high precision version of \ref a_sint_pow_n
|
|---|
| 81 | static real a_sint_pow_n_hp(real val, real cost, long int n)
|
|---|
| 82 | {
|
|---|
| 83 | real s2 = (1.-cost)*(1.+cost); // sin(t)^2 = 1 - cos(t)^2
|
|---|
| 84 | long int k = n >> 7;
|
|---|
| 85 | if (sizeof(s2) > 8) k = 0; // enough accuracy, we do not bother.
|
|---|
| 86 |
|
|---|
| 87 | #ifdef LEG_RANGE_CHECK
|
|---|
| 88 | if (s2 < 0) shtns_runerr("sin(t)^2 < 0 !!!");
|
|---|
| 89 | #endif
|
|---|
| 90 |
|
|---|
| 91 | if (n&1) val *= SQRT(s2); // = sin(t)
|
|---|
| 92 | do {
|
|---|
| 93 | if (n&2) val *= s2;
|
|---|
| 94 | n >>= 1;
|
|---|
| 95 | s2 *= s2;
|
|---|
| 96 | } while(n > k);
|
|---|
| 97 | n >>= 1;
|
|---|
| 98 | while(n > 0) { // take care of very large power n
|
|---|
| 99 | n--;
|
|---|
| 100 | val *= s2;
|
|---|
| 101 | }
|
|---|
| 102 | return val; // = sint(t)^n
|
|---|
| 103 | }
|
|---|
| 104 | #endif
|
|---|
| 105 |
|
|---|
| 106 |
|
|---|
| 107 | /// \internal computes val.sin(t)^n from cos(t). ie returns val.(1-x^2)^(n/2), with x = cos(t)
|
|---|
| 108 | /// assumes: -1 <= cost <= 1, n>=0, and nval<=0 is the extended exponent associated to val.
|
|---|
| 109 | /// updates nval, and returns val such as the result is val.SHT_SCALE_FACTOR^(nval)
|
|---|
| 110 | static double a_sint_pow_n_ext(double val, double cost, int n, int *nval)
|
|---|
| 111 | {
|
|---|
| 112 | double s2 = (1.-cost)*(1.+cost); // sin(t)^2 = 1 - cos(t)^2 >= 0
|
|---|
| 113 | double val0 = val; // store sign
|
|---|
| 114 | int ns2 = 0;
|
|---|
| 115 | int nv = *nval;
|
|---|
| 116 |
|
|---|
| 117 | #ifdef LEG_RANGE_CHECK
|
|---|
| 118 | if (s2 < 0) shtns_runerr("sin(t)^2 < 0 !!!");
|
|---|
| 119 | #endif
|
|---|
| 120 |
|
|---|
| 121 | val = fabs(val); // val >= 0
|
|---|
| 122 | if (n&1) val *= sqrt(s2); // = sin(t)
|
|---|
| 123 | while (n >>= 1) {
|
|---|
| 124 | if (n&1) {
|
|---|
| 125 | if (val < 1.0/SHT_SCALE_FACTOR) { // val >= 0
|
|---|
| 126 | nv--; val *= SHT_SCALE_FACTOR;
|
|---|
| 127 | }
|
|---|
| 128 | val *= s2; nv += ns2; // 1/S^2 < val < 1
|
|---|
| 129 | }
|
|---|
| 130 | s2 *= s2; ns2 += ns2;
|
|---|
| 131 | if (s2 < 1.0/SHT_SCALE_FACTOR) { // s2 >= 0
|
|---|
| 132 | ns2--; s2 *= SHT_SCALE_FACTOR; // 1/S < s2 < 1
|
|---|
| 133 | }
|
|---|
| 134 | }
|
|---|
| 135 | while ((nv < 0) && (val > 1.0/SHT_SCALE_FACTOR)) { // try to minimize |nv|
|
|---|
| 136 | ++nv; val *= 1.0/SHT_SCALE_FACTOR;
|
|---|
| 137 | }
|
|---|
| 138 | if (val0 < 0) val *= -1.0; // restore sign.
|
|---|
| 139 | *nval = nv;
|
|---|
| 140 | return val; // 1/S^2 < val < 1
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 |
|
|---|
| 144 | /// \internal Returns the value of a legendre polynomial of degree l and order im*MRES, noramalized for spherical harmonics, using recurrence.
|
|---|
| 145 | /// Requires a previous call to \ref legendre_precomp().
|
|---|
| 146 | /// Output compatible with the GSL function gsl_sf_legendre_sphPlm(l, m, x)
|
|---|
| 147 | static double legendre_sphPlm(shtns_cfg shtns, const int l, const int im, const double x)
|
|---|
| 148 | {
|
|---|
| 149 | double *al;
|
|---|
| 150 | int m, ny;
|
|---|
| 151 | double ymm, ymmp1;
|
|---|
| 152 |
|
|---|
| 153 | m = im*MRES;
|
|---|
| 154 | #ifdef LEG_RANGE_CHECK
|
|---|
| 155 | if ( (l>LMAX) || (l<m) || (im>MMAX) ) shtns_runerr("argument out of range in legendre_sphPlm");
|
|---|
| 156 | #endif
|
|---|
| 157 |
|
|---|
| 158 | ny = 0;
|
|---|
| 159 | al = alm_im(shtns, im);
|
|---|
| 160 | ymm = al[0];
|
|---|
| 161 | if (m>0) ymm = a_sint_pow_n_ext(ymm, x, m, &ny); // ny <= 0
|
|---|
| 162 |
|
|---|
| 163 | ymmp1 = ymm; // l=m
|
|---|
| 164 | if (l == m) goto done;
|
|---|
| 165 |
|
|---|
| 166 | ymmp1 = al[1] * (x*ymmp1); // l=m+1
|
|---|
| 167 | if (l == m+1) goto done;
|
|---|
| 168 |
|
|---|
| 169 | m+=2; al+=2;
|
|---|
| 170 | while ((ny < 0) && (m < l)) { // take care of scaled values
|
|---|
| 171 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 172 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 173 | m+=2; al+=4;
|
|---|
| 174 | if (fabs(ymm) > 1.0/SHT_SCALE_FACTOR) { // rescale when value is significant
|
|---|
| 175 | ++ny; ymm *= 1.0/SHT_SCALE_FACTOR; ymmp1 *= 1.0/SHT_SCALE_FACTOR;
|
|---|
| 176 | }
|
|---|
| 177 | }
|
|---|
| 178 | while (m < l) { // values are unscaled.
|
|---|
| 179 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 180 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 181 | m+=2; al+=4;
|
|---|
| 182 | }
|
|---|
| 183 | if (m == l) {
|
|---|
| 184 | ymmp1 = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 185 | }
|
|---|
| 186 | done:
|
|---|
| 187 | if (ny < 0) {
|
|---|
| 188 | if (ny+3 < 0) return 0.0;
|
|---|
| 189 | do { ymmp1 *= 1.0/SHT_SCALE_FACTOR; } while (++ny < 0); // return unscaled values.
|
|---|
| 190 | }
|
|---|
| 191 | return ymmp1;
|
|---|
| 192 | }
|
|---|
| 193 |
|
|---|
| 194 | #if HAVE_LONG_DOUBLE_WIDER
|
|---|
| 195 | static double legendre_sphPlm_hp(shtns_cfg shtns, const int l, const int im, double x)
|
|---|
| 196 | {
|
|---|
| 197 | double *al;
|
|---|
| 198 | int i,m;
|
|---|
| 199 | real ymm, ymmp1;
|
|---|
| 200 |
|
|---|
| 201 | if (long_double_caps < 3) legendre_sphPlm(shtns, l, im, x); // not worth it.
|
|---|
| 202 |
|
|---|
| 203 | m = im*MRES;
|
|---|
| 204 | #ifdef LEG_RANGE_CHECK
|
|---|
| 205 | if ( (l>LMAX) || (l<m) || (im>MMAX) ) shtns_runerr("argument out of range in legendre_sphPlm");
|
|---|
| 206 | #endif
|
|---|
| 207 |
|
|---|
| 208 | al = alm_im(shtns, im);
|
|---|
| 209 | ymm = al[0]; // l=m
|
|---|
| 210 | if (m>0) ymm *= SHT_LEG_SCALEF;
|
|---|
| 211 | ymmp1 = ymm;
|
|---|
| 212 | if (l==m) goto done;
|
|---|
| 213 |
|
|---|
| 214 | ymmp1 = al[1] * (x*ymmp1); // l=m+1
|
|---|
| 215 | al+=2;
|
|---|
| 216 | if (l == m+1) goto done;
|
|---|
| 217 |
|
|---|
| 218 | for (i=m+2; i<l; i+=2) {
|
|---|
| 219 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 220 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 221 | al+=4;
|
|---|
| 222 | }
|
|---|
| 223 | if (i==l) {
|
|---|
| 224 | ymmp1 = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 225 | }
|
|---|
| 226 | done:
|
|---|
| 227 | if (m>0) ymmp1 *= a_sint_pow_n_hp(1.0/SHT_LEG_SCALEF, x, m);
|
|---|
| 228 | return ((double) ymmp1);
|
|---|
| 229 | }
|
|---|
| 230 | #endif
|
|---|
| 231 |
|
|---|
| 232 |
|
|---|
| 233 | /// \internal Compute values of legendre polynomials noramalized for spherical harmonics,
|
|---|
| 234 | /// for a range of l=m..lmax, at given m and x, using recurrence.
|
|---|
| 235 | /// Requires a previous call to \ref legendre_precomp().
|
|---|
| 236 | /// Output compatible with the GSL function gsl_sf_legendre_sphPlm_array(lmax, m, x, yl)
|
|---|
| 237 | /// \param lmax maximum degree computed, \param im = m/MRES with m the SH order, \param x argument, x=cos(theta).
|
|---|
| 238 | /// \param[out] yl is a double array of size (lmax-m+1) filled with the values.
|
|---|
| 239 | static void legendre_sphPlm_array(shtns_cfg shtns, const int lmax, const int im, const double x, double *yl)
|
|---|
| 240 | {
|
|---|
| 241 | double *al;
|
|---|
| 242 | int l, m, ny;
|
|---|
| 243 | double ymm, ymmp1;
|
|---|
| 244 |
|
|---|
| 245 | m = im*MRES;
|
|---|
| 246 | #ifdef LEG_RANGE_CHECK
|
|---|
| 247 | if ( (lmax>LMAX) || (lmax<m) || (im>MMAX) ) shtns_runerr("argument out of range in legendre_sphPlm");
|
|---|
| 248 | #endif
|
|---|
| 249 |
|
|---|
| 250 | al = alm_im(shtns, im);
|
|---|
| 251 | yl -= m; // shift pointer
|
|---|
| 252 | for (l=m; l<=lmax; ++l) yl[l] = 0.0; // zero out array.
|
|---|
| 253 |
|
|---|
| 254 | ny = 0;
|
|---|
| 255 | ymm = al[0];
|
|---|
| 256 | if (m>0) ymm = a_sint_pow_n_ext(ymm, x, m, &ny); // l=m, ny <= 0
|
|---|
| 257 | if (ny==0) yl[m] = ymm;
|
|---|
| 258 | if (lmax==m) return;
|
|---|
| 259 |
|
|---|
| 260 | ymmp1 = ymm * al[1] * x; // l=m+1
|
|---|
| 261 | if (ny==0) yl[m+1] = ymmp1;
|
|---|
| 262 | if (lmax==m+1) return;
|
|---|
| 263 |
|
|---|
| 264 | l=m+2; al+=2;
|
|---|
| 265 | while ((ny < 0) && (l < lmax)) { // values are negligible => discard.
|
|---|
| 266 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 267 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 268 | l+=2; al+=4;
|
|---|
| 269 | if (fabs(ymm) > 1.0/SHT_SCALE_FACTOR) { // rescale when value is significant
|
|---|
| 270 | ++ny; ymm *= 1.0/SHT_SCALE_FACTOR; ymmp1 *= 1.0/SHT_SCALE_FACTOR;
|
|---|
| 271 | }
|
|---|
| 272 | }
|
|---|
| 273 | while (l < lmax) { // values are unscaled => store
|
|---|
| 274 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 275 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 276 | yl[l] = ymm; yl[l+1] = ymmp1;
|
|---|
| 277 | l+=2; al+=4;
|
|---|
| 278 | }
|
|---|
| 279 | if ((l == lmax) && (ny == 0)) {
|
|---|
| 280 | yl[l] = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 281 | }
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | #if HAVE_LONG_DOUBLE_WIDER
|
|---|
| 285 | /// \internal high precision version of \ref legendre_sphPlm_array
|
|---|
| 286 | static void legendre_sphPlm_array_hp(shtns_cfg shtns, const int lmax, const int im, const double cost, double *yl)
|
|---|
| 287 | {
|
|---|
| 288 | double *al;
|
|---|
| 289 | long int l,m;
|
|---|
| 290 | int rescale = 0; // flag for rescale.
|
|---|
| 291 | real ymm, ymmp1, x;
|
|---|
| 292 |
|
|---|
| 293 | if (long_double_caps < 3) { // not worth it.
|
|---|
| 294 | legendre_sphPlm_array(shtns, lmax, im, cost, yl); return;
|
|---|
| 295 | }
|
|---|
| 296 |
|
|---|
| 297 | m = im*MRES;
|
|---|
| 298 | #ifdef LEG_RANGE_CHECK
|
|---|
| 299 | if ((lmax > LMAX)||(lmax < m)||(im>MMAX)) shtns_runerr("argument out of range in legendre_sphPlm_array");
|
|---|
| 300 | #endif
|
|---|
| 301 |
|
|---|
| 302 | x = cost;
|
|---|
| 303 | al = alm_im(shtns, im);
|
|---|
| 304 | yl -= m; // shift pointer
|
|---|
| 305 | ymm = al[0]; // l=m
|
|---|
| 306 | if (m>0) {
|
|---|
| 307 | if ((lmax <= SHT_L_RESCALE) || (sizeof(ymm) > 8)) {
|
|---|
| 308 | ymm = a_sint_pow_n_hp(ymm, x, m);
|
|---|
| 309 | } else {
|
|---|
| 310 | rescale = 1;
|
|---|
| 311 | ymm *= SHT_LEG_SCALEF;
|
|---|
| 312 | }
|
|---|
| 313 | }
|
|---|
| 314 | yl[m] = ymm;
|
|---|
| 315 | if (lmax==m) goto done; // done.
|
|---|
| 316 |
|
|---|
| 317 | ymmp1 = ymm * al[1] * x; // l=m+1
|
|---|
| 318 | yl[m+1] = ymmp1;
|
|---|
| 319 | al+=2;
|
|---|
| 320 | if (lmax==m+1) goto done; // done.
|
|---|
| 321 |
|
|---|
| 322 | for (l=m+2; l<lmax; l+=2) {
|
|---|
| 323 | ymm = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 324 | ymmp1 = al[3]*(x*ymm) + al[2]*ymmp1;
|
|---|
| 325 | yl[l] = ymm;
|
|---|
| 326 | yl[l+1] = ymmp1;
|
|---|
| 327 | al+=4;
|
|---|
| 328 | }
|
|---|
| 329 | if (l==lmax) {
|
|---|
| 330 | yl[l] = al[1]*(x*ymmp1) + al[0]*ymm;
|
|---|
| 331 | }
|
|---|
| 332 | done:
|
|---|
| 333 | if (rescale != 0) {
|
|---|
| 334 | ymm = a_sint_pow_n_hp(1.0/SHT_LEG_SCALEF, x, m);
|
|---|
| 335 | for (l=m; l<=lmax; ++l) { // rescale.
|
|---|
| 336 | yl[l] *= ymm;
|
|---|
| 337 | }
|
|---|
| 338 | }
|
|---|
| 339 | return;
|
|---|
| 340 | }
|
|---|
| 341 | #endif
|
|---|
| 342 |
|
|---|
| 343 | /// \internal Compute values of a legendre polynomial normalized for spherical harmonics derivatives, for a range of l=m..lmax, using recurrence.
|
|---|
| 344 | /// Requires a previous call to \ref legendre_precomp(). Output is not directly compatible with GSL :
|
|---|
| 345 | /// - if m=0 : returns ylm(x) and d(ylm)/d(theta) = -sin(theta)*d(ylm)/dx
|
|---|
| 346 | /// - if m>0 : returns ylm(x)/sin(theta) and d(ylm)/d(theta).
|
|---|
| 347 | /// This way, there are no singularities, everything is well defined for x=[-1,1], for any m.
|
|---|
| 348 | /// \param lmax maximum degree computed, \param im = m/MRES with m the SH order, \param x argument, x=cos(theta).
|
|---|
| 349 | /// \param sint = sqrt(1-x^2) to avoid recomputation of sqrt.
|
|---|
| 350 | /// \param[out] yl is a double array of size (lmax-m+1) filled with the values (divided by sin(theta) if m>0)
|
|---|
| 351 | /// \param[out] dyl is a double array of size (lmax-m+1) filled with the theta-derivatives.
|
|---|
| 352 | static void legendre_sphPlm_deriv_array(shtns_cfg shtns, const int lmax, const int im, const double x, const double sint, double *yl, double *dyl)
|
|---|
| 353 | {
|
|---|
| 354 | double *al;
|
|---|
| 355 | int l,m, ny;
|
|---|
| 356 | double st, y0, y1, dy0, dy1;
|
|---|
| 357 |
|
|---|
| 358 | m = im*MRES;
|
|---|
| 359 | #ifdef LEG_RANGE_CHECK
|
|---|
| 360 | if ((lmax > LMAX)||(lmax < m)||(im>MMAX)) shtns_runerr("argument out of range in legendre_sphPlm_deriv_array");
|
|---|
| 361 | #endif
|
|---|
| 362 |
|
|---|
| 363 | al = alm_im(shtns, im);
|
|---|
| 364 | yl -= m; dyl -= m; // shift pointers
|
|---|
| 365 | for (l=m; l<=lmax; ++l) {
|
|---|
| 366 | yl[l] = 0.0; dyl[l] = 0.0; // zero out arrays.
|
|---|
| 367 | }
|
|---|
| 368 |
|
|---|
| 369 | ny = 0;
|
|---|
| 370 | st = sint;
|
|---|
| 371 | y0 = al[0];
|
|---|
| 372 | dy0 = 0.0;
|
|---|
| 373 | if (m>0) {
|
|---|
| 374 | y0 = a_sint_pow_n_ext(y0, x, m-1, &ny);
|
|---|
| 375 | dy0 = x*m*y0;
|
|---|
| 376 | st *= st; // st = sin(theta)^2 is used in the recurrence for m>0
|
|---|
| 377 | }
|
|---|
| 378 | if (ny==0) {
|
|---|
| 379 | yl[m] = y0; dyl[m] = dy0; // l=m
|
|---|
| 380 | }
|
|---|
| 381 | if (lmax==m) return; // done.
|
|---|
| 382 |
|
|---|
| 383 | y1 = al[1] * (x * y0);
|
|---|
| 384 | dy1 = al[1]*( x*dy0 - st*y0 );
|
|---|
| 385 | if (ny == 0) {
|
|---|
| 386 | yl[m+1] = y1; dyl[m+1] = dy1; // l=m+1
|
|---|
| 387 | }
|
|---|
| 388 | if (lmax==m+1) return; // done.
|
|---|
| 389 |
|
|---|
| 390 | l=m+2; al+=2;
|
|---|
| 391 | while ((ny < 0) && (l < lmax)) { // values are negligible => discard.
|
|---|
| 392 | y0 = al[1]*(x*y1) + al[0]*y0;
|
|---|
| 393 | dy0 = al[1]*(x*dy1 - y1*st) + al[0]*dy0;
|
|---|
| 394 | y1 = al[3]*(x*y0) + al[2]*y1;
|
|---|
| 395 | dy1 = al[3]*(x*dy0 - y0*st) + al[2]*dy1;
|
|---|
| 396 | l+=2; al+=4;
|
|---|
| 397 | if (fabs(y0) > 1.0/SHT_SCALE_FACTOR) { // rescale when value is significant
|
|---|
| 398 | ++ny; y0 *= 1.0/SHT_SCALE_FACTOR; dy0 *= 1.0/SHT_SCALE_FACTOR;
|
|---|
| 399 | y1 *= 1.0/SHT_SCALE_FACTOR; dy1 *= 1.0/SHT_SCALE_FACTOR;
|
|---|
| 400 | }
|
|---|
| 401 | }
|
|---|
| 402 | while (l < lmax) { // values are unscaled => store.
|
|---|
| 403 | y0 = al[1]*(x*y1) + al[0]*y0;
|
|---|
| 404 | dy0 = al[1]*(x*dy1 - y1*st) + al[0]*dy0;
|
|---|
| 405 | y1 = al[3]*(x*y0) + al[2]*y1;
|
|---|
| 406 | dy1 = al[3]*(x*dy0 - y0*st) + al[2]*dy1;
|
|---|
| 407 | yl[l] = y0; dyl[l] = dy0;
|
|---|
| 408 | yl[l+1] = y1; dyl[l+1] = dy1;
|
|---|
| 409 | l+=2; al+=4;
|
|---|
| 410 | }
|
|---|
| 411 | if ((l==lmax) && (ny == 0)) {
|
|---|
| 412 | yl[l] = al[1]*(x*y1) + al[0]*y0;
|
|---|
| 413 | dyl[l] = al[1]*(x*dy1 - y1*st) + al[0]*dy0;
|
|---|
| 414 | }
|
|---|
| 415 | }
|
|---|
| 416 |
|
|---|
| 417 | #if HAVE_LONG_DOUBLE_WIDER
|
|---|
| 418 | /// \internal high precision version of \ref legendre_sphPlm_deriv_array
|
|---|
| 419 | static void legendre_sphPlm_deriv_array_hp(shtns_cfg shtns, const int lmax, const int im, const double cost, const double sint, double *yl, double *dyl)
|
|---|
| 420 | {
|
|---|
| 421 | double *al;
|
|---|
| 422 | long int l,m;
|
|---|
| 423 | int rescale = 0; // flag for rescale.
|
|---|
| 424 | real x, st, y0, y1, dy0, dy1;
|
|---|
| 425 |
|
|---|
| 426 | if (long_double_caps < 3) { // not worth it.
|
|---|
| 427 | legendre_sphPlm_deriv_array(shtns, lmax, im, cost, sint, yl, dyl); return;
|
|---|
| 428 | }
|
|---|
| 429 |
|
|---|
| 430 | x = cost;
|
|---|
| 431 | m = im*MRES;
|
|---|
| 432 | #ifdef LEG_RANGE_CHECK
|
|---|
| 433 | if ((lmax > LMAX)||(lmax < m)||(im>MMAX)) shtns_runerr("argument out of range in legendre_sphPlm_deriv_array");
|
|---|
| 434 | #endif
|
|---|
| 435 | al = alm_im(shtns, im);
|
|---|
| 436 | yl -= m; dyl -= m; // shift pointers
|
|---|
| 437 |
|
|---|
| 438 | st = sint;
|
|---|
| 439 | y0 = al[0];
|
|---|
| 440 | dy0 = 0.0;
|
|---|
| 441 | if (m>0) { // m > 0
|
|---|
| 442 | l = m-1;
|
|---|
| 443 | if ((lmax <= SHT_L_RESCALE) || (sizeof(y0) > 8)) {
|
|---|
| 444 | if (l&1) {
|
|---|
| 445 | y0 = a_sint_pow_n_hp(y0, x, l-1) * sint; // avoid computation of sqrt
|
|---|
| 446 | } else y0 = a_sint_pow_n_hp(y0, x, l);
|
|---|
| 447 | } else {
|
|---|
| 448 | rescale = 1;
|
|---|
| 449 | y0 *= SHT_LEG_SCALEF;
|
|---|
| 450 | }
|
|---|
| 451 | dy0 = x*m*y0;
|
|---|
| 452 | st *= st; // st = sin(theta)^2 is used in the recurrence for m>0
|
|---|
| 453 | }
|
|---|
| 454 | yl[m] = y0; // l=m
|
|---|
| 455 | dyl[m] = dy0;
|
|---|
| 456 | if (lmax==m) goto done; // done.
|
|---|
| 457 |
|
|---|
| 458 | y1 = al[1] * (x * y0);
|
|---|
| 459 | dy1 = al[1]*( x*dy0 - st*y0 );
|
|---|
| 460 | yl[m+1] = y1; // l=m+1
|
|---|
| 461 | dyl[m+1] = dy1;
|
|---|
| 462 | if (lmax==m+1) goto done; // done.
|
|---|
| 463 | al+=2;
|
|---|
| 464 |
|
|---|
| 465 | for (l=m+2; l<lmax; l+=2) {
|
|---|
| 466 | y0 = al[1]*(x*y1) + al[0]*y0;
|
|---|
| 467 | dy0 = al[1]*(x*dy1 - y1*st) + al[0]*dy0;
|
|---|
| 468 | yl[l] = y0; dyl[l] = dy0;
|
|---|
| 469 | y1 = al[3]*(x*y0) + al[2]*y1;
|
|---|
| 470 | dy1 = al[3]*(x*dy0 - y0*st) + al[2]*dy1;
|
|---|
| 471 | yl[l+1] = y1; dyl[l+1] = dy1;
|
|---|
| 472 | al+=4;
|
|---|
| 473 | }
|
|---|
| 474 | if (l==lmax) {
|
|---|
| 475 | yl[l] = al[1]*(x*y1) + al[0]*y0;
|
|---|
| 476 | dyl[l] = al[1]*(x*dy1 - y1*st) + al[0]*dy0;
|
|---|
| 477 | }
|
|---|
| 478 | done:
|
|---|
| 479 | if (rescale != 0) {
|
|---|
| 480 | l = m-1; // compute sin(theta)^(m-1)
|
|---|
| 481 | if (l&1) {
|
|---|
| 482 | y0 = a_sint_pow_n_hp(1.0/SHT_LEG_SCALEF, x, l-1) * sint; // avoid computation of sqrt
|
|---|
| 483 | } else y0 = a_sint_pow_n_hp(1.0/SHT_LEG_SCALEF, x, l);
|
|---|
| 484 | for (l=m; l<=lmax; ++l) {
|
|---|
| 485 | yl[l] *= y0; dyl[l] *= y0; // rescale
|
|---|
| 486 | }
|
|---|
| 487 | }
|
|---|
| 488 | return;
|
|---|
| 489 | }
|
|---|
| 490 | #endif
|
|---|
| 491 |
|
|---|
| 492 | /// same as legendre_sphPlm_deriv_array for x=0 and sint=1 (equator)
|
|---|
| 493 | static void legendre_sphPlm_deriv_array_equ(shtns_cfg shtns, const int lmax, const int im, double *yl, double *dyl)
|
|---|
| 494 | {
|
|---|
| 495 | double *al;
|
|---|
| 496 | int l,m;
|
|---|
| 497 | double y0, dy1;
|
|---|
| 498 |
|
|---|
| 499 | m = im*MRES;
|
|---|
| 500 | #ifdef LEG_RANGE_CHECK
|
|---|
| 501 | if ((lmax > LMAX)||(lmax < m)||(im>MMAX)) shtns_runerr("argument out of range in legendre_sphPlm_deriv_array");
|
|---|
| 502 | #endif
|
|---|
| 503 |
|
|---|
| 504 | al = alm_im(shtns, im);
|
|---|
| 505 | yl -= m; dyl -= m; // shift pointers
|
|---|
| 506 |
|
|---|
| 507 | y0 = al[0];
|
|---|
| 508 | yl[m] = y0; dyl[m] = 0.0; // l=m
|
|---|
| 509 | if (lmax==m) return; // done.
|
|---|
| 510 |
|
|---|
| 511 | dy1 = -al[1]*y0;
|
|---|
| 512 | yl[m+1] = 0.0; dyl[m+1] = dy1; // l=m+1
|
|---|
| 513 | if (lmax==m+1) return; // done.
|
|---|
| 514 |
|
|---|
| 515 | l=m+2; al+=2;
|
|---|
| 516 | while (l < lmax) {
|
|---|
| 517 | y0 = al[0]*y0;
|
|---|
| 518 | dy1 = al[2]*dy1 - al[3]*y0;
|
|---|
| 519 | yl[l] = y0; dyl[l] = 0.0;
|
|---|
| 520 | yl[l+1] = 0.0; dyl[l+1] = dy1;
|
|---|
| 521 | l+=2; al+=4;
|
|---|
| 522 | }
|
|---|
| 523 | if (l==lmax) {
|
|---|
| 524 | yl[l] = al[0]*y0;
|
|---|
| 525 | dyl[l] = 0.0;
|
|---|
| 526 | }
|
|---|
| 527 | }
|
|---|
| 528 |
|
|---|
| 529 |
|
|---|
| 530 | /// \internal Precompute constants for the recursive generation of Legendre associated functions, with given normalization.
|
|---|
| 531 | /// this function is called by \ref shtns_set_size, and assumes up-to-date values in \ref shtns.
|
|---|
| 532 | /// For the same conventions as GSL, use \c legendre_precomp(sht_orthonormal,1);
|
|---|
| 533 | /// \param[in] norm : normalization of the associated legendre functions (\ref shtns_norm).
|
|---|
| 534 | /// \param[in] with_cs_phase : Condon-Shortley phase (-1)^m is included (1) or not (0)
|
|---|
| 535 | /// \param[in] mpos_renorm : Optional renormalization for m>0.
|
|---|
| 536 | /// 1.0 (no renormalization) is the "complex" convention, while 0.5 leads to the "real" convention (with FFTW).
|
|---|
| 537 | static void legendre_precomp(shtns_cfg shtns, enum shtns_norm norm, int with_cs_phase, double mpos_renorm)
|
|---|
| 538 | {
|
|---|
| 539 | double *alm, *blm;
|
|---|
| 540 | long int im, m, l, lm;
|
|---|
| 541 | real t1, t2;
|
|---|
| 542 |
|
|---|
| 543 | #if HAVE_LONG_DOUBLE_WIDER
|
|---|
| 544 | test_long_double();
|
|---|
| 545 | #endif
|
|---|
| 546 | #if SHT_VERBOSE > 1
|
|---|
| 547 | if (verbose) {
|
|---|
| 548 | printf(" > Condon-Shortley phase = %d, normalization = %d\n", with_cs_phase, norm);
|
|---|
| 549 | if (long_double_caps == 3) printf(" > long double has extended precision and large exponent\n");
|
|---|
| 550 | }
|
|---|
| 551 | #endif
|
|---|
| 552 |
|
|---|
| 553 | if (with_cs_phase != 0) with_cs_phase = 1; // force to 1 if !=0
|
|---|
| 554 |
|
|---|
| 555 | alm = (double *) malloc( (2*NLM)*sizeof(double) );
|
|---|
| 556 | blm = alm;
|
|---|
| 557 | if ((norm == sht_schmidt) || (mpos_renorm != 1.0)) {
|
|---|
| 558 | blm = (double *) malloc( (2*NLM)*sizeof(double) );
|
|---|
| 559 | }
|
|---|
| 560 | if ((alm==0) || (blm==0)) shtns_runerr("not enough memory.");
|
|---|
| 561 | shtns->alm = alm; shtns->blm = blm;
|
|---|
| 562 |
|
|---|
| 563 | /// - Compute and store the prefactor (independant of x) of the starting value for the recurrence :
|
|---|
| 564 | /// \f[ Y_m^m(x) = Y_0^0 \ \sqrt{ \prod_{k=1}^{m} \frac{2k+1}{2k} } \ \ (-1)^m \ (1-x^2)^{m/2} \f]
|
|---|
| 565 | if ((norm == sht_fourpi)||(norm == sht_schmidt)) {
|
|---|
| 566 | t1 = 1.0;
|
|---|
| 567 | alm[0] = t1; /// \f$ Y_0^0 = 1 \f$ for Schmidt or 4pi-normalized
|
|---|
| 568 | } else {
|
|---|
| 569 | t1 = 0.25L/M_PIl;
|
|---|
| 570 | alm[0] = SQRT(t1); /// \f$ Y_0^0 = 1/\sqrt{4\pi} \f$ for orthonormal
|
|---|
| 571 | }
|
|---|
| 572 | t1 *= mpos_renorm; // renormalization for m>0
|
|---|
| 573 | for (im=1, m=0; im<=MMAX; ++im) {
|
|---|
| 574 | while(m<im*MRES) {
|
|---|
| 575 | ++m;
|
|---|
| 576 | t1 *= ((real)m + 0.5)/m; // t1 *= (m+0.5)/m;
|
|---|
| 577 | }
|
|---|
| 578 | t2 = SQRT(t1);
|
|---|
| 579 | if ( m & with_cs_phase ) t2 = -t2; /// optional \f$ (-1)^m \f$ Condon-Shortley phase.
|
|---|
| 580 | alm_im(shtns, im)[0] = t2;
|
|---|
| 581 | }
|
|---|
| 582 |
|
|---|
| 583 | /// - Precompute the factors alm and blm of the recurrence relation :
|
|---|
| 584 | #pragma omp parallel for private(im,m,l,lm, t1, t2) schedule(dynamic)
|
|---|
| 585 | for (im=0; im<=MMAX; ++im) {
|
|---|
| 586 | m = im*MRES;
|
|---|
| 587 | lm = im*(2*LMAX - (im-1)*MRES);
|
|---|
| 588 | if (norm == sht_schmidt) { /// <b> For Schmidt semi-normalized </b>
|
|---|
| 589 | t2 = SQRT(2*m+1);
|
|---|
| 590 | alm[lm] /= t2; /// starting value divided by \f$ \sqrt{2m+1} \f$
|
|---|
| 591 | alm[lm+1] = t2; // l=m+1
|
|---|
| 592 | lm+=2;
|
|---|
| 593 | for (l=m+2; l<=LMAX; ++l) {
|
|---|
| 594 | t1 = SQRT((l+m)*(l-m));
|
|---|
| 595 | alm[lm+1] = (2*l-1)/t1; /// \f[ a_l^m = \frac{2l-1}{\sqrt{(l+m)(l-m)}} \f]
|
|---|
| 596 | alm[lm] = - t2/t1; /// \f[ b_l^m = -\sqrt{\frac{(l-1+m)(l-1-m)}{(l+m)(l-m)}} \f]
|
|---|
| 597 | t2 = t1; lm+=2;
|
|---|
| 598 | }
|
|---|
| 599 | } else { /// <b> For orthonormal or 4pi-normalized </b>
|
|---|
| 600 | t2 = 2*m+1;
|
|---|
| 601 | // starting value unchanged.
|
|---|
| 602 | alm[lm+1] = SQRT(2*m+3); // l=m+1
|
|---|
| 603 | lm+=2;
|
|---|
| 604 | for (l=m+2; l<=LMAX; ++l) {
|
|---|
| 605 | t1 = (l+m)*(l-m);
|
|---|
| 606 | alm[lm+1] = SQRT(((2*l+1)*(2*l-1))/t1); /// \f[ a_l^m = \sqrt{\frac{(2l+1)(2l-1)}{(l+m)(l-m)}} \f]
|
|---|
| 607 | alm[lm] = - SQRT(((2*l+1)*t2)/((2*l-3)*t1)); /// \f[ b_l^m = -\sqrt{\frac{2l+1}{2l-3}\,\frac{(l-1+m)(l-1-m)}{(l+m)(l-m)}} \f]
|
|---|
| 608 | t2 = t1; lm+=2;
|
|---|
| 609 | }
|
|---|
| 610 | }
|
|---|
| 611 | /// - Compute analysis recurrence coefficients if necessary
|
|---|
| 612 | if ((norm == sht_schmidt) || (mpos_renorm != 1.0)) {
|
|---|
| 613 | lm = im*(2*LMAX - (im-1)*MRES);
|
|---|
| 614 | t1 = 1.0;
|
|---|
| 615 | t2 = alm[lm+1];
|
|---|
| 616 | if (norm == sht_schmidt) {
|
|---|
| 617 | t1 = 2*m+1;
|
|---|
| 618 | t2 *= (2*m+3)/t1;
|
|---|
| 619 | }
|
|---|
| 620 | if (m>0) t1 /= mpos_renorm;
|
|---|
| 621 | blm[lm] = alm[lm]*t1;
|
|---|
| 622 | blm[lm+1] = t2;
|
|---|
| 623 | lm+=2;
|
|---|
| 624 | for (l=m+2; l<=LMAX; ++l) {
|
|---|
| 625 | t1 = alm[lm];
|
|---|
| 626 | t2 = alm[lm+1];
|
|---|
| 627 | if (norm == sht_schmidt) {
|
|---|
| 628 | double t3 = 2*l+1;
|
|---|
| 629 | t1 *= t3/(2*l-3);
|
|---|
| 630 | t2 *= t3/(2*l-1);
|
|---|
| 631 | }
|
|---|
| 632 | blm[lm] = t1;
|
|---|
| 633 | blm[lm+1] = t2;
|
|---|
| 634 | lm+=2;
|
|---|
| 635 | }
|
|---|
| 636 | }
|
|---|
| 637 | }
|
|---|
| 638 | }
|
|---|
| 639 |
|
|---|
| 640 | /// \internal returns the value of the Legendre Polynomial of degree l.
|
|---|
| 641 | /// l is arbitrary, a direct recurrence relation is used, and a previous call to legendre_precomp() is not required.
|
|---|
| 642 | static double legendre_Pl(const int l, double x)
|
|---|
| 643 | {
|
|---|
| 644 | long int i;
|
|---|
| 645 | real p, p0, p1;
|
|---|
| 646 |
|
|---|
| 647 | if ((l==0)||(x==1.0)) return ( 1. );
|
|---|
| 648 | if (x==-1.0) return ( (l&1) ? -1. : 1. );
|
|---|
| 649 |
|
|---|
| 650 | p0 = 1.0; /// \f$ P_0 = 1 \f$
|
|---|
| 651 | p1 = x; /// \f$ P_1 = x \f$
|
|---|
| 652 | for (i=2; i<=l; ++i) { /// recurrence : \f[ l P_l = (2l-1) x P_{l-1} - (l-1) P_{l-2} \f] (works ok up to l=100000)
|
|---|
| 653 | p = ((x*p1)*(2*i-1) - (i-1)*p0)/i;
|
|---|
| 654 | p0 = p1;
|
|---|
| 655 | p1 = p;
|
|---|
| 656 | }
|
|---|
| 657 | return ((double) p1);
|
|---|
| 658 | }
|
|---|
| 659 |
|
|---|
| 660 |
|
|---|
| 661 | /// \internal Generates the abscissa and weights for a Gauss-Legendre quadrature.
|
|---|
| 662 | /// Newton method from initial Guess to find the zeros of the Legendre Polynome
|
|---|
| 663 | /// \param x = abscissa, \param w = weights, \param n points.
|
|---|
| 664 | /// \note Reference: Numerical Recipes, Cornell press.
|
|---|
| 665 | static void gauss_nodes(real *x, real *w, int n)
|
|---|
| 666 | {
|
|---|
| 667 | long int i,l,m, k;
|
|---|
| 668 | real z, z1, p1, p2, p3, pp;
|
|---|
| 669 | real eps;
|
|---|
| 670 |
|
|---|
| 671 | eps = 2.3e-16; // desired precision, minimum = 2.2204e-16 (double)
|
|---|
| 672 | if ((sizeof(eps) > 8) && (long_double_caps > 1)) eps = 1.1e-19; // desired precision, minimum = 1.0842e-19 (long double i387)
|
|---|
| 673 |
|
|---|
| 674 | m = (n+1)/2;
|
|---|
| 675 | for (i=1;i<=m;++i) {
|
|---|
| 676 | k=10; // maximum Newton iteration count to prevent infinite loop.
|
|---|
| 677 | p1 = M_PIl; p2 = 2*n;
|
|---|
| 678 | z = (1.0 - (n-1)/(p2*p2*p2)) * COS((p1*(4*i-1))/(4*n+2)); // initial guess
|
|---|
| 679 | do {
|
|---|
| 680 | p1 = z; // P_1
|
|---|
| 681 | p2 = 1.0; // P_0
|
|---|
| 682 | for(l=2;l<=n;++l) { // recurrence : l P_l = (2l-1) z P_{l-1} - (l-1) P_{l-2} (works ok up to l=100000)
|
|---|
| 683 | p3 = p2;
|
|---|
| 684 | p2 = p1;
|
|---|
| 685 | p1 = ((2*l-1)*z*p2 - (l-1)*p3)/l; // The Legendre polynomial...
|
|---|
| 686 | }
|
|---|
| 687 | pp = ((1.-z)*(1.+z))/(n*(p2-z*p1)); // ... and its inverse derivative.
|
|---|
| 688 | z1 = z;
|
|---|
| 689 | z -= p1*pp; // Newton's method
|
|---|
| 690 | } while (( FABS(z-z1) > (z1+z)*0.5*eps ) && (--k > 0));
|
|---|
| 691 | x[i-1] = z; // Build up the abscissas.
|
|---|
| 692 | w[i-1] = 2.0*pp*pp/((1.-z)*(1.+z)); // Build up the weights.
|
|---|
| 693 | x[n-i] = -z;
|
|---|
| 694 | w[n-i] = w[i-1];
|
|---|
| 695 | }
|
|---|
| 696 | if (n&1) x[n/2] = 0.0; // exactly zero.
|
|---|
| 697 |
|
|---|
| 698 | #if SHT_VERBOSE > 1
|
|---|
| 699 | // test integral to compute :
|
|---|
| 700 | if (verbose) {
|
|---|
| 701 | z = 0;
|
|---|
| 702 | for (i=0;i<m;++i) {
|
|---|
| 703 | z += w[i]*x[i]*x[i];
|
|---|
| 704 | }
|
|---|
| 705 | #ifndef HAVE_LONG_DOUBLE_WIDER
|
|---|
| 706 | printf(" Gauss quadrature for 3/2.x^2 = %g (should be 1.0) error = %g\n",z*3.,z*3.-1.0);
|
|---|
| 707 | #else
|
|---|
| 708 | printf(" Gauss quadrature for 3/2.x^2 = %Lg (should be 1.0) error = %Lg\n",z*3.,z*3.-1.0);
|
|---|
| 709 | #endif
|
|---|
| 710 | }
|
|---|
| 711 | #endif
|
|---|
| 712 |
|
|---|
| 713 | // as we started with initial guesses, we should check if the gauss points are actually unique.
|
|---|
| 714 | for (i=m-1; i>0; i--) {
|
|---|
| 715 | if (((double) x[i]) == ((double) x[i-1])) shtns_runerr("bad gauss points");
|
|---|
| 716 | }
|
|---|
| 717 | }
|
|---|