1552 lines
59 KiB
Plaintext
1552 lines
59 KiB
Plaintext
//
|
|
// ********************************************************************
|
|
// * 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 2005 Vanderbilt University.
|
|
*
|
|
* \version c2_function.cc,v 1.169 2008/05/22 12:45:19 marcus Exp
|
|
*/
|
|
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
#include <numeric>
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <sstream>
|
|
|
|
template <typename float_type> const std::string c2_function<float_type>::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 <typename float_type> float_type c2_function<float_type>::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<float_type>::epsilon()*std::abs(value)+std::numeric_limits<float_type>::min());
|
|
float_type xtol=5*(std::numeric_limits<float_type>::epsilon()*(std::abs(upper_bracket)+std::abs(lower_bracket))+std::numeric_limits<float_type>::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 || root<lower_bracket || root>upper_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 <typename float_type> float_type c2_function<float_type>::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<float_type>::epsilon();
|
|
rb.abs_tol_min=10.0*std::numeric_limits<float_type>::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<float_type> &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1;
|
|
c2_fblock<float_type> &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 <typename float_type> bool c2_function<float_type>::check_monotonicity(
|
|
const std::vector<float_type> &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 <typename float_type> void c2_function<float_type>::set_sampling_grid(const std::vector<float_type> &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<float_type>;
|
|
(*sampling_grid)=grid; no_overwrite_grid=0;
|
|
|
|
if(rev) std::reverse(sampling_grid->begin(), sampling_grid->end()); // make it increasing
|
|
}
|
|
|
|
template <typename float_type> void c2_function<float_type>::
|
|
get_sampling_grid(float_type xmin, float_type xmax, std::vector<float_type> &grid) const
|
|
{
|
|
std::vector<float_type> *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<float_type> &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 <typename float_type> void c2_function<float_type>::preen_sampling_grid(std::vector<float_type> *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<float_type>::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::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<float_type>::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::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 <typename float_type> void c2_function<float_type>::
|
|
refine_sampling_grid(std::vector<float_type> &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<float_type> 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<refinement; j++, x+=dx) result[i*refinement+j]=x;
|
|
}
|
|
result.back()=grid.back();
|
|
grid=result; // copy the expanded grid back to the input
|
|
}
|
|
|
|
template <typename float_type> float_type c2_function<float_type>::integral(float_type xmin, float_type xmax, std::vector<float_type> *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<float_type> grid;
|
|
get_sampling_grid(xmin, xmax, grid);
|
|
float_type intg=partial_integrals(grid, partials, abs_tol, rel_tol, derivs, adapt, extrapolate);
|
|
return intg;
|
|
}
|
|
|
|
template <typename float_type> c2_function<float_type> &c2_function<float_type>::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<float_type>(*this, norm/intg);
|
|
}
|
|
|
|
template <typename float_type> c2_function<float_type> &c2_function<float_type>::square_normalized_function(float_type xmin, float_type xmax, float_type norm)
|
|
const throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> mesquared((*new c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this));
|
|
|
|
std::vector<float_type> grid;
|
|
get_sampling_grid(xmin, xmax, grid);
|
|
float_type intg=mesquared->partial_integrals(grid);
|
|
|
|
return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
|
|
}
|
|
|
|
template <typename float_type> c2_function<float_type> &c2_function<float_type>::square_normalized_function(
|
|
float_type xmin, float_type xmax, const c2_function<float_type> &weight, float_type norm)
|
|
const throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> weighted((*new c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this) * weight);
|
|
|
|
std::vector<float_type> grid;
|
|
get_sampling_grid(xmin, xmax, grid);
|
|
float_type intg=weighted->partial_integrals(grid);
|
|
|
|
return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
|
|
}
|
|
|
|
template <typename float_type> float_type c2_function<float_type>::partial_integrals(
|
|
std::vector<float_type> xgrid, std::vector<float_type> *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<float_type> 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<np-1; i++) {
|
|
f0=f2; // copy upper bound to lower before computing new upper bound
|
|
|
|
f2.x=xgrid[i+1];
|
|
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
|
|
}
|
|
|
|
rb.abs_tol=abs_tol*std::abs(f2.x-f0.x)*dx_inv; // distribute error tolerance over whole domain
|
|
rb.f0=&f0; rb.f1=&f2;
|
|
float_type ps=integrate_step(rb);
|
|
sum+=ps;
|
|
if(partials) (*partials)[i]=ps;
|
|
if(c2_isnan(ps)) break; // NaN stops integration
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
// 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 <typename float_type> void c2_sin_p<float_type>::
|
|
get_sampling_grid(float_type xmin, float_type xmax, std::vector<float_type> &grid) const
|
|
{
|
|
grid.clear();
|
|
for(; xmin < xmax; xmin+=5.0) grid.push_back(xmin);
|
|
grid.push_back(xmax);
|
|
this->preen_sampling_grid(&grid);
|
|
}
|
|
|
|
template <typename float_type> float_type c2_function_transformation<float_type>::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 <typename float_type> interpolating_function_p<float_type> & interpolating_function_p<float_type>::load(
|
|
const std::vector<float_type> &x, const std::vector<float_type> &f,
|
|
bool lowerSlopeNatural, float_type lowerSlope,
|
|
bool upperSlopeNatural, float_type upperSlope,
|
|
bool splined
|
|
) throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> 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<np; i++) X[i]=fTransform.X.fIn(X[i]);
|
|
}
|
|
if(fTransform.Y.fTransformed) { // check if Y scale is nonlinear, and if so, do transform
|
|
if(!lowerSlopeNatural) lowerSlope *= fTransform.Y.fInPrime(F[0]);
|
|
if(!upperSlopeNatural) upperSlope *= fTransform.Y.fInPrime(F[np-1]);
|
|
for(size_t i=0; i<np; i++) F[i]=fTransform.Y.fIn(F[i]);
|
|
}
|
|
|
|
xInverted=check_monotonicity(X,
|
|
"interpolating_function::init() non-monotonic transformed x input");
|
|
|
|
if(splined) spline(lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope);
|
|
else y2.assign(np,0.0);
|
|
|
|
lastKLow=0;
|
|
keepme.release_for_return();
|
|
return *this;
|
|
}
|
|
|
|
/*
|
|
// The constructor
|
|
template <typename float_type> interpolating_function_p<float_type> & interpolating_function_p<float_type>::load_pairs(
|
|
std::vector<std::pair<float_type, float_type> > &data,
|
|
bool lowerSlopeNatural, float_type lowerSlope,
|
|
bool upperSlopeNatural, float_type upperSlope,
|
|
bool splined
|
|
) throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> 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<float_type> xtmp, ytmp;
|
|
xtmp.reserve(np);
|
|
ytmp.reserve(np);
|
|
for (size_t i=0; i<np; i++) {
|
|
xtmp.push_back(data[i].first);
|
|
ytmp.push_back(data[i].second);
|
|
}
|
|
this->load(xtmp, ytmp, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, splined);
|
|
|
|
keepme.release_for_return();
|
|
return *this;
|
|
}
|
|
|
|
template <typename float_type> interpolating_function_p<float_type> &
|
|
interpolating_function_p<float_type>::load_random_generator_function(
|
|
const std::vector<float_type> &bincenters, const c2_function<float_type> &binheights)
|
|
throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> keepme(*this);
|
|
|
|
std::vector<float_type> integral;
|
|
c2_const_ptr<float_type> 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; i<integral.size(); i++) integral[i]=integral[i]*scale + integral[i-1];
|
|
integral.back()=1.0; // force exact value on boundary
|
|
|
|
this->load(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 <typename float_type> interpolating_function_p<float_type> &
|
|
interpolating_function_p<float_type>::load_random_generator_bins(
|
|
const std::vector<float_type> &bins, const std::vector<float_type> &binheights)
|
|
throw(c2_exception)
|
|
{
|
|
c2_ptr<float_type> keepme(*this);
|
|
|
|
size_t np=binheights.size();
|
|
std::vector<float_type> 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; i<np; i++) {
|
|
bin_edges[i]=(bins[i]+bins[i-1])*0.5;
|
|
}
|
|
bin_edges.back()=bins[np-1] + (bins[np-1]-bins[np-2])*0.5; // edge bin
|
|
} else {
|
|
throw c2_exception("inconsistent bin vectors passed to load_random_generator_bins");
|
|
}
|
|
|
|
float_type running_sum=0.0;
|
|
for(size_t i=0; i<np; i++) {
|
|
integral[i]=running_sum;
|
|
if(!binheights[i]) throw c2_exception("empty bin passed to load_random_generator_bins");
|
|
running_sum+=binheights[i]*std::abs(bin_edges[i+1]-bin_edges[i]);
|
|
}
|
|
float_type scale=1.0/running_sum;
|
|
for(size_t i=0; i<np; i++) integral[i]*=scale;
|
|
integral.back()=1.0; // force exactly correct value on boundary
|
|
this->load(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 <typename float_type> void interpolating_function_p<float_type>::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<float_type> 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<float_type>() ); // dx=X[1:] - X [:-1]
|
|
for(size_t i=0; i<dxi.size(); i++) dxi[i]=1.0/dx[i]; // dxi = 1/dx
|
|
for(size_t i=0; i<dx2i.size(); i++) dx2i[i]=1.0/(X[i+2]-X[i]);
|
|
|
|
std::transform(F.begin()+1, F.end(), F.begin(), dy.begin(), std::minus<float_type>() ); // dy = F[i+1]-F[i]
|
|
std::transform(dx2i.begin(), dx2i.end(), dx.begin(), siga.begin(), std::multiplies<float_type>()); // siga = dx[:-1]*dx2i
|
|
std::transform(dxi.begin(), dxi.end(), dy.begin(), dydx.begin(), std::multiplies<float_type>()); // 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<float_type>() ); // 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 <typename float_type> interpolating_function_p<float_type> &interpolating_function_p<float_type>::sample_function(
|
|
const c2_function<float_type> &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<float_type> keepme(*this);
|
|
|
|
const c2_transformation<float_type> &XX=fTransform.X, &YY=fTransform.Y; // shortcuts
|
|
|
|
// set up our params to look like the samplng function for now
|
|
sampler_function=func;
|
|
std::vector<float_type> grid;
|
|
func.get_sampling_grid(xmin, xmax, grid);
|
|
size_t gsize=grid.size();
|
|
if(XX.fTransformed) for(size_t i=0; i<gsize; i++) grid[i]=XX.fIn(grid[i]);
|
|
set_sampling_grid_pointer(grid);
|
|
|
|
// float_type xmin1=fXin(xmin), xmax1=fXin(xmax); // bounds in transformed space
|
|
// get a list of points needed in transformed space, directly into our tables
|
|
this->adaptively_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; i<np-1; i++) Xraw[i]=XX.fOut(X[i]);
|
|
Xraw.front()=xmin;
|
|
Xraw.back()=xmax;
|
|
}
|
|
|
|
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(XX.fTransformed) { // check if X scale is nonlinear, and if so, do transform
|
|
if(!lowerSlopeNatural) lowerSlope /= XX.fInPrime(xmin);
|
|
if(!upperSlopeNatural) upperSlope /= XX.fInPrime(xmax);
|
|
}
|
|
if(YY.fTransformed) { // check if Y scale is nonlinear, and if so, do transform
|
|
if(!lowerSlopeNatural) lowerSlope *= YY.fInPrime(func(xmin));
|
|
if(!upperSlopeNatural) upperSlope *= YY.fInPrime(func(xmax));
|
|
}
|
|
// note that each of ends has 3 points with two equal gaps, since they were obtained by bisection
|
|
// so the step sizes are easy to get
|
|
// the 'natural slope' option for sampled functions has a different meaning than
|
|
// for normal splines. In this case, the derivative is adjusted to make the
|
|
// second derivative constant on the last two points at each end
|
|
// which is consistent with the error sampling technique we used to get here
|
|
if(lowerSlopeNatural) {
|
|
float_type hlower=X[1]-X[0];
|
|
lowerSlope=0.5*(-F[2]-3*F[0]+4*F[1])/hlower;
|
|
lowerSlopeNatural=false; // it's not the usual meaning of natural any more
|
|
}
|
|
if(upperSlopeNatural) {
|
|
float_type hupper=X[np-1]-X[np-2];
|
|
upperSlope=0.5*(F[np-3]+3*F[np-1]-4*F[np-2])/hupper;
|
|
upperSlopeNatural=false; // it's not the usual meaning of natural any more
|
|
}
|
|
this->set_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 <typename float_type> float_type interpolating_function_p<float_type>::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 <typename float_type> void interpolating_function_p<float_type>::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<X.size(); i++) printf("%4d %10.4f %10.4f %10.4f %10.4f \n", i, Xraw[i], X[i], F[i], y2[i]);
|
|
}
|
|
|
|
template <typename float_type> void interpolating_function_p<float_type>::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<X.size(); i++) printf("%4d %10.4f %10.4f %10.4f %10.4f \n", i, Xraw[i], X[i], F[i], y2[i]);
|
|
}
|
|
|
|
// 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 <typename float_type> interpolating_function_p<float_type>&
|
|
interpolating_function_p<float_type>::unary_operator(const c2_function<float_type> &source) const
|
|
{
|
|
size_t np=X.size();
|
|
std::vector<float_type>yv(np);
|
|
c2_ptr<float_type> comp(source(*this));
|
|
float_type yp0, yp1, ypp;
|
|
|
|
for(size_t i=1; i<np-1; i++) {
|
|
yv[i]=source(fTransform.Y.fOut(F[i])); // copy pointwise the function of our data values
|
|
}
|
|
|
|
yv.front()=comp(Xraw.front(), &yp0, &ypp); // get derivative at front
|
|
yv.back()= comp(Xraw.back(), &yp1, &ypp); // get derivative at back
|
|
|
|
interpolating_function_p ©=clone();
|
|
copy.load(this->Xraw, yv, false, yp0, false, yp1);
|
|
|
|
return copy;
|
|
}
|
|
|
|
template <typename float_type> void
|
|
interpolating_function_p<float_type>::get_data(std::vector<float_type> &xvals, std::vector<float_type> &yvals) const throw()
|
|
{
|
|
|
|
xvals=Xraw;
|
|
yvals.resize(F.size());
|
|
|
|
for(size_t i=0; i<F.size(); i++) yvals[i]=fTransform.Y.fOut(F[i]);
|
|
}
|
|
|
|
template <typename float_type> interpolating_function_p<float_type> &
|
|
interpolating_function_p<float_type>::binary_operator(const c2_function<float_type> &rhs,
|
|
const c2_binary_function<float_type> *combining_stub) const
|
|
{
|
|
size_t np=X.size();
|
|
std::vector<float_type> yv(np);
|
|
c2_constant_p<float_type> fval(0);
|
|
float_type yp0, yp1, ypp;
|
|
|
|
c2_const_ptr<float_type> stub(*combining_stub); // manage ownership
|
|
|
|
for(size_t i=1; i<np-1; i++) {
|
|
fval.reset(fTransform.Y.fOut(F[i])); // update the constant function pointwise
|
|
yv[i]=combining_stub->combine(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 <typename float_type> c2_inverse_function_p<float_type>::c2_inverse_function_p(const c2_function<float_type> &source)
|
|
: c2_function<float_type>(), 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 <typename float_type> float_type c2_inverse_function_p<float_type>::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 <typename float_type> accumulated_histogram<float_type>::accumulated_histogram(
|
|
const std::vector<float_type>binedges, const std::vector<float_type> binheights,
|
|
bool normalize, bool inverse_function, bool drop_zeros)
|
|
{
|
|
|
|
int np=binheights.size();
|
|
|
|
std::vector<float_type> 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<np-1; i++) {
|
|
if(binheights[i]) {
|
|
be.push_back(binedges[i]);
|
|
bh.push_back(binheights[i]);
|
|
}
|
|
}
|
|
if(binheights[np-1] || !inverse_function) {
|
|
bh.push_back(binheights[np-1]);
|
|
be.push_back(binedges[np-1]);
|
|
be.push_back(binedges[np]); // push both sides of the last bin if needed
|
|
}
|
|
np=bh.size(); // set np to compressed size of bin array
|
|
} else {
|
|
be=binedges;
|
|
bh=binheights;
|
|
}
|
|
std::vector<float_type> 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.size(); i++) cum[i]*=-1; // flip sign on reversed data
|
|
}
|
|
if(normalize) {
|
|
float_type m=1.0/std::max(cum[0], cum[np]);
|
|
for(int i=0; i<=np; i++) cum[i]*=m;
|
|
}
|
|
if(inverse_function) interpolating_function_p<float_type>(cum, be); // use cum as x axis in inverse function
|
|
else interpolating_function_p<float_type>(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 <typename float_type> c2_piecewise_function_p<float_type>::c2_piecewise_function_p()
|
|
: c2_function<float_type>(), lastKLow(-1)
|
|
{
|
|
this->sampling_grid=new std::vector<float_type>; // this always has a smapling grid
|
|
}
|
|
|
|
template <typename float_type> c2_piecewise_function_p<float_type>::~c2_piecewise_function_p()
|
|
{
|
|
}
|
|
|
|
template <typename float_type> float_type c2_piecewise_function_p<float_type>::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 <typename float_type> void c2_piecewise_function_p<float_type>::append_function(
|
|
const c2_function<float_type> &func) throw(c2_exception)
|
|
{
|
|
c2_const_ptr<float_type> 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<float_type> &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<float_type> &connector=*new c2_linear_p<float_type>(x0, y0, (y1-y0)/(x1-x0));
|
|
connector.set_domain(x0,x1);
|
|
functions.push_back(c2_const_ptr<float_type>(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<float_type> newgrid;
|
|
func.get_sampling_grid(func.xmin(), func.xmax(), newgrid);
|
|
this->sampling_grid->insert(this->sampling_grid->end(), newgrid.begin()+1, newgrid.end());
|
|
}
|
|
|
|
template <typename float_type> c2_connector_function_p<float_type>::c2_connector_function_p(
|
|
float_type x0, const c2_function<float_type> &f0, float_type x2, const c2_function<float_type> &f2,
|
|
bool auto_center, float_type y1)
|
|
: c2_function<float_type>()
|
|
{
|
|
c2_const_ptr<float_type> left(f0), right(f2); // make sure if these are unowned, they get deleted
|
|
c2_fblock<float_type> fb0, fb2;
|
|
fb0.x=x0;
|
|
f0.fill_fblock(fb0);
|
|
fb2.x=x2;
|
|
f2.fill_fblock(fb2);
|
|
init(fb0, fb2, auto_center, y1);
|
|
}
|
|
|
|
template <typename float_type> c2_connector_function_p<float_type>::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<float_type>()
|
|
{
|
|
c2_fblock<float_type> 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 <typename float_type> c2_connector_function_p<float_type>::c2_connector_function_p(
|
|
const c2_fblock<float_type> &fb0,
|
|
const c2_fblock<float_type> &fb2,
|
|
bool auto_center, float_type y1)
|
|
: c2_function<float_type>()
|
|
{
|
|
init(fb0, fb2, auto_center, y1);
|
|
}
|
|
|
|
template <typename float_type> void c2_connector_function_p<float_type>::init(
|
|
const c2_fblock<float_type> &fb0,
|
|
const c2_fblock<float_type> &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 <typename float_type> c2_connector_function_p<float_type>::~c2_connector_function_p()
|
|
{
|
|
}
|
|
|
|
template <typename float_type> float_type c2_connector_function_p<float_type>::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 <typename float_type> void c2_function<float_type>::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<float_type>::epsilon();
|
|
rb.abs_tol_min=10.0*std::numeric_limits<float_type>::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<float_type> &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1;
|
|
c2_fblock<float_type> &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<float_type>(f0, f1, true, 0.0)
|
|
);
|
|
rb.out->append_function(
|
|
*new c2_connector_function_p<float_type>(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 <typename float_type> c2_piecewise_function_p<float_type> *
|
|
c2_function<float_type>::adaptively_sample(
|
|
float_type xmin, float_type xmax,
|
|
float_type abs_tol, float_type rel_tol,
|
|
int derivs, std::vector<float_type> *xvals, std::vector<float_type> *yvals) const throw(c2_exception)
|
|
{
|
|
c2_fblock<float_type> 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<float_type>();
|
|
c2_ptr<float_type> 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<float_type> 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; i<np-1; i++) {
|
|
f0=f2; // copy upper bound to lower before computing new upper bound
|
|
|
|
f2.x=xgrid[i+1];
|
|
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");
|
|
}
|
|
|
|
rb.f0=&f0; rb.f1=&f2;
|
|
sample_step(rb);
|
|
}
|
|
if(xvals && yvals) { // push final point in vector
|
|
xvals->push_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 <typename float_type, typename Final>
|
|
interpolating_function_p<float_type> & inverse_integrated_density_function(
|
|
const std::vector<float_type> &bincenters, const c2_function<float_type> &binheights)
|
|
throw(c2_exception)
|
|
{
|
|
return (new Final())->load_random_generator_function(bincenters, binheights);
|
|
}
|
|
|
|
template <typename float_type, typename Final>
|
|
interpolating_function_p<float_type> & inverse_integrated_density_bins(
|
|
const std::vector<float_type> &bins, const std::vector<float_type> &binheights)
|
|
throw(c2_exception)
|
|
{
|
|
return (new Final())->load_random_generator_bins(bins, binheights);
|
|
}
|