Index: trunk/tests/test_MathUtils.cc =================================================================== --- trunk/tests/test_MathUtils.cc (revision 867) +++ trunk/tests/test_MathUtils.cc (revision 868) @@ -1,38 +1,138 @@ #include +#include +#include + #include "UnitTest++.h" #include "test_utils.hh" + #include "npstat/nm/MathUtils.hh" +#include "npstat/nm/SimpleFunctors.hh" using namespace npstat; using namespace std; namespace { + class PolyFunctor : public npstat::Functor1 + { + public: + inline PolyFunctor(const double* coeffs, const unsigned degree) + : coeffs_(coeffs, coeffs+(degree+1U)) {} + + inline double operator()(const double& x) const + {return polySeriesSum(&coeffs_[0], coeffs_.size()-1U, x);} + + private: + std::vector coeffs_; + }; + TEST(parabolicExtremum) { const double eps = 1.0e-11; const unsigned ntry = 100; double x = 0.0, y = 0.0; for (unsigned i=0; i 0.0; const double x1 = test_rng()*4.0 - 2.0; const double x2 = test_rng()*4.0 - 2.0; const double x3 = test_rng()*4.0 - 2.0; const double y1 = a*(x1 - x0)*(x1 - x0) + y0; const double y2 = a*(x2 - x0)*(x2 - x0) + y0; const double y3 = a*(x3 - x0)*(x3 - x0) + y0; CHECK_EQUAL(isMinimum, parabolicExtremum(x1,y1,x2,y2,x3,y3,&x,&y)); CHECK_CLOSE(x0, x, eps); CHECK_CLOSE(y0, y, eps); } } + + TEST(polyIntegralCoeffs) + { + const unsigned deg = 4; + + double coeffs[deg+1U]; + double intCoeffs[deg+2U]; + + for (unsigned i=0; i<10U; ++i) + { + for (unsigned j=0; j<=deg; ++j) + coeffs[j] = 2.0*test_rng() - 1.0; + + PolyFunctor fcn(coeffs, deg); + polyIntegralCoeffs(coeffs, deg, intCoeffs); + + for (unsigned j=0; j<20U; ++j) + { + const double x = 2.0*test_rng() - 1.0; + long double d; + polyAndDeriv(intCoeffs, deg+1U, x, 0, &d); + const long double ref = polySeriesSum(coeffs, deg, x); + CHECK_CLOSE(ref, d, 1.0e-12); + + const double xmin = 2.0*test_rng() - 1.0; + const double xmax = 2.0*test_rng() - 1.0; + const double integ1 = polySeriesSum(intCoeffs, deg+1U, xmax) - + polySeriesSum(intCoeffs, deg+1U, xmin); + const double integ2 = simpson_integral(fcn, xmin, xmax); + CHECK_CLOSE(integ1, integ2, 1.0e-6); + } + } + } + + TEST(chebyshevIntegralCoeffs) + { + const unsigned deg = 4; + + double coeffs[deg+1U]; + double intCoeffs[deg+2U]; + + double chebcoeffs[deg+1U]; + double chebIntCoeffs[deg+2U]; + + for (unsigned itry=0; itry<10U; ++itry) + { + for (unsigned j=0; j<=deg; ++j) + coeffs[j] = 2.0*test_rng() - 1.0; + + double xmin = 4.0*test_rng() - 2.0; + double xmax = 4.0*test_rng() - 2.0; + // double xmin = -1.0; + // double xmax = 1.0; + // if (xmin > xmax) + // std::swap(xmin, xmax); + + const PolyFunctor fcn(coeffs, deg); + chebyshevSeriesCoeffs(fcn, xmin, xmax, deg, chebcoeffs); + chebyshevIntegralCoeffs(chebcoeffs, deg, xmin, xmax, chebIntCoeffs); + polyIntegralCoeffs(coeffs, deg, intCoeffs); + + for (unsigned j=0; j<20U; ++j) + { + const double x = xmin + test_rng()*(xmax - xmin); + const double ref = fcn(x); + CHECK_CLOSE(ref, polySeriesSum(coeffs, deg, x), 1.0e-14); + + const double cheb = chebyshevSeriesSum( + chebcoeffs, deg, xmin, xmax, x); + CHECK_CLOSE(ref, cheb, 1.0e-14); + + const double refIntegral = polySeriesSum(intCoeffs, deg+1U, x) - + polySeriesSum(intCoeffs, deg+1U, xmin); + + const double i0 = chebyshevSeriesSum(chebIntCoeffs, deg+1U, xmin, xmax, xmin); + CHECK_CLOSE(0.0, i0, 1.0e-12); + + const double integ = chebyshevSeriesSum(chebIntCoeffs, deg+1U, xmin, xmax, x); + CHECK_CLOSE(refIntegral, integ, 1.0e-12); + } + } + } } Index: trunk/tests/test_ContOrthoPoly1D.cc =================================================================== --- trunk/tests/test_ContOrthoPoly1D.cc (revision 867) +++ trunk/tests/test_ContOrthoPoly1D.cc (revision 868) @@ -1,406 +1,406 @@ #include "UnitTest++.h" #include "test_utils.hh" #include "npstat/nm/ContOrthoPoly1D.hh" #include "npstat/nm/ClassicalOrthoPolys1D.hh" #include "npstat/nm/FejerQuadrature.hh" #include "npstat/nm/StorablePolySeries1D.hh" #include "npstat/rng/MersenneTwister.hh" #include "npstat/stat/Distributions1D.hh" using namespace npstat; using namespace std; inline static int kdelta(const unsigned i, const unsigned j) { return i == j ? 1 : 0; } namespace { TEST(ContOrthoPoly1D_orthonormalization) { const double eps = 1.0e-15; const OrthoPolyMethod method[] = {OPOLY_STIELTJES, OPOLY_LANCZOS}; const unsigned nMethods = sizeof(method)/sizeof(method[0]); const unsigned npoints = 64; const unsigned maxdeg = 10; const unsigned ntries = 10; std::vector points(2U*npoints); std::vector measure; measure.reserve(npoints); std::vector coeffs(maxdeg + 1); MersenneTwister rng; for (unsigned imeth=0; imeth(kdelta(i, j)), d, eps); } measure.clear(); for (unsigned i=0; i(kdelta(i, 0)), coeffs[i], eps); } } #ifdef USE_LAPACK_QUAD TEST(ContOrthoPoly1D_orthonormalization_quad_STIELTJES) { const lapack_double eps = FLT128_EPSILON*100.0; const OrthoPolyMethod method = OPOLY_STIELTJES; const unsigned npoints = 64; const unsigned maxdeg = 10; const unsigned ntries = 5; const double xmin = -1.0; const double xmax = 1.0; const double range = xmax - xmin; std::vector points(2U*npoints); std::vector measure; measure.reserve(npoints); MersenneTwister rng; for (unsigned itry=0; itry(kdelta(i, j)), d, eps); } } } TEST(ContOrthoPoly1D_orthonormalization_quad_LANCZOS) { const lapack_double eps = FLT128_EPSILON*100.0; const OrthoPolyMethod method = OPOLY_LANCZOS; const unsigned npoints = 64; const unsigned maxdeg = 10; const unsigned ntries = 5; const double xmin = -1.0; const double xmax = 1.0; const double range = xmax - xmin; std::vector points(2U*npoints); std::vector measure; measure.reserve(npoints); MersenneTwister rng; for (unsigned itry=0; itry(kdelta(i, j)), d, eps); } } } #endif TEST(ContOrthoPoly1D_weightCoeffs) { const double eps = 1.0e-7; const unsigned maxdeg = 10; const unsigned ntries = 10; const unsigned npoints = maxdeg + 1U; std::vector points(2U*npoints); std::vector measure; measure.reserve(npoints); std::vector coeffs(maxdeg + 1); MersenneTwister rng; for (unsigned itry=0; itry poly2( poly.makeStorablePolySeries(xmin, xmax)); for (unsigned deg=0; deg<=maxdeg; ++deg) for (unsigned ipow=0; ipow<4; ++ipow) { const double i1 = poly.integratePoly(deg, ipow, xmin, xmax); const double i2 = poly2->integratePoly(deg, ipow); if (fabs(i2) > 1.0e-10) CHECK_CLOSE(1.0, i1/i2, 1.0e-10); } for (unsigned i=0; iseries(&coeffs[0], maxdeg, x); CHECK_CLOSE(w, fvalue, 1000*eps); - CHECK_CLOSE(fvalue, f2, 50*eps); + CHECK_CLOSE(fvalue, f2, 200*eps); } } } TEST(ContOrthoPoly1D_cov8) { const double eps = 1.0e-12; MersenneTwister rng; const unsigned npoints = 64; const unsigned maxdeg = 6; const unsigned ntries = 5; const unsigned degtries = 100; std::vector points(npoints); for (unsigned itry=0; itry points(npoints); std::vector aves(maxdeg + 1); Beta1D beta(0.0, 1.0, 2.0, 2.0); GaussLegendreQuadrature glq(nInteg); for (unsigned itry=0; itry& mat = poly.extWeightProductAverages( df, glq, maxdeg, 0.0, 1.0); poly.extWeightAverages(df, glq, maxdeg, 0.0, 1.0, &aves[0]); for (unsigned i=0; i<=maxdeg; ++i) for (unsigned j=0; j<=maxdeg; ++j) { const double integ = poly.integrateEWPolyProduct( DensityFunctor1D(beta), i, j, 0.0, 1.0, nInteg); CHECK_CLOSE(integ, mat[i][j], eps); } for (unsigned j=0; j<=maxdeg; ++j) CHECK_CLOSE(aves[j], mat[0][j], eps); } } TEST(ContOrthoPoly1D_epsCovariance) { const double eps = 1.0e-15; const unsigned npoints = 64; const unsigned maxdeg = 6; const unsigned ntries = 5; MersenneTwister rng; std::vector points(npoints); for (unsigned itry=0; itry r(nrandom); for (unsigned i=0; i result(maxdeg + 1); poly.sampleAverages(&r[0], r.size(), &result[0], maxdeg); Matrix mat(maxdeg + 1, maxdeg + 1, 0); std::vector acc(maxdeg + 1, 0.0L); std::vector tmp(maxdeg + 1); for (unsigned i=0; i(acc[deg]), result[deg], eps); } // Test "pairwiseSampleAverages" method mat /= nrandom; Matrix averages = poly.sampleProductAverages( &r[0], r.size(), maxdeg); Matrix refmat(mat); CHECK((averages - refmat).maxAbsValue() < eps); } TEST(ContOrthoPoly1D_jacobi_2) { typedef ContOrthoPoly1D::PreciseQuadrature Quadrature; const double eps = 1.0e-13; const unsigned alpha = 3; const unsigned beta = 4; const unsigned maxdeg = 5; const unsigned npoints = 64; JacobiOrthoPoly1D jac(alpha, beta); OrthoPoly1DWeight weight(jac); const unsigned npt = Quadrature::minimalExactRule(2*maxdeg+alpha+beta); Quadrature glq(npt); ContOrthoPoly1D poly(maxdeg, glq.weightedIntegrationPoints(weight, -1.0, 1.0), OPOLY_LANCZOS); for (unsigned i=0; i >; } %clear (const double *c, const unsigned degreep1); Index: trunk/npstat/nm/MathUtils.icc =================================================================== --- trunk/npstat/nm/MathUtils.icc (revision 867) +++ trunk/npstat/nm/MathUtils.icc (revision 868) @@ -1,174 +1,234 @@ #include #include #include namespace npstat { template long double polySeriesSum(const Numeric *a, const unsigned degree, const long double x) { // Dimension of "a" should be at least degree+1 assert(a); long double sum = 0.0L; for (int deg=degree; deg>=0; --deg) { sum *= x; sum += a[deg]; } return sum; } template void polyAndDeriv(const Numeric *a, unsigned deg, const long double x, long double *value, long double *deriv) { // Dimension of "a" should be at least deg+1 assert(a); long double sum = 0.0L, der = 0.0L; for (; deg>=1; --deg) { sum *= x; der *= x; sum += a[deg]; der += deg*a[deg]; } if (value) *value = sum*x + a[0]; if (deriv) *deriv = der; } template + void polyIntegralCoeffs(const Numeric* coeffs, const unsigned degree, + Numeric* integralCoeffs) + { + assert(coeffs); + assert(integralCoeffs); + assert(coeffs != integralCoeffs); + + integralCoeffs[0] = static_cast(0); + for (unsigned i=0; i<=degree; ++i) + { + const unsigned ip1 = i + 1U; + integralCoeffs[ip1] = coeffs[i]/ip1; + } + } + + template long double legendreSeriesSum(const Numeric *a, const unsigned degree, const long double x) { assert(a); long double result = a[0], pminus2 = 1.0L, pminus1 = x; if (degree) { result += a[1]*x; for (unsigned i=2; i<=degree; ++i) { const long double p = ((2*i-1U)*x*pminus1 - (i-1U)*pminus2)/i; result += p*a[i]; pminus2 = pminus1; pminus1 = p; } } return result; } template long double hermiteSeriesSumProb(const Numeric *a, const unsigned degree, const long double x) { assert(a); long double result = a[0], pminus2 = 1.0L, pminus1 = x; if (degree) { result += a[1]*x; for (unsigned i=2; i<=degree; ++i) { const long double p = x*pminus1 - (i-1U)*pminus2; result += p*a[i]; pminus2 = pminus1; pminus1 = p; } } return result; } template long double hermiteSeriesSumPhys(const Numeric *a, const unsigned degree, const long double x) { assert(a); long double result = a[0], pminus2 = 1.0L, pminus1 = 2.0L*x; if (degree) { result += a[1]*2.0L*x; for (unsigned i=2; i<=degree; ++i) { const long double p = 2.0L*(x*pminus1 - (i-1U)*pminus2); result += p*a[i]; pminus2 = pminus1; pminus1 = p; } } return result; } template long double gegenbauerSeriesSum(const Numeric *a, const unsigned degree, const long double lambda, const long double x) { assert(a); long double result = a[0], pminus2 = 1.0L, pminus1 = 2.0L*lambda*x; if (degree) { result += a[1]*pminus1; const long double lm = 2.0L*(lambda - 1.0L); for (unsigned i=2; i<=degree; ++i) { const long double p = ((2*i+lm)*x*pminus1 - (i+lm)*pminus2)/i; result += p*a[i]; pminus2 = pminus1; pminus1 = p; } } return result; } template - long double chebyshevSeriesSum(const Numeric *a, - const unsigned degree, const long double x) + long double chebyshevSeriesSum(const Numeric *a, const unsigned degree, + const long double x) { assert(a); const long double twox = 2.0L*x; // Clenshaw recursion long double rp2 = 0.0L, rp1 = 0.0L, r = 0.0L; for (unsigned k = degree; k > 0U; --k) { r = twox*rp1 - rp2 + a[k]; rp2 = rp1; rp1 = r; } return x*rp1 - rp2 + a[0]; } + template + long double chebyshevSeriesSum(const Numeric *a, const unsigned degree, + const long double xmin, const long double xmax, + const long double x) + { + assert(xmin != xmax); + const long double xtrans = 2.0L*(x - xmin)/(xmax - xmin) - 1.0L; + return chebyshevSeriesSum(a, degree, xtrans); + } + template void chebyshevSeriesCoeffs(const Functor& f, const long double xmin, const long double xmax, const unsigned degree, Numeric *coeffs) { const long double Pi = 3.141592653589793238462643383L; assert(coeffs); assert(xmin != xmax); // Simple direct formula is used for calculations // (faster calculations for large degrees can be done // by the cosine transform). const unsigned N = degree + 1U; const long double halfwidth = (xmax - xmin)/2.0L; std::vector fval(N); for (unsigned k=0; k(norm*sum); } } + + template + void chebyshevIntegralCoeffs(const Numeric* coeffs, const unsigned degree, + const long double xmin, const long double xmax, + Numeric* integralCoeffs) + { + assert(coeffs); + assert(integralCoeffs); + assert(integralCoeffs != coeffs); + assert(xmin != xmax); + + const long double norm = 0.25L*(xmax - xmin); + long double sum = 0.0L; + long double f = -1.0L; + + integralCoeffs[1U] = norm*(2.0*coeffs[0] - coeffs[2]); + sum += integralCoeffs[1U]; + + for (unsigned j=2U; j #include namespace npstat { /** // Solve the quadratic equation x*x + b*x + c == 0 // in a numerically sound manner. Return the number of roots. */ unsigned solveQuadratic(double b, double c, double *x1, double *x2); /** // Find the real roots of the cubic: // x**3 + p*x**2 + q*x + r = 0 // The number of real roots is returned, and the roots are placed // into the "v3" array. Original code by Don Herbison-Evans (see // his article "Solving Quartics and Cubics for Graphics" in the // book "Graphics Gems V", page 3), with minimal adaptation for // this package by igv. */ unsigned solveCubic(double p, double q, double r, double v3[3]); /** // Find an extremum of a parabola passing through the three given points. // The returned value is "true" for minimum and "false" for maximum. // std::invalid_argument will be thrown in case some of the x values // coincide or if the coordinates describe a straight line. */ bool parabolicExtremum(double x0, double y0, double x1, double y1, double x2, double y2, double* extremumCoordinate, double* extremumValue); /** // Volume of the n-dimensional sphere of unit radius. Should be // multuplied by R^n to get the volume of the sphere with arbitrary radius. */ double ndUnitSphereVolume(unsigned n); /** // Area of the sphere of unit radius embedded in the n-dimensional space. // Should be multuplied by R^(n-1) to get the area of the sphere with // arbitrary radius. */ double ndUnitSphereArea(unsigned n); /** // Sum of polynomial series. The length of the // array of coefficients should be at least degree+1. // The highest degree coefficient is assumed to be // the last one in the "coeffs" array (0th degree // coefficient comes first). */ template long double polySeriesSum(const Numeric *coeffs, unsigned degree, long double x); /** // Sum and derivative of polynomial series. The length of the // array of coefficients should be at least degree+1. */ template void polyAndDeriv(const Numeric *coeffs, unsigned degree, long double x, long double *value, long double *deriv); /** + // Coefficients for the integral of polynomial series. + // The integration constant is set to 0. The length of the + // array of coefficients should be at least degree+1, + // and the length of the buffer for the integral coefficients + // should be at least degree+2. + */ + template + void polyIntegralCoeffs(const Numeric* coeffs, unsigned degree, + Numeric* integralCoeffs); + + /** // Series using Legendre polynomials. 0th degree coefficient // comes first. Although any value of x can be specified, the result // is not going to be terribly meaningful in case |x| > 1. */ template long double legendreSeriesSum(const Numeric *coeffs, unsigned degree, long double x); /** // Series for Hermite polynomials orthogonal with weight exp(-x*x/2). // These are sometimes called the "probabilists' Hermite polynomials". */ template long double hermiteSeriesSumProb(const Numeric *coeffs, unsigned degree, long double x); /** // Series for Hermite polynomials orthogonal with weight exp(-x*x). These // are sometimes called the "physicists' Hermite polynomials". The weight // exp(-x*x) is also used in the "GaussHermiteQuadrature" class. */ template long double hermiteSeriesSumPhys(const Numeric *coeffs, unsigned degree, long double x); /** Series for Gegenbauer polynomials. "lambda" is the parameter. */ template long double gegenbauerSeriesSum(const Numeric *coeffs, unsigned degree, long double lambda, long double x); /** Series for the Chebyshev polynomials of the first kind */ template - long double chebyshevSeriesSum(const Numeric *coeffs, - unsigned degree, long double x); + long double chebyshevSeriesSum(const Numeric *coeffs, unsigned degree, + long double x); + + /** + // Series for the Chebyshev polynomials of the first kind + // on the given interval [xmin, xmax] + */ + template + long double chebyshevSeriesSum(const Numeric *coeffs, unsigned degree, + long double xmin, long double xmax, + long double x); /** // Generate Chebyshev series coefficients for a given functor on the // interval [xmin, xmax]. The array of coefficients must be at least // degree+1 long. The functor will be given a long double argument. */ template void chebyshevSeriesCoeffs(const Functor& f, long double xmin, long double xmax, unsigned degree, Numeric *coeffs); + /** + // Converts Chebyshev series coefficients into coefficients + // for the function integral. The 0's degree coefficient + // will be chosen in such a way that the integral is 0 + // at xmin. The array of coefficients must be at least + // degree+1 long, and the buffer for the integral coefficients + // must be at least degree+2 long. + */ + template + void chebyshevIntegralCoeffs(const Numeric* coeffs, unsigned degree, + long double xmin, long double xmax, + Numeric* integralCoeffs); + #ifdef SWIG inline double polySeriesSum2(const double *c, const unsigned degreep1, const double x) {return polySeriesSum(c, degreep1-1U, x);} inline std::pair polyAndDeriv2( const double *c, const unsigned degreep1, const double x) { long double p, d; polyAndDeriv(c, degreep1-1U, x, &p, &d); return std::pair(p, d); } + inline std::vector polyIntegralCoeffs2(const double *c, const unsigned degreep1) + { + std::vector tmp(degreep1+1U); + polyIntegralCoeffs(c, degreep1-1U, &tmp[0]); + return tmp; + } + inline double legendreSeriesSum2(const double *c, const unsigned degreep1, const double x) {return legendreSeriesSum(c, degreep1-1U, x);} inline double hermiteSeriesSumProb2(const double *c, const unsigned degreep1, const double x) {return hermiteSeriesSumProb(c, degreep1-1U, x);} inline double hermiteSeriesSumPhys2(const double *c, const unsigned degreep1, const double x) {return hermiteSeriesSumPhys(c, degreep1-1U, x);} inline double gegenbauerSeriesSum2(const double *c, const unsigned degreep1, const double lambda, const double x) {return gegenbauerSeriesSum(c, degreep1-1U, lambda, x);} inline double chebyshevSeriesSum2(const double *c, const unsigned degreep1, + const double xmin, const double xmax, const double x) - {return chebyshevSeriesSum(c, degreep1-1U, x);} + {return chebyshevSeriesSum(c, degreep1-1U, xmin, xmax, x);} + + inline std::vector chebyshevIntegralCoeffs2(const double *c, const unsigned degreep1, + const double xmin, const double xmax) + { + std::vector tmp(degreep1+1U); + chebyshevIntegralCoeffs(c, degreep1-1U, xmin, xmax, &tmp[0]); + return tmp; + } template inline std::vector chebyshevSeriesCoeffs2(const Functor& f, const double xmin, const double xmax, const unsigned degree) { std::vector tmp(degree+1U); chebyshevSeriesCoeffs(f, xmin, xmax, degree, &tmp[0]); return tmp; } inline std::vector solveQuadratic2(const double b, const double c) { double x[2]; const unsigned nRoots = solveQuadratic(b, c, &x[0], &x[1]); return std::vector(x, x+nRoots); } inline std::vector solveCubic2(const double p, const double q, const double r) { double v3[3]; const unsigned nRoots = solveCubic(p, q, r, v3); return std::vector(v3, v3+nRoots); } #endif // SWIG } #include "npstat/nm/MathUtils.icc" #endif // NPSTAT_MATHUTILS_HH_