template < int n >
struct Tchebychev
{
double operator () ( double x )
{
return 2.0 * x * Tchebychev < n-1>()(x) - Tchebychev < n-2>()(x);
}
};
template < >
struct Tchebychev < 0 >
{
double operator () ( double x )
{
return 1.0;
}
};
template < >
struct Tchebychev < 1 >
{
double operator () ( double x )
{
return x;
}
};
double TchebychevN(int n,double x)
{
switch(n) {
case 0 : return Tchebychev<0>()(x);
case 1 : return Tchebychev<1>()(x);
case 2 : return Tchebychev<2>()(x);
case 3 : return Tchebychev<3>()(x);
case 4 : return Tchebychev<4>()(x);
case 5 : return Tchebychev<5>()(x);
case 6 : return Tchebychev<6>()(x);
case 7 : return Tchebychev<7>()(x);
case 8 : return Tchebychev<8>()(x);
case 9 : return Tchebychev<9>()(x);
NO_DEFAULT_CASE
}
return 0;
}
template < typename functor >
void FindTchebychevCoefficients(functor f,double lo = 0.0, double hi = 1.0, int N = (1<<20))
{
double PI_over_N = PI/N;
#define NUM_T 6
double t[NUM_T] = { 0 };
for(int k=0;k < N;k++)
{
double pk = PI_over_N * (k + 0.5);
double x_k = cos(pk);
double farg = lo + (hi - lo) * 0.5 * (x_k+1.0);
double fval = f( farg );
for(int j=0;j < NUM_T;j++)
{
t[j] += fval * cos( pk * j );
}
}
for(int j=0;j < NUM_T;j++)
{
t[j] *= 1.0 / N;
if ( j != 0 )
t[j] *= 2.0;
//lprintfvar(t[j]);
lprintf("t %d: %16.9f , %16.8g\n",j,t[j],t[j]);
}
double errSqr[NUM_T] = { 0 };
double errMax[NUM_T] = { 0 };
for(int i=0;i < N;i++)
{
double xunit = (i+0.5)/N;
double farg = lo + (hi - lo) * xunit;
double xt = xunit*2.0 - 1.0;
double y = f(farg);
double p = 0.0;
for(int j=0;j < NUM_T;j++)
{
p += t[j] * TchebychevN(j,xt);
errSqr[j] += fsquare(y-p);
errMax[j] = MAX( errMax[j] , fabs(y-p) );
}
}
for(int j=0;j < NUM_T;j++)
{
lprintf("%d : errSqr = %g errMax = %g\n",j,errSqr[j]/N,errMax[j]);
}
}
You can also very easily find inner products by using the Integrate<> template I posted earlier plus this simple product adaptor :
template < typename f1, typename f2 >
struct FunctorProduct
{
double operator () ( double x )
{
return f1()(x) * f2()(x);
}
};
(eg. you can trivially find Hermite or Legendre series this way by doing inner products).
Another handy helper is a range remapper :
template < typename functor >
struct RemapFmTo
{
double m_fmLo; double m_fmHi;
double m_toLo; double m_toHi;
RemapFmTo(
double fmLo, double fmHi,
double toLo, double toHi )
: m_fmLo(fmLo), m_fmHi(fmHi), m_toLo(toLo), m_toHi(toHi)
{
}
double operator () ( double x )
{
double t = (x - m_fmLo) / (m_fmHi - m_fmLo);
double y = t * (m_toHi - m_toLo) + m_toLo;
return functor()(y);
}
};
template < typename functor >
struct RemapUnitTo
{
double m_toLo; double m_toHi;
RemapUnitTo(
double toLo, double toHi )
: m_toLo(toLo), m_toHi(toHi)
{
}
double operator () ( double x )
{
double y = x * (m_toHi - m_toLo) + m_toLo;
return functor()(y);
}
};
Now we can trivially do what we did before to find the optimal approximation in a known range :
struct SqrtOnePlusX
{
double operator () ( double x )
{
return sqrt( 1 + x );
}
};
RemapUnitTo< SqrtOnePlusX > tf(-0.075f,0.075f);
FindTchebychevCoefficients( tf );
and the output is :
t 0: 0.999647973 , 0.99964797 t 1: 0.037519818 , 0.037519818 t 2: -0.000352182 , -0.00035218223 t 3: 0.000006612 , 6.6121459e-006 t 4: -0.000000155 , -1.5518238e-007 t 5: 0.000000004 , 4.0790168e-009 0 : errSqr = 0.000469204 errMax = 0.0378787 1 : errSqr = 5.78832e-008 errMax = 0.000358952 2 : errSqr = 2.12381e-011 errMax = 6.77147e-006 3 : errSqr = 1.18518e-014 errMax = 1.59378e-007 4 : errSqr = 8.23726e-018 errMax = 4.19794e-009 5 : errSqr = 6.54835e-021 errMax = 1.19024e-010I guess Remez minimax polynomials are better, but this is so easy and it gives you a good starting point, then you can just numerically optimize after this anyway.
ADDENDUM :
obviously the TchebychevN dispatcher sucks and is silly, but you don't actually use it in practice; you know which polynomials you want and you use something like :
double approx =
c0 * Tchebychev<0>(x) +
c1 * Tchebychev<1>(x) +
c2 * Tchebychev<2>(x) +
c3 * Tchebychev<3>(x);
which the compiler handles quite well.
I also did Legendre polynomials :
template < int n >
struct Legendre
{
double operator () ( double x )
{
double L_n1 = Legendre < n-1 >()(x);
double L_n2 = Legendre < n-2 >()(x);
const double B = (n-1)/(double)n; // n-1/n
return (1+B)*x*L_n1 - B*L_n2;
}
};
template< > struct Legendre<0>
{
double operator () ( double x ) { return 1.0; }
};
template< > struct Legendre<1>
{
double operator () ( double x ) { return x; }
};
// but ryg's loop variants are mostly just better :
__forceinline double legendre_ryg(int n, double x)
{
if (n == 0) return 1.0;
if (n == 1) return x;
double t = x, t1 = 1, t2;
for (int i=1; i < n; i++) {
t2 = t1;
t1 = t;
const double B = (i)/(double)(i+1);
t = (1+B)*x*t1 - B*t2;
}
return t;
}
__forceinline double chebyshev_ryg(int n, double x)
{
if (n == 0) return 1.0;
if (n == 1) return x;
double t = x, t1 = 1, t2;
for (int i=1; i < n; i++) {
t2 = t1;
t1 = t;
t = 2.0 * x * t1 - t2;
}
return t;
}
// you can find Legendre coefficients like so :
RemapUnitTo< Legendre<0> > ShiftedLegendre0(-1,1);
double c0 = Integrate(0.0,1.0,steps,MakeFunctorProduct(functor(),ShiftedLegendre0));
// (don't forget normalization)
// using new helper :
template < typename f1, typename f2 >
FunctorProduct< f1,f2 > MakeFunctorProduct( f1 _f1 , f2 _f2 ) { return FunctorProduct< f1,f2 >(_f1,_f2); }