1 /*
2  *                This source code is part of
3  *
4  *                     E  R  K  A  L  E
5  *                             -
6  *                       DFT from Hel
7  *
8  * Written by Susi Lehtola, 2010-2011
9  * Copyright (c) 2010-2011, Susi Lehtola
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  */
16 
17 
18 
19 #include "mathf.h"
20 #include <cmath>
21 #include <cfloat>
22 
23 // For exceptions
24 #include <sstream>
25 #include <stdexcept>
26 
27 extern "C" {
28   // For factorials and so on
29 #include <gsl/gsl_sf_gamma.h>
30   // For spline interpolation
31 #include <gsl/gsl_spline.h>
32   // For Bessel functions
33 #include <gsl/gsl_sf_bessel.h>
34   // For hypergeometric functions
35 #include <gsl/gsl_sf_hyperg.h>
36   // For trigonometric functions
37 #include <gsl/gsl_sf_trig.h>
38   // Random number generation
39 #include <gsl/gsl_rng.h>
40   // Random number distributions
41 #include <gsl/gsl_randist.h>
42 }
43 
doublefact(int n)44 double doublefact(int n) {
45   if(n<-1) {
46     ERROR_INFO();
47     std::ostringstream oss;
48     oss << "Trying to compute double factorial for n="<<n<<"!";
49     throw std::runtime_error(oss.str());
50   }
51 
52   if(n>=-1 && n<=1)
53     return 1.0;
54   else
55     return gsl_sf_doublefact(n);
56 }
57 
fact(int n)58 double fact(int n) {
59   if(n<0) {
60     ERROR_INFO();
61     std::ostringstream oss;
62     oss << "Trying to compute factorial for n="<<n<<"\
63 !";
64     throw std::runtime_error(oss.str());
65   }
66 
67   return gsl_sf_fact(n);
68 }
69 
fact_ratio(int i,int r)70 double fact_ratio(int i, int r) {
71   return gsl_sf_fact(i)/(gsl_sf_fact(r)*gsl_sf_fact(i-2*r));
72 }
73 
fgamma(double x)74 double fgamma(double x) {
75   return gsl_sf_gamma(x);
76 }
77 
sinc(double x)78 double sinc(double x) {
79   return gsl_sf_sinc(x/M_PI);
80 }
81 
bessel_jl(int l,double x)82 double bessel_jl(int l, double x) {
83   // Small x: use series expansion
84   double series=pow(x,l)/doublefact(2*l+1);
85   if(fabs(series)<sqrt(DBL_EPSILON))
86     return series;
87 
88   // Large x: truncate due to incorrect GSL asymptotics
89   // (GSL bug https://savannah.gnu.org/bugs/index.php?36152)
90   if(x>1.0/DBL_EPSILON)
91     return 0.0;
92 
93   // Default case: use GSL
94   return gsl_sf_bessel_jl(l,x);
95 }
96 
boysF(int m,double x)97 double boysF(int m, double x) {
98   // Compute Boys' function
99 
100   // Check whether we are operating in the range where the Taylor series is OK
101   if( x<=1.0 ) {
102 
103     // Taylor series uses -x
104     x=-x;
105 
106     // (-x)^k
107     double xk=1.0;
108     // k!
109     double kf=1.0;
110     // Value of Boys' function
111     double fm=0.0;
112     // index k
113     int k=0;
114 
115     while(k<=15) {
116       // \f$ F_m(x) = \sum_{k=0}^\infty \frac {(-x)^k} { k! (2m+2k+1)} \f$
117       fm+=xk/(kf*(2*(m+k)+1));
118 
119       k++;
120       xk*=x;
121       kf*=k;
122     }
123 
124     return fm;
125 
126   } else if(x>=38.0) {
127     // Use asymptotic expansion, which is precise to <1e-16 for F_n(x), n = 0 .. 60
128     return doublefact(2*m-1)/pow(2,m+1)*sqrt(M_PI/pow(x,2*m+1));
129 
130   } else
131     // Need to use the exact formula
132     return 0.5*gsl_sf_gamma(m+0.5)*pow(x,-m-0.5)*gsl_sf_gamma_inc_P(m+0.5,x);
133 }
134 
boysF_arr(int mmax,double x,arma::vec & F)135 void boysF_arr(int mmax, double x, arma::vec & F) {
136   // Resize array
137   F.zeros(mmax+1);
138   // Exp(-x) for recursion
139   double emx=exp(-x);
140 
141   if(x<mmax) {
142     // Fill in highest value
143     F[mmax]=boysF(mmax,x);
144     // and fill in the rest with downward recursion
145     for(int m=mmax-1;m>=0;m--)
146       F[m]=(2*x*F[m+1]+emx)/(2*m+1);
147   } else {
148     // Fill in lowest value
149     F[0]=boysF(0,x);
150     // and fill in the rest with upward recursion
151     for(int m=1;m<=mmax;m++)
152       F[m]=((2*m-1)*F[m-1]-emx)/(2.0*x);
153   }
154 }
155 
hyperg_1F1(double a,double b,double x)156 double hyperg_1F1(double a, double b, double x) {
157   // Handle possible underflows with a Kummer transformation
158   if(x>=-500.0) {
159     return gsl_sf_hyperg_1F1(a,b,x);
160   } else {
161     return exp(x)*gsl_sf_hyperg_1F1(b-a,b,-x);
162   }
163 }
164 
choose(int m,int n)165 double choose(int m, int n) {
166   if(m<0 || n<0) {
167     ERROR_INFO();
168     throw std::domain_error("Choose called with a negative argument!\n");
169   }
170 
171   return gsl_sf_choose(m,n);
172 }
173 
getind(int l,int m,int n)174 int getind(int l, int m, int n) {
175   // Silence compiler warning
176   (void) l;
177   // In the loop the indices will be
178   int ii=m+n;
179   int jj=n;
180   // so the corresponding index is
181   return ii*(ii+1)/2 + jj;
182 }
183 
max_abs(const arma::mat & R)184 double max_abs(const arma::mat & R) {
185   return arma::max(arma::max(arma::abs(R)));
186 }
187 
max_cabs(const arma::cx_mat & R)188 double max_cabs(const arma::cx_mat & R) {
189   return arma::max(arma::max(arma::abs(R)));
190 }
191 
rms_norm(const arma::mat & R)192 double rms_norm(const arma::mat & R) {
193   // Calculate \sum_ij R_ij^2
194   double rms=arma::trace(arma::trans(R)*R);
195   // and convert to rms
196   rms=sqrt(rms/(R.n_rows*R.n_cols));
197 
198   return rms;
199 }
200 
rms_cnorm(const arma::cx_mat & R)201 double rms_cnorm(const arma::cx_mat & R) {
202   // Calculate \sum_ij R_ij^2
203   double rms=std::abs(arma::trace(arma::trans(R)*R));
204   // and convert to rms
205   rms=sqrt(rms/(R.n_rows*R.n_cols));
206 
207   return rms;
208 }
209 
spline_interpolation(const std::vector<double> & xt,const std::vector<double> & yt,const std::vector<double> & x)210 std::vector<double> spline_interpolation(const std::vector<double> & xt, const std::vector<double> & yt, const std::vector<double> & x) {
211   if(xt.size()!=yt.size()) {
212     ERROR_INFO();
213     std::ostringstream oss;
214     oss << "xt and yt are of different lengths - " << xt.size() << " vs " << yt.size() << "!\n";
215     throw std::runtime_error(oss.str());
216   }
217 
218   // Returned data
219   std::vector<double> y(x.size());
220 
221   // Index accelerator
222   gsl_interp_accel *acc=gsl_interp_accel_alloc();
223   // Interpolant
224   gsl_interp *interp=gsl_interp_alloc(gsl_interp_cspline,xt.size());
225 
226   // Initialize interpolant
227   gsl_interp_init(interp,&(xt[0]),&(yt[0]),xt.size());
228 
229   // Perform interpolation.
230   for(size_t i=0;i<x.size();i++) {
231     y[i]=gsl_interp_eval(interp,&(xt[0]),&(yt[0]),x[i],acc);
232   }
233 
234   // Free memory
235   gsl_interp_accel_free(acc);
236   gsl_interp_free(interp);
237 
238   return y;
239 }
240 
spline_interpolation(const arma::vec & xtv,const arma::vec & ytv,const arma::vec & xv)241 arma::vec spline_interpolation(const arma::vec & xtv, const arma::vec & ytv, const arma::vec & xv) {
242   return arma::conv_to<arma::colvec>::from(spline_interpolation(arma::conv_to< std::vector<double> >::from(xtv), arma::conv_to< std::vector<double> >::from(ytv), arma::conv_to< std::vector<double> >::from(xv)));
243 }
244 
spline_interpolation(const std::vector<double> & xt,const std::vector<double> & yt,double x)245 double spline_interpolation(const std::vector<double> & xt, const std::vector<double> & yt, double x) {
246   if(xt.size()!=yt.size()) {
247     ERROR_INFO();
248     std::ostringstream oss;
249     oss << "xt and yt are of different lengths - " << xt.size() << " vs " << yt.size() << "!\n";
250     throw std::runtime_error(oss.str());
251   }
252 
253   // Index accelerator
254   gsl_interp_accel *acc=gsl_interp_accel_alloc();
255   // Interpolant
256   gsl_interp *interp=gsl_interp_alloc(gsl_interp_cspline,xt.size());
257 
258   // Initialize interpolant
259   gsl_interp_init(interp,&(xt[0]),&(yt[0]),xt.size());
260 
261   // Perform interpolation.
262   double y=gsl_interp_eval(interp,&(xt[0]),&(yt[0]),x,acc);
263 
264   // Free memory
265   gsl_interp_accel_free(acc);
266   gsl_interp_free(interp);
267 
268   return y;
269 }
270 
randu_mat(size_t M,size_t N,unsigned long int seed)271 arma::mat randu_mat(size_t M, size_t N, unsigned long int seed) {
272   // Use Mersenne Twister algorithm
273   gsl_rng *r = gsl_rng_alloc(gsl_rng_mt19937);
274   // Set seed
275   gsl_rng_set(r,seed);
276 
277   // Matrix
278   arma::mat mat(M,N);
279   // Fill it
280   for(size_t i=0;i<M;i++)
281     for(size_t j=0;j<N;j++)
282       mat(i,j)=gsl_rng_uniform(r);
283 
284   // Free rng
285   gsl_rng_free(r);
286   return mat;
287 }
288 
randn_mat(size_t M,size_t N,unsigned long int seed)289 arma::mat randn_mat(size_t M, size_t N, unsigned long int seed) {
290   // Use Mersenne Twister algorithm
291   gsl_rng *r = gsl_rng_alloc(gsl_rng_mt19937);
292   // Set seed
293   gsl_rng_set(r,seed);
294 
295   // Matrix
296   arma::mat mat(M,N);
297   // Fill it
298   for(size_t i=0;i<M;i++)
299     for(size_t j=0;j<N;j++)
300       mat(i,j)=gsl_ran_gaussian(r,1.0);
301 
302   // Free rng
303   gsl_rng_free(r);
304   return mat;
305 }
306 
real_orthogonal(size_t N,unsigned long int seed)307 arma::mat real_orthogonal(size_t N, unsigned long int seed) {
308   arma::mat U(N,N);
309   U.zeros();
310 
311   // Generate totally random matrix
312   arma::mat A=randn_mat(N,N,seed);
313 
314   // Perform QR decomposition on matrix
315   arma::mat Q, R;
316   bool ok=arma::qr(Q,R,A);
317   if(!ok) {
318     ERROR_INFO();
319     throw std::runtime_error("QR decomposition failure in complex_unitary.\n");
320   }
321 
322   // Check that Q is orthogonal
323   arma::mat test=Q*arma::trans(Q);
324   for(size_t i=0;i<test.n_cols;i++)
325     test(i,i)-=1.0;
326   double n=rms_norm(test);
327   if(n>10*DBL_EPSILON) {
328     ERROR_INFO();
329     throw std::runtime_error("Generated matrix is not unitary!\n");
330   }
331 
332   return Q;
333 }
334 
complex_unitary(size_t N,unsigned long int seed)335 arma::cx_mat complex_unitary(size_t N, unsigned long int seed) {
336   arma::cx_mat U(N,N);
337   U.zeros();
338 
339   // Generate totally random matrix
340   arma::cx_mat A=std::complex<double>(1.0,0.0)*randn_mat(N,N,seed) + std::complex<double>(0.0,1.0)*randn_mat(N,N,seed+1);
341 
342   // Perform QR decomposition on matrix
343   arma::cx_mat Q, R;
344   bool ok=arma::qr(Q,R,A);
345   if(!ok) {
346     ERROR_INFO();
347     throw std::runtime_error("QR decomposition failure in complex_unitary.\n");
348   }
349 
350   // Check that Q is unitary
351   arma::cx_mat test=Q*arma::trans(Q);
352   for(size_t i=0;i<test.n_cols;i++)
353     test(i,i)-=1.0;
354   double n=rms_cnorm(test);
355   if(n>10*DBL_EPSILON) {
356     ERROR_INFO();
357     throw std::runtime_error("Generated matrix is not unitary!\n");
358   }
359 
360   return Q;
361 }
362 
round(double x,unsigned n)363 double round(double x, unsigned n) {
364   double fac=pow(10.0,n);
365   return round(fac*x)/fac;
366 }
367 
find_minima(const arma::vec & x,const arma::vec & y,size_t runave,double thr)368 arma::vec find_minima(const arma::vec & x, const arma::vec & y, size_t runave, double thr) {
369   if(x.n_elem != y.n_elem) {
370     ERROR_INFO();
371     throw std::runtime_error("Input vectors are of inconsistent size!\n");
372   }
373 
374   // Create averaged vectors
375   arma::vec xave(x.n_elem-2*runave);
376   arma::vec yave(y.n_elem-2*runave);
377   if(runave==0) {
378     xave=x;
379     yave=y;
380   } else {
381     for(arma::uword i=runave;i<x.n_elem-runave;i++) {
382       xave(i-runave)=arma::mean(x.subvec(i-runave,i+runave));
383       yave(i-runave)=arma::mean(y.subvec(i-runave,i+runave));
384     }
385   }
386 
387   // Find minima
388   std::vector<size_t> minloc;
389   if(yave(0)<yave(1))
390     minloc.push_back(0);
391 
392   for(arma::uword i=1;i<yave.n_elem-1;i++)
393     if(yave(i)<yave(i-1) && yave(i)<yave(i+1))
394       minloc.push_back(i);
395 
396   if(yave(yave.n_elem-1)<yave(yave.n_elem-2))
397     minloc.push_back(yave.n_elem-1);
398 
399   // Check the minimum values
400   for(size_t i=minloc.size()-1;i<minloc.size();i--)
401     if(yave(minloc[i]) >= thr)
402       minloc.erase(minloc.begin()+i);
403 
404   // Returned minima
405   arma::vec ret(minloc.size());
406   for(size_t i=0;i<minloc.size();i++)
407     ret(i)=xave(minloc[i]);
408 
409   return ret;
410 }
411 
stencil(double z,const arma::vec & x,arma::mat & w)412 void stencil(double z, const arma::vec & x, arma::mat & w) {
413   /*
414     z: location where approximations are wanted
415     x: grid points at which value of function is known
416     w: matrix holding weights for grid points for different orders
417        of the derivatives: w(x, order)
418 
419        This implementation is based on B. Fornberg, "Calculation of
420        weights in finite difference formulas", SIAM Rev. 40, 685
421        (1998).
422    */
423 
424   /* Extract variables */
425   size_t n = w.n_rows - 1;
426   size_t m = w.n_cols - 1;
427 
428   /* Sanity check */
429   if(w.n_rows != x.n_elem)
430     throw std::logic_error("Grid points and weight matrix sizes aren't compatible!\n");
431 
432   /* Initialize elements */
433   double c1 = 1.0;
434   double c4 = x(0)-z;
435   w.zeros();
436   w(0,0)=1.0;
437 
438   for(size_t i=1;i<=n;i++) {
439     size_t mn = std::min(i,m);
440     double c2 = 1.0;
441     double c5 = c4;
442     c4 = x(i) - z;
443 
444     for(size_t j=0;j<i;j++) {
445       double c3 = x(i) - x(j);
446       c2 *= c3;
447 
448       if(j == i-1) {
449 	for(size_t k=mn;k>0;k--)
450 	  w(i,k) = c1*(k*w(i-1,k-1) - c5*w(i-1,k))/c2;
451 	w(i,0)=-c1*c5*w(i-1,0)/c2;
452       }
453       for(size_t k=mn;k>0;k--)
454 	w(j,k)=(c4*w(j,k) - k*w(j,k-1))/c3;
455       w(j,0)=c4*w(j,0)/c3;
456     }
457     c1 = c2;
458   }
459 }
460