/** * \file * \brief Provides code for the general c2_function algebra which supports * fast, flexible operations on piecewise-twice-differentiable functions * * \author Created by R. A. Weller and Marcus H. Mendenhall on 7/9/05. * \author Copyright 2005 __Vanderbilt University__. All rights reserved. * * \version c2_function.cc,v 1.43 2007/11/12 20:22:54 marcus Exp */ #include #include #include #include #include #include #include #include #include #include template const std::string c2_function::cvs_file_vers() const { return "c2_function.cc,v 1.43 2007/11/12 20:22:54 marcus Exp"; } // find a pre-bracketed root of a c2_function, which is a MUCH easier job than general root finding // since the derivatives are known exactly, and smoothness is guaranteed. // this searches for f(x)=value, to make life a little easier than always searching for f(x)=0 template float_type c2_function::find_root(float_type lower_bracket, float_type upper_bracket, float_type start, float_type value, int *error, float_type *final_yprime, float_type *final_yprime2) const throw(c2_exception) { // find f(x)=value within the brackets, using the guarantees of smoothness associated with a c2_function // can use local f(x)=a*x**2 + b*x + c and solve quadratic to find root, then iterate reset_evaluations(); float_type yp, yp2; // we will make unused pointers point here, to save null checks later if (!final_yprime) final_yprime=&yp; if (!final_yprime2) final_yprime2=&yp2; float_type ftol=5*(std::numeric_limits::epsilon()*std::abs(value)+std::numeric_limits::min()); float_type xtol=5*(std::numeric_limits::epsilon()*(std::abs(upper_bracket)+std::abs(lower_bracket))+std::numeric_limits::min()); float_type root=start; // start looking in the middle if(error) *error=0; // start out with error flag set to OK, if it is expected float_type c, b; // this new logic is to keep track of where we were before, and lower the number of // function evaluations if we are searching inside the same bracket as before. // Since this root finder has, very often, the bracket of the entire domain of the function, // this makes a big difference, especially to c2_inverse_function if(!rootInitialized || upper_bracket != lastRootUpperX || lower_bracket != lastRootLowerX) { lastRootUpperY=value_with_derivatives(upper_bracket, final_yprime, final_yprime2); increment_evaluations(); lastRootUpperX=upper_bracket; lastRootLowerY=value_with_derivatives(lower_bracket, final_yprime, final_yprime2); increment_evaluations(); lastRootLowerX=lower_bracket; rootInitialized=true; } float_type clower=lastRootLowerY-value; float_type cupper=lastRootUpperY-value; if(clower*cupper >0) { // argh, no sign change in here! if(error) { *error=1; return 0.0; } else { std::ostringstream outstr; outstr << "unbracketed root in find_root at xlower= " << lower_bracket << ", xupper= " << upper_bracket; outstr << ", value= " << value << ": bailing"; throw c2_exception(outstr.str().c_str()); } } float_type delta=upper_bracket-lower_bracket; // first error step c=value_with_derivatives(root, final_yprime, final_yprime2)-value; // compute initial values b=*final_yprime; // make a local copy for readability increment_evaluations(); while( std::abs(delta) > xtol && // absolute x step check std::abs(c) > ftol && // absolute y tolerance std::abs(c) > xtol*std::abs(b) // comparison to smallest possible Y step from derivative ) { float_type a=(*final_yprime2)/2; // second derivative is 2*a float_type disc=b*b-4*a*c; // std::cout << std::endl << "find_root_debug a,b,c,d " << a << " " << b << " " << c << " " << disc << std::endl; if(disc >= 0) { float_type q=-0.5*((b>=0)?(b+std::sqrt(disc)):(b-std::sqrt(disc))); if(q*q > std::abs(a*c)) delta=c/q; // since x1=q/a, x2=c/q, x1/x2=q^2/ac, this picks smaller step else delta=q/a; root+=delta; } if(disc < 0 || rootupper_bracket || std::abs(delta) >= 0.5*(upper_bracket-lower_bracket)) { // if we jump out of the bracket, or aren't converging well, bisect root=0.5*(lower_bracket+upper_bracket); delta=upper_bracket-lower_bracket; } c=value_with_derivatives(root, final_yprime, final_yprime2)-value; // compute initial values b=*final_yprime; // make a local copy for readability increment_evaluations(); // now, close in bracket on whichever side this still brackets if(c*clower < 0.0) { cupper=c; upper_bracket=root; } else { clower=c; lower_bracket=root; } // std::cout << "find_root_debug x, y, dx " << root << " " << c << " " << delta << std::endl; } return root; } /* def partial_integrals(self, xgrid): Return the integrals of a function between the sampling points xgrid. The sum is the definite integral. This method uses an exact integration of the polynomial which matches the values and derivatives at the endpoints of a segment. Its error scales as h**6, if the input functions really are smooth. This could very well be used as a stepper for adaptive Romberg integration. For InterpolatingFunctions, it is likely that the Simpson's rule integrator is sufficient. #the weights come from an exact mathematica solution to the 5th order polynomial with the given values & derivatives #yint = (y0+y1)*dx/2 + dx^2*(yp0-yp1)/10 + dx^3 * (ypp0+ypp1)/120 ) */ // the recursive part of the integrator is agressively designed to minimize copying of data... lots of pointers template float_type c2_function::integrate_step(c2_integrate_recur &rb) const { struct c2_integrate_fblock *fbl[3]={rb.f0, rb.f1, rb.f2}; struct c2_integrate_fblock f1; // will hold new middle values float_type retvals[2]={0.0,0.0}; float_type lr[2]; // std::cout << "entering with " << rb.f0->x << " " << rb.f1->x << " " << rb.f2->x << std::endl; int depth=rb.depth; // save this from the recursion block float_type abs_tol=rb.abs_tol; // this is the value we will pass down float_type *rblr=rb.lr; // save pointer to our parent's lr[2] array since it will get trampled in recursion if(!depth) { switch(rb.derivs) { case 0: rb.eps_scale=0.1; rb.extrap_coef=16; break; case 1: rb.eps_scale=0.1; rb.extrap_coef=64; break; case 2: rb.eps_scale=0.02; rb.extrap_coef=1024; break; default: throw c2_exception("derivs must be 0, 1 or 2 in partial_integrals"); } rb.extrap2=1.0/(rb.extrap_coef-1.0); } for (int i=0; i<(depth==0?1:2); i++) { // handle left and right intervals, but only left one for depth=0 struct c2_integrate_fblock *f0=fbl[i], *f2=fbl[i+1]; f1.x=0.5*(f0->x + f2->x); // center of interval float_type dx=f2->x - f0->x; float_type dx2 = 0.5*dx; float_type total; f1.y=value_with_derivatives(f1.x, &(f1.yp), &(f1.ypp)); increment_evaluations(); // check for underflow on step size, which prevents us from achieving specified accuracy. if(std::abs(dx) < std::abs(f1.x)*rb.rel_tol) { std::ostringstream outstr; outstr << "Step size underflow in adaptive_partial_integrals at depth=" << depth << ", x= " << f1.x; throw c2_exception(outstr.str().c_str()); } if(!depth) { // top level, total has not been initialized yet switch(rb.derivs) { // create estimate of next lower order for first try case 0: total=0.5*(f0->y+f2->y)*dx; break; case 1: total=(f0->y+4.0*f1.y+f2->y)*dx/6.0; break; case 2: total=( (14*f0->y + 32*f1.y + 14*f2->y) + dx * (f0->yp - f2->yp) ) * dx /60.; break; default: total=0.0; // just to suppress missing default warnings } } else total=rblr[i]; // otherwise, get it from previous level float_type left, right; switch(rb.derivs) { case 2: // use ninth-order estimates for each side, from full set of all values (!) (Thanks, Mathematica!) left= ( ( (169*f0->ypp + 1024*f1.ypp - 41*f2->ypp)*dx2 + (2727*f0->yp - 5040*f1.yp + 423*f2->yp) )*dx2 + (17007*f0->y + 24576*f1.y - 1263*f2->y) )* (dx2/40320.0); right= ( ( (169*f2->ypp + 1024*f1.ypp - 41*f0->ypp)*dx2 - (2727*f2->yp - 5040*f1.yp + 423*f0->yp) )*dx2 + (17007*f2->y + 24576*f1.y - 1263*f0->y) )* (dx2/40320.0); // std::cout << f0->x << " " << f1.x << " " << f2->x << std::endl ; // std::cout << f0->y << " " << f1.y << " " << f2->y << " " << left << " " << right << " " << total << std::endl ; break; case 1: left= ( (202*f0->y + 256*f1.y + 22*f2->y) + dx*(13*f0->yp - 40*f1.yp - 3*f2->yp) ) * dx /960.; right= ( (202*f2->y + 256*f1.y + 22*f0->y) - dx*(13*f2->yp - 40*f1.yp - 3*f0->yp) ) * dx /960.; break; case 0: left= (5*f0->y + 8*f1.y - f2->y)*dx/24.; right= (5*f2->y + 8*f1.y - f0->y)*dx/24.; break; default: left=right=0.0; // suppress warnings about missing default break; } lr[0]= left; // left interval lr[1]= right; // right interval float_type lrsum=left+right; float_type eps=std::abs(total-lrsum)*rb.eps_scale; if(rb.extrapolate) eps*=rb.eps_scale; if(!rb.adapt || eps < abs_tol || eps < std::abs(total)*rb.rel_tol) { if(depth==0 || !rb.extrapolate) retvals[i]=lrsum; else { retvals[i]=(rb.extrap_coef*lrsum - total)*rb.extrap2; // std::cout << "extrapolating " << lrsum << " " << total << " " << retvals[i] << std::endl; } } else { rb.depth=depth+1; // increment depth counter rb.lr=lr; // point to our left-right values array for recursion rb.abs_tol=abs_tol*0.5; // each half has half the error budget rb.f0=f0; rb.f1=&f1; rb.f2=f2; // insert pointers to data into our recursion block // std::cout << "recurring with " << f0->x << " " << f1.x << " " << f2->x << std::endl ; retvals[i]=integrate_step(rb); // and recur } } return retvals[0]+retvals[1]; } template bool c2_function::check_monotonicity( const std::vector &data, const char message[]) throw(c2_exception) { size_t np=data.size(); if(np < 2) return false; // one point has no direction! bool rev=(data[1] < data[0]); // which way do data point? size_t i; if(!rev) for(i = 2; i < np && (data[i-1] < data[i]) ; i++); else for(i = 2; i < np &&(data[i-1] > data[i]) ; i++); if(i != np) throw c2_exception(message); return rev; } template void c2_function::set_sampling_grid(const std::vector &grid) throw(c2_exception) { bool rev=check_monotonicity(grid, "set_sampling_grid: sampling grid not monotonic"); if(!sampling_grid || no_overwrite_grid) sampling_grid=new std::vector; (*sampling_grid)=grid; no_overwrite_grid=0; if(rev) std::reverse(sampling_grid->begin(), sampling_grid->end()); // make it increasing } template std::vector &c2_function::get_sampling_grid(float_type xmin, float_type xmax) const { std::vector *result=new std::vector; if( !(sampling_grid) || !(sampling_grid->size()) || (xmax <= sampling_grid->front()) || (xmin >= sampling_grid->back()) ) { // nothing is known about the function in this region, return xmin and xmax result->push_back(xmin); result->push_back(xmax); } else { std::vector &sg=*sampling_grid; // just a shortcut int np=sg.size(); int klo=0, khi=np-1, firstindex=0, lastindex=np-1; result->push_back(xmin); if(xmin > sg.front() ) { // hunt through table for position bracketing starting point while(khi-klo > 1) { int km=(khi+klo)/2; if(sg[km] > xmin) khi=km; else klo=km; } khi=klo+1; // khi now points to first point definitively beyond our first point, or last point of array firstindex=khi; khi=np-1; // restart upper end of search } if(xmax < sg.back()) { // hunt through table for position bracketing starting point while(khi-klo > 1) { int km=(khi+klo)/2; if(sg[km] > xmax) khi=km; else klo=km; } khi=klo+1; // khi now points to first point definitively beyond our last point, or last point of array lastindex=klo; } int initsize=result->size(); result->resize(initsize+(lastindex-firstindex+2)); std::copy(sg.begin()+firstindex, sg.begin()+lastindex+1, result->begin()+initsize); result->back()=xmax; // this is the unrefined sampling grid... now check for very close points on front & back and fix if needed. preen_sampling_grid(result); } return *result; } template void c2_function::preen_sampling_grid(std::vector *result) const { // this is the unrefined sampling grid... now check for very close points on front & back and fix if needed. if(result->size() > 2) { // may be able to prune dangerously close points near the ends if there are at least 3 points bool deleteit=false; float_type x0=(*result)[0], x1=(*result)[1]; float_type dx1=x1-x0; float_type ftol=10.0*(std::numeric_limits::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits::min()); if(dx1 < ftol) deleteit=true; float_type dx2=(*result)[2]-x0; if(dx1/dx2 < 0.1) deleteit=true; // endpoint is very close to internal interesting point if(deleteit) result->erase(result->begin()+1); // delete redundant interesting point } if(result->size() > 2) { // may be able to prune dangerously close points near the ends if there are at least 3 points bool deleteit=false; int pos=result->size()-3; float_type x0=(*result)[pos+1], x1=(*result)[pos+2]; float_type dx1=x1-x0; float_type ftol=10.0*(std::numeric_limits::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits::min()); if(dx1 < ftol) deleteit=true; float_type dx2=x1-(*result)[pos]; if(dx1/dx2 < 0.1) deleteit=true; // endpoint is very close to internal interesting point if(deleteit) result->erase(result->end()-2); // delete redundant interesting point } } template std::vector &c2_function:: refine_sampling_grid(const std::vector &grid, size_t refinement) const { size_t np=grid.size(); size_t count=(np-1)*refinement + 1; float_type dxscale=1.0/refinement; std::vector *result=new std::vector(count); for(size_t i=0; i<(np-1); i++) { float_type x=grid[i]; float_type dx=(grid[i+1]-x)*dxscale; for(size_t j=0; j float_type c2_function::integral(float_type xmin, float_type xmax, std::vector *partials, float_type abs_tol, float_type rel_tol, int derivs, bool adapt, bool extrapolate) const { std::vector &grid=get_sampling_grid(xmin, xmax); float_type intg=partial_integrals(grid, partials, abs_tol, rel_tol, adapt, extrapolate); delete &grid; return intg; } template c2_function &c2_function::normalized_function(float_type xmin, float_type xmax, float_type norm) { float_type intg=integral(xmin, xmax); return *new c2_scaled_function(*this, norm/intg); } template c2_function &c2_function::square_normalized_function(float_type xmin, float_type xmax, float_type norm) { c2_quadratic q(0., 0., 0., 1.); c2_composed_function mesquared(q,*this); std::vector grid(get_sampling_grid(xmin, xmax)); float_type intg=mesquared.partial_integrals(grid); return *new c2_scaled_function(*this, std::sqrt(norm/intg)); } template c2_function &c2_function::square_normalized_function( float_type xmin, float_type xmax, const c2_function &weight, float_type norm) { c2_quadratic q(0., 0., 0., 1.); c2_composed_function mesquared(q,*this); c2_product weighted(mesquared, weight); std::vector grid(get_sampling_grid(xmin, xmax)); float_type intg=weighted.partial_integrals(grid); return *new c2_scaled_function(*this, std::sqrt(norm/intg)); } template float_type c2_function::partial_integrals( std::vector xgrid, std::vector *partials, float_type abs_tol, float_type rel_tol, int derivs, bool adapt, bool extrapolate) const { int np=xgrid.size(); struct c2_integrate_fblock f0, f2; struct c2_integrate_recur rb; rb.rel_tol=rel_tol; rb.extrapolate=extrapolate; rb.adapt=adapt; rb.derivs=derivs; reset_evaluations(); // counter returns with total evaluations needed for this integral if(partials) partials->resize(np-1); float_type sum=0.0; f2.x=xgrid[0]; f2.y=value_with_derivatives(f2.x, &f2.yp, &f2.ypp); increment_evaluations(); for(int i=0; i const c2_##X c2_##X::X=c2_##X(); c2_singleton(sin) c2_singleton(cos) c2_singleton(tan) c2_singleton(log) c2_singleton(exp) c2_singleton(sqrt) c2_singleton(identity) // reciprocal is actually parametric (a/x), but make singleton 1/x template const c2_recip c2_recip::recip=c2_recip(1.0); #undef c2_singleton // generate a sampling grid at points separated by dx=5, which is intentionally // incommensurate with pi and 2*pi so grid errors are somewhat randomized template std::vector &c2_sin::get_sampling_grid(float_type xmin, float_type xmax) { std::vector *result=new std::vector; for(; xmin < xmax; xmin+=5.0) result->push_back(xmin); result->push_back(xmax); this->preen_sampling_grid(result); return *result; } template float_type Identity(float_type x) { return x; } // a useful function template float_type f_one(float_type) { return 1.0; } // the first derivative of identity template float_type f_zero(float_type) { return 0.0; } // the second derivative of identity // The constructor template void interpolating_function::init( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope, float_type (*inputXConversion)(float_type), float_type (*inputXConversionPrime)(float_type), float_type (*inputXConversionDPrime)(float_type), float_type (*inputYConversion)(float_type), float_type (*inputYConversionPrime)(float_type), float_type (*inputYConversionDPrime)(float_type), float_type (*outputYConversion)(float_type) ) throw(c2_exception) { X= x; F= f; // Xraw is useful in some of the arithmetic operations between interpolating functions Xraw=x; set_domain(std::min(Xraw.front(), Xraw.back()),std::max(Xraw.front(), Xraw.back())); fXin=inputXConversion; fXinPrime=inputXConversionPrime; fXinDPrime=inputXConversionDPrime; fYin=inputYConversion; fYinPrime=inputYConversionPrime; fYinDPrime=inputYConversionDPrime; fYout=outputYConversion; if(x.size() != f.size()) { throw c2_exception("interpolating_function::init() -- x & y inputs are of different size"); } size_t np=X.size(); // they are the same now, so lets take a short cut if(np < 2) { throw c2_exception("interpolating_function::init() -- input < 2 elements "); } bool xraw_rev=check_monotonicity(Xraw, "interpolating_function::init() non-monotonic raw x input"); // which way does raw X point? sampling grid MUST be increasing if(!xraw_rev) { // we can use pointer to raw X values if they are in the right order set_sampling_grid_pointer(Xraw); // our intial grid of x values is certainly a good guess for 'interesting' points } else { set_sampling_grid(Xraw); // make a copy of it, and assure it is in right order } if(fXin) { // check if X scale is nonlinear, and if so, do transform if(!lowerSlopeNatural) lowerSlope /= fXinPrime(X[0]); if(!upperSlopeNatural) upperSlope /= fXinPrime(X[np-1]); for(size_t i=0; i; fXinPrime=f_one; fXinDPrime=f_zero; } if(inputYConversion) { // check if Y scale is nonlinear, and if so, do transform if(!lowerSlopeNatural) lowerSlope *= fYinPrime(F[0]); if(!upperSlopeNatural) upperSlope *= fYinPrime(F[np-1]); for(size_t i=0; i; fYinPrime=f_one; fYinDPrime=f_zero; fYout=Identity; } xInverted=check_monotonicity(X, "interpolating_function::init() non-monotonic transformed x input"); // construct spline tables here. // this code is a re-translation of the pythonlabtools spline algorithm from pythonlabtools.sourceforge.net std::vector u(np), dy(np-1), dx(np-1), dxi(np-1), dx2i(np-2), siga(np-2), dydx(np-1); std::transform(X.begin()+1, X.end(), X.begin(), dx.begin(), std::minus() ); // dx=X[1:] - X [:-1] for(size_t i=0; i() ); // dy = F[i+1]-F[i] std::transform(dx2i.begin(), dx2i.end(), dx.begin(), siga.begin(), std::multiplies()); // siga = dx[:-1]*dx2i std::transform(dxi.begin(), dxi.end(), dy.begin(), dydx.begin(), std::multiplies()); // dydx=dy/dx // u[i]=(y[i+1]-y[i])/float(x[i+1]-x[i]) - (y[i]-y[i-1])/float(x[i]-x[i-1]) std::transform(dydx.begin()+1, dydx.end(), dydx.begin(), u.begin()+1, std::minus() ); // incomplete rendering of u = dydx[1:]-dydx[:-1] y2.resize(np,0.0); if(lowerSlopeNatural) { y2[0]=u[0]=0.0; } else { y2[0]= -0.5; u[0]=(3.0*dxi[0])*(dy[0]*dxi[0] -lowerSlope); } for(size_t i=1; i < np -1; i++) { // the inner loop float_type sig=siga[i-1]; float_type p=sig*y2[i-1]+2.0; y2[i]=(sig-1.0)/p; u[i]=(6.0*u[i]*dx2i[i-1] - sig*u[i-1])/p; } float_type qn, un; if(upperSlopeNatural) { qn=un=0.0; } else { qn= 0.5; un=(3.0*dxi[dxi.size()-1])*(upperSlope- dy[dy.size()-1]*dxi[dxi.size()-1] ); } y2[np-1]=(un-qn*u[np-2])/(qn*y2[np-2]+1.0); for (size_t k=np-1; k != 0; k--) y2[k-1]=y2[k-1]*y2[k]+u[k-1]; lastKLow=-1; // flag new X search required for next evaluation } // This function is the reason for this class to exist // it computes the interpolated function, and (if requested) its proper first and second derivatives including all coordinate transforms template float_type interpolating_function::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2) const throw(c2_exception) { if(x < this->xmin() || x > this->xmax()) { std::ostringstream outstr; outstr << "Interpolating function argument " << x << " out of range " << this->xmin() << " -- " << this ->xmax() << ": bailing"; throw c2_exception(outstr.str().c_str()); } float_type xraw=x; // template here is impossible! if(fXin && fXin != (Identity) ) x=fXin(x); // save time by explicitly testing for identity function here int klo=0, khi=X.size()-1; if(!xInverted) { // select search depending on whether transformed X is increasing or decreasing if(lastKLow >=0 && (X[lastKLow] <= x) && (X[lastKLow+1] >= x) ) { // already bracketed klo=lastKLow; } else if(lastKLow >=0 && (X[lastKLow+1] <= x) && (X[lastKLow+2] > x)) { // in next bracket to the right klo=lastKLow+1; } else if(lastKLow > 0 && (X[lastKLow-1] <= x) && (X[lastKLow] > x)) { // in next bracket to the left klo=lastKLow-1; } else { // not bracketed, not close, start over // search for new KLow while(khi-klo > 1) { int km=(khi+klo)/2; if(X[km] > x) khi=km; else klo=km; } } } else { if(lastKLow >=0 && (X[lastKLow] >= x) && (X[lastKLow+1] <= x) ) { // already bracketed klo=lastKLow; } else if(lastKLow >=0 && (X[lastKLow+1] >= x) && (X[lastKLow+2] < x)) { // in next bracket to the right klo=lastKLow+1; } else if(lastKLow > 0 && (X[lastKLow-1] >= x) && (X[lastKLow] < x)) { // in next bracket to the left klo=lastKLow-1; } else { // not bracketed, not close, start over // search for new KLow while(khi-klo > 1) { int km=(khi+klo)/2; if(X[km] < x) khi=km; else klo=km; } } } khi=klo+1; lastKLow=klo; float_type h=X[khi]-X[klo]; float_type a=(X[khi]-x)/h; float_type b=1.0-a; float_type ylo=F[klo], yhi=F[khi], y2lo=y2[klo], y2hi=y2[khi]; float_type y=a*ylo+b*yhi+((a*a*a-a)*y2lo+(b*b*b-b)*y2hi)*(h*h)/6.0; // template here is impossible! if(fYin && fYin != Identity) y=fYout(y); // save time by explicitly testing for identity function here if(yprime || yprime2) { float_type fpi=1.0/fYinPrime(y); float_type gp=fXinPrime(xraw); float_type yp0=(yhi-ylo)/h+((3*b*b-1)*y2hi-(3*a*a-1)*y2lo)*h/6.0; // the derivative in interpolating table coordinates // from Mathematica Dt[InverseFunction[f][y[g[x]]], x] if(yprime) *yprime=gp*yp0*fpi; // the real derivative of the inverse transformed output if(yprime2) { float_type ypp0=b*y2hi+a*y2lo; float_type fpp=fYinDPrime(y); float_type gpp=fXinDPrime(xraw); // also from Mathematica Dt[InverseFunction[f][y[g[x]]], {x,2}] if(yprime2) *yprime2=(gp*gp*ypp0 + yp0*gpp - gp*gp*yp0*yp0*fpp*fpi*fpi)*fpi; } } return y; } template void interpolating_function::set_lower_extrapolation(float_type bound) { int kl = 0 ; int kh=kl+1; float_type xx=fXin(bound); float_type h0=X[kh]-X[kl]; float_type h1=xx-X[kl]; float_type yextrap=F[kl]+((F[kh]-F[kl])/h0 - h0*(y2[kl]+2.0*y2[kh])/6.0)*h1+y2[kl]*h1*h1/2.0; X.insert(X.begin(), xx); F.insert(F.begin(), yextrap); y2.insert(y2.begin(), y2.front()); // duplicate first or last element Xraw.insert(Xraw.begin(), bound); if (bound < this->fXMin) this->fXMin=bound; // check for reversed data else this->fXMax=bound; //printf("%10.4f %10.4f %10.4f %10.4f %10.4f\n", bound, xx, h0, h1, yextrap); //for(int i=0; i void interpolating_function::set_upper_extrapolation(float_type bound) { int kl = X.size()-2 ; int kh=kl+1; float_type xx=fXin(bound); float_type h0=X[kh]-X[kl]; float_type h1=xx-X[kl]; float_type yextrap=F[kl]+((F[kh]-F[kl])/h0 - h0*(y2[kl]+2.0*y2[kh])/6.0)*h1+y2[kl]*h1*h1/2.0; X.insert(X.end(), xx); F.insert(F.end(), yextrap); y2.insert(y2.end(), y2.back()); // duplicate first or last element Xraw.insert(Xraw.end(), bound); if (bound < this->fXMin) this->fXMin=bound; // check for reversed data else this->fXMax=bound; //printf("%10.4f %10.4f %10.4f %10.4f %10.4f\n", bound, xx, h0, h1, yextrap); //for(int i=0; i void interpolating_function::localize_derivatives( float_type xraw, float_type y, float_type yp, float_type ypp, float_type *y0, float_type *yprime, float_type *yprime2) const { float_type fp=fYinPrime(y); float_type gp=fXinPrime(xraw); float_type fpp=fYinDPrime(y); float_type gpp=fXinDPrime(xraw); if(y0) *y0=fYin(y); if(yprime) *yprime=yp*fp/gp; // Mathematica Dt[f[y[InverseFunction[g][x]]], x] if(yprime2) *yprime2=( yp*yp*fpp - fp*yp*gpp/gp + fp*ypp )/(gp*gp) ; // Mathematica Dt[f[y[InverseFunction[g][x]]], {x,2}] } // return a new interpolating_function which is the unary function of an existing interpolating_function // can also be used to generate a resampling of another c2_function on a different grid // by creating a=interpolating_function(x,x) // and doing b=a.unary_operator(c) where c is a c2_function (probably another interpolating_function) template interpolating_function& interpolating_function::unary_operator(const c2_function &source) const { size_t np=X.size(); std::vectoryv(np); c2_composed_function comp(source, *this); float_type yp0, yp1, ypp; for(size_t i=0; i void interpolating_function::get_data(std::vector &xvals, std::vector &yvals) const throw() { xvals=Xraw; yvals.resize(F.size()); for(size_t i=0; i interpolating_function & interpolating_function::binary_operator(const c2_function &rhs, c2_binary_function *combining_stub) const { size_t np=X.size(); std::vector yv(np); c2_constant fval; c2_constant yval; float_type yp0, yp1, ypp; for(size_t i=0; i float_type c2_f_logprime(float_type x) { return 1.0/x; } // the derivative of log(x) template float_type c2_f_logprime2(float_type x) { return -1.0/(x*x); } // the second derivative of log(x) template log_lin_interpolating_function::log_lin_interpolating_function( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope) : interpolating_function() { init(x, f, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, (float_type (*)(float_type)) (std::log), c2_f_logprime, c2_f_logprime2, 0, 0, 0, 0); } template lin_log_interpolating_function::lin_log_interpolating_function( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope) : interpolating_function() { init(x, f, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, 0, 0, 0, (float_type (*)(float_type)) (std::log), c2_f_logprime, c2_f_logprime2, (float_type (*)(float_type)) (std::exp) ); } template log_log_interpolating_function::log_log_interpolating_function( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope) : interpolating_function() { init(x, f, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, (float_type (*)(float_type)) (std::log), c2_f_logprime, c2_f_logprime2, (float_type (*)(float_type)) (std::log), c2_f_logprime, c2_f_logprime2, (float_type (*)(float_type)) (std::exp) ); } template float_type c2_f_recip(float_type x) { return 1.0/x; } template float_type c2_f_recipprime(float_type x) { return -1.0/(x*x); } // the derivative of 1/x template float_type c2_f_recipprime2(float_type x) { return 2.0/(x*x*x); } // the second derivative of 1/x template arrhenius_interpolating_function::arrhenius_interpolating_function( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope) : interpolating_function() { init(x, f, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, c2_f_recip, c2_f_recipprime, c2_f_recipprime2, (float_type (*)(float_type)) (std::log), c2_f_logprime, c2_f_logprime2, (float_type (*)(float_type)) (std::exp) ); } template c2_inverse_function::c2_inverse_function(const c2_function &source) : c2_plugin_function(source) { float_type l=source.xmin(); float_type r=source.xmax(); start_hint=(l+r)*0.5; // guess that we start in the middle // compute our domain assuming the function is monotonic so its values on its domain boundaries are our domain float_type ly=source(l); float_type ry=source(r); if (ly > ry) { float_type t=ly; ly=ry; ry=t; } set_domain(ly, ry); } template float_type c2_inverse_function::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2 ) const throw(c2_exception) { float_type l=this->func->xmin(); float_type r=this->func->xmax(); float_type yp, ypp; float_type y=this->func->find_root(l, r, get_start_hint(x), x, 0, &yp, &ypp); start_hint=y; if(yprime) *yprime=1.0/yp; if(yprime2) *yprime2=-ypp/(yp*yp*yp); return y; } //accumulated_histogram starts with binned data, generates the integral, and generates a piecewise linear interpolating_function //If drop_zeros is true, it merges empty bins together before integration //Note that the resulting interpolating_function is guaranteed to be increasing (if drop_zeros is false) // or stricly increasing (if drop_zeros is true) //If inverse_function is true, it drop zeros, integrates, and returns the inverse function which is useful // for random number generation based on the input distribution. //If normalize is true, the big end of the integral is scaled to 1. //If the data are passed in reverse order (large X first), the integral is carried out from the big end, // and then the data are reversed to the result in in increasing X order. template accumulated_histogram::accumulated_histogram( const std::vectorbinedges, const std::vector binheights, bool normalize, bool inverse_function, bool drop_zeros) { int np=binheights.size(); std::vector be, bh; if(drop_zeros || inverse_function) { //inverse functions cannot have any zero bins or they have vertical sections if(binheights[0] || !inverse_function) { // conserve lower x bound if not an inverse function be.push_back(binedges[0]); bh.push_back(binheights[0]); } for(int i=1; i cum(np+1, 0.0); for(int i=1; i<=np; i++) cum[i]=bh[i]*(be[i]-be[i-1])+cum[i-1]; // accumulate bins, leaving bin 0 as 0 if(be[1] < be[0]) { // if bins passed in backwards, reverse them std::reverse(be.begin(), be.end()); std::reverse(cum.begin(), cum.end()); for(unsigned int i=0; i(cum, be); // use cum as x axis in inverse function else interpolating_function(be, cum); // else use lower bin edge as x axis std::fill(this->y2.begin(), this->y2.end(), 0.0); // clear second derivatives, to we are piecewise linear } template c2_piecewise_function::c2_piecewise_function() : c2_function(), lastKLow(-1) { this->sampling_grid=new std::vector; // this always has a smapling grid } template c2_piecewise_function::~c2_piecewise_function() { size_t np=functions.size(); for(size_t i=0; i float_type c2_piecewise_function::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2 ) const throw(c2_exception) { size_t np=functions.size(); if(!np) throw c2_exception("attempting to evaluate an empty piecewise function"); if(x < this->xmin() || x > this->xmax()) { std::ostringstream outstr; outstr << "piecewise function argument " << x << " out of range " << this->xmin() << " -- " << this->xmax(); throw c2_exception(outstr.str().c_str()); } int klo=0; if(lastKLow >= 0 && functions[lastKLow]->xmin() <= x && functions[lastKLow]->xmax() > x) { klo=lastKLow; } else { int khi=np; while(khi-klo > 1) { int km=(khi+klo)/2; if(functions[km]->xmin() > x) khi=km; else klo=km; } } lastKLow=klo; return functions[klo]->value_with_derivatives(x, yprime, yprime2); } template void c2_piecewise_function::append_function( c2_function &func, bool pass_ownership) throw(c2_exception) { if(functions.size()) { // check whether there are any gaps to fill, etc. c2_function &tail=*(functions.back()); float_type x0=tail.xmax(); float_type x1=func.xmin(); if(x0 < x1) { // must insert a connector if x0 < x1 float_type y0=tail(x0); float_type y1=func(x1); c2_function *connector=new c2_linear(x0, y0, (y1-y0)/(x1-x0)); connector->set_domain(x0,x1); functions.push_back(connector); owns.push_back(true); this->sampling_grid->push_back(x1); } else if(x0>x1) throw c2_exception("function domains not increasing in c2_piecewise_function"); } functions.push_back(&func); owns.push_back(pass_ownership); // extend our domain to include all known functions this->set_domain(functions.front()->xmin(), functions.back()->xmax()); // extend our sampling grid with the new function's grid, with the first point dropped to avoid duplicates std::vector &newgrid=func.get_sampling_grid(func.xmin(), func.xmax()); this->sampling_grid->insert(this->sampling_grid->end(), newgrid.begin()+1, newgrid.end()); delete &newgrid; } template c2_connector_function::c2_connector_function( const c2_function &f1, const c2_function &f2, float_type x0, float_type x2, bool auto_center, float_type y1) : c2_function() { float_type y0, yp0, ypp0, y2, yp2, ypp2; fdx=(x2-x0)/2.0; fhinv=1.0/fdx; fx1=(x0+x2)/2.0; y0=f1.value_with_derivatives(x0, &yp0, &ypp0); // get left wall values from conventional computation y2=f2.value_with_derivatives(x2, &yp2, &ypp2); // get right wall values from conventional computation // scale derivs to put function on [-1,1] since mma solution is done this way yp0*=fdx; yp2*=fdx; ypp0*=fdx*fdx; ypp2*=fdx*fdx; float_type ff0=(8*(y0 + y2) + 5*(yp0 - yp2) + ypp0 + ypp2)/16.0; if(auto_center) y1=ff0; // forces ff to be 0 if we are auto-centering // y[x_] = y1 + x (a + b x) + (x-1) x (x+1) (c + d x + e x^2 + f x^3) fy1=y1; fa=-(y0 - y2)/2.; fb=(y0 - 2*y1 + y2)/2.; fc=(7*(y0 - y2 + yp0 + yp2) + ypp0 - ypp2)/16.; fd=(32*y1 - 16*(y2 + y0) + 9*(yp2 - yp0) - ypp0 - ypp2)/16.; fe=(3*(y2 - y0 - yp0 - yp2) - ypp0 + ypp2)/16.; ff=(ff0 - y1); // y'[x] = a + 2 b x + (3x^2 - 1) (c + d x + e x^2 + f x^3) + (x-1) x (x+1) (d + 2 e x + 3 f x^2 ) // y''[x] = 2b + (x-1) x (x+1) (2 e + 6 f x) + 2 (3 x^2 -1) (d + 2 e x + 3 f x^2 ) + 6 x (c + d x + e x^2 + f x^3) this->set_domain(x0,x2); // this is where the function is valid } template c2_connector_function::~c2_connector_function() { } template float_type c2_connector_function::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2 ) const throw(c2_exception) { float_type dx=(x-fx1)*fhinv; float_type q1=fc + dx*(fd + dx*(fe + dx*ff)); float_type xp1=(dx-1)*(dx+1)*dx; float_type y= fy1 + dx*(fa+fb*dx) + xp1*q1; if(yprime || yprime2) { float_type q2 =fd + dx*(2*fe + dx*3*ff); float_type q3=2*fe+6*ff*dx; float_type xp2=(3*dx*dx-1); if(yprime) *yprime=(fa + 2*fb*dx + xp2*q1 + xp1*q2)*fhinv; if(yprime2) *yprime2=(2*fb+xp1*q3+2*xp2*q2+6*dx*q1)*fhinv*fhinv; } return y; }