2 // ********************************************************************
3 // * License and Disclaimer *
5 // * The Geant4 software is copyright of the Copyright Holders of *
6 // * the Geant4 Collaboration. It is provided under the terms and *
7 // * conditions of the Geant4 Software License, included in the file *
8 // * LICENSE and available at http://cern.ch/geant4/license . These *
9 // * include a list of copyright holders. *
11 // * Neither the authors of this software system, nor their employing *
12 // * institutes,nor the agencies providing financial support for this *
13 // * work make any representation or warranty, express or implied, *
14 // * regarding this software system or assume any liability for its *
15 // * use. Please see the license in the file LICENSE and URL above *
16 // * for the full disclaimer and the limitation of liability. *
18 // * This code implementation is the result of the scientific and *
19 // * technical work of the GEANT4 collaboration. *
20 // * By using, copying, modifying or distributing the software (or *
21 // * any work based on the software) you agree to acknowledge its *
22 // * use in resulting scientific publications, and indicate your *
23 // * acceptance of all terms of the Geant4 Software license. *
24 // ********************************************************************
28 * \file electromagnetic/TestEm7/include/c2_function.icc
29 * \brief Provides code for the general c2_function algebra which supports
30 * fast, flexible operations on piecewise-twice-differentiable functions
32 * \author Created by R. A. Weller and Marcus H. Mendenhall on 7/9/05.
33 * \author Copyright 2005 __Vanderbilt University__. All rights reserved.
35 *\version c2_function.cc 488 2012-04-04 11:30:21Z marcus
52 template <typename float_type> const std::string
53 c2_function<float_type>::cvs_file_vers() const
54 { return "c2_function.cc 488 2012-04-04 11:30:21Z marcus "; }
56 template <typename float_type> float_type
57 c2_function<float_type>::find_root(float_type lower_bracket,
58 float_type upper_bracket,
59 float_type start, float_type value, int *error,
60 float_type *final_yprime, float_type *final_yprime2) const
63 // find f(x)=value within the brackets, using the guarantees of
64 // smoothness associated with a c2_function
65 // can use local f(x)=a*x**2 + b*x + c and solve quadratic to find root,
69 if (!final_yprime) final_yprime=&yp;
70 if (!final_yprime2) final_yprime2=&yp2;
72 float_type ftol=5*(std::numeric_limits<float_type>::epsilon()
73 *std::abs(value)+std::numeric_limits<float_type>::min());
74 float_type xtol=5*(std::numeric_limits<float_type>::epsilon()
75 *(std::abs(upper_bracket)+std::abs(lower_bracket))
76 +std::numeric_limits<float_type>::min());
78 float_type root=start; // start looking in the middle
79 if(error) *error=0; // start out with error flag set to OK, if it is expected
83 root_info=new struct c2_root_info;
84 root_info->inited=false;
86 // this new logic is to keep track of where we were before,
87 // and lower the number of
88 // function evaluations if we are searching inside the same bracket as before.
89 // Since this root finder has, very often, the bracket of the entire
90 // domain of the function,
91 // this makes a big difference, especially to c2_inverse_function
92 if(!root_info->inited || upper_bracket != root_info->upper.x
93 || lower_bracket != root_info->lower.x) {
94 root_info->upper.x=upper_bracket;
95 fill_fblock(root_info->upper);
96 root_info->lower.x=lower_bracket;
97 fill_fblock(root_info->lower);
98 root_info->inited=true;
101 float_type clower=root_info->lower.y-value;
103 *final_yprime=root_info->lower.yp;
104 *final_yprime2=root_info->lower.ypp;
105 return lower_bracket;
108 float_type cupper=root_info->upper.y-value;
110 *final_yprime=root_info->upper.yp;
111 *final_yprime2=root_info->upper.ypp;
112 return upper_bracket;
114 const float_type lower_sign = (clower < 0) ? -1 : 1;
116 if(lower_sign*cupper >0) {
117 // argh, no sign change in here!
118 if(error) { *error=1; return 0.0; }
120 std::ostringstream outstr;
121 outstr << "unbracketed root in find_root at xlower= " << lower_bracket
122 << ", xupper= " << upper_bracket;
123 outstr << ", value= " << value << ": bailing";
124 throw c2_exception(outstr.str().c_str());
128 float_type delta=upper_bracket-lower_bracket; // first error step
129 c=value_with_derivatives(root, final_yprime, final_yprime2)-value;
130 b=*final_yprime; // make a local copy for readability
131 increment_evaluations();
134 std::abs(delta) > xtol && // absolute x step check
135 std::abs(c) > ftol && // absolute y tolerance
136 std::abs(c) > xtol*std::abs(b)
139 float_type a=(*final_yprime2)/2; // second derivative is 2*a
140 float_type disc=b*b-4*a*c;
141 // std::cout << std::endl << "find_root_debug a,b,c,d " << a
142 //<< " " << b << " " << c << " " << disc << std::endl;
145 float_type q=-0.5*((b>=0)?(b+std::sqrt(disc)):(b-std::sqrt(disc)));
146 if(q*q > std::abs(a*c)) delta=c/q;
147 // since x1=q/a, x2=c/q, x1/x2=q^2/ac, this picks smaller step
152 if(disc < 0 || root<lower_bracket || root>upper_bracket ||
153 std::abs(delta) >= 0.5*(upper_bracket-lower_bracket)) {
154 // if we jump out of the bracket, or aren't converging well, bisect
155 root=0.5*(lower_bracket+upper_bracket);
156 delta=upper_bracket-lower_bracket;
158 c=value_with_derivatives(root, final_yprime, final_yprime2)-value;
159 // compute initial values
162 return c; // return the nan if a computation failed
164 b=*final_yprime; // make a local copy for readability
165 increment_evaluations();
167 // now, close in bracket on whichever side this still brackets
168 if(c*lower_sign < 0.0) {
175 // std::cout << "find_root_debug x, y, dx " << root << " "
176 // << c << " " << delta << std::endl;
181 /* def partial_integrals(self, xgrid):
182 Return the integrals of a function between the sampling points xgrid.
183 The sum is the definite integral.
184 This method uses an exact integration of the polynomial which matches
185 the values and derivatives at the
186 endpoints of a segment. Its error scales as h**6, if the input functions
188 This could very well be used as a stepper for adaptive Romberg integration.
189 For InterpolatingFunctions, it is likely that the Simpson's rule integrator
191 #the weights come from an exact mathematica solution to the 5th order
192 polynomial with the given values & derivatives
193 #yint = (y0+y1)*dx/2 + dx^2*(yp0-yp1)/10 + dx^3 * (ypp0+ypp1)/120 )
196 // the recursive part of the integrator is agressively designed to
197 // minimize copying of data... lots of pointers
198 template <typename float_type> float_type
199 c2_function<float_type>::integrate_step(c2_integrate_recur &rb) const
202 std::vector< recur_item > &rb_stack=*rb.rb_stack;
203 // heap-based stack of data for recursion
207 top.depth=0; top.done=false; top.f0index=0; top.f2index=0; top.step_sum=0;
209 // push storage for our initial elements
210 rb_stack.push_back(top);
211 rb_stack.back().f1=*rb.f0;
212 rb_stack.back().done=true; // this element will never be evaluated further
214 rb_stack.push_back(top);
215 rb_stack.back().f1=*rb.f1;
216 rb_stack.back().done=true; // this element will never be evaluated further
221 rb.eps_scale=0.1; rb.extrap_coef=16; break;
223 rb.eps_scale=0.1; rb.extrap_coef=64; break;
225 rb.eps_scale=0.02; rb.extrap_coef=1024; break;
227 throw c2_exception("derivs must be 0, 1 or 2 in partial_integrals");
230 rb.extrap2=1.0/(rb.extrap_coef-1.0);
231 rb.dx_tolerance=10.0*std::numeric_limits<float_type>::epsilon();
232 rb.abs_tol_min=10.0*std::numeric_limits<float_type>::min();
236 // now, push our first real element
237 top.f0index=0; // left element is stack[0]
238 top.f2index=1; // right element is stack[1]
239 top.abs_tol=rb.abs_tol;
240 rb_stack.push_back(top);
242 while(rb_stack.size() > 2) {
243 recur_item &back=rb_stack.back();
245 float_type sum=back.step_sum;
247 rb_stack.back().step_sum+=sum; // bump our sum up to the parent
252 c2_fblock<float_type> &f0=rb_stack[back.f0index].f1,
253 &f2=rb_stack[back.f2index].f1;
254 c2_fblock<float_type> &f1=back.f1; // will hold new middle values
255 size_t f1index=rb_stack.size()-1; // our current offset
256 float_type abs_tol=back.abs_tol;
258 f1.x=0.5*(f0.x + f2.x); // center of interval
259 float_type dx2=0.5*(f2.x - f0.x);
261 // check for underflow on step size
262 if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance
263 || std::abs(dx2) < rb.abs_tol_min) {
264 std::ostringstream outstr;
265 outstr << "Step size underflow in adaptive_partial_integrals at depth="
266 << back.depth << ", x= " << f1.x;
267 throw c2_exception(outstr.str().c_str());
273 return f1.y; // can't go any further if a nan has appeared
276 bool yptrouble=f0.ypbad || f2.ypbad || f1.ypbad;
277 bool ypptrouble=f0.yppbad || f2.yppbad || f1.yppbad;
279 // select the real derivative count based on whether we are at
280 // a point where derivatives exist
281 int derivs = std::min(rb.derivs, (yptrouble||ypptrouble)?(yptrouble?0:1):2);
283 if(!back.depth) { // top level, total has not been initialized yet
284 switch(derivs) { // create estimate of next lower order for first try
286 back.previous_estimate=(f0.y+f2.y)*dx2; break;
288 back.previous_estimate=(f0.y+4.0*f1.y+f2.y)*dx2/3.0; break;
290 back.previous_estimate=( (14*f0.y + 32*f1.y + 14*f2.y)
291 + 2*dx2 * (f0.yp - f2.yp) ) * dx2 /30.; break;
293 back.previous_estimate=0.0; // just to suppress missing default warnings
297 float_type left, right;
299 // pre-compute constants so all multiplies use a small dynamic range
300 // note the forced csting of the denominators to assure full precision
301 // for (e.g.) long double
302 // constants for 0 derivative integrator
303 static const float_type c0c1=5./((float_type)12.),
304 c0c2=8./((float_type)12.), c0c3=-1./((float_type)12.);
305 // constants for 1 derivative integrator
306 static const float_type c1c1=101./((float_type)240.),
307 c1c2=128./((float_type)240.), c1c3=11./((float_type)240.),
308 c1c4=13./((float_type)240.), c1c5=-40./((float_type)240.),
309 c1c6=-3./((float_type)240.);
310 // constants for 2 derivative integrator
311 static const float_type c2c1=169./((float_type)40320.),
312 c2c2=1024./ ((float_type)40320.), c2c3=-41./((float_type)40320.),
313 c2c4=2727./((float_type)40320.), c2c5=-5040./((float_type)40320.),
314 c2c6=423./((float_type)40320.),
315 c2c7=17007./((float_type)40320.), c2c8=24576./((float_type)40320.),
316 c2c9=-1263./((float_type)40320.);
320 // use ninth-order estimates for each side, from full set of all values (!)
321 left=( ( (c2c1*f0.ypp + c2c2*f1.ypp + c2c3*f2.ypp)*dx2 +
322 (c2c4*f0.yp + c2c5*f1.yp + c2c6*f2.yp) )*dx2 +
323 (c2c7*f0.y + c2c8*f1.y + c2c9*f2.y) )* dx2;
324 right=( ( (c2c1*f2.ypp + c2c2*f1.ypp + c2c3*f0.ypp)*dx2 -
325 (c2c4*f2.yp + c2c5*f1.yp + c2c6*f0.yp) )*dx2 +
326 (c2c7*f2.y + c2c8*f1.y + c2c9*f0.y) )* dx2;
327 // std::cout << f0.x << " " << f1.x << " " << f2.x << std::endl ;
328 // std::cout << f0.y << " " << f1.y << " " << f2.y << " " << left
329 // << " " << right << " " << total << std::endl ;
332 left= ( (c1c1*f0.y + c1c2*f1.y + c1c3*f2.y)
333 + dx2*(c1c4*f0.yp + c1c5*f1.yp + c1c6*f2.yp) ) * dx2 ;
334 right= ( (c1c1*f2.y + c1c2*f1.y + c1c3*f0.y)
335 - dx2*(c1c4*f2.yp + c1c5*f1.yp + c1c6*f0.yp) ) * dx2 ;
338 left=(c0c1*f0.y + c0c2*f1.y + c0c3*f2.y)*dx2;
339 right=(c0c1*f2.y + c0c2*f1.y + c0c3*f0.y)*dx2;
342 left=right=0.0; // suppress warnings about missing default
346 float_type lrsum=left+right;
348 bool extrapolate=back.depth && rb.extrapolate && (derivs==rb.derivs);
349 // only extrapolate if no trouble with derivs
350 float_type eps=std::abs(back.previous_estimate-lrsum)*rb.eps_scale;
351 if(extrapolate) eps*=rb.eps_scale;
353 if(rb.adapt && eps > abs_tol && eps > std::abs(lrsum)*rb.rel_tol) {
354 // tolerance not met, subdivide & recur
355 if(abs_tol > rb.abs_tol_min) abs_tol=abs_tol*0.5;
356 // each half has half the error budget
358 top.depth=back.depth+1;
360 // save the last things we need from back before a push happens, in case
361 // the push causes a reallocation and moves the whole stack.
362 size_t f0index=back.f0index, f2index=back.f2index;
364 top.f0index=f1index; top.f2index=f2index;
365 // insert pointers to right side data into our recursion block
366 top.previous_estimate=right;
367 rb_stack.push_back(top);
369 top.f0index=f0index; top.f2index=f1index;
370 // insert pointers to left side data into our recursion block
371 top.previous_estimate=left;
372 rb_stack.push_back(top);
374 } else if(extrapolate) {
375 // extrapolation only happens on leaf nodes, where the tolerance was met.
376 back.step_sum+=(rb.extrap_coef*lrsum - back.previous_estimate)*rb.extrap2;
378 back.step_sum+=lrsum;
381 return rb_stack.back().step_sum; // last element on the stack holds the sum
384 template <typename float_type> bool c2_function<float_type>::check_monotonicity(
385 const std::vector<float_type> &data,
386 const char message[]) const throw(c2_exception)
388 size_t np=data.size();
389 if(np < 2) return false; // one point has no direction!
391 bool rev=(data[1] < data[0]); // which way do data point?
394 if(!rev) for(i = 2; i < np && (data[i-1] < data[i]) ; i++) { }
395 else for(i = 2; i < np &&(data[i-1] > data[i]) ; i++) { }
397 if(i != np) throw c2_exception(message);
402 template <typename float_type> void
403 c2_function<float_type>::set_sampling_grid(const std::vector<float_type> &grid)
406 bool rev=this->check_monotonicity(grid,
407 "set_sampling_grid: sampling grid not monotonic");
409 if(!sampling_grid || no_overwrite_grid) sampling_grid=
410 new std::vector<float_type>;
411 (*sampling_grid)=grid; no_overwrite_grid=0;
413 if(rev) std::reverse(sampling_grid->begin(), sampling_grid->end());
414 // make it increasing
417 template <typename float_type> void c2_function<float_type>::
418 get_sampling_grid(float_type amin, float_type amax,
419 std::vector<float_type> &grid) const
421 std::vector<float_type> *result=&grid;
424 if( !(sampling_grid) || !(sampling_grid->size())
425 || (amax <= sampling_grid->front()) || (amin >= sampling_grid->back()) ) {
426 // nothing is known about the function in this region, return amin and amax
427 result->push_back(amin);
428 result->push_back(amax);
430 std::vector<float_type> &sg=*sampling_grid; // just a shortcut
432 int klo=0, khi=np-1, firstindex=0, lastindex=np-1;
434 result->push_back(amin);
436 if(amin > sg.front() ) {
437 // hunt through table for position bracketing starting point
440 if(sg[kk] > amin) khi=kk;
444 // khi now points to first point definitively beyond our first point,
445 // or last point of array
447 khi=np-1; // restart upper end of search
450 if(amax < sg.back()) {
451 // hunt through table for position bracketing starting point
454 if(sg[kk] > amax) khi=kk;
458 // khi now points to first point definitively beyond our last point,
459 // or last point of array
463 int initsize=result->size();
464 result->resize(initsize+(lastindex-firstindex+2));
465 std::copy(sg.begin()+firstindex, sg.begin()+lastindex+1,
466 result->begin()+initsize);
469 // this is the unrefined sampling grid... now check for very close
470 // points on front & back and fix if needed.
471 preen_sampling_grid(result);
475 template <typename float_type> void
476 c2_function<float_type>::preen_sampling_grid(std::vector<float_type> *result)
479 // this is the unrefined sampling grid...
480 // now check for very close points on front & back and fix if needed.
481 if(result->size() > 2) {
482 // may be able to prune dangerously close points near the ends
483 // if there are at least 3 points
485 float_type x0=(*result)[0], x1=(*result)[1];
486 float_type dx1=x1-x0;
488 float_type ftol=10.0*(std::numeric_limits<float_type>::epsilon()
489 *(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::min());
490 if(dx1 < ftol) deleteit=true;
491 float_type dx2=(*result)[2]-x0;
492 if(dx1/dx2 < 0.1) deleteit=true;
493 // endpoint is very close to internal interesting point
495 if(deleteit) result->erase(result->begin()+1);
496 // delete redundant interesting point
499 if(result->size() > 2) {
500 // may be able to prune dangerously close points near the ends
501 // if there are at least 3 points
503 int pos=result->size()-3;
504 float_type x0=(*result)[pos+1], x1=(*result)[pos+2];
505 float_type dx1=x1-x0;
507 float_type ftol=10.0*(std::numeric_limits<float_type>::epsilon()
508 *(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::min());
509 if(dx1 < ftol) deleteit=true;
510 float_type dx2=x1-(*result)[pos];
511 if(dx1/dx2 < 0.1) deleteit=true;
512 // endpoint is very close to internal interesting point
514 if(deleteit) result->erase(result->end()-2);
515 // delete redundant interesting point
519 template <typename float_type> void c2_function<float_type>::
520 refine_sampling_grid(std::vector<float_type> &grid, size_t refinement) const
522 size_t np=grid.size();
523 size_t count=(np-1)*refinement + 1;
524 float_type dxscale=1.0/refinement;
526 std::vector<float_type> result(count);
528 for(size_t i=0; i<(np-1); i++) {
529 float_type x=grid[i];
530 float_type dx=(grid[i+1]-x)*dxscale;
531 for(size_t j=0; j<refinement; j++, x+=dx) result[i*refinement+j]=x;
533 result.back()=grid.back();
534 grid=result; // copy the expanded grid back to the input
537 template <typename float_type> float_type
538 c2_function<float_type>::integral(float_type amin, float_type amax,
539 std::vector<float_type> *partials,
540 float_type abs_tol, float_type rel_tol, int derivs,
541 bool adapt, bool extrapolate) const throw(c2_exception)
544 if(partials) partials->clear();
547 std::vector<float_type> grid;
548 get_sampling_grid(amin, amax, grid);
549 float_type intg=partial_integrals(grid, partials, abs_tol, rel_tol,
550 derivs, adapt, extrapolate);
554 template <typename float_type> c2_function<float_type>
555 &c2_function<float_type>::normalized_function(float_type amin,
556 float_type amax, float_type norm)
557 const throw(c2_exception)
559 float_type intg=integral(amin, amax);
560 return *new c2_scaled_function_p<float_type>(*this, norm/intg);
563 template <typename float_type> c2_function<float_type>
564 &c2_function<float_type>::square_normalized_function(float_type amin,
565 float_type amax, float_type norm)
566 const throw(c2_exception)
568 c2_ptr<float_type> mesquared((*new
569 c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this));
571 std::vector<float_type> grid;
572 get_sampling_grid(amin, amax, grid);
573 float_type intg=mesquared->partial_integrals(grid);
575 return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
578 template <typename float_type> c2_function<float_type>
579 &c2_function<float_type>::square_normalized_function(
580 float_type amin, float_type amax, const c2_function<float_type> &weight,
582 const throw(c2_exception)
584 c2_ptr<float_type> weighted((*new
585 c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this) * weight);
587 std::vector<float_type> grid;
588 get_sampling_grid(amin, amax, grid);
589 float_type intg=weighted->partial_integrals(grid);
591 return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
594 template <typename float_type> float_type
595 c2_function<float_type>::partial_integrals(
596 std::vector<float_type> xgrid, std::vector<float_type> *partials,
597 float_type abs_tol, float_type rel_tol, int derivs,
598 bool adapt, bool extrapolate)
599 const throw(c2_exception)
603 c2_fblock<float_type> f0, f2;
604 struct c2_integrate_recur rb;
606 rb.extrapolate=extrapolate;
609 std::vector< recur_item > rb_stack;
610 rb_stack.reserve(20); // enough for most operations
611 rb.rb_stack=&rb_stack;
613 float_type dx_inv=1.0/std::abs(xgrid.back()-xgrid.front());
615 if(partials) partials->resize(np-1);
623 return f2.y; // can't go any further if a nan has appeared
626 for(int i=0; i<np-1; i++) {
627 f0=f2; // copy upper bound to lower before computing new upper bound
633 return f2.y; // can't go any further if a nan has appeared
636 rb.abs_tol=abs_tol*std::abs(f2.x-f0.x)*dx_inv;
637 // distribute error tolerance over whole domain
638 rb.f0=&f0; rb.f1=&f2;
639 float_type psss=integrate_step(rb);
641 if(partials) (*partials)[i]=psss;
642 if(c2_isnan(psss)) break; // NaN stops integration
647 // generate a sampling grid at points separated by dx=5, which is intentionally
648 // incommensurate with pi and 2*pi so grid errors are somewhat randomized
649 template <typename float_type> void c2_sin_p<float_type>::
650 get_sampling_grid(float_type amin, float_type amax,
651 std::vector<float_type> &grid) const
654 for(; amin < amax; amin+=5.0) grid.push_back(amin);
655 grid.push_back(amax);
656 this->preen_sampling_grid(&grid);
659 template <typename float_type> float_type
660 c2_function_transformation<float_type>::evaluate(
662 float_type y, float_type yp0, float_type ypp0,
663 float_type *yprime, float_type *yprime2) const
665 y=Y.fHasStaticTransforms ? Y.pOut(y) : Y.fOut(y);
667 if(yprime || yprime2) {
670 if(X.fHasStaticTransforms && Y.fHasStaticTransforms) {
671 float_type fpi=1.0/Y.pInPrime(y);
672 float_type gp=X.pInPrime(xraw);
673 // from Mathematica Dt[InverseFunction[f][y[g[x]]], x]
674 yp=gp*yp0*fpi; // transformed derivative
675 yp2=(gp*gp*ypp0 + X.pInDPrime(xraw)*yp0 - Y.pInDPrime(y)*yp*yp )*fpi;
677 float_type fpi=1.0/Y.fInPrime(y);
678 float_type gp=X.fInPrime(xraw);
679 // from Mathematica Dt[InverseFunction[f][y[g[x]]], x]
680 yp=gp*yp0*fpi; // transformed derivative
681 yp2=(gp*gp*ypp0 + X.fInDPrime(xraw)*yp0 - Y.fInDPrime(y)*yp*yp )*fpi;
683 if(yprime) *yprime=yp;
684 if(yprime2) *yprime2=yp2;
690 template <typename float_type> interpolating_function_p<float_type>
691 &interpolating_function_p<float_type>::load(
692 const std::vector<float_type> &x, const std::vector<float_type> &f,
693 bool lowerSlopeNatural, float_type lowerSlope,
694 bool upperSlopeNatural, float_type upperSlope,
696 ) throw(c2_exception)
698 c2_ptr<float_type> keepme(*this);
704 this->set_domain(std::min(Xraw.front(), Xraw.back()),
705 std::max(Xraw.front(), Xraw.back()));
707 if(x.size() != f.size()) {
709 "interpolating_function::init() -- x & y inputs are of different size");
712 size_t np=X.size(); // they are the same now, so lets take a short cut
715 throw c2_exception("interpolating_function::init() -- input < 2 elements ");
718 bool xraw_rev=this->check_monotonicity(Xraw,
719 "interpolating_function::init() non-monotonic raw x input");
720 // which way does raw X point? sampling grid MUST be increasing
723 // we can use pointer to raw X values if they are in the right order
724 this->set_sampling_grid_pointer(Xraw);
725 // our intial grid of x values is certainly a good guess for points
727 this->set_sampling_grid(Xraw);
728 // make a copy of it, and assure it is in right order
731 if(fTransform.X.fTransformed) {
732 // check if X scale is nonlinear, and if so, do transform
733 if(!lowerSlopeNatural) lowerSlope /= fTransform.X.fInPrime(X[0]);
734 if(!upperSlopeNatural) upperSlope /= fTransform.X.fInPrime(X[np-1]);
735 for(size_t i=0; i<np; i++) X[i]=fTransform.X.fIn(X[i]);
737 if(fTransform.Y.fTransformed) {
738 // check if Y scale is nonlinear, and if so, do transform
739 if(!lowerSlopeNatural) lowerSlope *= fTransform.Y.fInPrime(F[0]);
740 if(!upperSlopeNatural) upperSlope *= fTransform.Y.fInPrime(F[np-1]);
741 for(size_t i=0; i<np; i++) F[i]=fTransform.Y.fIn(F[i]);
744 xInverted=this->check_monotonicity(X,
745 "interpolating_function::init() non-monotonic transformed x input");
747 if(splined) spline(lowerSlopeNatural, lowerSlope,
748 upperSlopeNatural, upperSlope);
749 else y2.assign(np,0.0);
752 keepme.release_for_return();
758 template <typename float_type> interpolating_function_p<float_type>
759 &interpolating_function_p<float_type>::load_pairs(
760 std::vector<std::pair<float_type, float_type> > &data,
761 bool lowerSlopeNatural, float_type lowerSlope,
762 bool upperSlopeNatural, float_type upperSlope,
764 ) throw(c2_exception)
766 c2_ptr<float_type> keepme(*this);
768 size_t np=data.size();
770 throw c2_exception("interpolating_function::init() -- input < 2 elements ");
773 // sort into ascending order
774 std::sort(data.begin(), data.end(), comp_pair);
776 std::vector<float_type> xtmp, ytmp;
779 for (size_t i=0; i<np; i++) {
780 xtmp.push_back(data[i].first);
781 ytmp.push_back(data[i].second);
783 this->load(xtmp, ytmp, lowerSlopeNatural, lowerSlope,
784 upperSlopeNatural, upperSlope, splined);
786 keepme.release_for_return();
790 template <typename float_type> interpolating_function_p<float_type> &
791 interpolating_function_p<float_type>::load_random_generator_function(
792 const std::vector<float_type> &bincenters,
793 const c2_function<float_type> &binheights)
796 c2_ptr<float_type> keepme(*this);
798 std::vector<float_type> integral;
799 c2_const_ptr<float_type> keepit(binheights);
800 // integrate from first to last bin in original order,
801 // leaving results in integral
802 // ask for relative error of 1e-6 on each bin, with absolute
803 // error set to 0 (since we don't know the data scale).
804 float_type sum=binheights.partial_integrals(bincenters, &integral, 0.0, 1e-6);
805 integral.insert(integral.begin(), 0.0); // integral from start to start is 0
806 float_type scale=1.0/sum;
807 for(size_t i=1; i<integral.size(); i++) integral[i]=integral[i]*scale
809 integral.back()=1.0; // force exact value on boundary
811 this->load(integral, bincenters,
812 false, 1.0/(scale*binheights(bincenters.front() )),
813 false, 1.0/(scale*binheights(bincenters.back() ))
814 ); // use integral as x axis in inverse function
815 keepme.release_for_return();
819 template <typename float_type> interpolating_function_p<float_type> &
820 interpolating_function_p<float_type>::load_random_generator_bins(
821 const std::vector<float_type> &bins,
822 const std::vector<float_type> &binheights, bool splined)
825 c2_ptr<float_type> keepme(*this);
827 size_t np=binheights.size();
828 std::vector<float_type> integral(np+1), bin_edges(np+1);
830 // compute the integral based on estimates of the bin edges
831 // from the given bin centers...
832 // except for bin 0 & final bin, the edge of a bin is halfway
833 // between then center of the
834 // bin and the center of the previous/next bin.
836 if(bins.size() == binheights.size()+1) {
837 bin_edges=bins; // edges array was passed in
838 } else if (bins.size() == binheights.size()) {
839 bin_edges.front()=bins[0] - (bins[1]-bins[0])*0.5; // edge bin
840 for(size_t i=1; i<np; i++) {
841 bin_edges[i]=(bins[i]+bins[i-1])*0.5;
843 bin_edges.back()=bins[np-1] + (bins[np-1]-bins[np-2])*0.5; // edge bin
846 "inconsistent bin vectors passed to load_random_generator_bins");
849 float_type running_sum=0.0;
850 for(size_t i=0; i<np; i++) {
851 integral[i]=running_sum;
852 if(!binheights[i]) throw c2_exception(
853 "empty bin passed to load_random_generator_bins");
854 running_sum+=binheights[i]*std::abs(bin_edges[i+1]-bin_edges[i]);
856 float_type scale=1.0/running_sum;
857 for(size_t i=0; i<np; i++) integral[i]*=scale;
858 integral.back()=1.0; // force exactly correct value on boundary
859 this->load(integral, bin_edges,
860 false, 1.0/(scale*binheights.front()),
861 false, 1.0/(scale*binheights.back()),
862 splined); // use integral as x axis in inverse function
863 keepme.release_for_return();
868 // The spline table generator
869 template <typename float_type> void
870 interpolating_function_p<float_type>::spline(
871 bool lowerSlopeNatural, float_type lowerSlope,
872 bool upperSlopeNatural, float_type upperSlope
873 ) throw(c2_exception)
875 // construct spline tables here.
876 // this code is a re-translation of the pythonlabtools spline
877 // algorithm from pythonlabtools.sourceforge.net
879 std::vector<float_type> u(np), dy(np-1), dx(np-1),
880 dxi(np-1), dx2i(np-2), siga(np-2), dydx(np-1);
882 std::transform(X.begin()+1, X.end(), X.begin(), dx.begin(),
883 std::minus<float_type>() ); // dx=X[1:] - X [:-1]
884 for(size_t i=0; i<dxi.size(); i++) dxi[i]=1.0/dx[i]; // dxi = 1/dx
885 for(size_t i=0; i<dx2i.size(); i++) dx2i[i]=1.0/(X[i+2]-X[i]);
887 std::transform(F.begin()+1, F.end(), F.begin(), dy.begin(),
888 std::minus<float_type>() ); // dy = F[i+1]-F[i]
889 std::transform(dx2i.begin(), dx2i.end(), dx.begin(), siga.begin(),
890 std::multiplies<float_type>()); // siga = dx[:-1]*dx2i
891 std::transform(dxi.begin(), dxi.end(), dy.begin(), dydx.begin(),
892 std::multiplies<float_type>()); // dydx=dy/dx
894 // u[i]=(y[i+1]-y[i])/float(x[i+1]-x[i]) - (y[i]-y[i-1])/float(x[i]-x[i-1])
895 std::transform(dydx.begin()+1, dydx.end(), dydx.begin(), u.begin()+1,
896 std::minus<float_type>() ); // incomplete rendering of u = dydx[1:]-dydx[:-1]
900 if(lowerSlopeNatural) {
904 u[0]=(3.0*dxi[0])*(dy[0]*dxi[0] -lowerSlope);
907 for(size_t i=1; i < np -1; i++) { // the inner loop
908 float_type sig=siga[i-1];
909 float_type p=sig*y2[i-1]+2.0;
911 u[i]=(6.0*u[i]*dx2i[i-1] - sig*u[i-1])/p;
916 if(upperSlopeNatural) {
920 un=(3.0*dxi[dxi.size()-1])*(upperSlope- dy[dy.size()-1]*dxi[dxi.size()-1] );
923 y2[np-1]=(un-qn*u[np-2])/(qn*y2[np-2]+1.0);
924 for (size_t k=np-1; k != 0; k--) y2[k-1]=y2[k-1]*y2[k]+u[k-1];
927 template <typename float_type> interpolating_function_p<float_type>
928 &interpolating_function_p<float_type>::sample_function(
929 const c2_function<float_type> &func,
930 float_type amin, float_type amax, float_type abs_tol, float_type rel_tol,
931 bool lowerSlopeNatural, float_type lowerSlope,
932 bool upperSlopeNatural, float_type upperSlope
933 ) throw(c2_exception)
935 c2_ptr<float_type> keepme(*this);
937 const c2_transformation<float_type> &XX=fTransform.X, &YY=fTransform.Y;
939 // set up our params to look like the samplng function for now
940 sampler_function=func;
941 std::vector<float_type> grid;
942 func.get_sampling_grid(amin, amax, grid);
943 size_t gsize=grid.size();
944 if(XX.fTransformed) for(size_t i=0; i<gsize; i++) grid[i]=XX.fIn(grid[i]);
945 this->set_sampling_grid_pointer(grid);
947 this->adaptively_sample(grid.front(), grid.back(), 8*abs_tol,
948 8*rel_tol, 0, &X, &F);
949 // clear the sampler function now, since otherwise our
950 // value_with_derivatives is broken
951 sampler_function.unset_function();
953 xInverted=this->check_monotonicity(X,
954 "interpolating_function::init() non-monotonic transformed x input");
958 // Xraw is useful in some of the arithmetic operations between
959 // interpolating functions
960 if(!XX.fTransformed) Xraw=X;
963 for (size_t i=1; i<np-1; i++) Xraw[i]=XX.fOut(X[i]);
968 bool xraw_rev=this->check_monotonicity(Xraw,
969 "interpolating_function::init() non-monotonic raw x input");
970 // which way does raw X point? sampling grid MUST be increasing
973 this->set_sampling_grid_pointer(Xraw);
976 this->set_sampling_grid(Xraw);
979 if(XX.fTransformed) { // check if X scale is nonlinear, and if so, do transform
980 if(!lowerSlopeNatural) lowerSlope /= XX.fInPrime(amin);
981 if(!upperSlopeNatural) upperSlope /= XX.fInPrime(amax);
983 if(YY.fTransformed) {
984 if(!lowerSlopeNatural) lowerSlope *= YY.fInPrime(func(amin));
985 if(!upperSlopeNatural) upperSlope *= YY.fInPrime(func(amax));
987 // note that each of ends has 3 points with two equal gaps,
988 // since they were obtained by bisection
989 // so the step sizes are easy to get
990 // the 'natural slope' option for sampled functions has a
991 // different meaning than
992 // for normal splines. In this case, the derivative is adjusted to make the
993 // second derivative constant on the last two points at each end
994 // which is consistent with the error sampling technique we used to get here
995 if(lowerSlopeNatural) {
996 float_type hlower=X[1]-X[0];
997 lowerSlope=0.5*(-F[2]-3*F[0]+4*F[1])/hlower;
998 lowerSlopeNatural=false; // it's not the usual meaning of natural any more
1000 if(upperSlopeNatural) {
1001 float_type hupper=X[np-1]-X[np-2];
1002 upperSlope=0.5*(F[np-3]+3*F[np-1]-4*F[np-2])/hupper;
1003 upperSlopeNatural=false; // it's not the usual meaning of natural any more
1005 this->set_domain(amin, amax);
1007 spline(lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope);
1009 keepme.release_for_return();
1013 // This function is the reason for this class to exist
1014 // it computes the interpolated function, and (if requested) its proper
1015 // first and second derivatives including all coordinate transforms
1016 template <typename float_type> float_type
1017 interpolating_function_p<float_type>::value_with_derivatives(
1018 float_type x, float_type *yprime, float_type *yprime2) const throw(c2_exception)
1020 if(sampler_function.valid()) {
1021 // if this is non-null, we are sampling data for later,
1022 // so just return raw function
1023 // however, transform it into our sampling space, first.
1024 if(yprime) *yprime=0;
1025 if(yprime2) *yprime2=0;
1026 sampler_function->increment_evaluations();
1027 return fTransform.Y.fIn(sampler_function(fTransform.X.fOut(x)));
1028 // derivatives are completely undefined
1031 if(x < this->xmin() || x > this->xmax()) {
1032 std::ostringstream outstr;
1033 outstr << "Interpolating function argument " << x
1034 << " out of range " << this->xmin() << " -- " << this ->xmax() << ": bailing";
1035 throw c2_exception(outstr.str().c_str());
1040 if(fTransform.X.fTransformed) x=fTransform.X.fHasStaticTransforms?
1041 fTransform.X.pIn(x) : fTransform.X.fIn(x);
1042 // save time by explicitly testing for identity function here
1044 int klo=0, khi=X.size()-1;
1046 if(khi < 0) throw c2_exception(
1047 "Uninitialized interpolating function being evaluated");
1049 const float_type *XX=&X[lastKLow];
1050 // make all fast checks short offsets from here
1053 // select search depending on whether transformed X is increasing or decreasing
1054 if((XX[0] <= x) && (XX[1] >= x) ) { // already bracketed
1056 } else if((XX[1] <= x) && (XX[2] >= x)) { // in next bracket to the right
1058 } else if(lastKLow > 0 && (XX[-1] <= x) && (XX[0] >= x)) {
1059 // in next bracket to the left
1061 } else { // not bracketed, not close, start over
1062 // search for new KLow
1063 while(khi-klo > 1) {
1065 if(X[kk] > x) khi=kk;
1070 if((XX[0] >= x) && (XX[1] <= x) ) { // already bracketed
1072 } else if((XX[1] >= x) && (XX[2] <= x)) { // in next bracket to the right
1074 } else if(lastKLow > 0 && (XX[-1] >= x) && (XX[0] <= x)) {
1075 // in next bracket to the left
1077 } else { // not bracketed, not close, start over
1078 // search for new KLow
1079 while(khi-klo > 1) {
1081 if(X[kk] < x) khi=kk;
1090 float_type h=X[khi]-X[klo];
1092 float_type a=(X[khi]-x)/h;
1094 float_type ylo=F[klo], yhi=F[khi], y2lo=y2[klo], y2hi=y2[khi];
1095 float_type y=a*ylo+b*yhi+((a*a*a-a)*y2lo+(b*b*b-b)*y2hi)*(h*h)/6.0;
1097 float_type yp0=0; // the derivative in interpolating table coordinates
1098 float_type ypp0=0; // second derivative
1100 if(yprime || yprime2) {
1101 yp0=(yhi-ylo)/h+((3*b*b-1)*y2hi-(3*a*a-1)*y2lo)*h/6.0;
1102 // the derivative in interpolating table coordinates
1103 ypp0=b*y2hi+a*y2lo; // second derivative
1106 if(fTransform.isIdentity) {
1107 if(yprime) *yprime=yp0;
1108 if(yprime2) *yprime2=ypp0;
1110 } else return fTransform.evaluate(xraw, y, yp0, ypp0, yprime, yprime2);
1113 template <typename float_type> void
1114 interpolating_function_p<float_type>::set_lower_extrapolation(float_type bound)
1118 float_type xx=fTransform.X.fIn(bound);
1119 float_type h0=X[kh]-X[kl];
1120 float_type h1=xx-X[kl];
1121 float_type yextrap=F[kl]+((F[kh]-F[kl])/h0
1122 - h0*(y2[kl]+2.0*y2[kh])/6.0)*h1+y2[kl]*h1*h1/2.0;
1124 X.insert(X.begin(), xx);
1125 F.insert(F.begin(), yextrap);
1126 y2.insert(y2.begin(), y2.front()); // duplicate first or last element
1127 Xraw.insert(Xraw.begin(), bound);
1128 if (bound < this->fXMin) this->fXMin=bound; // check for reversed data
1129 else this->fXMax=bound;
1133 template <typename float_type> void
1134 interpolating_function_p<float_type>::set_upper_extrapolation(float_type bound)
1136 int kl = X.size()-2 ;
1138 float_type xx=fTransform.X.fIn(bound);
1139 float_type h0=X[kh]-X[kl];
1140 float_type h1=xx-X[kl];
1141 float_type yextrap=F[kl]+((F[kh]-F[kl])/h0
1142 - h0*(y2[kl]+2.0*y2[kh])/6.0)*h1+y2[kl]*h1*h1/2.0;
1144 X.insert(X.end(), xx);
1145 F.insert(F.end(), yextrap);
1146 y2.insert(y2.end(), y2.back()); // duplicate first or last element
1147 Xraw.insert(Xraw.end(), bound);
1148 if (bound < this->fXMin) this->fXMin=bound; // check for reversed data
1149 else this->fXMax=bound;
1150 //printf("%10.4f %10.4f %10.4f %10.4f %10.4f\n", bound, xx, h0, h1, yextrap);
1154 template <typename float_type> interpolating_function_p<float_type>&
1155 interpolating_function_p<float_type>::unary_operator(
1156 const c2_function<float_type> &source) const
1159 std::vector<float_type>yv(np);
1160 c2_ptr<float_type> comp(source(*this));
1161 float_type yp0, yp1, ypp;
1163 for(size_t i=1; i<np-1; i++) {
1164 yv[i]=source(fTransform.Y.fOut(F[i]));
1167 yv.front()=comp(Xraw.front(), &yp0, &ypp); // get derivative at front
1168 yv.back()= comp(Xraw.back(), &yp1, &ypp); // get derivative at back
1170 interpolating_function_p ©=clone();
1171 copy.load(this->Xraw, yv, false, yp0, false, yp1);
1176 template <typename float_type> void
1177 interpolating_function_p<float_type>::get_data(std::vector<float_type> &xvals,
1178 std::vector<float_type> &yvals) const throw()
1182 yvals.resize(F.size());
1184 for(size_t i=0; i<F.size(); i++) yvals[i]=fTransform.Y.fOut(F[i]);
1187 template <typename float_type> interpolating_function_p<float_type> &
1188 interpolating_function_p<float_type>::binary_operator(const
1189 c2_function<float_type> &rhs,
1190 const c2_binary_function<float_type> *combining_stub) const
1193 std::vector<float_type> yv(np);
1194 c2_constant_p<float_type> fval(0);
1195 float_type yp0, yp1, ypp;
1197 c2_const_ptr<float_type> stub(*combining_stub); // manage ownership
1199 for(size_t i=1; i<np-1; i++) {
1200 fval.reset(fTransform.Y.fOut(F[i])); // update the constant function pointwise
1201 yv[i]=combining_stub->combine(fval, rhs, Xraw[i], (float_type *)0,
1202 (float_type *)0); // compute rhs & combine without derivatives
1205 yv.front()=combining_stub->combine(*this, rhs, Xraw.front(), &yp0, &ypp);
1206 yv.back()= combining_stub->combine(*this, rhs, Xraw.back(), &yp1, &ypp);
1208 interpolating_function_p ©=clone();
1209 copy.load(this->Xraw, yv, false, yp0, false, yp1);
1214 template <typename float_type>
1215 c2_inverse_function_p<float_type>::c2_inverse_function_p(
1216 const c2_function<float_type> &source)
1217 : c2_function<float_type>(), func(source)
1219 float_type l=source.xmin();
1220 float_type r=source.xmax();
1221 start_hint=(l+r)*0.5; // guess that we start in the middle
1223 float_type ly=source(l);
1224 float_type ry=source(r);
1226 float_type t=ly; ly=ry; ry=t;
1228 this->set_domain(ly, ry);
1231 template <typename float_type> float_type
1232 c2_inverse_function_p<float_type>::value_with_derivatives(
1233 float_type x, float_type *yprime, float_type *yprime2
1234 ) const throw(c2_exception)
1236 float_type l=this->func->xmin();
1237 float_type r=this->func->xmax();
1239 float_type y=this->func->find_root(l, r, get_start_hint(x), x, 0, &yp, &ypp);
1241 if(yprime) *yprime=1.0/yp;
1242 if(yprime2) *yprime2=-ypp/(yp*yp*yp);
1246 template <typename float_type>
1247 accumulated_histogram<float_type>::accumulated_histogram(
1248 const std::vector<float_type>binedges, const std::vector<float_type> binheights,
1249 bool normalize, bool inverse_function, bool drop_zeros)
1252 int np=binheights.size();
1254 std::vector<float_type> be, bh;
1255 if(drop_zeros || inverse_function) {
1256 if(binheights[0] || !inverse_function) {
1257 be.push_back(binedges[0]);
1258 bh.push_back(binheights[0]);
1260 for(int i=1; i<np-1; i++) {
1262 be.push_back(binedges[i]);
1263 bh.push_back(binheights[i]);
1266 if(binheights[np-1] || !inverse_function) {
1267 bh.push_back(binheights[np-1]);
1268 be.push_back(binedges[np-1]);
1269 be.push_back(binedges[np]); // push both sides of the last bin if needed
1271 np=bh.size(); // set np to compressed size of bin array
1276 std::vector<float_type> cum(np+1, 0.0);
1277 for(int i=1; i<=np; i++) cum[i]=bh[i]*(be[i]-be[i-1])+cum[i-1];
1278 // accumulate bins, leaving bin 0 as 0
1279 if(be[1] < be[0]) { // if bins passed in backwards, reverse them
1280 std::reverse(be.begin(), be.end());
1281 std::reverse(cum.begin(), cum.end());
1282 for(unsigned int i=0; i<cum.size(); i++) cum[i]*=-1;
1283 // flip sign on reversed data
1286 float_type mmm=1.0/std::max(cum[0], cum[np]);
1287 for(int i=0; i<=np; i++) cum[i]*=mmm;
1289 if(inverse_function) interpolating_function_p<float_type>(cum, be);
1290 // use cum as x axis in inverse function
1291 else interpolating_function_p<float_type>(be, cum);
1292 // else use lower bin edge as x axis
1293 std::fill(this->y2.begin(), this->y2.end(), 0.0);
1294 // clear second derivatives, to we are piecewise linear
1297 template <typename float_type>
1298 c2_piecewise_function_p<float_type>::c2_piecewise_function_p()
1299 : c2_function<float_type>(), lastKLow(-1)
1301 this->sampling_grid=new std::vector<float_type>;
1304 template <typename float_type>
1305 c2_piecewise_function_p<float_type>::~c2_piecewise_function_p()
1309 template <typename float_type> float_type
1310 c2_piecewise_function_p<float_type>::value_with_derivatives(
1311 float_type x, float_type *yprime, float_type *yprime2
1312 ) const throw(c2_exception)
1315 size_t np=functions.size();
1316 if(!np) throw c2_exception(
1317 "attempting to evaluate an empty piecewise function");
1319 if(x < this->xmin() || x > this->xmax()) {
1320 std::ostringstream outstr;
1321 outstr << "piecewise function argument " << x << " out of range "
1322 << this->xmin() << " -- " << this->xmax();
1323 throw c2_exception(outstr.str().c_str());
1328 if(lastKLow >= 0 && functions[lastKLow]->xmin() <= x
1329 && functions[lastKLow]->xmax() > x) {
1333 while(khi-klo > 1) {
1335 if(functions[kk]->xmin() > x) khi=kk;
1340 return functions[klo]->value_with_derivatives(x, yprime, yprime2);
1343 template <typename float_type> void
1344 c2_piecewise_function_p<float_type>::append_function(
1345 const c2_function<float_type> &func) throw(c2_exception)
1347 c2_const_ptr<float_type> keepfunc(func);
1348 if(functions.size()) { // check whether there are any gaps to fill, etc.
1349 const c2_function<float_type> &tail=functions.back();
1350 float_type x0=tail.xmax();
1351 float_type x1=func.xmin();
1353 // must insert a connector if x0 < x1
1354 float_type y0=tail(x0);
1355 float_type y1=func(x1);
1356 c2_function<float_type> &connector=
1357 *new c2_linear_p<float_type>(x0, y0, (y1-y0)/(x1-x0));
1358 connector.set_domain(x0,x1);
1359 functions.push_back(c2_const_ptr<float_type>(connector));
1360 this->sampling_grid->push_back(x1);
1361 } else if(x0>x1) throw c2_exception(
1362 "function domains not increasing in c2_piecewise_function");
1364 functions.push_back(keepfunc);
1365 // extend our domain to include all known functions
1366 this->set_domain(functions.front()->xmin(), functions.back()->xmax());
1367 // extend our sampling grid with the new function's grid,
1368 // with the first point dropped to avoid duplicates
1369 std::vector<float_type> newgrid;
1370 func.get_sampling_grid(func.xmin(), func.xmax(), newgrid);
1371 this->sampling_grid->insert(this->sampling_grid->end(),
1372 newgrid.begin()+1, newgrid.end());
1375 template <typename float_type>
1376 c2_connector_function_p<float_type>::c2_connector_function_p(
1377 float_type x0, const c2_function<float_type> &f0, float_type x2,
1378 const c2_function<float_type> &f2,
1379 bool auto_center, float_type y1)
1380 : c2_function<float_type>()
1382 c2_const_ptr<float_type> left(f0), right(f2);
1383 c2_fblock<float_type> fb0, fb2;
1385 f0.fill_fblock(fb0);
1387 f2.fill_fblock(fb2);
1388 init(fb0, fb2, auto_center, y1);
1391 template <typename float_type>
1392 c2_connector_function_p<float_type>::c2_connector_function_p(
1393 float_type x0, float_type y0, float_type yp0, float_type ypp0,
1394 float_type x2, float_type y2, float_type yp2, float_type ypp2,
1395 bool auto_center, float_type y1)
1396 : c2_function<float_type>()
1398 c2_fblock<float_type> fb0, fb2;
1399 fb0.x=x0; fb0.y=y0; fb0.yp=yp0; fb0.ypp=ypp0;
1400 fb2.x=x2; fb2.y=y2; fb2.yp=yp2; fb2.ypp=ypp2;
1401 init(fb0, fb2, auto_center, y1);
1404 template <typename float_type>
1405 c2_connector_function_p<float_type>::c2_connector_function_p(
1406 const c2_fblock<float_type> &fb0,
1407 const c2_fblock<float_type> &fb2,
1408 bool auto_center, float_type y1)
1409 : c2_function<float_type>()
1411 init(fb0, fb2, auto_center, y1);
1414 template <typename float_type> void c2_connector_function_p<float_type>::init(
1415 const c2_fblock<float_type> &fb0,
1416 const c2_fblock<float_type> &fb2,
1417 bool auto_center, float_type y1)
1419 float_type dx=(fb2.x-fb0.x)/2.0;
1422 // scale derivs to put function on [-1,1] since mma solution is done this way
1423 float_type yp0=fb0.yp*dx;
1424 float_type yp2=fb2.yp*dx;
1425 float_type ypp0=fb0.ypp*dx*dx;
1426 float_type ypp2=fb2.ypp*dx*dx;
1428 float_type ff0=(8*(fb0.y + fb2.y) + 5*(yp0 - yp2) + ypp0 + ypp2)*0.0625;
1429 if(auto_center) y1=ff0; // forces ff to be 0 if we are auto-centering
1431 // y[x_] = y1 + x (a + b x) + x [(x-1) (x+1)] (c + d x)
1432 // + x (x-1)^2 (x+1)^2 (e + f x)
1433 // y' = a + 2 b x + d x [(x+1)(x-1)] + (c + d x)(3x^2-1)
1434 // + f x [(x+1)(x-1)]^2 + (e + f x)[(x+1)(x-1)](5x^2-1)
1435 // y'' = 2 b + 6x(c + d x) + 2d(3x^2-1) + 4x(e + f x)(5x^2-3)
1436 // + 2f(x^2-1)(5x^2-1)
1438 fa=(fb2.y - fb0.y)*0.5;
1439 fb=(fb0.y + fb2.y)*0.5 - y1;
1440 fc=(yp2+yp0-2.*fa)*0.25;
1441 fd=(yp2-yp0-4.*fb)*0.25;
1442 fe=(ypp2-ypp0-12.*fc)*0.0625;
1444 this->set_domain(fb0.x, fb2.x); // this is where the function is valid
1447 template <typename float_type>
1448 c2_connector_function_p<float_type>::~c2_connector_function_p()
1452 template <typename float_type> float_type
1453 c2_connector_function_p<float_type>::value_with_derivatives(
1454 float_type x, float_type *yprime, float_type *yprime2
1455 ) const throw(c2_exception)
1457 float_type x0=this->xmin(), x2=this->xmax();
1458 float_type dx=(x-(x0+x2)*0.5)*fhinv;
1459 float_type q1=(x-x0)*(x-x2)*fhinv*fhinv;
1460 float_type q2=dx*q1;
1462 float_type r1=fa+fb*dx;
1463 float_type r2=fc+fd*dx;
1464 float_type r3=fe+ff*dx;
1466 float_type y=fy1+dx*r1+q2*r2+q1*q2*r3;
1468 if(yprime || yprime2) {
1469 float_type q3=3*q1+2;
1470 float_type q4=5*q1+4;
1471 if(yprime) *yprime=(fa+2*fb*dx+fd*q2+r2*q3+ff*q1*q2+q1*q4*r3)*fhinv;
1472 if(yprime2) *yprime2=2*(fb+fd*q3+3*dx*r2+ff*q1*q4
1473 +r3*(2*dx*(5*q1+2)))*fhinv*fhinv;
1478 // the recursive part of the sampler is agressively designed
1479 // to minimize copying of data... lots of pointers
1480 template <typename float_type> void
1481 c2_function<float_type>::sample_step(c2_sample_recur &rb) const
1484 std::vector< recur_item > &rb_stack=*rb.rb_stack;
1485 // heap-based stack of data for recursion
1489 top.depth=0; top.done=false; top.f0index=0; top.f2index=0;
1491 // push storage for our initial elements
1492 rb_stack.push_back(top);
1493 rb_stack.back().f1=*rb.f0;
1494 rb_stack.back().done=true;
1496 rb_stack.push_back(top);
1497 rb_stack.back().f1=*rb.f1;
1498 rb_stack.back().done=true;
1501 rb.dx_tolerance=10.0*std::numeric_limits<float_type>::epsilon();
1502 rb.abs_tol_min=10.0*std::numeric_limits<float_type>::min();
1506 // now, push our first real element
1507 top.f0index=0; // left element is stack[0]
1508 top.f2index=1; // right element is stack[1]
1509 rb_stack.push_back(top);
1511 while(rb_stack.size() > 2) {
1512 recur_item &back=rb_stack.back();
1514 rb_stack.pop_back();
1519 c2_fblock<float_type> &f0=rb_stack[back.f0index].f1,
1520 &f2=rb_stack[back.f2index].f1;
1521 c2_fblock<float_type> &f1=back.f1; // will hold new middle values
1522 size_t f1index=rb_stack.size()-1; // our current offset
1525 f1.x=0.5*(f0.x + f2.x); // center of interval
1526 float_type dx2=0.5*(f2.x - f0.x);
1528 if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance || std::abs(dx2)
1530 std::ostringstream outstr;
1531 outstr << "Step size underflow in adaptive_sampling at depth="
1532 << back.depth << ", x= " << f1.x;
1533 throw c2_exception(outstr.str().c_str());
1538 if(c2_isnan(f1.y) || f1.ypbad || f1.yppbad) {
1539 // can't go any further if a nan has appeared
1541 throw c2_exception("NaN encountered while sampling function");
1546 // this is code from connector_function to compute the value at the midpoint
1547 // it is re-included here to avoid constructing a complete c2connector
1548 // just to find out if we are close enough
1549 float_type ff0=(8*(f0.y + f2.y) + 5*(f0.yp - f2.yp)*dx2
1550 + (f0.ypp+f2.ypp)*dx2*dx2)*0.0625;
1551 // we are converging as at least x**5 and bisecting,
1552 // so real error on final step is smaller
1553 eps=std::abs(ff0-f1.y)/32.0;
1555 // there are two tolerances to meet... the shift in the estimate
1556 // of the actual point,
1557 // and the difference between the current points and the extremum
1558 // build all the coefficients needed to construct the local parabola
1559 float_type ypcenter, ypp;
1561 // linear extrapolation error using exact derivs
1562 eps = (std::abs(f0.y+f0.yp*dx2-f1.y)+std::abs(f2.y-f2.yp*dx2-f1.y))*0.125;
1563 ypcenter=2*f1.yp*dx2; // first deriv scaled so this interval is on [-1,1]
1564 ypp=2*(f2.yp-f0.yp)*dx2*dx2;
1566 // linear interpolation error without derivs if we are at top level
1567 // or 3-point parabolic interpolation estimates from previous level
1568 ypcenter=(f2.y-f0.y)*0.5; // derivative estimate at center
1569 ypp=(f2.y+f0.y-2*f1.y); // second deriv estimate
1570 if(back.depth==0) eps=std::abs((f0.y+f2.y)*0.5 - f1.y)*2;
1571 // penalize first step
1572 else eps=std::abs(f1.y-back.previous_estimate)*0.25;
1574 float_type ypleft=ypcenter-ypp; // derivative at left edge
1575 float_type ypright=ypcenter+ypp; // derivative at right edge
1576 float_type extremum_eps=0;
1577 if((ypleft*ypright) <=0) // y' changes sign if we have an extremum
1579 // compute position and value of the extremum this way
1580 float_type xext=-ypcenter/ypp;
1581 float_type yext=f1.y + xext*ypcenter + 0.5*xext*xext*ypp;
1582 // and then find the the smallest offset of it from a point,
1583 // looking in the left or right side
1584 if(xext <=0) extremum_eps=std::min(std::abs(f0.y-yext), std::abs(f1.y-yext));
1585 else extremum_eps=std::min(std::abs(f2.y-yext), std::abs(f1.y-yext));
1587 eps=std::max(eps, extremum_eps); // if previous shot was really bad, keep trying
1590 if(eps < rb.abs_tol || eps < std::abs(f1.y)*rb.rel_tol) {
1592 // we've met the tolerance, and are building a function, append two connectors
1593 rb.out->append_function(
1594 *new c2_connector_function_p<float_type>(f0, f1, true, 0.0)
1596 rb.out->append_function(
1597 *new c2_connector_function_p<float_type>(f1, f2, true, 0.0)
1600 if(rb.xvals && rb.yvals) {
1601 rb.xvals->push_back(f0.x);
1602 rb.xvals->push_back(f1.x);
1603 rb.yvals->push_back(f0.y);
1604 rb.yvals->push_back(f1.y);
1605 // the value at f2 will get pushed in the next segment... it is not forgotten
1608 top.depth=back.depth+1; // increment depth counter
1610 // save the last things we need from back before a push happens, in case
1611 // the push causes a reallocation and moves the whole stack.
1612 size_t f0index=back.f0index, f2index=back.f2index;
1613 float_type left=0, right=0;
1615 // compute three-point parabolic interpolation estimate of right-hand
1616 // and left-hand midpoint
1617 left=(6*f1.y + 3*f0.y - f2.y) * 0.125;
1618 right=(6*f1.y + 3*f2.y - f0.y) * 0.125;
1621 top.f0index=f1index; top.f2index=f2index;
1622 // insert pointers to right side data into our recursion block
1623 top.previous_estimate=right;
1624 rb_stack.push_back(top);
1626 top.f0index=f0index; top.f2index=f1index;
1627 // insert pointers to left side data into our recursion block
1628 top.previous_estimate=left;
1629 rb_stack.push_back(top);
1634 template <typename float_type> c2_piecewise_function_p<float_type> *
1635 c2_function<float_type>::adaptively_sample(
1636 float_type amin, float_type amax,
1637 float_type abs_tol, float_type rel_tol,
1638 int derivs, std::vector<float_type> *xvals,
1639 std::vector<float_type> *yvals) const throw(c2_exception)
1641 c2_fblock<float_type> f0, f2;
1643 std::vector< recur_item > rb_stack;
1644 rb_stack.reserve(20); // enough for most operations
1645 rb.rb_stack=&rb_stack;
1647 if(derivs==2) rb.out=new c2_piecewise_function_p<float_type>();
1648 c2_ptr<float_type> pieces(*rb.out);
1656 if(xvals && yvals) {
1661 // create xgrid as a automatic-variable copy of the sampling
1662 // grid so the exception handler correctly
1664 std::vector<float_type> xgrid;
1665 get_sampling_grid(amin, amax, xgrid);
1666 int np=xgrid.size();
1670 if(c2_isnan(f2.y) || f2.ypbad || f2.yppbad) {
1671 // can't go any further if a nan has appeared
1673 throw c2_exception("NaN encountered while sampling function");
1676 for(int i=0; i<np-1; i++) {
1677 f0=f2; // copy upper bound to lower before computing new upper bound
1681 if(c2_isnan(f2.y) || f2.ypbad || f2.yppbad) {
1682 // can't go any further if a nan has appeared
1684 throw c2_exception("NaN encountered while sampling function");
1687 rb.f0=&f0; rb.f1=&f2;
1690 if(xvals && yvals) { // push final point in vector
1691 xvals->push_back(f2.x);
1692 yvals->push_back(f2.y);
1695 if(rb.out) rb.out->set_sampling_grid(xgrid);
1696 // reflect old sampling grid, which still should be right
1697 pieces.release_for_return();
1698 // unmanage the piecewise_function so we can return it