Files
geant4/examples/extended/electromagnetic/TestEm7/include/c2_function.icc
T
2016-06-09 16:15:05 +02:00

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 &copy=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 &copy=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);
}