source: trunk/examples/extended/electromagnetic/TestEm7/include/c2_function.icc@ 1303

Last change on this file since 1303 was 1230, checked in by garnier, 16 years ago

update to geant4.9.3

File size: 58.6 KB
Line 
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
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. *
10// * *
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. *
17// * *
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// ********************************************************************
25//
26//
27/**
28 * \file
29 * \brief Provides code for the general c2_function algebra which supports
30 * fast, flexible operations on piecewise-twice-differentiable functions
31 *
32 * \author Created by R. A. Weller and Marcus H. Mendenhall on 7/9/05.
33 * \author Copyright 2005 __Vanderbilt University__. All rights reserved.
34 *
35 * \version c2_function.cc,v 1.169 2008/05/22 12:45:19 marcus Exp
36 */
37
38#include <iostream>
39#include <vector>
40#include <algorithm>
41#include <cstdlib>
42#include <numeric>
43#include <functional>
44#include <iterator>
45#include <cmath>
46#include <limits>
47#include <sstream>
48
49template <typename float_type> const std::string c2_function<float_type>::cvs_file_vers() const
50{ return "c2_function.cc,v 1.169 2008/05/22 12:45:19 marcus Exp"; }
51
52// find a pre-bracketed root of a c2_function, which is a MUCH easier job than general root finding
53// since the derivatives are known exactly, and smoothness is guaranteed.
54// this searches for f(x)=value, to make life a little easier than always searching for f(x)=0
55template <typename float_type> float_type c2_function<float_type>::find_root(float_type lower_bracket, float_type upper_bracket,
56 float_type start, float_type value, int *error,
57 float_type *final_yprime, float_type *final_yprime2) const throw(c2_exception)
58{
59 // find f(x)=value within the brackets, using the guarantees of smoothness associated with a c2_function
60 // can use local f(x)=a*x**2 + b*x + c and solve quadratic to find root, then iterate
61
62 float_type yp, yp2; // we will make unused pointers point here, to save null checks later
63 if (!final_yprime) final_yprime=&yp;
64 if (!final_yprime2) final_yprime2=&yp2;
65
66 float_type ftol=5*(std::numeric_limits<float_type>::epsilon()*std::abs(value)+std::numeric_limits<float_type>::min());
67 float_type xtol=5*(std::numeric_limits<float_type>::epsilon()*(std::abs(upper_bracket)+std::abs(lower_bracket))+std::numeric_limits<float_type>::min());
68
69 float_type root=start; // start looking in the middle
70 if(error) *error=0; // start out with error flag set to OK, if it is expected
71
72 float_type c, b;
73
74 if(!root_info) {
75 root_info=new struct c2_root_info;
76 root_info->inited=false;
77 }
78 // this new logic is to keep track of where we were before, and lower the number of
79 // function evaluations if we are searching inside the same bracket as before.
80 // Since this root finder has, very often, the bracket of the entire domain of the function,
81 // this makes a big difference, especially to c2_inverse_function
82 if(!root_info->inited || upper_bracket != root_info->upper.x || lower_bracket != root_info->lower.x) {
83 root_info->upper.x=upper_bracket;
84 fill_fblock(root_info->upper);
85 root_info->lower.x=lower_bracket;
86 fill_fblock(root_info->lower);
87 root_info->inited=true;
88 }
89
90 float_type clower=root_info->lower.y-value;
91 if(!clower) {
92 *final_yprime=root_info->lower.yp;
93 *final_yprime2=root_info->lower.ypp;
94 return lower_bracket;
95 }
96
97 float_type cupper=root_info->upper.y-value;
98 if(!cupper) {
99 *final_yprime=root_info->upper.yp;
100 *final_yprime2=root_info->upper.ypp;
101 return upper_bracket;
102 }
103 const float_type lower_sign = (clower < 0) ? -1 : 1;
104
105 if(lower_sign*cupper >0) {
106 // argh, no sign change in here!
107 if(error) { *error=1; return 0.0; }
108 else {
109 std::ostringstream outstr;
110 outstr << "unbracketed root in find_root at xlower= " << lower_bracket << ", xupper= " << upper_bracket;
111 outstr << ", value= " << value << ": bailing";
112 throw c2_exception(outstr.str().c_str());
113 }
114 }
115
116 float_type delta=upper_bracket-lower_bracket; // first error step
117 c=value_with_derivatives(root, final_yprime, final_yprime2)-value; // compute initial values
118 b=*final_yprime; // make a local copy for readability
119 increment_evaluations();
120
121 while(
122 std::abs(delta) > xtol && // absolute x step check
123 std::abs(c) > ftol && // absolute y tolerance
124 std::abs(c) > xtol*std::abs(b) // comparison to smallest possible Y step from derivative
125 )
126 {
127 float_type a=(*final_yprime2)/2; // second derivative is 2*a
128 float_type disc=b*b-4*a*c;
129 // std::cout << std::endl << "find_root_debug a,b,c,d " << a << " " << b << " " << c << " " << disc << std::endl;
130
131 if(disc >= 0) {
132 float_type q=-0.5*((b>=0)?(b+std::sqrt(disc)):(b-std::sqrt(disc)));
133 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
134 else delta=q/a;
135 root+=delta;
136 }
137
138 if(disc < 0 || root<lower_bracket || root>upper_bracket ||
139 std::abs(delta) >= 0.5*(upper_bracket-lower_bracket)) {
140 // if we jump out of the bracket, or aren't converging well, bisect
141 root=0.5*(lower_bracket+upper_bracket);
142 delta=upper_bracket-lower_bracket;
143 }
144 c=value_with_derivatives(root, final_yprime, final_yprime2)-value; // compute initial values
145 if(c2_isnan(c)) {
146 bad_x_point=root;
147 return c; // return the nan if a computation failed
148 }
149 b=*final_yprime; // make a local copy for readability
150 increment_evaluations();
151
152 // now, close in bracket on whichever side this still brackets
153 if(c*lower_sign < 0.0) {
154 cupper=c;
155 upper_bracket=root;
156 } else {
157 clower=c;
158 lower_bracket=root;
159 }
160 // std::cout << "find_root_debug x, y, dx " << root << " " << c << " " << delta << std::endl;
161 }
162 return root;
163}
164
165/* def partial_integrals(self, xgrid):
166 Return the integrals of a function between the sampling points xgrid. The sum is the definite integral.
167 This method uses an exact integration of the polynomial which matches the values and derivatives at the
168 endpoints of a segment. Its error scales as h**6, if the input functions really are smooth.
169 This could very well be used as a stepper for adaptive Romberg integration.
170 For InterpolatingFunctions, it is likely that the Simpson's rule integrator is sufficient.
171 #the weights come from an exact mathematica solution to the 5th order polynomial with the given values & derivatives
172 #yint = (y0+y1)*dx/2 + dx^2*(yp0-yp1)/10 + dx^3 * (ypp0+ypp1)/120 )
173*/
174
175// the recursive part of the integrator is agressively designed to minimize copying of data... lots of pointers
176template <typename float_type> float_type c2_function<float_type>::integrate_step(c2_integrate_recur &rb) const throw(c2_exception)
177{
178 std::vector< recur_item > &rb_stack=*rb.rb_stack; // heap-based stack of data for recursion
179 rb_stack.clear();
180
181 recur_item top;
182 top.depth=0; top.done=false; top.f0index=0; top.f2index=0; top.step_sum=0;
183
184 // push storage for our initial elements
185 rb_stack.push_back(top);
186 rb_stack.back().f1=*rb.f0;
187 rb_stack.back().done=true; // this element will never be evaluated further
188
189 rb_stack.push_back(top);
190 rb_stack.back().f1=*rb.f1;
191 rb_stack.back().done=true; // this element will never be evaluated further
192
193 if(!rb.inited) {
194 switch(rb.derivs) {
195 case 0:
196 rb.eps_scale=0.1; rb.extrap_coef=16; break;
197 case 1:
198 rb.eps_scale=0.1; rb.extrap_coef=64; break;
199 case 2:
200 rb.eps_scale=0.02; rb.extrap_coef=1024; break;
201 default:
202 throw c2_exception("derivs must be 0, 1 or 2 in partial_integrals");
203 }
204
205 rb.extrap2=1.0/(rb.extrap_coef-1.0);
206 rb.dx_tolerance=10.0*std::numeric_limits<float_type>::epsilon();
207 rb.abs_tol_min=10.0*std::numeric_limits<float_type>::min();
208 rb.inited=true;
209 }
210
211 // now, push our first real element
212 top.f0index=0; // left element is stack[0]
213 top.f2index=1; // right element is stack[1]
214 top.abs_tol=rb.abs_tol;
215 rb_stack.push_back(top);
216
217 while(rb_stack.size() > 2) {
218 recur_item &back=rb_stack.back();
219 if(back.done) {
220 float_type sum=back.step_sum;
221 rb_stack.pop_back();
222 rb_stack.back().step_sum+=sum; // bump our sum up to the parent
223 continue;
224 }
225 back.done=true;
226
227 c2_fblock<float_type> &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1;
228 c2_fblock<float_type> &f1=back.f1; // will hold new middle values
229 size_t f1index=rb_stack.size()-1; // our current offset
230 float_type abs_tol=back.abs_tol;
231
232 f1.x=0.5*(f0.x + f2.x); // center of interval
233 float_type dx2=0.5*(f2.x - f0.x);
234
235 // check for underflow on step size, which prevents us from achieving specified accuracy.
236 if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance || std::abs(dx2) < rb.abs_tol_min) {
237 std::ostringstream outstr;
238 outstr << "Step size underflow in adaptive_partial_integrals at depth=" << back.depth << ", x= " << f1.x;
239 throw c2_exception(outstr.str().c_str());
240 }
241
242 fill_fblock(f1);
243 if(c2_isnan(f1.y)) {
244 bad_x_point=f1.x;
245 return f1.y; // can't go any further if a nan has appeared
246 }
247
248 bool yptrouble=f0.ypbad || f2.ypbad || f1.ypbad;
249 bool ypptrouble=f0.yppbad || f2.yppbad || f1.yppbad;
250
251 // select the real derivative count based on whether we are at a point where derivatives exist
252 int derivs = std::min(rb.derivs, (yptrouble||ypptrouble)?(yptrouble?0:1):2);
253
254 if(!back.depth) { // top level, total has not been initialized yet
255 switch(derivs) { // create estimate of next lower order for first try
256 case 0:
257 back.previous_estimate=(f0.y+f2.y)*dx2; break;
258 case 1:
259 back.previous_estimate=(f0.y+4.0*f1.y+f2.y)*dx2/3.0; break;
260 case 2:
261 back.previous_estimate=( (14*f0.y + 32*f1.y + 14*f2.y) + 2*dx2 * (f0.yp - f2.yp) ) * dx2 /30.; break;
262 default:
263 back.previous_estimate=0.0; // just to suppress missing default warnings
264 }
265 }
266
267 float_type left, right;
268
269 // pre-compute constants so all multiplies use a small dynamic range
270 // constants for 0 derivative integrator
271 static const float_type c0c1=5./12., c0c2=8./12., c0c3=-1./12.;
272 // constants for 1 derivative integrator
273 static const float_type c1c1=101./240., c1c2=128./240., c1c3=11./240.,
274 c1c4=13./240., c1c5=-40./240., c1c6=-3./240.;
275 // constants for 2 derivative integrator
276 static const float_type c2c1=169./40320., c2c2=1024./ 40320., c2c3=-41./40320.,
277 c2c4=2727./40320., c2c5=-5040./40320., c2c6=423./40320.,
278 c2c7=17007./40320., c2c8=24576./40320., c2c9=-1263./40320.;
279
280 switch(derivs) {
281 case 2:
282 // use ninth-order estimates for each side, from full set of all values (!) (Thanks, Mathematica!)
283 left= ( ( (c2c1*f0.ypp + c2c2*f1.ypp + c2c3*f2.ypp)*dx2 +
284 (c2c4*f0.yp + c2c5*f1.yp + c2c6*f2.yp) )*dx2 +
285 (c2c7*f0.y + c2c8*f1.y + c2c9*f2.y) )* dx2;
286 right= ( ( (c2c1*f2.ypp + c2c2*f1.ypp + c2c3*f0.ypp)*dx2 -
287 (c2c4*f2.yp + c2c5*f1.yp + c2c6*f0.yp) )*dx2 +
288 (c2c7*f2.y + c2c8*f1.y + c2c9*f0.y) )* dx2;
289 // std::cout << f0.x << " " << f1.x << " " << f2.x << std::endl ;
290 // std::cout << f0.y << " " << f1.y << " " << f2.y << " " << left << " " << right << " " << total << std::endl ;
291 break;
292 case 1:
293 left= ( (c1c1*f0.y + c1c2*f1.y + c1c3*f2.y) + dx2*(c1c4*f0.yp + c1c5*f1.yp + c1c6*f2.yp) ) * dx2 ;
294 right= ( (c1c1*f2.y + c1c2*f1.y + c1c3*f0.y) - dx2*(c1c4*f2.yp + c1c5*f1.yp + c1c6*f0.yp) ) * dx2 ;
295 break;
296 case 0:
297 left= (c0c1*f0.y + c0c2*f1.y + c0c3*f2.y)*dx2;
298 right= (c0c1*f2.y + c0c2*f1.y + c0c3*f0.y)*dx2;
299 break;
300 default:
301 left=right=0.0; // suppress warnings about missing default
302 break;
303 }
304
305 float_type lrsum=left+right;
306
307 bool extrapolate=back.depth && rb.extrapolate && (derivs==rb.derivs); // only extrapolate if no trouble with derivs
308 float_type eps=std::abs(back.previous_estimate-lrsum)*rb.eps_scale;
309 if(extrapolate) eps*=rb.eps_scale;
310
311 if(rb.adapt && eps > abs_tol && eps > std::abs(lrsum)*rb.rel_tol) {
312 // tolerance not met, subdivide & recur
313 if(abs_tol > rb.abs_tol_min) abs_tol=abs_tol*0.5; // each half has half the error budget
314 top.abs_tol=abs_tol;
315 top.depth=back.depth+1;
316
317 // save the last things we need from back before a push happens, in case
318 // the push causes a reallocation and moves the whole stack.
319 size_t f0index=back.f0index, f2index=back.f2index;
320
321 top.f0index=f1index; top.f2index=f2index; // insert pointers to right side data into our recursion block
322 top.previous_estimate=right;
323 rb_stack.push_back(top);
324
325 top.f0index=f0index; top.f2index=f1index; // insert pointers to left side data into our recursion block
326 top.previous_estimate=left;
327 rb_stack.push_back(top);
328
329 } else if(extrapolate) {
330 // extrapolation only happens on leaf nodes, where the tolerance was met.
331 back.step_sum+=(rb.extrap_coef*lrsum - back.previous_estimate)*rb.extrap2;
332 } else {
333 back.step_sum+=lrsum;
334 }
335 }
336 return rb_stack.back().step_sum; // last element on the stack holds the sum
337}
338
339template <typename float_type> bool c2_function<float_type>::check_monotonicity(
340 const std::vector<float_type> &data, const char message[]) const throw(c2_exception)
341{
342 size_t np=data.size();
343 if(np < 2) return false; // one point has no direction!
344
345 bool rev=(data[1] < data[0]); // which way do data point?
346 size_t i;
347
348 if(!rev) for(i = 2; i < np && (data[i-1] < data[i]) ; i++);
349 else for(i = 2; i < np &&(data[i-1] > data[i]) ; i++);
350
351 if(i != np) throw c2_exception(message);
352
353 return rev;
354}
355
356template <typename float_type> void c2_function<float_type>::set_sampling_grid(const std::vector<float_type> &grid) throw(c2_exception)
357{
358 bool rev=check_monotonicity(grid, "set_sampling_grid: sampling grid not monotonic");
359
360 if(!sampling_grid || no_overwrite_grid) sampling_grid=new std::vector<float_type>;
361 (*sampling_grid)=grid; no_overwrite_grid=0;
362
363 if(rev) std::reverse(sampling_grid->begin(), sampling_grid->end()); // make it increasing
364}
365
366template <typename float_type> void c2_function<float_type>::
367 get_sampling_grid(float_type xmin, float_type xmax, std::vector<float_type> &grid) const
368{
369 std::vector<float_type> *result=&grid;
370 result->clear();
371
372 if( !(sampling_grid) || !(sampling_grid->size()) || (xmax <= sampling_grid->front()) || (xmin >= sampling_grid->back()) ) {
373 // nothing is known about the function in this region, return xmin and xmax
374 result->push_back(xmin);
375 result->push_back(xmax);
376 } else {
377 std::vector<float_type> &sg=*sampling_grid; // just a shortcut
378 int np=sg.size();
379 int klo=0, khi=np-1, firstindex=0, lastindex=np-1;
380
381 result->push_back(xmin);
382
383 if(xmin > sg.front() ) {
384 // hunt through table for position bracketing starting point
385 while(khi-klo > 1) {
386 int km=(khi+klo)/2;
387 if(sg[km] > xmin) khi=km;
388 else klo=km;
389 }
390 khi=klo+1;
391 // khi now points to first point definitively beyond our first point, or last point of array
392 firstindex=khi;
393 khi=np-1; // restart upper end of search
394 }
395
396 if(xmax < sg.back()) {
397 // hunt through table for position bracketing starting point
398 while(khi-klo > 1) {
399 int km=(khi+klo)/2;
400 if(sg[km] > xmax) khi=km;
401 else klo=km;
402 }
403 khi=klo+1;
404 // khi now points to first point definitively beyond our last point, or last point of array
405 lastindex=klo;
406 }
407
408 int initsize=result->size();
409 result->resize(initsize+(lastindex-firstindex+2));
410 std::copy(sg.begin()+firstindex, sg.begin()+lastindex+1, result->begin()+initsize);
411 result->back()=xmax;
412
413 // this is the unrefined sampling grid... now check for very close points on front & back and fix if needed.
414 preen_sampling_grid(result);
415 }
416}
417
418template <typename float_type> void c2_function<float_type>::preen_sampling_grid(std::vector<float_type> *result) const
419{
420 // this is the unrefined sampling grid... now check for very close points on front & back and fix if needed.
421 if(result->size() > 2) { // may be able to prune dangerously close points near the ends if there are at least 3 points
422 bool deleteit=false;
423 float_type x0=(*result)[0], x1=(*result)[1];
424 float_type dx1=x1-x0;
425
426 float_type ftol=10.0*(std::numeric_limits<float_type>::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::min());
427 if(dx1 < ftol) deleteit=true;
428 float_type dx2=(*result)[2]-x0;
429 if(dx1/dx2 < 0.1) deleteit=true; // endpoint is very close to internal interesting point
430
431 if(deleteit) result->erase(result->begin()+1); // delete redundant interesting point
432 }
433
434 if(result->size() > 2) { // may be able to prune dangerously close points near the ends if there are at least 3 points
435 bool deleteit=false;
436 int pos=result->size()-3;
437 float_type x0=(*result)[pos+1], x1=(*result)[pos+2];
438 float_type dx1=x1-x0;
439
440 float_type ftol=10.0*(std::numeric_limits<float_type>::epsilon()*(std::abs(x0)+std::abs(x1))+std::numeric_limits<float_type>::min());
441 if(dx1 < ftol) deleteit=true;
442 float_type dx2=x1-(*result)[pos];
443 if(dx1/dx2 < 0.1) deleteit=true; // endpoint is very close to internal interesting point
444
445 if(deleteit) result->erase(result->end()-2); // delete redundant interesting point
446 }
447}
448
449template <typename float_type> void c2_function<float_type>::
450 refine_sampling_grid(std::vector<float_type> &grid, size_t refinement) const
451{
452 size_t np=grid.size();
453 size_t count=(np-1)*refinement + 1;
454 float_type dxscale=1.0/refinement;
455
456 std::vector<float_type> result(count);
457
458 for(size_t i=0; i<(np-1); i++) {
459 float_type x=grid[i];
460 float_type dx=(grid[i+1]-x)*dxscale;
461 for(size_t j=0; j<refinement; j++, x+=dx) result[i*refinement+j]=x;
462 }
463 result.back()=grid.back();
464 grid=result; // copy the expanded grid back to the input
465}
466
467template <typename float_type> float_type c2_function<float_type>::integral(float_type xmin, float_type xmax, std::vector<float_type> *partials,
468 float_type abs_tol, float_type rel_tol, int derivs, bool adapt, bool extrapolate) const throw(c2_exception)
469{
470 if(xmin==xmax) {
471 if(partials) partials->clear();
472 return 0.0;
473 }
474 std::vector<float_type> grid;
475 get_sampling_grid(xmin, xmax, grid);
476 float_type intg=partial_integrals(grid, partials, abs_tol, rel_tol, derivs, adapt, extrapolate);
477 return intg;
478}
479
480template <typename float_type> c2_function<float_type> &c2_function<float_type>::normalized_function(float_type xmin, float_type xmax, float_type norm)
481 const throw(c2_exception)
482{
483 float_type intg=integral(xmin, xmax);
484 return *new c2_scaled_function_p<float_type>(*this, norm/intg);
485}
486
487template <typename float_type> c2_function<float_type> &c2_function<float_type>::square_normalized_function(float_type xmin, float_type xmax, float_type norm)
488 const throw(c2_exception)
489{
490 c2_ptr<float_type> mesquared((*new c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this));
491
492 std::vector<float_type> grid;
493 get_sampling_grid(xmin, xmax, grid);
494 float_type intg=mesquared->partial_integrals(grid);
495
496 return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
497}
498
499template <typename float_type> c2_function<float_type> &c2_function<float_type>::square_normalized_function(
500 float_type xmin, float_type xmax, const c2_function<float_type> &weight, float_type norm)
501 const throw(c2_exception)
502{
503 c2_ptr<float_type> weighted((*new c2_quadratic_p<float_type>(0., 0., 0., 1.))(*this) * weight);
504
505 std::vector<float_type> grid;
506 get_sampling_grid(xmin, xmax, grid);
507 float_type intg=weighted->partial_integrals(grid);
508
509 return *new c2_scaled_function_p<float_type>(*this, std::sqrt(norm/intg));
510}
511
512template <typename float_type> float_type c2_function<float_type>::partial_integrals(
513 std::vector<float_type> xgrid, std::vector<float_type> *partials,
514 float_type abs_tol, float_type rel_tol, int derivs, bool adapt, bool extrapolate)
515 const throw(c2_exception)
516{
517 int np=xgrid.size();
518
519 c2_fblock<float_type> f0, f2;
520 struct c2_integrate_recur rb;
521 rb.rel_tol=rel_tol;
522 rb.extrapolate=extrapolate;
523 rb.adapt=adapt;
524 rb.derivs=derivs;
525 std::vector< recur_item > rb_stack;
526 rb_stack.reserve(20); // enough for most operations
527 rb.rb_stack=&rb_stack;
528 rb.inited=false;
529 float_type dx_inv=1.0/std::abs(xgrid.back()-xgrid.front());
530
531 if(partials) partials->resize(np-1);
532
533 float_type sum=0.0;
534
535 f2.x=xgrid[0];
536 fill_fblock(f2);
537 if(c2_isnan(f2.y)) {
538 bad_x_point=f2.x;
539 return f2.y; // can't go any further if a nan has appeared
540 }
541
542 for(int i=0; i<np-1; i++) {
543 f0=f2; // copy upper bound to lower before computing new upper bound
544
545 f2.x=xgrid[i+1];
546 fill_fblock(f2);
547 if(c2_isnan(f2.y)) {
548 bad_x_point=f2.x;
549 return f2.y; // can't go any further if a nan has appeared
550 }
551
552 rb.abs_tol=abs_tol*std::abs(f2.x-f0.x)*dx_inv; // distribute error tolerance over whole domain
553 rb.f0=&f0; rb.f1=&f2;
554 float_type ps=integrate_step(rb);
555 sum+=ps;
556 if(partials) (*partials)[i]=ps;
557 if(c2_isnan(ps)) break; // NaN stops integration
558 }
559 return sum;
560}
561
562// generate a sampling grid at points separated by dx=5, which is intentionally
563// incommensurate with pi and 2*pi so grid errors are somewhat randomized
564template <typename float_type> void c2_sin_p<float_type>::
565 get_sampling_grid(float_type xmin, float_type xmax, std::vector<float_type> &grid) const
566{
567 grid.clear();
568 for(; xmin < xmax; xmin+=5.0) grid.push_back(xmin);
569 grid.push_back(xmax);
570 this->preen_sampling_grid(&grid);
571}
572
573template <typename float_type> float_type c2_function_transformation<float_type>::evaluate(
574 float_type xraw,
575 float_type y, float_type yp0, float_type ypp0,
576 float_type *yprime, float_type *yprime2) const
577{
578 y=Y.fHasStaticTransforms ? Y.pOut(y) : Y.fOut(y);
579
580 if(yprime || yprime2) {
581
582 float_type yp, yp2;
583 if(X.fHasStaticTransforms && Y.fHasStaticTransforms) {
584 float_type fpi=1.0/Y.pInPrime(y);
585 float_type gp=X.pInPrime(xraw);
586 // from Mathematica Dt[InverseFunction[f][y[g[x]]], x]
587 yp=gp*yp0*fpi; // transformed derivative
588 yp2=(gp*gp*ypp0 + X.pInDPrime(xraw)*yp0 - Y.pInDPrime(y)*yp*yp )*fpi;
589 } else {
590 float_type fpi=1.0/Y.fInPrime(y);
591 float_type gp=X.fInPrime(xraw);
592 // from Mathematica Dt[InverseFunction[f][y[g[x]]], x]
593 yp=gp*yp0*fpi; // transformed derivative
594 yp2=(gp*gp*ypp0 + X.fInDPrime(xraw)*yp0 - Y.fInDPrime(y)*yp*yp )*fpi;
595 }
596 if(yprime) *yprime=yp;
597 if(yprime2) *yprime2=yp2;
598 }
599 return y;
600}
601
602// The constructor
603template <typename float_type> interpolating_function_p<float_type> & interpolating_function_p<float_type>::load(
604 const std::vector<float_type> &x, const std::vector<float_type> &f,
605 bool lowerSlopeNatural, float_type lowerSlope,
606 bool upperSlopeNatural, float_type upperSlope,
607 bool splined
608 ) throw(c2_exception)
609{
610 c2_ptr<float_type> keepme(*this);
611 X= x;
612 F= f;
613
614 // Xraw is useful in some of the arithmetic operations between interpolating functions
615 Xraw=x;
616
617 set_domain(std::min(Xraw.front(), Xraw.back()),std::max(Xraw.front(), Xraw.back()));
618
619 if(x.size() != f.size()) {
620 throw c2_exception("interpolating_function::init() -- x & y inputs are of different size");
621 }
622
623 size_t np=X.size(); // they are the same now, so lets take a short cut
624
625 if(np < 2) {
626 throw c2_exception("interpolating_function::init() -- input < 2 elements ");
627 }
628
629 bool xraw_rev=check_monotonicity(Xraw,
630 "interpolating_function::init() non-monotonic raw x input"); // which way does raw X point? sampling grid MUST be increasing
631
632 if(!xraw_rev) { // we can use pointer to raw X values if they are in the right order
633 set_sampling_grid_pointer(Xraw); // our intial grid of x values is certainly a good guess for 'interesting' points
634 } else {
635 set_sampling_grid(Xraw); // make a copy of it, and assure it is in right order
636 }
637
638 if(fTransform.X.fTransformed) { // check if X scale is nonlinear, and if so, do transform
639 if(!lowerSlopeNatural) lowerSlope /= fTransform.X.fInPrime(X[0]);
640 if(!upperSlopeNatural) upperSlope /= fTransform.X.fInPrime(X[np-1]);
641 for(size_t i=0; i<np; i++) X[i]=fTransform.X.fIn(X[i]);
642 }
643 if(fTransform.Y.fTransformed) { // check if Y scale is nonlinear, and if so, do transform
644 if(!lowerSlopeNatural) lowerSlope *= fTransform.Y.fInPrime(F[0]);
645 if(!upperSlopeNatural) upperSlope *= fTransform.Y.fInPrime(F[np-1]);
646 for(size_t i=0; i<np; i++) F[i]=fTransform.Y.fIn(F[i]);
647 }
648
649 xInverted=check_monotonicity(X,
650 "interpolating_function::init() non-monotonic transformed x input");
651
652 if(splined) spline(lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope);
653 else y2.assign(np,0.0);
654
655 lastKLow=0;
656 keepme.release_for_return();
657 return *this;
658}
659
660/*
661// The constructor
662template <typename float_type> interpolating_function_p<float_type> & interpolating_function_p<float_type>::load_pairs(
663 std::vector<std::pair<float_type, float_type> > &data,
664 bool lowerSlopeNatural, float_type lowerSlope,
665 bool upperSlopeNatural, float_type upperSlope,
666 bool splined
667 ) throw(c2_exception)
668{
669 c2_ptr<float_type> keepme(*this);
670
671 size_t np=data.size();
672 if(np < 2) {
673 throw c2_exception("interpolating_function::init() -- input < 2 elements ");
674 }
675
676 // sort into ascending order
677 std::sort(data.begin(), data.end(), comp_pair);
678
679 std::vector<float_type> xtmp, ytmp;
680 xtmp.reserve(np);
681 ytmp.reserve(np);
682 for (size_t i=0; i<np; i++) {
683 xtmp.push_back(data[i].first);
684 ytmp.push_back(data[i].second);
685 }
686 this->load(xtmp, ytmp, lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope, splined);
687
688 keepme.release_for_return();
689 return *this;
690}
691
692template <typename float_type> interpolating_function_p<float_type> &
693 interpolating_function_p<float_type>::load_random_generator_function(
694 const std::vector<float_type> &bincenters, const c2_function<float_type> &binheights)
695 throw(c2_exception)
696{
697 c2_ptr<float_type> keepme(*this);
698
699 std::vector<float_type> integral;
700 c2_const_ptr<float_type> keepit(binheights); // manage function... not really needed here, but always safe.
701 // integrate from first to last bin in original order, leaving results in integral
702 // ask for relative error of 1e-6 on each bin, with absolute error set to 0 (since we don't know the data scale).
703 float_type sum=binheights.partial_integrals(bincenters, &integral, 0.0, 1e-6);
704 // the integral vector now has partial integrals... it must be accumulated by summing
705 integral.insert(integral.begin(), 0.0); // integral from start to start is 0
706 float_type scale=1.0/sum;
707 for(size_t i=1; i<integral.size(); i++) integral[i]=integral[i]*scale + integral[i-1];
708 integral.back()=1.0; // force exact value on boundary
709
710 this->load(integral, bincenters,
711 false, 1.0/(scale*binheights(bincenters.front() )),
712 false, 1.0/(scale*binheights(bincenters.back() ))
713 ); // use integral as x axis in inverse function
714 keepme.release_for_return();
715 return *this;
716}
717
718template <typename float_type> interpolating_function_p<float_type> &
719 interpolating_function_p<float_type>::load_random_generator_bins(
720 const std::vector<float_type> &bins, const std::vector<float_type> &binheights)
721 throw(c2_exception)
722{
723 c2_ptr<float_type> keepme(*this);
724
725 size_t np=binheights.size();
726 std::vector<float_type> integral(np+1), bin_edges(np+1);
727
728 // compute the integral based on estimates of the bin edges from the given bin centers...
729 // except for bin 0 & final bin, the edge of a bin is halfway between then center of the
730 // bin and the center of the previous/next bin.
731 // This gives width[n] = (center[n+1]+center[n])/2 - (center[n]+center[n-1])/2 = (center[n+1]-center[n-1])/2
732 // for the edges, assume a bin of width (center[1]-center[0]) or (center[np-1]-center[np-2])
733 // be careful that absolute values are used in case data are reversed.
734
735 if(bins.size() == binheights.size()+1) {
736 bin_edges=bins; // edges array was passed in
737 } else if (bins.size() == binheights.size()) {
738 bin_edges.front()=bins[0] - (bins[1]-bins[0])*0.5; // edge bin
739 for(size_t i=1; i<np; i++) {
740 bin_edges[i]=(bins[i]+bins[i-1])*0.5;
741 }
742 bin_edges.back()=bins[np-1] + (bins[np-1]-bins[np-2])*0.5; // edge bin
743 } else {
744 throw c2_exception("inconsistent bin vectors passed to load_random_generator_bins");
745 }
746
747 float_type running_sum=0.0;
748 for(size_t i=0; i<np; i++) {
749 integral[i]=running_sum;
750 if(!binheights[i]) throw c2_exception("empty bin passed to load_random_generator_bins");
751 running_sum+=binheights[i]*std::abs(bin_edges[i+1]-bin_edges[i]);
752 }
753 float_type scale=1.0/running_sum;
754 for(size_t i=0; i<np; i++) integral[i]*=scale;
755 integral.back()=1.0; // force exactly correct value on boundary
756 this->load(integral, bin_edges,
757 false, 1.0/(scale*binheights.front()),
758 false, 1.0/(scale*binheights.back())
759 ); // use integral as x axis in inverse function
760 keepme.release_for_return();
761 return *this;
762}
763*/
764
765// The spline table generator
766template <typename float_type> void interpolating_function_p<float_type>::spline(
767 bool lowerSlopeNatural, float_type lowerSlope,
768 bool upperSlopeNatural, float_type upperSlope
769 ) throw(c2_exception)
770{
771// construct spline tables here.
772 // this code is a re-translation of the pythonlabtools spline algorithm from pythonlabtools.sourceforge.net
773 size_t np=X.size();
774 std::vector<float_type> u(np), dy(np-1), dx(np-1), dxi(np-1), dx2i(np-2), siga(np-2), dydx(np-1);
775
776 std::transform(X.begin()+1, X.end(), X.begin(), dx.begin(), std::minus<float_type>() ); // dx=X[1:] - X [:-1]
777 for(size_t i=0; i<dxi.size(); i++) dxi[i]=1.0/dx[i]; // dxi = 1/dx
778 for(size_t i=0; i<dx2i.size(); i++) dx2i[i]=1.0/(X[i+2]-X[i]);
779
780 std::transform(F.begin()+1, F.end(), F.begin(), dy.begin(), std::minus<float_type>() ); // dy = F[i+1]-F[i]
781 std::transform(dx2i.begin(), dx2i.end(), dx.begin(), siga.begin(), std::multiplies<float_type>()); // siga = dx[:-1]*dx2i
782 std::transform(dxi.begin(), dxi.end(), dy.begin(), dydx.begin(), std::multiplies<float_type>()); // dydx=dy/dx
783
784 // u[i]=(y[i+1]-y[i])/float(x[i+1]-x[i]) - (y[i]-y[i-1])/float(x[i]-x[i-1])
785 std::transform(dydx.begin()+1, dydx.end(), dydx.begin(), u.begin()+1, std::minus<float_type>() ); // incomplete rendering of u = dydx[1:]-dydx[:-1]
786
787 y2.resize(np,0.0);
788
789 if(lowerSlopeNatural) {
790 y2[0]=u[0]=0.0;
791 } else {
792 y2[0]= -0.5;
793 u[0]=(3.0*dxi[0])*(dy[0]*dxi[0] -lowerSlope);
794 }
795
796 for(size_t i=1; i < np -1; i++) { // the inner loop
797 float_type sig=siga[i-1];
798 float_type p=sig*y2[i-1]+2.0;
799 y2[i]=(sig-1.0)/p;
800 u[i]=(6.0*u[i]*dx2i[i-1] - sig*u[i-1])/p;
801 }
802
803 float_type qn, un;
804
805 if(upperSlopeNatural) {
806 qn=un=0.0;
807 } else {
808 qn= 0.5;
809 un=(3.0*dxi[dxi.size()-1])*(upperSlope- dy[dy.size()-1]*dxi[dxi.size()-1] );
810 }
811
812 y2[np-1]=(un-qn*u[np-2])/(qn*y2[np-2]+1.0);
813 for (size_t k=np-1; k != 0; k--) y2[k-1]=y2[k-1]*y2[k]+u[k-1];
814}
815
816template <typename float_type> interpolating_function_p<float_type> &interpolating_function_p<float_type>::sample_function(
817 const c2_function<float_type> &func,
818 float_type xmin, float_type xmax, float_type abs_tol, float_type rel_tol,
819 bool lowerSlopeNatural, float_type lowerSlope,
820 bool upperSlopeNatural, float_type upperSlope
821 ) throw(c2_exception)
822{
823 c2_ptr<float_type> keepme(*this);
824
825 const c2_transformation<float_type> &XX=fTransform.X, &YY=fTransform.Y; // shortcuts
826
827 // set up our params to look like the samplng function for now
828 sampler_function=func;
829 std::vector<float_type> grid;
830 func.get_sampling_grid(xmin, xmax, grid);
831 size_t gsize=grid.size();
832 if(XX.fTransformed) for(size_t i=0; i<gsize; i++) grid[i]=XX.fIn(grid[i]);
833 set_sampling_grid_pointer(grid);
834
835 // float_type xmin1=fXin(xmin), xmax1=fXin(xmax); // bounds in transformed space
836 // get a list of points needed in transformed space, directly into our tables
837 this->adaptively_sample(grid.front(), grid.back(), 8*abs_tol, 8*rel_tol, 0, &X, &F);
838 // clear the sampler function now, since otherwise our value_with_derivatives is broken
839 sampler_function.unset_function();
840
841 xInverted=check_monotonicity(X,
842 "interpolating_function::init() non-monotonic transformed x input");
843
844 size_t np=X.size();
845
846 // Xraw is useful in some of the arithmetic operations between interpolating functions
847 if(!XX.fTransformed) Xraw=X;
848 else {
849 Xraw.resize(np);
850 for (size_t i=1; i<np-1; i++) Xraw[i]=XX.fOut(X[i]);
851 Xraw.front()=xmin;
852 Xraw.back()=xmax;
853 }
854
855 bool xraw_rev=check_monotonicity(Xraw,
856 "interpolating_function::init() non-monotonic raw x input");
857 // which way does raw X point? sampling grid MUST be increasing
858
859 if(!xraw_rev) { // we can use pointer to raw X values if they are in the right order
860 set_sampling_grid_pointer(Xraw);
861 // our intial grid of x values is certainly a good guess for 'interesting' points
862 } else {
863 set_sampling_grid(Xraw); // make a copy of it, and assure it is in right order
864 }
865
866 if(XX.fTransformed) { // check if X scale is nonlinear, and if so, do transform
867 if(!lowerSlopeNatural) lowerSlope /= XX.fInPrime(xmin);
868 if(!upperSlopeNatural) upperSlope /= XX.fInPrime(xmax);
869 }
870 if(YY.fTransformed) { // check if Y scale is nonlinear, and if so, do transform
871 if(!lowerSlopeNatural) lowerSlope *= YY.fInPrime(func(xmin));
872 if(!upperSlopeNatural) upperSlope *= YY.fInPrime(func(xmax));
873 }
874 // note that each of ends has 3 points with two equal gaps, since they were obtained by bisection
875 // so the step sizes are easy to get
876 // the 'natural slope' option for sampled functions has a different meaning than
877 // for normal splines. In this case, the derivative is adjusted to make the
878 // second derivative constant on the last two points at each end
879 // which is consistent with the error sampling technique we used to get here
880 if(lowerSlopeNatural) {
881 float_type hlower=X[1]-X[0];
882 lowerSlope=0.5*(-F[2]-3*F[0]+4*F[1])/hlower;
883 lowerSlopeNatural=false; // it's not the usual meaning of natural any more
884 }
885 if(upperSlopeNatural) {
886 float_type hupper=X[np-1]-X[np-2];
887 upperSlope=0.5*(F[np-3]+3*F[np-1]-4*F[np-2])/hupper;
888 upperSlopeNatural=false; // it's not the usual meaning of natural any more
889 }
890 this->set_domain(xmin, xmax);
891
892 spline(lowerSlopeNatural, lowerSlope, upperSlopeNatural, upperSlope);
893 lastKLow=0;
894 keepme.release_for_return();
895 return *this;
896}
897
898// This function is the reason for this class to exist
899// it computes the interpolated function, and (if requested) its proper first and second derivatives including all coordinate transforms
900template <typename float_type> float_type interpolating_function_p<float_type>::value_with_derivatives(
901 float_type x, float_type *yprime, float_type *yprime2) const throw(c2_exception)
902{
903 if(sampler_function.valid()) {
904 // if this is non-null, we are sampling data for later, so just return raw function
905 // however, transform it into our sampling space, first.
906 if(yprime) *yprime=0;
907 if(yprime2) *yprime2=0;
908 sampler_function->increment_evaluations();
909 return fTransform.Y.fIn(sampler_function(fTransform.X.fOut(x))); // derivatives are completely undefined
910 }
911
912 if(x < this->xmin() || x > this->xmax()) {
913 std::ostringstream outstr;
914 outstr << "Interpolating function argument " << x << " out of range " << this->xmin() << " -- " << this ->xmax() << ": bailing";
915 throw c2_exception(outstr.str().c_str());
916 }
917
918 float_type xraw=x;
919
920 if(fTransform.X.fTransformed) x=fTransform.X.fHasStaticTransforms?
921 fTransform.X.pIn(x) : fTransform.X.fIn(x); // save time by explicitly testing for identity function here
922
923 int klo=0, khi=X.size()-1;
924
925 if(khi < 0) throw c2_exception("Uninitialized interpolating function being evaluated");
926
927 const float_type *XX=&X[lastKLow]; // make all fast checks short offsets from here
928
929 if(!xInverted) { // select search depending on whether transformed X is increasing or decreasing
930 if((XX[0] <= x) && (XX[1] >= x) ) { // already bracketed
931 klo=lastKLow;
932 } else if((XX[1] <= x) && (XX[2] >= x)) { // in next bracket to the right
933 klo=lastKLow+1;
934 } else if(lastKLow > 0 && (XX[-1] <= x) && (XX[0] >= x)) { // in next bracket to the left
935 klo=lastKLow-1;
936 } else { // not bracketed, not close, start over
937 // search for new KLow
938 while(khi-klo > 1) {
939 int km=(khi+klo)/2;
940 if(X[km] > x) khi=km;
941 else klo=km;
942 }
943 }
944 } else {
945 if((XX[0] >= x) && (XX[1] <= x) ) { // already bracketed
946 klo=lastKLow;
947 } else if((XX[1] >= x) && (XX[2] <= x)) { // in next bracket to the right
948 klo=lastKLow+1;
949 } else if(lastKLow > 0 && (XX[-1] >= x) && (XX[0] <= x)) { // in next bracket to the left
950 klo=lastKLow-1;
951 } else { // not bracketed, not close, start over
952 // search for new KLow
953 while(khi-klo > 1) {
954 int km=(khi+klo)/2;
955 if(X[km] < x) khi=km;
956 else klo=km;
957 }
958 }
959 }
960
961 khi=klo+1;
962 lastKLow=klo;
963
964 float_type h=X[khi]-X[klo];
965
966 float_type a=(X[khi]-x)/h;
967 float_type b=1.0-a;
968 float_type ylo=F[klo], yhi=F[khi], y2lo=y2[klo], y2hi=y2[khi];
969 float_type y=a*ylo+b*yhi+((a*a*a-a)*y2lo+(b*b*b-b)*y2hi)*(h*h)/6.0;
970
971 float_type yp0=0; // the derivative in interpolating table coordinates
972 float_type ypp0=0; // second derivative
973
974 if(yprime || yprime2) {
975 yp0=(yhi-ylo)/h+((3*b*b-1)*y2hi-(3*a*a-1)*y2lo)*h/6.0; // the derivative in interpolating table coordinates
976 ypp0=b*y2hi+a*y2lo; // second derivative
977 }
978
979 if(fTransform.isIdentity) {
980 if(yprime) *yprime=yp0;
981 if(yprime2) *yprime2=ypp0;
982 return y;
983 } else return fTransform.evaluate(xraw, y, yp0, ypp0, yprime, yprime2);
984}
985
986template <typename float_type> void interpolating_function_p<float_type>::set_lower_extrapolation(float_type bound)
987{
988 int kl = 0 ;
989 int kh=kl+1;
990 float_type xx=fTransform.X.fIn(bound);
991 float_type h0=X[kh]-X[kl];
992 float_type h1=xx-X[kl];
993 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;
994
995 X.insert(X.begin(), xx);
996 F.insert(F.begin(), yextrap);
997 y2.insert(y2.begin(), y2.front()); // duplicate first or last element
998 Xraw.insert(Xraw.begin(), bound);
999 if (bound < this->fXMin) this->fXMin=bound; // check for reversed data
1000 else this->fXMax=bound;
1001
1002 //printf("%10.4f %10.4f %10.4f %10.4f %10.4f\n", bound, xx, h0, h1, yextrap);
1003 //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]);
1004}
1005
1006template <typename float_type> void interpolating_function_p<float_type>::set_upper_extrapolation(float_type bound)
1007{
1008 int kl = X.size()-2 ;
1009 int kh=kl+1;
1010 float_type xx=fTransform.X.fIn(bound);
1011 float_type h0=X[kh]-X[kl];
1012 float_type h1=xx-X[kl];
1013 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;
1014
1015 X.insert(X.end(), xx);
1016 F.insert(F.end(), yextrap);
1017 y2.insert(y2.end(), y2.back()); // duplicate first or last element
1018 Xraw.insert(Xraw.end(), bound);
1019 if (bound < this->fXMin) this->fXMin=bound; // check for reversed data
1020 else this->fXMax=bound;
1021 //printf("%10.4f %10.4f %10.4f %10.4f %10.4f\n", bound, xx, h0, h1, yextrap);
1022 //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]);
1023}
1024
1025// return a new interpolating_function which is the unary function of an existing interpolating_function
1026// can also be used to generate a resampling of another c2_function on a different grid
1027// by creating a=interpolating_function(x,x)
1028// and doing b=a.unary_operator(c) where c is a c2_function (probably another interpolating_function)
1029
1030template <typename float_type> interpolating_function_p<float_type>&
1031 interpolating_function_p<float_type>::unary_operator(const c2_function<float_type> &source) const
1032{
1033 size_t np=X.size();
1034 std::vector<float_type>yv(np);
1035 c2_ptr<float_type> comp(source(*this));
1036 float_type yp0, yp1, ypp;
1037
1038 for(size_t i=1; i<np-1; i++) {
1039 yv[i]=source(fTransform.Y.fOut(F[i])); // copy pointwise the function of our data values
1040 }
1041
1042 yv.front()=comp(Xraw.front(), &yp0, &ypp); // get derivative at front
1043 yv.back()= comp(Xraw.back(), &yp1, &ypp); // get derivative at back
1044
1045 interpolating_function_p &copy=clone();
1046 copy.load(this->Xraw, yv, false, yp0, false, yp1);
1047
1048 return copy;
1049}
1050
1051template <typename float_type> void
1052interpolating_function_p<float_type>::get_data(std::vector<float_type> &xvals, std::vector<float_type> &yvals) const throw()
1053{
1054
1055 xvals=Xraw;
1056 yvals.resize(F.size());
1057
1058 for(size_t i=0; i<F.size(); i++) yvals[i]=fTransform.Y.fOut(F[i]);
1059}
1060
1061template <typename float_type> interpolating_function_p<float_type> &
1062 interpolating_function_p<float_type>::binary_operator(const c2_function<float_type> &rhs,
1063 const c2_binary_function<float_type> *combining_stub) const
1064{
1065 size_t np=X.size();
1066 std::vector<float_type> yv(np);
1067 c2_constant_p<float_type> fval(0);
1068 float_type yp0, yp1, ypp;
1069
1070 c2_const_ptr<float_type> stub(*combining_stub); // manage ownership
1071
1072 for(size_t i=1; i<np-1; i++) {
1073 fval.reset(fTransform.Y.fOut(F[i])); // update the constant function pointwise
1074 yv[i]=combining_stub->combine(fval, rhs, Xraw[i], (float_type *)0, (float_type *)0); // compute rhs & combine without derivatives
1075 }
1076
1077 yv.front()=combining_stub->combine(*this, rhs, Xraw.front(), &yp0, &ypp); // get derivative at front
1078 yv.back()= combining_stub->combine(*this, rhs, Xraw.back(), &yp1, &ypp); // get derivative at back
1079
1080 interpolating_function_p &copy=clone();
1081 copy.load(this->Xraw, yv, false, yp0, false, yp1);
1082
1083 return copy;
1084}
1085
1086template <typename float_type> c2_inverse_function_p<float_type>::c2_inverse_function_p(const c2_function<float_type> &source)
1087 : c2_function<float_type>(), func(source)
1088{
1089 float_type l=source.xmin();
1090 float_type r=source.xmax();
1091 start_hint=(l+r)*0.5; // guess that we start in the middle
1092 // compute our domain assuming the function is monotonic so its values on its domain boundaries are our domain
1093 float_type ly=source(l);
1094 float_type ry=source(r);
1095 if (ly > ry) {
1096 float_type t=ly; ly=ry; ry=t;
1097 }
1098 set_domain(ly, ry);
1099}
1100
1101template <typename float_type> float_type c2_inverse_function_p<float_type>::value_with_derivatives(
1102 float_type x, float_type *yprime, float_type *yprime2
1103 ) const throw(c2_exception)
1104{
1105 float_type l=this->func->xmin();
1106 float_type r=this->func->xmax();
1107 float_type yp, ypp;
1108 float_type y=this->func->find_root(l, r, get_start_hint(x), x, 0, &yp, &ypp);
1109 start_hint=y;
1110 if(yprime) *yprime=1.0/yp;
1111 if(yprime2) *yprime2=-ypp/(yp*yp*yp);
1112 return y;
1113}
1114
1115//accumulated_histogram starts with binned data, generates the integral, and generates a piecewise linear interpolating_function
1116//If drop_zeros is true, it merges empty bins together before integration
1117//Note that the resulting interpolating_function is guaranteed to be increasing (if drop_zeros is false)
1118// or stricly increasing (if drop_zeros is true)
1119//If inverse_function is true, it drop zeros, integrates, and returns the inverse function which is useful
1120// for random number generation based on the input distribution.
1121//If normalize is true, the big end of the integral is scaled to 1.
1122//If the data are passed in reverse order (large X first), the integral is carried out from the big end,
1123// and then the data are reversed to the result in in increasing X order.
1124template <typename float_type> accumulated_histogram<float_type>::accumulated_histogram(
1125 const std::vector<float_type>binedges, const std::vector<float_type> binheights,
1126 bool normalize, bool inverse_function, bool drop_zeros)
1127{
1128
1129 int np=binheights.size();
1130
1131 std::vector<float_type> be, bh;
1132 if(drop_zeros || inverse_function) { //inverse functions cannot have any zero bins or they have vertical sections
1133 if(binheights[0] || !inverse_function) { // conserve lower x bound if not an inverse function
1134 be.push_back(binedges[0]);
1135 bh.push_back(binheights[0]);
1136 }
1137 for(int i=1; i<np-1; i++) {
1138 if(binheights[i]) {
1139 be.push_back(binedges[i]);
1140 bh.push_back(binheights[i]);
1141 }
1142 }
1143 if(binheights[np-1] || !inverse_function) {
1144 bh.push_back(binheights[np-1]);
1145 be.push_back(binedges[np-1]);
1146 be.push_back(binedges[np]); // push both sides of the last bin if needed
1147 }
1148 np=bh.size(); // set np to compressed size of bin array
1149 } else {
1150 be=binedges;
1151 bh=binheights;
1152 }
1153 std::vector<float_type> cum(np+1, 0.0);
1154 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
1155 if(be[1] < be[0]) { // if bins passed in backwards, reverse them
1156 std::reverse(be.begin(), be.end());
1157 std::reverse(cum.begin(), cum.end());
1158 for(unsigned int i=0; i<cum.size(); i++) cum[i]*=-1; // flip sign on reversed data
1159 }
1160 if(normalize) {
1161 float_type m=1.0/std::max(cum[0], cum[np]);
1162 for(int i=0; i<=np; i++) cum[i]*=m;
1163 }
1164 if(inverse_function) interpolating_function_p<float_type>(cum, be); // use cum as x axis in inverse function
1165 else interpolating_function_p<float_type>(be, cum); // else use lower bin edge as x axis
1166 std::fill(this->y2.begin(), this->y2.end(), 0.0); // clear second derivatives, to we are piecewise linear
1167}
1168
1169template <typename float_type> c2_piecewise_function_p<float_type>::c2_piecewise_function_p()
1170: c2_function<float_type>(), lastKLow(-1)
1171{
1172 this->sampling_grid=new std::vector<float_type>; // this always has a smapling grid
1173}
1174
1175template <typename float_type> c2_piecewise_function_p<float_type>::~c2_piecewise_function_p()
1176{
1177}
1178
1179template <typename float_type> float_type c2_piecewise_function_p<float_type>::value_with_derivatives(
1180 float_type x, float_type *yprime, float_type *yprime2
1181 ) const throw(c2_exception)
1182{
1183
1184 size_t np=functions.size();
1185 if(!np) throw c2_exception("attempting to evaluate an empty piecewise function");
1186
1187 if(x < this->xmin() || x > this->xmax()) {
1188 std::ostringstream outstr;
1189 outstr << "piecewise function argument " << x << " out of range " << this->xmin() << " -- " << this->xmax();
1190 throw c2_exception(outstr.str().c_str());
1191 }
1192
1193 int klo=0;
1194
1195 if(lastKLow >= 0 && functions[lastKLow]->xmin() <= x && functions[lastKLow]->xmax() > x) {
1196 klo=lastKLow;
1197 } else {
1198 int khi=np;
1199 while(khi-klo > 1) {
1200 int km=(khi+klo)/2;
1201 if(functions[km]->xmin() > x) khi=km;
1202 else klo=km;
1203 }
1204 }
1205 lastKLow=klo;
1206 return functions[klo]->value_with_derivatives(x, yprime, yprime2);
1207}
1208
1209template <typename float_type> void c2_piecewise_function_p<float_type>::append_function(
1210 const c2_function<float_type> &func) throw(c2_exception)
1211{
1212 c2_const_ptr<float_type> keepfunc(func); // manage function before we can throw any exceptions
1213 if(functions.size()) { // check whether there are any gaps to fill, etc.
1214 const c2_function<float_type> &tail=functions.back();
1215 float_type x0=tail.xmax();
1216 float_type x1=func.xmin();
1217 if(x0 < x1) {
1218 // must insert a connector if x0 < x1
1219 float_type y0=tail(x0);
1220 float_type y1=func(x1);
1221 c2_function<float_type> &connector=*new c2_linear_p<float_type>(x0, y0, (y1-y0)/(x1-x0));
1222 connector.set_domain(x0,x1);
1223 functions.push_back(c2_const_ptr<float_type>(connector));
1224 this->sampling_grid->push_back(x1);
1225 } else if(x0>x1) throw c2_exception("function domains not increasing in c2_piecewise_function");
1226 }
1227 functions.push_back(keepfunc);
1228 // extend our domain to include all known functions
1229 this->set_domain(functions.front()->xmin(), functions.back()->xmax());
1230 // extend our sampling grid with the new function's grid, with the first point dropped to avoid duplicates
1231 std::vector<float_type> newgrid;
1232 func.get_sampling_grid(func.xmin(), func.xmax(), newgrid);
1233 this->sampling_grid->insert(this->sampling_grid->end(), newgrid.begin()+1, newgrid.end());
1234}
1235
1236template <typename float_type> c2_connector_function_p<float_type>::c2_connector_function_p(
1237 float_type x0, const c2_function<float_type> &f0, float_type x2, const c2_function<float_type> &f2,
1238 bool auto_center, float_type y1)
1239 : c2_function<float_type>()
1240{
1241 c2_const_ptr<float_type> left(f0), right(f2); // make sure if these are unowned, they get deleted
1242 c2_fblock<float_type> fb0, fb2;
1243 fb0.x=x0;
1244 f0.fill_fblock(fb0);
1245 fb2.x=x2;
1246 f2.fill_fblock(fb2);
1247 init(fb0, fb2, auto_center, y1);
1248}
1249
1250template <typename float_type> c2_connector_function_p<float_type>::c2_connector_function_p(
1251 float_type x0, float_type y0, float_type yp0, float_type ypp0,
1252 float_type x2, float_type y2, float_type yp2, float_type ypp2,
1253 bool auto_center, float_type y1)
1254 : c2_function<float_type>()
1255{
1256 c2_fblock<float_type> fb0, fb2;
1257 fb0.x=x0; fb0.y=y0; fb0.yp=yp0; fb0.ypp=ypp0;
1258 fb2.x=x2; fb2.y=y2; fb2.yp=yp2; fb2.ypp=ypp2;
1259 init(fb0, fb2, auto_center, y1);
1260}
1261
1262template <typename float_type> c2_connector_function_p<float_type>::c2_connector_function_p(
1263 const c2_fblock<float_type> &fb0,
1264 const c2_fblock<float_type> &fb2,
1265 bool auto_center, float_type y1)
1266 : c2_function<float_type>()
1267{
1268 init(fb0, fb2, auto_center, y1);
1269}
1270
1271template <typename float_type> void c2_connector_function_p<float_type>::init(
1272 const c2_fblock<float_type> &fb0,
1273 const c2_fblock<float_type> &fb2,
1274 bool auto_center, float_type y1)
1275{
1276 float_type dx=(fb2.x-fb0.x)/2.0;
1277 fhinv=1.0/dx;
1278
1279 // scale derivs to put function on [-1,1] since mma solution is done this way
1280 float_type yp0=fb0.yp*dx;
1281 float_type yp2=fb2.yp*dx;
1282 float_type ypp0=fb0.ypp*dx*dx;
1283 float_type ypp2=fb2.ypp*dx*dx;
1284
1285 float_type ff0=(8*(fb0.y + fb2.y) + 5*(yp0 - yp2) + ypp0 + ypp2)*0.0625;
1286 if(auto_center) y1=ff0; // forces ff to be 0 if we are auto-centering
1287
1288 // y[x_] = y1 + x (a + b x) + x [(x-1) (x+1)] (c + d x) + x (x-1)^2 (x+1)^2 (e + f x)
1289 // y' = a + 2 b x + d x [(x+1)(x-1)] + (c + d x)(3x^2-1) + f x [(x+1)(x-1)]^2 + (e + f x)[(x+1)(x-1)](5x^2-1)
1290 // y'' = 2 b + 6x(c + d x) + 2d(3x^2-1) + 4x(e + f x)(5x^2-3) + 2f(x^2-1)(5x^2-1)
1291 fy1=y1;
1292 fa=(fb2.y - fb0.y)*0.5;
1293 fb=(fb0.y + fb2.y)*0.5 - y1;
1294 fc=(yp2+yp0-2.*fa)*0.25;
1295 fd=(yp2-yp0-4.*fb)*0.25;
1296 fe=(ypp2-ypp0-12.*fc)*0.0625;
1297 ff=(ff0 - y1);
1298 this->set_domain(fb0.x, fb2.x); // this is where the function is valid
1299}
1300
1301template <typename float_type> c2_connector_function_p<float_type>::~c2_connector_function_p()
1302{
1303}
1304
1305template <typename float_type> float_type c2_connector_function_p<float_type>::value_with_derivatives(
1306 float_type x, float_type *yprime, float_type *yprime2
1307 ) const throw(c2_exception)
1308{
1309 float_type x0=this->xmin(), x2=this->xmax();
1310 float_type dx=(x-(x0+x2)*0.5)*fhinv;
1311 float_type q1=(x-x0)*(x-x2)*fhinv*fhinv; // exactly vanish all bits at both ends
1312 float_type q2=dx*q1;
1313
1314 float_type r1=fa+fb*dx;
1315 float_type r2=fc+fd*dx;
1316 float_type r3=fe+ff*dx;
1317
1318 float_type y=fy1+dx*r1+q2*r2+q1*q2*r3;
1319
1320 if(yprime || yprime2) {
1321 float_type q3=3*q1+2;
1322 float_type q4=5*q1+4;
1323 if(yprime) *yprime=(fa+2*fb*dx+fd*q2+r2*q3+ff*q1*q2+q1*q4*r3)*fhinv;
1324 if(yprime2) *yprime2=2*(fb+fd*q3+3*dx*r2+ff*q1*q4+r3*(2*dx*(5*q1+2)))*fhinv*fhinv;
1325 }
1326 return y;
1327}
1328
1329// the recursive part of the sampler is agressively designed to minimize copying of data... lots of pointers
1330template <typename float_type> void c2_function<float_type>::sample_step(c2_sample_recur &rb) const throw(c2_exception)
1331{
1332 std::vector< recur_item > &rb_stack=*rb.rb_stack; // heap-based stack of data for recursion
1333 rb_stack.clear();
1334
1335 recur_item top;
1336 top.depth=0; top.done=false; top.f0index=0; top.f2index=0;
1337
1338 // push storage for our initial elements
1339 rb_stack.push_back(top);
1340 rb_stack.back().f1=*rb.f0;
1341 rb_stack.back().done=true;
1342
1343 rb_stack.push_back(top);
1344 rb_stack.back().f1=*rb.f1;
1345 rb_stack.back().done=true;
1346
1347 if(!rb.inited) {
1348 rb.dx_tolerance=10.0*std::numeric_limits<float_type>::epsilon();
1349 rb.abs_tol_min=10.0*std::numeric_limits<float_type>::min();
1350 rb.inited=true;
1351 }
1352
1353 // now, push our first real element
1354 top.f0index=0; // left element is stack[0]
1355 top.f2index=1; // right element is stack[1]
1356 rb_stack.push_back(top);
1357
1358 while(rb_stack.size() > 2) {
1359 recur_item &back=rb_stack.back();
1360 if(back.done) {
1361 rb_stack.pop_back();
1362 continue;
1363 }
1364 back.done=true;
1365
1366 c2_fblock<float_type> &f0=rb_stack[back.f0index].f1, &f2=rb_stack[back.f2index].f1;
1367 c2_fblock<float_type> &f1=back.f1; // will hold new middle values
1368 size_t f1index=rb_stack.size()-1; // our current offset
1369
1370 // std::cout << "processing: " << rb_stack.size() << " " <<
1371 // (&back-&rb_stack.front()) << " " << back.depth << " " << f0.x << " " << f2.x << std::endl;
1372
1373 f1.x=0.5*(f0.x + f2.x); // center of interval
1374 float_type dx2=0.5*(f2.x - f0.x);
1375
1376 // check for underflow on step size, which prevents us from achieving specified accuracy.
1377 if(std::abs(dx2) < std::abs(f1.x)*rb.dx_tolerance || std::abs(dx2) < rb.abs_tol_min) {
1378 std::ostringstream outstr;
1379 outstr << "Step size underflow in adaptive_sampling at depth=" << back.depth << ", x= " << f1.x;
1380 throw c2_exception(outstr.str().c_str());
1381 }
1382
1383 fill_fblock(f1);
1384
1385 if(c2_isnan(f1.y) || f1.ypbad || f1.yppbad) {
1386 // can't go any further if a nan has appeared
1387 bad_x_point=f1.x;
1388 throw c2_exception("NaN encountered while sampling function");
1389 }
1390
1391 float_type eps;
1392 if(rb.derivs==2) {
1393 // this is code from connector_function to compute the value at the midpoint
1394 // it is re-included here to avoid constructing a complete c2connector
1395 // just to find out if we are close enough
1396 float_type ff0=(8*(f0.y + f2.y) + 5*(f0.yp - f2.yp)*dx2 + (f0.ypp+f2.ypp)*dx2*dx2)*0.0625;
1397 // we are converging as at least x**5 and bisecting, so real error on final step is smaller
1398 eps=std::abs(ff0-f1.y)/32.0;
1399 } else {
1400 // there are two tolerances to meet... the shift in the estimate of the actual point,
1401 // and the difference between the current points and the extremum
1402 // build all the coefficients needed to construct the local parabola
1403 float_type ypcenter, ypp;
1404 if (rb.derivs==1) {
1405 // linear extrapolation error using exact derivs
1406 eps = (std::abs(f0.y+f0.yp*dx2-f1.y)+std::abs(f2.y-f2.yp*dx2-f1.y))*0.125;
1407 ypcenter=2*f1.yp*dx2; // first deriv scaled so this interval is on [-1,1]
1408 ypp=2*(f2.yp-f0.yp)*dx2*dx2; // second deriv estimate scaled so this interval is on [-1,1]
1409 } else {
1410 // linear interpolation error without derivs if we are at top level
1411 // or 3-point parabolic interpolation estimates from previous level, if available
1412 ypcenter=(f2.y-f0.y)*0.5; // derivative estimate at center
1413 ypp=(f2.y+f0.y-2*f1.y); // second deriv estimate
1414 if(back.depth==0) eps=std::abs((f0.y+f2.y)*0.5 - f1.y)*2; // penalize first step
1415 else eps=std::abs(f1.y-back.previous_estimate)*0.25;
1416 }
1417 float_type ypleft=ypcenter-ypp; // derivative at left edge
1418 float_type ypright=ypcenter+ypp; // derivative at right edge
1419 float_type extremum_eps=0;
1420 if((ypleft*ypright) <=0) // y' changes sign if we have an extremum
1421 {
1422 // compute position and value of the extremum this way
1423 float_type xext=-ypcenter/ypp;
1424 float_type yext=f1.y + xext*ypcenter + 0.5*xext*xext*ypp;
1425 // and then find the the smallest offset of it from a point, looking in the left or right side
1426 if(xext <=0) extremum_eps=std::min(std::abs(f0.y-yext), std::abs(f1.y-yext));
1427 else extremum_eps=std::min(std::abs(f2.y-yext), std::abs(f1.y-yext));
1428 }
1429 eps=std::max(eps, extremum_eps); // if previous shot was really bad, keep trying
1430 }
1431
1432 if(eps < rb.abs_tol || eps < std::abs(f1.y)*rb.rel_tol) {
1433 if(rb.out) {
1434 // we've met the tolerance, and are building a function, append two connectors
1435 rb.out->append_function(
1436 *new c2_connector_function_p<float_type>(f0, f1, true, 0.0)
1437 );
1438 rb.out->append_function(
1439 *new c2_connector_function_p<float_type>(f1, f2, true, 0.0)
1440 );
1441 }
1442 if(rb.xvals && rb.yvals) {
1443 rb.xvals->push_back(f0.x);
1444 rb.xvals->push_back(f1.x);
1445 rb.yvals->push_back(f0.y);
1446 rb.yvals->push_back(f1.y);
1447 // the value at f2 will get pushed in the next segment... it is not forgotten
1448 }
1449 } else {
1450 top.depth=back.depth+1; // increment depth counter
1451
1452 // save the last things we need from back before a push happens, in case
1453 // the push causes a reallocation and moves the whole stack.
1454 size_t f0index=back.f0index, f2index=back.f2index;
1455 float_type left=0, right=0;
1456 if(rb.derivs==0) {
1457 // compute three-point parabolic interpolation estimate of right-hand and left-hand midpoint
1458 left=(6*f1.y + 3*f0.y - f2.y) * 0.125;
1459 right=(6*f1.y + 3*f2.y - f0.y) * 0.125;
1460 }
1461
1462 top.f0index=f1index; top.f2index=f2index; // insert pointers to right side data into our recursion block
1463 top.previous_estimate=right;
1464 rb_stack.push_back(top);
1465
1466 top.f0index=f0index; top.f2index=f1index; // insert pointers to left side data into our recursion block
1467 top.previous_estimate=left;
1468 rb_stack.push_back(top);
1469 }
1470 }
1471}
1472
1473template <typename float_type> c2_piecewise_function_p<float_type> *
1474 c2_function<float_type>::adaptively_sample(
1475 float_type xmin, float_type xmax,
1476 float_type abs_tol, float_type rel_tol,
1477 int derivs, std::vector<float_type> *xvals, std::vector<float_type> *yvals) const throw(c2_exception)
1478{
1479 c2_fblock<float_type> f0, f2;
1480 c2_sample_recur rb;
1481 std::vector< recur_item > rb_stack;
1482 rb_stack.reserve(20); // enough for most operations
1483 rb.rb_stack=&rb_stack;
1484 rb.out=0;
1485 if(derivs==2) rb.out=new c2_piecewise_function_p<float_type>();
1486 c2_ptr<float_type> pieces(*rb.out); // manage this function, if any, so it deletes on an exception
1487 rb.rel_tol=rel_tol;
1488 rb.abs_tol=abs_tol;
1489 rb.xvals=xvals;
1490 rb.yvals=yvals;
1491 rb.derivs=derivs;
1492 rb.inited=false;
1493
1494 if(xvals && yvals) {
1495 xvals->clear();
1496 yvals->clear();
1497 }
1498
1499 // create xgrid as a automatic-variable copy of the sampling grid so the exception handler correctly
1500 // disposes of it.
1501 std::vector<float_type> xgrid;
1502 get_sampling_grid(xmin, xmax, xgrid);
1503 int np=xgrid.size();
1504
1505 f2.x=xgrid[0];
1506 fill_fblock(f2);
1507 if(c2_isnan(f2.y) || f2.ypbad || f2.yppbad) {
1508 // can't go any further if a nan has appeared
1509 bad_x_point=f2.x;
1510 throw c2_exception("NaN encountered while sampling function");
1511 }
1512
1513 for(int i=0; i<np-1; i++) {
1514 f0=f2; // copy upper bound to lower before computing new upper bound
1515
1516 f2.x=xgrid[i+1];
1517 fill_fblock(f2);
1518 if(c2_isnan(f2.y) || f2.ypbad || f2.yppbad) {
1519 // can't go any further if a nan has appeared
1520 bad_x_point=f2.x;
1521 throw c2_exception("NaN encountered while sampling function");
1522 }
1523
1524 rb.f0=&f0; rb.f1=&f2;
1525 sample_step(rb);
1526 }
1527 if(xvals && yvals) { // push final point in vector
1528 xvals->push_back(f2.x);
1529 yvals->push_back(f2.y);
1530 }
1531
1532 if(rb.out) rb.out->set_sampling_grid(xgrid); // reflect old sampling grid, which still should be right
1533 pieces.release_for_return(); // unmanage the piecewise_function so we can return it
1534 return rb.out;
1535}
1536
1537template <typename float_type, typename Final>
1538 interpolating_function_p<float_type> & inverse_integrated_density_function(
1539 const std::vector<float_type> &bincenters, const c2_function<float_type> &binheights)
1540 throw(c2_exception)
1541{
1542 return (new Final())->load_random_generator_function(bincenters, binheights);
1543}
1544
1545template <typename float_type, typename Final>
1546 interpolating_function_p<float_type> & inverse_integrated_density_bins(
1547 const std::vector<float_type> &bins, const std::vector<float_type> &binheights)
1548 throw(c2_exception)
1549{
1550 return (new Final())->load_random_generator_bins(bins, binheights);
1551}
Note: See TracBrowser for help on using the repository browser.