Files
LA_library/miscfunc.cc
T

478 lines
11 KiB
C++

/*
LA: linear algebra C++ interface library
Copyright (C) 2024 Jiri Pittner <jiri.pittner@jh-inst.cas.cz> or <jiri@pittnerovi.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <stdint.h>
#include <limits>
#include <math.h>
#include "miscfunc.h"
#include "laerror.h"
#include "polynomial.h"
namespace LA {
#define MAXFACT 20
unsigned long long factorial(const int n)
{
static int ntop=20;
static unsigned long long a[MAXFACT+1]={1,1,2,6,24,120,720,5040,
40320,
362880,
3628800,
39916800ULL,
479001600ULL,
6227020800ULL,
87178291200ULL,
1307674368000ULL,
20922789888000ULL,
355687428096000ULL,
6402373705728000ULL,
121645100408832000ULL,
2432902008176640000ULL};
int j;
if (n < 0) laerror("negative argument of factorial");
if (n > MAXFACT) laerror("overflow in factorial");
while (ntop<n) {
j=ntop++;
a[ntop]=a[j]*ntop;
}
return a[n];
}
//n! in double precision for the cases where the exact factorial() would overflow
#define MAXDOUBLEFACT 170
double double_factorial(const int n)
{
static int ntop=MAXFACT;
static double a[MAXDOUBLEFACT+1];
int j;
if (n < 0) laerror("negative argument of double_factorial");
if (n > MAXDOUBLEFACT) laerror("overflow in double_factorial");
if (n <= MAXFACT) return (double)factorial(n);
if (ntop == MAXFACT) a[MAXFACT]=(double)factorial(MAXFACT);
while (ntop<n) {
j=ntop++;
a[ntop]=a[j]*ntop;
}
return a[n];
}
#define EPS 1e-15
static double stirfact(double n) /* n should not be less than 9 or 7 at worst */
{
double logz,cor,nn,z;
cor=0.0;
z=n+1.0;
logz= std::log(z);
if(z>=2.0) /*does not have sense for smaller anyway and could make troubles*/
{
int i,ii,m;
m=(int)std::ceil(0.5-std::log(EPS/10000.)/logz/2.0);
if(m<2)m=2; if(m>16)m=16;
nn=z*z;
for(i=m;i>0;i--) {ii=2*i;cor = cor/nn + bernoulli_number(ii)/((double)ii*(ii-1));}
cor /=z;
}
/*0.5*ln(2*M_PI)=0.91893853320467275836*/
return((z-0.5)*logz-z+0.918938533204672741780329736406+cor);
/*cor=std::log((1.0/(24.0*n)+1.0)/(12.0*n)+1.0)*/
}
#define LIMITFACT 180
double factorialln(int n)
{
static int top=MAXFACT;
static double a[LIMITFACT+1]={
0.0,
0.0,
0.69314718055994530843,
1.7917594692280549573,
3.1780538303479457518,
4.7874917427820458116,
6.579251212010101213,
8.5251613610654146669,
10.604602902745250859,
12.801827480081469091,
15.10441257307551588,
17.502307845873886549,
19.987214495661884683,
22.552163853123424531,
25.191221182738679829,
27.899271383840890337,
30.671860106080671926,
33.505073450136890756,
36.395445208033052609,
39.339884187199494647,
42.335616460753485057,
45.380138898476907627,
48.471181351835227247,
51.606675567764376922,
54.784729398112318677,
58.003605222980517908,
61.261701761002001376,
64.557538627006337606,
67.889743137181540078,
71.257038967168014665,
74.658236348830158136};
if(n < 0) laerror("negative argument of factln");
if(n < 2) return(0.0);
if(n <= LIMITFACT) /*supposed LIMITFACT > MAXFACT*/
if(a[n]) return(a[n]); else
{
if(n<=MAXFACT) return(a[n]= std::log(factorial(n)));
{
int j;
if(!a[top]) a[top]= std::log(factorial(top));
while (top<n) {
j=top++;
a[top]=a[j]+std::log((double)top);
}
return a[n];
}
}
else return(stirfact((double)n));
}
#define MAXBINOM 100
#define ibidxmaxeven(n) ((n-2)*(n-2)/4)
#define ibidx(n,k) (k-2+(n&1?(n-3)*(n-3)/4:(n/2-1)*(n/2-2)))
static unsigned long long ullgcd(unsigned long long a, unsigned long long b)
{
while(b) {unsigned long long t=a%b; a=b; b=t;}
return a;
}
unsigned long long binom(int n, int k)
{
unsigned long long value;
static unsigned long long ibitab[ibidxmaxeven(MAXBINOM)]= /*only nontrivial are stored,
zero initialization by compiler assumed*/
{
6,
10,
15,20,
21,35,
28,56,70
};
if(k>n||k<0) return(0);
if(k>n/2) k=n-k;
if(k==0) return(1);
if(k==1) return(n);
int ind=0;
if(n<=MAXBINOM)
{
ind=ibidx(n,k);
if (ibitab[ind]) return ibitab[ind];
}
/* nonrecurent method used anyway */
/* value = C(n-k+i,i) after step i is exact; divide by the common factor first to avoid overflow of intermediates */
value=1;
for(int i=1; i<=k; ++i)
{
unsigned long long m=n-k+i;
unsigned long long g=ullgcd(m,i);
m /= g;
value /= (i/g); /*exact, since value*m is divisible by i and m/g is coprime to i/g*/
if(value > std::numeric_limits<unsigned long long>::max()/m) laerror("overflow in binom");
value *= m;
}
if(n<=MAXBINOM) ibitab[ind]=value;
return value;
}
#undef ibidx
#undef ibidxmaxeven
#undef MAXBINOM
unsigned long long longpow(unsigned long long x, int i)
{
if(i<0) return 0;
unsigned long long y=1;
do
{
if(i&1) y *= x;
i >>= 1;
if(i) x *= x;
}
while(i);
return y ;
}
/* Euler-Maclaurin summation: zeta(x)= sum_{k=1}^{N-1} k^-x + N^(1-x)/(x-1) + N^-x/2 + sum_{j=1}^m B_2j/(2j)! x(x+1)...(x+2j-2) N^(-x-2j+1) */
/* accurate to about 1e-15 for -1.02<=x<9.1 (the pole term 1/(x-1) is exact) */
static double zeta_euler_maclaurin(double x)
{
const int N=10, m=10;
double s,nx,t;
int j,k;
s=0.0;
for(k=1;k<N;k++) s += std::pow((double)k,-x);
nx=std::pow((double)N,-x);
s += N*nx/(x-1.0) + 0.5*nx;
t=x*nx/N;
for(j=1;j<=m;j++)
{
s += bernoulli_number(2*j)/double_factorial(2*j)*t;
t *= (x+2*j-1)*(x+2*j)/((double)N*N);
}
return s;
}
double zeta(double x)
{
if(x==std::floor(x) && std::fabs(x)<=(((unsigned int) 1)<<(4*sizeof(int)-1)) )
{
int n;
n=(int)x;
if(n==1) laerror("singular point in zeta");
if(n==0) return(-0.5);
if(n<0)
{
if(n&1) return(-bernoulli_number(1-n)/(1-n));
else return(0.0);
}
if(n==2||n==4||n==6||n==8) /*precalculated few bernoulli numbers */
return(std::fabs(bernoulli_number(n))*pow(2*M_PI,(double)n)/double_factorial(n)/2.0);
/*otherwise continue like if general real argument*/
}
if(x<-1.02) return (2.0*pow(2.0*M_PI,x-1.0)*sin(M_PI*x/2.0)*gamma(1.0-x)*zeta(1.0-x));
if(x>=9.1) /* in this region the summation per definition is best and quite exact*/
{
double s;
int i,n;
n=(int)std::ceil(pow(EPS/1000.0,-1.0/x));
s=0.0;for(i=n;i>1;i--) s+= pow((double)i,-x);
return(1.0+s);
}
return zeta_euler_maclaurin(x);
}
//end zeta
double gamma(double x)
{
bool f;
double p;
if(x>MAXDOUBLEFACT+1) laerror("too big argument in gamma");
f= (std::fabs(x-std::floor(x+0.5))<EPS);
if(x<=0.0 && f) laerror("nonpositive integer argument in gamma");
if(f) return double_factorial(std::floor(x+0.5)-1);
if(x<8.0)
{
p=1.0;
while(x<8.0) {p /= x; x += 1.0;}
return(p* std::exp(stirfact(x-1.0)));
}
return( std::exp(stirfact(x-1.0)));
}
static double factln(double nn)
{
if(nn<=LIMITFACT+0.5) return factorialln((int)(nn+.5));
return stirfact(nn);
}
double gammln(double x)
{
bool f;
f= (std::fabs(x-std::floor(x+0.5))<EPS);
if(x<=0.0 && f) laerror("nonpositive integer argument in gamma");
if(std::fabs(x-std::floor(x+0.5))<EPS) return factln(x-1.0);
if(x<8.0)
{
double p;
p=0.0;
while(x<8.0) {p -= std::log(std::fabs(x)); x += 1.0;}
return(p+stirfact(x-1.0));
}
return(stirfact(x-1.0));
}
/* Bernoulli numbers */
#define MAXBERN 50
double bernoulli_number(int n)
{
static double bern[MAXBERN]={1.0,0.166666666666666666,-0.03333333333333333333,0.0238095238095238,-0.033333333333333333,0.07575757575757575757};
static int top=5;
int i;
if(n<0) laerror("negative index of Bernoulli number");
if(n==1) return(-0.5);
if(n&1) return(0.0);
n /= 2;
if( n >= MAXBERN) /* use zeta function for it */
{int m;
m=2*n;
return(zeta((double)m)*(n&1?2.0:-2.0)*double_factorial(m)/pow(2.0*M_PI,(double)m));
}
if(n<=top) return(bern[n]);
/* this is very unexact - recurrent formula with binom. coef.
for(i=top+1;i<=n;i++)
{
int j,d;
double s;
d=2*i+1;
s=0.0;
for(j=1;j<i;j++) s += bern[j]*ibinom(d,2*j);
bern[i]=0.5-(1.0+s)/d;
}
*/
/* precalculate and save for next use, because the calc. is expensive */
/* could be calculated just one at need, but let's do all of them */
for(i=top+1;i<=n;i++)
{int m;
m=2*i;
bern[i]=zeta((double)m)*(i&1?2.0:-2.0)*double_factorial(m)/pow(2.0*M_PI,(double)m);
}
top=n;
return(bern[n]);
}
//there is a closed form s(d,n) = binom(n+d-1,d)
//we could also pre-generate these polynomials for small d
//but that would be inefficient, better is to buffer all values
//we use recurrence s(d,n) = s(d,n-1) + s(d-1,n)
//we cannot also use the above efficient binom() since here we need laerge n while d is small
//
//enlarge storage size if needed
//
#define SIMPLICIAL_MAXD 16
#define SIMPLICIAL_MAXN 1024
unsigned long long simplicial(int d, int n)
{
static unsigned long long stored[SIMPLICIAL_MAXD-2][SIMPLICIAL_MAXN]={{0,1,3}};
static int maxd=2;
static int maxn=2;
if(n<=0) return 0;
if(d==0||n==1) return 1;
if(d==1) return n;
//resize precomputed part as needed
if(n>maxn)
{
if(n>=SIMPLICIAL_MAXN) laerror("storage overflow in simplicial, increase MAXN");
int newdim=2*n;
if(newdim<2*maxn) newdim=2*maxn;
if(newdim>=SIMPLICIAL_MAXN) newdim=SIMPLICIAL_MAXN-1;
for(int i=maxn+1; i<=newdim;++i) stored[0][i] = i*(i+1)/2;
for(int dd=1; dd<=maxd-2; ++dd)
{
for(int i=maxn+1; i<=newdim;++i) stored[dd][i] = stored[dd][i-1] + stored[dd-1][i];
}
maxn=newdim;
}
if(d>maxd)
{
if(d>=SIMPLICIAL_MAXD) laerror("storage overflow in simplicial, increase MAXD");
for(int dd=maxd+1; dd<=d; ++dd)
{
stored[dd-2][0]=0;
stored[dd-2][1]=1;
for(int i=2; i<=maxn; ++i) stored[dd-2][i] = stored[dd-3][i] + stored[dd-2][i-1];
}
maxd=d;
}
return stored[d-2][n];
}
//find largest n such that simplicial(d,n)<=s
int inverse_simplicial(int d, unsigned long long s)
{
if(s==0) return 0;
if(d==0 || s==1) return 1;
if(d==1) return (int)s;
if(d>=SIMPLICIAL_MAXD) laerror("storage overflow in inverse_simplicial - increase MAXD");
static int maxd=0;
static Polynomial<double> polynomials[SIMPLICIAL_MAXD];
static Polynomial<double> polynomials_der[SIMPLICIAL_MAXD];
for(int i=maxd+1; i<=d; ++i) //generate as needed
{
NRVec<double> roots(i);
for(int j=0; j<i;++j) roots[j] = -j;
polynomials[i-1] = polyfromroots(roots) * (1./factorial(i));
polynomials_der[i-1] = polynomials[i-1].derivative(1);
maxd=i;
}
//solve polynomial equation by newton
double x=std::pow(factorial(d)*(double)s,1./d); //good init guess
double dx;
do
{
double residual = value(polynomials[d-1],x)-s;
dx = residual/value(polynomials_der[d-1],x);
x -= dx;
}
while(std::fabs(dx)>.5); //so usually 2 iterations are enough
int n= std::floor(x);
while(simplicial(d,n)>s) --n;
return n;
}
#undef SIMPLICIAL_MAXD
#undef SIMPLICIAL_MAXN
}//namespace