// // ******************************************************************** // * License and Disclaimer * // * * // * The Geant4 software is copyright of the Copyright Holders of * // * the Geant4 Collaboration. It is provided under the terms and * // * conditions of the Geant4 Software License, included in the file * // * LICENSE and available at http://cern.ch/geant4/license . These * // * include a list of copyright holders. * // * * // * Neither the authors of this software system, nor their employing * // * institutes,nor the agencies providing financial support for this * // * work make any representation or warranty, express or implied, * // * regarding this software system or assume any liability for its * // * use. Please see the license in the file LICENSE and URL above * // * for the full disclaimer and the limitation of liability. * // * * // * This code implementation is the result of the scientific and * // * technical work of the GEANT4 collaboration. * // * By using, copying, modifying or distributing the software (or * // * any work based on the software) you agree to acknowledge its * // * use in resulting scientific publications, and indicate your * // * acceptance of all terms of the Geant4 Software license. * // ******************************************************************** // // /** * \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.169 2008/05/22 12:45:19 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.169 2008/05/22 12:45:19 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 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; if(!root_info) { root_info=new struct c2_root_info; root_info->inited=false; } // 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(!root_info->inited || upper_bracket != root_info->upper.x || lower_bracket != root_info->lower.x) { root_info->upper.x=upper_bracket; fill_fblock(root_info->upper); root_info->lower.x=lower_bracket; fill_fblock(root_info->lower); root_info->inited=true; } float_type clower=root_info->lower.y-value; if(!clower) { *final_yprime=root_info->lower.yp; *final_yprime2=root_info->lower.ypp; return lower_bracket; } float_type cupper=root_info->upper.y-value; if(!cupper) { *final_yprime=root_info->upper.yp; *final_yprime2=root_info->upper.ypp; return upper_bracket; } const float_type lower_sign = (clower < 0) ? -1 : 1; if(lower_sign*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 if(c2_isnan(c)) { bad_x_point=root; return c; // return the nan if a computation failed } b=*final_yprime; // make a local copy for readability increment_evaluations(); // now, close in bracket on whichever side this still brackets if(c*lower_sign < 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 throw(c2_exception) { std::vector< recur_item > &rb_stack=*rb.rb_stack; // heap-based stack of data for recursion rb_stack.clear(); recur_item top; top.depth=0; top.done=false; top.f0index=0; top.f2index=0; top.step_sum=0; // push storage for our initial elements rb_stack.push_back(top); rb_stack.back().f1=*rb.f0; rb_stack.back().done=true; // this element will never be evaluated further rb_stack.push_back(top); rb_stack.back().f1=*rb.f1; rb_stack.back().done=true; // this element will never be evaluated further if(!rb.inited) { 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); rb.dx_tolerance=10.0*std::numeric_limits::epsilon(); rb.abs_tol_min=10.0*std::numeric_limits::min(); rb.inited=true; } // now, push our first real element top.f0index=0; // left element is stack[0] top.f2index=1; // right element is stack[1] top.abs_tol=rb.abs_tol; rb_stack.push_back(top); while(rb_stack.size() > 2) { recur_item &back=rb_stack.back(); if(back.done) { float_type sum=back.step_sum; rb_stack.pop_back(); rb_stack.back().step_sum+=sum; // bump our sum up to the parent continue; } back.done=true; c2_fblock &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1; c2_fblock &f1=back.f1; // will hold new middle values size_t f1index=rb_stack.size()-1; // our current offset float_type abs_tol=back.abs_tol; f1.x=0.5*(f0.x + f2.x); // center of interval float_type dx2=0.5*(f2.x - f0.x); // check for underflow on step size, which prevents us from achieving specified accuracy. if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance || std::abs(dx2) < rb.abs_tol_min) { std::ostringstream outstr; outstr << "Step size underflow in adaptive_partial_integrals at depth=" << back.depth << ", x= " << f1.x; throw c2_exception(outstr.str().c_str()); } fill_fblock(f1); if(c2_isnan(f1.y)) { bad_x_point=f1.x; return f1.y; // can't go any further if a nan has appeared } bool yptrouble=f0.ypbad || f2.ypbad || f1.ypbad; bool ypptrouble=f0.yppbad || f2.yppbad || f1.yppbad; // select the real derivative count based on whether we are at a point where derivatives exist int derivs = std::min(rb.derivs, (yptrouble||ypptrouble)?(yptrouble?0:1):2); if(!back.depth) { // top level, total has not been initialized yet switch(derivs) { // create estimate of next lower order for first try case 0: back.previous_estimate=(f0.y+f2.y)*dx2; break; case 1: back.previous_estimate=(f0.y+4.0*f1.y+f2.y)*dx2/3.0; break; case 2: back.previous_estimate=( (14*f0.y + 32*f1.y + 14*f2.y) + 2*dx2 * (f0.yp - f2.yp) ) * dx2 /30.; break; default: back.previous_estimate=0.0; // just to suppress missing default warnings } } float_type left, right; // pre-compute constants so all multiplies use a small dynamic range // constants for 0 derivative integrator static const float_type c0c1=5./12., c0c2=8./12., c0c3=-1./12.; // constants for 1 derivative integrator static const float_type c1c1=101./240., c1c2=128./240., c1c3=11./240., c1c4=13./240., c1c5=-40./240., c1c6=-3./240.; // constants for 2 derivative integrator static const float_type c2c1=169./40320., c2c2=1024./ 40320., c2c3=-41./40320., c2c4=2727./40320., c2c5=-5040./40320., c2c6=423./40320., c2c7=17007./40320., c2c8=24576./40320., c2c9=-1263./40320.; switch(derivs) { case 2: // use ninth-order estimates for each side, from full set of all values (!) (Thanks, Mathematica!) left= ( ( (c2c1*f0.ypp + c2c2*f1.ypp + c2c3*f2.ypp)*dx2 + (c2c4*f0.yp + c2c5*f1.yp + c2c6*f2.yp) )*dx2 + (c2c7*f0.y + c2c8*f1.y + c2c9*f2.y) )* dx2; right= ( ( (c2c1*f2.ypp + c2c2*f1.ypp + c2c3*f0.ypp)*dx2 - (c2c4*f2.yp + c2c5*f1.yp + c2c6*f0.yp) )*dx2 + (c2c7*f2.y + c2c8*f1.y + c2c9*f0.y) )* dx2; // 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= ( (c1c1*f0.y + c1c2*f1.y + c1c3*f2.y) + dx2*(c1c4*f0.yp + c1c5*f1.yp + c1c6*f2.yp) ) * dx2 ; right= ( (c1c1*f2.y + c1c2*f1.y + c1c3*f0.y) - dx2*(c1c4*f2.yp + c1c5*f1.yp + c1c6*f0.yp) ) * dx2 ; break; case 0: left= (c0c1*f0.y + c0c2*f1.y + c0c3*f2.y)*dx2; right= (c0c1*f2.y + c0c2*f1.y + c0c3*f0.y)*dx2; break; default: left=right=0.0; // suppress warnings about missing default break; } float_type lrsum=left+right; bool extrapolate=back.depth && rb.extrapolate && (derivs==rb.derivs); // only extrapolate if no trouble with derivs float_type eps=std::abs(back.previous_estimate-lrsum)*rb.eps_scale; if(extrapolate) eps*=rb.eps_scale; if(rb.adapt && eps > abs_tol && eps > std::abs(lrsum)*rb.rel_tol) { // tolerance not met, subdivide & recur if(abs_tol > rb.abs_tol_min) abs_tol=abs_tol*0.5; // each half has half the error budget top.abs_tol=abs_tol; top.depth=back.depth+1; // save the last things we need from back before a push happens, in case // the push causes a reallocation and moves the whole stack. size_t f0index=back.f0index, f2index=back.f2index; top.f0index=f1index; top.f2index=f2index; // insert pointers to right side data into our recursion block top.previous_estimate=right; rb_stack.push_back(top); top.f0index=f0index; top.f2index=f1index; // insert pointers to left side data into our recursion block top.previous_estimate=left; rb_stack.push_back(top); } else if(extrapolate) { // extrapolation only happens on leaf nodes, where the tolerance was met. back.step_sum+=(rb.extrap_coef*lrsum - back.previous_estimate)*rb.extrap2; } else { back.step_sum+=lrsum; } } return rb_stack.back().step_sum; // last element on the stack holds the sum } template bool c2_function::check_monotonicity( const std::vector &data, const char message[]) const 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 void c2_function:: get_sampling_grid(float_type xmin, float_type xmax, std::vector &grid) const { std::vector *result=&grid; result->clear(); 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); } } 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 void c2_function:: refine_sampling_grid(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(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 throw(c2_exception) { if(xmin==xmax) { if(partials) partials->clear(); return 0.0; } std::vector grid; get_sampling_grid(xmin, xmax, grid); float_type intg=partial_integrals(grid, partials, abs_tol, rel_tol, derivs, adapt, extrapolate); return intg; } template c2_function &c2_function::normalized_function(float_type xmin, float_type xmax, float_type norm) const throw(c2_exception) { float_type intg=integral(xmin, xmax); return *new c2_scaled_function_p(*this, norm/intg); } template c2_function &c2_function::square_normalized_function(float_type xmin, float_type xmax, float_type norm) const throw(c2_exception) { c2_ptr mesquared((*new c2_quadratic_p(0., 0., 0., 1.))(*this)); std::vector grid; get_sampling_grid(xmin, xmax, grid); float_type intg=mesquared->partial_integrals(grid); return *new c2_scaled_function_p(*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) const throw(c2_exception) { c2_ptr weighted((*new c2_quadratic_p(0., 0., 0., 1.))(*this) * weight); std::vector grid; get_sampling_grid(xmin, xmax, grid); float_type intg=weighted->partial_integrals(grid); return *new c2_scaled_function_p(*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 throw(c2_exception) { int np=xgrid.size(); c2_fblock f0, f2; struct c2_integrate_recur rb; rb.rel_tol=rel_tol; rb.extrapolate=extrapolate; rb.adapt=adapt; rb.derivs=derivs; std::vector< recur_item > rb_stack; rb_stack.reserve(20); // enough for most operations rb.rb_stack=&rb_stack; rb.inited=false; float_type dx_inv=1.0/std::abs(xgrid.back()-xgrid.front()); if(partials) partials->resize(np-1); float_type sum=0.0; f2.x=xgrid[0]; fill_fblock(f2); if(c2_isnan(f2.y)) { bad_x_point=f2.x; return f2.y; // can't go any further if a nan has appeared } for(int i=0; i void c2_sin_p:: get_sampling_grid(float_type xmin, float_type xmax, std::vector &grid) const { grid.clear(); for(; xmin < xmax; xmin+=5.0) grid.push_back(xmin); grid.push_back(xmax); this->preen_sampling_grid(&grid); } template float_type c2_function_transformation::evaluate( float_type xraw, float_type y, float_type yp0, float_type ypp0, float_type *yprime, float_type *yprime2) const { y=Y.fHasStaticTransforms ? Y.pOut(y) : Y.fOut(y); if(yprime || yprime2) { float_type yp, yp2; if(X.fHasStaticTransforms && Y.fHasStaticTransforms) { float_type fpi=1.0/Y.pInPrime(y); float_type gp=X.pInPrime(xraw); // from Mathematica Dt[InverseFunction[f][y[g[x]]], x] yp=gp*yp0*fpi; // transformed derivative yp2=(gp*gp*ypp0 + X.pInDPrime(xraw)*yp0 - Y.pInDPrime(y)*yp*yp )*fpi; } else { float_type fpi=1.0/Y.fInPrime(y); float_type gp=X.fInPrime(xraw); // from Mathematica Dt[InverseFunction[f][y[g[x]]], x] yp=gp*yp0*fpi; // transformed derivative yp2=(gp*gp*ypp0 + X.fInDPrime(xraw)*yp0 - Y.fInDPrime(y)*yp*yp )*fpi; } if(yprime) *yprime=yp; if(yprime2) *yprime2=yp2; } return y; } // The constructor template interpolating_function_p & interpolating_function_p::load( const std::vector &x, const std::vector &f, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope, bool splined ) throw(c2_exception) { c2_ptr keepme(*this); 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())); 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(fTransform.X.fTransformed) { // check if X scale is nonlinear, and if so, do transform if(!lowerSlopeNatural) lowerSlope /= fTransform.X.fInPrime(X[0]); if(!upperSlopeNatural) upperSlope /= fTransform.X.fInPrime(X[np-1]); for(size_t i=0; i interpolating_function_p & interpolating_function_p::load_pairs( std::vector > &data, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope, bool splined ) throw(c2_exception) { c2_ptr keepme(*this); size_t np=data.size(); if(np < 2) { throw c2_exception("interpolating_function::init() -- input < 2 elements "); } // sort into ascending order std::sort(data.begin(), data.end(), comp_pair); std::vector xtmp, ytmp; xtmp.reserve(np); ytmp.reserve(np); for (size_t i=0; iload(xtmp, ytmp, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, splined); keepme.release_for_return(); return *this; } template interpolating_function_p & interpolating_function_p::load_random_generator_function( const std::vector &bincenters, const c2_function &binheights) throw(c2_exception) { c2_ptr keepme(*this); std::vector integral; c2_const_ptr keepit(binheights); // manage function... not really needed here, but always safe. // integrate from first to last bin in original order, leaving results in integral // ask for relative error of 1e-6 on each bin, with absolute error set to 0 (since we don't know the data scale). float_type sum=binheights.partial_integrals(bincenters, &integral, 0.0, 1e-6); // the integral vector now has partial integrals... it must be accumulated by summing integral.insert(integral.begin(), 0.0); // integral from start to start is 0 float_type scale=1.0/sum; for(size_t i=1; iload(integral, bincenters, false, 1.0/(scale*binheights(bincenters.front() )), false, 1.0/(scale*binheights(bincenters.back() )) ); // use integral as x axis in inverse function keepme.release_for_return(); return *this; } template interpolating_function_p & interpolating_function_p::load_random_generator_bins( const std::vector &bins, const std::vector &binheights) throw(c2_exception) { c2_ptr keepme(*this); size_t np=binheights.size(); std::vector integral(np+1), bin_edges(np+1); // compute the integral based on estimates of the bin edges from the given bin centers... // except for bin 0 & final bin, the edge of a bin is halfway between then center of the // bin and the center of the previous/next bin. // This gives width[n] = (center[n+1]+center[n])/2 - (center[n]+center[n-1])/2 = (center[n+1]-center[n-1])/2 // for the edges, assume a bin of width (center[1]-center[0]) or (center[np-1]-center[np-2]) // be careful that absolute values are used in case data are reversed. if(bins.size() == binheights.size()+1) { bin_edges=bins; // edges array was passed in } else if (bins.size() == binheights.size()) { bin_edges.front()=bins[0] - (bins[1]-bins[0])*0.5; // edge bin for(size_t i=1; iload(integral, bin_edges, false, 1.0/(scale*binheights.front()), false, 1.0/(scale*binheights.back()) ); // use integral as x axis in inverse function keepme.release_for_return(); return *this; } */ // The spline table generator template void interpolating_function_p::spline( bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope ) throw(c2_exception) { // construct spline tables here. // this code is a re-translation of the pythonlabtools spline algorithm from pythonlabtools.sourceforge.net size_t np=X.size(); 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]; } template interpolating_function_p &interpolating_function_p::sample_function( const c2_function &func, float_type xmin, float_type xmax, float_type abs_tol, float_type rel_tol, bool lowerSlopeNatural, float_type lowerSlope, bool upperSlopeNatural, float_type upperSlope ) throw(c2_exception) { c2_ptr keepme(*this); const c2_transformation &XX=fTransform.X, &YY=fTransform.Y; // shortcuts // set up our params to look like the samplng function for now sampler_function=func; std::vector grid; func.get_sampling_grid(xmin, xmax, grid); size_t gsize=grid.size(); if(XX.fTransformed) for(size_t i=0; iadaptively_sample(grid.front(), grid.back(), 8*abs_tol, 8*rel_tol, 0, &X, &F); // clear the sampler function now, since otherwise our value_with_derivatives is broken sampler_function.unset_function(); xInverted=check_monotonicity(X, "interpolating_function::init() non-monotonic transformed x input"); size_t np=X.size(); // Xraw is useful in some of the arithmetic operations between interpolating functions if(!XX.fTransformed) Xraw=X; else { Xraw.resize(np); for (size_t i=1; iset_domain(xmin, xmax); spline(lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope); lastKLow=0; keepme.release_for_return(); return *this; } // 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_p::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2) const throw(c2_exception) { if(sampler_function.valid()) { // if this is non-null, we are sampling data for later, so just return raw function // however, transform it into our sampling space, first. if(yprime) *yprime=0; if(yprime2) *yprime2=0; sampler_function->increment_evaluations(); return fTransform.Y.fIn(sampler_function(fTransform.X.fOut(x))); // derivatives are completely undefined } 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; if(fTransform.X.fTransformed) x=fTransform.X.fHasStaticTransforms? fTransform.X.pIn(x) : fTransform.X.fIn(x); // save time by explicitly testing for identity function here int klo=0, khi=X.size()-1; if(khi < 0) throw c2_exception("Uninitialized interpolating function being evaluated"); const float_type *XX=&X[lastKLow]; // make all fast checks short offsets from here if(!xInverted) { // select search depending on whether transformed X is increasing or decreasing if((XX[0] <= x) && (XX[1] >= x) ) { // already bracketed klo=lastKLow; } else if((XX[1] <= x) && (XX[2] >= x)) { // in next bracket to the right klo=lastKLow+1; } else if(lastKLow > 0 && (XX[-1] <= x) && (XX[0] >= 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((XX[0] >= x) && (XX[1] <= x) ) { // already bracketed klo=lastKLow; } else if((XX[1] >= x) && (XX[2] <= x)) { // in next bracket to the right klo=lastKLow+1; } else if(lastKLow > 0 && (XX[-1] >= x) && (XX[0] <= 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; float_type yp0=0; // the derivative in interpolating table coordinates float_type ypp0=0; // second derivative if(yprime || yprime2) { yp0=(yhi-ylo)/h+((3*b*b-1)*y2hi-(3*a*a-1)*y2lo)*h/6.0; // the derivative in interpolating table coordinates ypp0=b*y2hi+a*y2lo; // second derivative } if(fTransform.isIdentity) { if(yprime) *yprime=yp0; if(yprime2) *yprime2=ypp0; return y; } else return fTransform.evaluate(xraw, y, yp0, ypp0, yprime, yprime2); } template void interpolating_function_p::set_lower_extrapolation(float_type bound) { int kl = 0 ; int kh=kl+1; float_type xx=fTransform.X.fIn(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_p::set_upper_extrapolation(float_type bound) { int kl = X.size()-2 ; int kh=kl+1; float_type xx=fTransform.X.fIn(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 interpolating_function_p& interpolating_function_p::unary_operator(const c2_function &source) const { size_t np=X.size(); std::vectoryv(np); c2_ptr comp(source(*this)); float_type yp0, yp1, ypp; for(size_t i=1; iXraw, yv, false, yp0, false, yp1); return copy; } template void interpolating_function_p::get_data(std::vector &xvals, std::vector &yvals) const throw() { xvals=Xraw; yvals.resize(F.size()); for(size_t i=0; i interpolating_function_p & interpolating_function_p::binary_operator(const c2_function &rhs, const c2_binary_function *combining_stub) const { size_t np=X.size(); std::vector yv(np); c2_constant_p fval(0); float_type yp0, yp1, ypp; c2_const_ptr stub(*combining_stub); // manage ownership for(size_t i=1; icombine(fval, rhs, Xraw[i], (float_type *)0, (float_type *)0); // compute rhs & combine without derivatives } yv.front()=combining_stub->combine(*this, rhs, Xraw.front(), &yp0, &ypp); // get derivative at front yv.back()= combining_stub->combine(*this, rhs, Xraw.back(), &yp1, &ypp); // get derivative at back interpolating_function_p ©=clone(); copy.load(this->Xraw, yv, false, yp0, false, yp1); return copy; } template c2_inverse_function_p::c2_inverse_function_p(const c2_function &source) : c2_function(), func(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_p::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_p(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_p::c2_piecewise_function_p() : c2_function(), lastKLow(-1) { this->sampling_grid=new std::vector; // this always has a smapling grid } template c2_piecewise_function_p::~c2_piecewise_function_p() { } template float_type c2_piecewise_function_p::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_p::append_function( const c2_function &func) throw(c2_exception) { c2_const_ptr keepfunc(func); // manage function before we can throw any exceptions if(functions.size()) { // check whether there are any gaps to fill, etc. const 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_p(x0, y0, (y1-y0)/(x1-x0)); connector.set_domain(x0,x1); functions.push_back(c2_const_ptr(connector)); this->sampling_grid->push_back(x1); } else if(x0>x1) throw c2_exception("function domains not increasing in c2_piecewise_function"); } functions.push_back(keepfunc); // 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(), newgrid); this->sampling_grid->insert(this->sampling_grid->end(), newgrid.begin()+1, newgrid.end()); } template c2_connector_function_p::c2_connector_function_p( float_type x0, const c2_function &f0, float_type x2, const c2_function &f2, bool auto_center, float_type y1) : c2_function() { c2_const_ptr left(f0), right(f2); // make sure if these are unowned, they get deleted c2_fblock fb0, fb2; fb0.x=x0; f0.fill_fblock(fb0); fb2.x=x2; f2.fill_fblock(fb2); init(fb0, fb2, auto_center, y1); } template c2_connector_function_p::c2_connector_function_p( float_type x0, float_type y0, float_type yp0, float_type ypp0, float_type x2, float_type y2, float_type yp2, float_type ypp2, bool auto_center, float_type y1) : c2_function() { c2_fblock fb0, fb2; fb0.x=x0; fb0.y=y0; fb0.yp=yp0; fb0.ypp=ypp0; fb2.x=x2; fb2.y=y2; fb2.yp=yp2; fb2.ypp=ypp2; init(fb0, fb2, auto_center, y1); } template c2_connector_function_p::c2_connector_function_p( const c2_fblock &fb0, const c2_fblock &fb2, bool auto_center, float_type y1) : c2_function() { init(fb0, fb2, auto_center, y1); } template void c2_connector_function_p::init( const c2_fblock &fb0, const c2_fblock &fb2, bool auto_center, float_type y1) { float_type dx=(fb2.x-fb0.x)/2.0; fhinv=1.0/dx; // scale derivs to put function on [-1,1] since mma solution is done this way float_type yp0=fb0.yp*dx; float_type yp2=fb2.yp*dx; float_type ypp0=fb0.ypp*dx*dx; float_type ypp2=fb2.ypp*dx*dx; float_type ff0=(8*(fb0.y + fb2.y) + 5*(yp0 - yp2) + ypp0 + ypp2)*0.0625; if(auto_center) y1=ff0; // forces ff to be 0 if we are auto-centering // y[x_] = y1 + x (a + b x) + x [(x-1) (x+1)] (c + d x) + x (x-1)^2 (x+1)^2 (e + f x) // y' = a + 2 b x + d x [(x+1)(x-1)] + (c + d x)(3x^2-1) + f x [(x+1)(x-1)]^2 + (e + f x)[(x+1)(x-1)](5x^2-1) // y'' = 2 b + 6x(c + d x) + 2d(3x^2-1) + 4x(e + f x)(5x^2-3) + 2f(x^2-1)(5x^2-1) fy1=y1; fa=(fb2.y - fb0.y)*0.5; fb=(fb0.y + fb2.y)*0.5 - y1; fc=(yp2+yp0-2.*fa)*0.25; fd=(yp2-yp0-4.*fb)*0.25; fe=(ypp2-ypp0-12.*fc)*0.0625; ff=(ff0 - y1); this->set_domain(fb0.x, fb2.x); // this is where the function is valid } template c2_connector_function_p::~c2_connector_function_p() { } template float_type c2_connector_function_p::value_with_derivatives( float_type x, float_type *yprime, float_type *yprime2 ) const throw(c2_exception) { float_type x0=this->xmin(), x2=this->xmax(); float_type dx=(x-(x0+x2)*0.5)*fhinv; float_type q1=(x-x0)*(x-x2)*fhinv*fhinv; // exactly vanish all bits at both ends float_type q2=dx*q1; float_type r1=fa+fb*dx; float_type r2=fc+fd*dx; float_type r3=fe+ff*dx; float_type y=fy1+dx*r1+q2*r2+q1*q2*r3; if(yprime || yprime2) { float_type q3=3*q1+2; float_type q4=5*q1+4; if(yprime) *yprime=(fa+2*fb*dx+fd*q2+r2*q3+ff*q1*q2+q1*q4*r3)*fhinv; if(yprime2) *yprime2=2*(fb+fd*q3+3*dx*r2+ff*q1*q4+r3*(2*dx*(5*q1+2)))*fhinv*fhinv; } return y; } // the recursive part of the sampler is agressively designed to minimize copying of data... lots of pointers template void c2_function::sample_step(c2_sample_recur &rb) const throw(c2_exception) { std::vector< recur_item > &rb_stack=*rb.rb_stack; // heap-based stack of data for recursion rb_stack.clear(); recur_item top; top.depth=0; top.done=false; top.f0index=0; top.f2index=0; // push storage for our initial elements rb_stack.push_back(top); rb_stack.back().f1=*rb.f0; rb_stack.back().done=true; rb_stack.push_back(top); rb_stack.back().f1=*rb.f1; rb_stack.back().done=true; if(!rb.inited) { rb.dx_tolerance=10.0*std::numeric_limits::epsilon(); rb.abs_tol_min=10.0*std::numeric_limits::min(); rb.inited=true; } // now, push our first real element top.f0index=0; // left element is stack[0] top.f2index=1; // right element is stack[1] rb_stack.push_back(top); while(rb_stack.size() > 2) { recur_item &back=rb_stack.back(); if(back.done) { rb_stack.pop_back(); continue; } back.done=true; c2_fblock &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1; c2_fblock &f1=back.f1; // will hold new middle values size_t f1index=rb_stack.size()-1; // our current offset // std::cout << "processing: " << rb_stack.size() << " " << // (&back-&rb_stack.front()) << " " << back.depth << " " << f0.x << " " << f2.x << std::endl; f1.x=0.5*(f0.x + f2.x); // center of interval float_type dx2=0.5*(f2.x - f0.x); // check for underflow on step size, which prevents us from achieving specified accuracy. if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance || std::abs(dx2) < rb.abs_tol_min) { std::ostringstream outstr; outstr << "Step size underflow in adaptive_sampling at depth=" << back.depth << ", x= " << f1.x; throw c2_exception(outstr.str().c_str()); } fill_fblock(f1); if(c2_isnan(f1.y) || f1.ypbad || f1.yppbad) { // can't go any further if a nan has appeared bad_x_point=f1.x; throw c2_exception("NaN encountered while sampling function"); } float_type eps; if(rb.derivs==2) { // this is code from connector_function to compute the value at the midpoint // it is re-included here to avoid constructing a complete c2connector // just to find out if we are close enough float_type ff0=(8*(f0.y + f2.y) + 5*(f0.yp - f2.yp)*dx2 + (f0.ypp+f2.ypp)*dx2*dx2)*0.0625; // we are converging as at least x**5 and bisecting, so real error on final step is smaller eps=std::abs(ff0-f1.y)/32.0; } else { // there are two tolerances to meet... the shift in the estimate of the actual point, // and the difference between the current points and the extremum // build all the coefficients needed to construct the local parabola float_type ypcenter, ypp; if (rb.derivs==1) { // linear extrapolation error using exact derivs eps = (std::abs(f0.y+f0.yp*dx2-f1.y)+std::abs(f2.y-f2.yp*dx2-f1.y))*0.125; ypcenter=2*f1.yp*dx2; // first deriv scaled so this interval is on [-1,1] ypp=2*(f2.yp-f0.yp)*dx2*dx2; // second deriv estimate scaled so this interval is on [-1,1] } else { // linear interpolation error without derivs if we are at top level // or 3-point parabolic interpolation estimates from previous level, if available ypcenter=(f2.y-f0.y)*0.5; // derivative estimate at center ypp=(f2.y+f0.y-2*f1.y); // second deriv estimate if(back.depth==0) eps=std::abs((f0.y+f2.y)*0.5 - f1.y)*2; // penalize first step else eps=std::abs(f1.y-back.previous_estimate)*0.25; } float_type ypleft=ypcenter-ypp; // derivative at left edge float_type ypright=ypcenter+ypp; // derivative at right edge float_type extremum_eps=0; if((ypleft*ypright) <=0) // y' changes sign if we have an extremum { // compute position and value of the extremum this way float_type xext=-ypcenter/ypp; float_type yext=f1.y + xext*ypcenter + 0.5*xext*xext*ypp; // and then find the the smallest offset of it from a point, looking in the left or right side if(xext <=0) extremum_eps=std::min(std::abs(f0.y-yext), std::abs(f1.y-yext)); else extremum_eps=std::min(std::abs(f2.y-yext), std::abs(f1.y-yext)); } eps=std::max(eps, extremum_eps); // if previous shot was really bad, keep trying } if(eps < rb.abs_tol || eps < std::abs(f1.y)*rb.rel_tol) { if(rb.out) { // we've met the tolerance, and are building a function, append two connectors rb.out->append_function( *new c2_connector_function_p(f0, f1, true, 0.0) ); rb.out->append_function( *new c2_connector_function_p(f1, f2, true, 0.0) ); } if(rb.xvals && rb.yvals) { rb.xvals->push_back(f0.x); rb.xvals->push_back(f1.x); rb.yvals->push_back(f0.y); rb.yvals->push_back(f1.y); // the value at f2 will get pushed in the next segment... it is not forgotten } } else { top.depth=back.depth+1; // increment depth counter // save the last things we need from back before a push happens, in case // the push causes a reallocation and moves the whole stack. size_t f0index=back.f0index, f2index=back.f2index; float_type left=0, right=0; if(rb.derivs==0) { // compute three-point parabolic interpolation estimate of right-hand and left-hand midpoint left=(6*f1.y + 3*f0.y - f2.y) * 0.125; right=(6*f1.y + 3*f2.y - f0.y) * 0.125; } top.f0index=f1index; top.f2index=f2index; // insert pointers to right side data into our recursion block top.previous_estimate=right; rb_stack.push_back(top); top.f0index=f0index; top.f2index=f1index; // insert pointers to left side data into our recursion block top.previous_estimate=left; rb_stack.push_back(top); } } } template c2_piecewise_function_p * c2_function::adaptively_sample( float_type xmin, float_type xmax, float_type abs_tol, float_type rel_tol, int derivs, std::vector *xvals, std::vector *yvals) const throw(c2_exception) { c2_fblock f0, f2; c2_sample_recur rb; std::vector< recur_item > rb_stack; rb_stack.reserve(20); // enough for most operations rb.rb_stack=&rb_stack; rb.out=0; if(derivs==2) rb.out=new c2_piecewise_function_p(); c2_ptr pieces(*rb.out); // manage this function, if any, so it deletes on an exception rb.rel_tol=rel_tol; rb.abs_tol=abs_tol; rb.xvals=xvals; rb.yvals=yvals; rb.derivs=derivs; rb.inited=false; if(xvals && yvals) { xvals->clear(); yvals->clear(); } // create xgrid as a automatic-variable copy of the sampling grid so the exception handler correctly // disposes of it. std::vector xgrid; get_sampling_grid(xmin, xmax, xgrid); int np=xgrid.size(); f2.x=xgrid[0]; fill_fblock(f2); if(c2_isnan(f2.y) || f2.ypbad || f2.yppbad) { // can't go any further if a nan has appeared bad_x_point=f2.x; throw c2_exception("NaN encountered while sampling function"); } for(int i=0; ipush_back(f2.x); yvals->push_back(f2.y); } if(rb.out) rb.out->set_sampling_grid(xgrid); // reflect old sampling grid, which still should be right pieces.release_for_return(); // unmanage the piecewise_function so we can return it return rb.out; } template interpolating_function_p & inverse_integrated_density_function( const std::vector &bincenters, const c2_function &binheights) throw(c2_exception) { return (new Final())->load_random_generator_function(bincenters, binheights); } template interpolating_function_p & inverse_integrated_density_bins( const std::vector &bins, const std::vector &binheights) throw(c2_exception) { return (new Final())->load_random_generator_bins(bins, binheights); }