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

1063 lines
41 KiB
Plaintext

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