Integration Using Numerical Recipes: Realfunction Realfunction Operator
Integration Using Numerical Recipes: Realfunction Realfunction Operator
if (n == 1) {
return (s=0.5*(b-a)*(FUNC(a)+FUNC(b)));
} else {
for (it=1,j=1;j<n-1;j++) it <<= 1;
tnm=it;
del=(b-a)/tnm; /*This is the spacing of points to be
added. */
x=a+0.5*del;
for (sum=0.0,j=1;j<=it;j++,x+=del) sum += FUNC(x);
s=0.5*(s+(b-a)*sum/tnm); /*This replaces s by its refined value.*/
return s;
}
}
#undef FUNC
qtrap renes the trapezoidal rule so that you can specify the desired accuracy. The
parameter EPS can be set to the desired fractional accuracy and JMAX can be set so that
2JMAX,1 is the maximum allowed number of steps.
#include <math.h>
#define EPS 1.0e-5
#define JMAX 20
olds = -1.0e30;
for (j=1;j<=JMAX;j++) {
s=trapzd(func,a,b,j);
if (j > 5)
if (fabs(s-olds) < EPS*fabs(olds) ||
99
(s == 0.0 && olds == 0.0)) return s;
olds=s;
}
nrerror("Too many steps in routine qtrap");
return 0.0;
}
#undef EPS
#undef JMAX
10,6 is about the smallest you should make EPS. If EPS is too small, qtrap takes too
many steps to converge, and the accumulated roundo error may prevent the routine
from converging. The routine qtrap is not very sophisticated, but it's a fairly robust
way of doing integrals of functions that are not very smooth. In other words, it's good
for jagged functions.
Romberg Integration
If the function you are integrating over is fairly smooth, then the best way to do the
integral is using Romberg's method. I think of this as approximating an interval of the
function by a polynomial of order k where k is determined by the routine. qromb is a
powerful and fast routine. It calls 3 other functions that are given by Numerical Recipes:
polint, trapzd, and nerror.
#include <math.h>
#define EPS 1.0e-6
#define JMAX 20
#define JMAXP (JMAX+1)
#define K 5
#define NRANSI
/* #include "nrutil.h" */
h[1]=1.0;
for (j=1;j<=JMAX;j++) {
s[j]=trapzd(func,a,b,j);
if (j >= K) {
polint(&h[j-K],&s[j-K],K,0.0,&ss,&dss);
100
if (fabs(dss) <= EPS*fabs(ss)) return ss;
}
h[j+1]=0.25*h[j];
}
nrerror("Too many steps in routine qromb");
return 0.0;
}
/* ********************************************************************* */
void polint(float xa[], float ya[], int n, float x, float *y, float *dy)
{
int i,m,ns=1;
float den,dif,dift,ho,hp,w;
float *c,*d;
dif=fabs(x-xa[1]);
c=vector(1,n);
d=vector(1,n);
for (i=1;i<=n;i++) {
if ( (dift=fabs(x-xa[i])) < dif) {
ns=i;
dif=dift;
}
c[i]=ya[i];
d[i]=ya[i];
}
*y=ya[ns--];
for (m=1;m<n;m++) {
for (i=1;i<=n-m;i++) {
ho=xa[i]-x;
hp=xa[i+m]-x;
w=c[i+1]-d[i];
if ( (den=ho-hp) == 0.0)
nrerror("Error in routine polint");
den=w/den;
d[i]=hp*den;
c[i]=ho*den;
}
*y += (*dy=(2*ns < (n-m) ? c[ns+1] : d[ns--]));
}
free_vector(d,1,n);
free_vector(c,1,n);
}
101
/* ********************************************************************* */
real *vector(long nl, long nh)
/* allocate a real vector with subscript range v[nl..nh]
can replace with "new" in C++ */
{
real *v;
#undef NRANSI
#undef EPS
#undef JMAX
#undef JMAXP
#undef K
Having the exact answer allows us to test how accurate our numerical result is. The code
is
/* Driver for routine qromb */
102
#include <stdio.h>
#include <math.h>
#define PIO2 1.5707963
/* Test function */
float func(float x)
{
return x*x*(x*x-2.0)*sin(x);
}
int main(void)
{
float a=0.0,b=PIO2,s,t;
So it looks like qromb does a little better. One of the disadvantages of the C routines
is that you can only integrate over one variable. If you want to integrate f (x; y) over x
and y, you have to make another copy of qromb (call it qromb2), and then use qromb2 to
integrate over func2 which in turn calls qromb:
float func(float x, float y)
{
103
return f(x,y); /* This is schematic */
}
float func2(float y) /* Fix y, integrate over x */
{
return qromb(func,ax,bx);
}
int main(void)
{
float integral=qromb2(func2,ay,by);
return 0;
}
104
int JMAX; //2**(JMAX)=max number of func. evaluation
public:
virtual real operator()(); //overloads pure virtual function
real operator()(real min,real max, //redo integral with different
real precision=1.0e-7,int max_iter=20){ //parameters
a=min;b=max;EPS=precision;
JMAX=max_iter; return (*this)();};
Trapeze(RealFunction &f,real &Var,real min,real max,
real precision=1.0e-7,int max_iter=20):func(f),x(Var)
{a=min;b=max;EPS=precision;JMAX=max_iter;};
real Trap(int n); //workhorse that really does the integral
};
Integration.C
These member functions have been adapted from the subroutines in Numerical Recipes
in C. I have marked the lines which have been changed.
#include "integration.h"
#include <iostream.h>
#define JMAXP 14
#define K 5
real s,olds;
105
int j;
olds = -1.0e30;
for (j=1;j<=JMAX;j++){
s=Trap(j); //changed; Trap is integrating fcn
if (fabs(s-olds) <= EPS*fabs(olds)) return s;
if(s==0.0 && olds == 0.0 && j>6 ) return s;
olds=s;
}
printf("Too many steps in routine qtrap_y\n");
exit(1);
return 0.0;
}
real Trapeze::Trap(int n) { //adapted from trapzd
real tnm,sum,del;
int j,it;
if (n == 1){
it=1;
x=a; s=func(); //changed
x=b; s+=func(); //changed
return (s*=0.5*(b-a));
}else{
for (it=1,j=1;j<n-1;j++) it <<=1;
tnm=it;
del=(b-a)/tnm;
x=a+0.5*del;
for (sum=0.0,j=1;j<=it;j++,x+=del) sum+=func(); //changed
s=0.5*(s+(b-a)*sum/tnm);
return s;
}
}
real Romberg::operator()(){ //adapted from qromb
int j;
real ss,dss,h[JMAXP+2],s[JMAXP+2];
h[1]=1.0;
for(j=1;j<=JMAXP;j++){
s[j]=midpnt(j); //changed; midpnt is integrating
if(j>=K){ //function
polint(&h[j-K],&s[j-K],K,0.0,ss,dss);
if(fabs(dss)<=EPS*fabs(ss)) return ss;
106
}
s[j+1]=s[j];
h[j+1]=h[j]/9.0;
}
cout << "Too many steps in routine Romberg" << endl; exit(1);
return 0.0;
}
real Romberg::midpnt(int n){ //adapted from midpoint
real tnm,sum,del,ddel;
int it,j;
if(n==1){
x=0.5*(a+b); //changed; set x
return (s=(b-a)*func()); //changed; evaluate func
}else{
for(it=1,j=1;j<n-1;j++) it *=3;
tnm=it;
del=(b-a)/(3.0*tnm);
ddel=del+del;
x=a+0.5*del;
sum=0.0;
for(j=1;j<=it;j++){
sum+=func(); //changed; evaluate func
x+=ddel; //changed; set x
sum+=func(); //changed; evaluate func
x+=del; //changed; set x
}
s=(s+(b-a)*sum/tnm)/3.0;
return s;
}
}
polint.C
#include <iostream.h>
#include <stdlib.h>
#include <math.h>
typedef float real;
107
real den,dif,dift,ho,hp,w;
real *c,*d;
dif=fabs(x-xa[1]);
c=vector(1,n);
d=vector(1,n);
for(i=1;i<=n;i++){
if((dift=fabs(x-xa[i])) < dif){
ns=i;
dif=dift;
}
c[i]=ya[i];
d[i]=ya[i];
}
y=ya[ns--];
for(m=1;m<n;m++){
for(i=1;i<=n-m;i++){
ho=xa[i]-x;
hp=xa[i+m]-x;
w=c[i+1]-d[i];
if((den=ho-hp)==0.0) {printf("error in routine polint\n");
exit(1);}
den=w/den;
d[i]=hp*den;
c[i]=ho*den;
}
y += (dy=(2*ns < (n-m) ? c[ns+1] : d[ns--]));
}
delete [] d; //replaces free_vector
delete [] c; //replaces free_vector
}
#define NR_END 1
real *vector(int nl,int nh)
// allocate a real vector with subscript range v[nl .. nh]
{
real *v;
v=new real[nh-nl+1+NR_END];
if(!v) error("allocation failure in vector1()");
return v-nl+NR_END;
}
108
void error(const char *s)
{
cerr << endl << s << endl; //1st "endl" is in case program is printing
cout << endl << s << endl; //something out when the error occurs
cout.flush(); //write the output buffer to the screen
//or wherever the output of cout goes.
abort();
}
#undef NR_END
int main() {
float integral, a=0.0, b=1.5707963;
Func1 Testfunc; //call default constructor
Trapeze Trap(Testfunc,Testfunc.x,a,b);
float integralt= Trap();
cout << "C++ Trapezoidal Integration Result = " << integralt << endl;
Romberg Rom(Testfunc,Testfunc.x,a,b);
integral=Rom();
cout << "C++ Romberg Integration Result = " << integral << endl;
return 0;
To compile we type
g++ -lm integration.C polint.C testinteg.C
We have dened an abstract class RealFunction which has a pure virtual function
virtual real operator()()=0; //pure virtual function
This acts as a placeholder for the function that will be integrated. We tell the integration
routine that it will be integrating a RealFunction. Then we derive the actual function
from RealFunction. The function and the variable of integration are passed by reference
as an argument of the constructor. In the integration subroutine, the FUNC(a) which
appeared in the C program is replaced by
109
x=a; s=func();
in the C++ program. The arguments that were passed to the integration function in the
C program have become data members of the class. Most of the variables used in the
integration subroutine do not need to be made class members, but the input and output
parameters should be made class members. In addition, if there are static variables in the
C program, these should be made (nonstatic) members of the class. In the C subroutines
from Numerical Recipes, the static variables are ones that retain their values between
dierent calls to the function. (Normally, the variables local to a function lose their
values when the function goes out of scope.) By making them nonstatic data members
of the class, they will keep their values between dierent calls to the member functions.
In fact they will keep their values for as long as the object exists. (These values are not
const and can be changed by the member functions.)
We have rewritten the C function vector so that it uses new. We have replaced the
free_vector function with delete.
Revised C++ Code for Integration
Romberg::midpnt(int n) and Trapeze::trap(int n) are examples of what I will
call \integrators". They are subroutines that really do the integration. Let's make
Romberg and Trapeze more
exible so that they can easily accept any integrator. We can
do this by making them template classes which accept the type of integrator. Integrator
will be a base class with midpnt and trap derived from it.
We will also replace the error statement with throwing and catching exceptions.
integration.h
#include <math.h>
#include <iostream.h>
#include "exception.h"
#define JMAXP 14
#define K 5
110
};
template<class SomeIntegrator>
class Trapeze: public RealFunction{
real EPS; //Desired precision
int JMAX; //2**(JMAX)=max number of func. evaluation
SomeIntegrator integrator; //contains integration routine
public:
virtual real operator()();
111
real operator() //Reset integration limits and accuracy
(real min,real max,real precision=1.0e-7,int max_iter=20){
integrator.SetLimits(min,max);
EPS=precision;
JMAX=max_iter;
return (*this)();}
Trapeze(RealFunction &f,real &Var,real min,real max,
real precision=1.0e-7,int max_iter=20):
integrator(f, Var, min, max)
{EPS=precision;JMAX=max_iter;}
};
template<class SomeIntegrator>
class Romberg : public RealFunction{
real EPS; //Desired precision
SomeIntegrator integrator; //Integration routine
public:
virtual real operator()();
real operator() //Reset integration limits and accuracy
(real min,real max,real precision=1.0e-7)
{integrator.SetLimits(min,max);
EPS=precision;
return (*this)();}
Romberg(RealFunction &f,real &Var,real min,real max,
real precision=1.0e-7):
integrator(f, Var, min, max)
{EPS=precision;}
};
template<class SomeIntegrator>
real Trapeze<SomeIntegrator>::operator()() {
real s,olds;
int j;
olds = -1.0e30;
for (j=1;j<=JMAX;j++){
s=integrator(j); //call integration subroutine
if (fabs(s-olds) <= EPS*fabs(olds)) return s;
if(s==0.0 && olds == 0.0 && j>6 ) return s;
olds=s;
}
throw Exception("Too many steps in Trapeze()",__FILE__,__LINE__);
112
return 0.0;
}
template<class SomeIntegrator>
real Romberg<SomeIntegrator>::operator()(){
int j;
real ss,dss,h[JMAXP+2],s[JMAXP+2];
h[1]=1.0;
for(j=1;j<=JMAXP;j++){
s[j]=integrator(j); //call integration subroutine
if(j>=K){
polint(&h[j-K],&s[j-K],K,0.0,ss,dss);
if(fabs(dss)<=EPS*fabs(ss)) return ss;
}
s[j+1]=s[j];
h[j+1]=h[j]/9.0;
}
throw Exception("Too many steps in Romberg",__FILE__,__LINE__);
return 0.0;
}
exception.h
#include<iostream.h>
class Exception
{
public:
Exception(char* pMsg, char* pFile, int nLine)
{
strcpy(msg,pMsg);
msg[sizeof(msg)-1]='\0'; //make sure msg is terminated
strcpy(file,pFile);
file[sizeof(file)-1]='\0';
lineNum=nLine;
}
virtual void display(ostream& out)
{
out << "Error: " << msg << endl;
out << "Occurred on line number " << lineNum << ", file "
<< endl;
113
}
protected:
// error message
char msg[80];
// file name and line number where error occurred
char file[80];
int lineNum;
};
integration.C
#include <iostream.h>
#include "integration.h"
real Trapzd::operator()(int n) {
real tnm,sum,del;
int j,it;
if (n == 1){
it=1;
x=a; s=func(); //set x; evaluate func()
x=b; s+=func(); //set x; evaluate func()
return (s*=0.5*(b-a));
}else{
for (it=1,j=1;j<n-1;j++) it <<=1;
tnm=it;
del=(b-a)/tnm;
x=a+0.5*del;
for (sum=0.0,j=1;j<=it;j++,x += del) //set x
sum+=func(); //evaluate func()
s=0.5*(s+(b-a)*sum/tnm);
return s;
}
}
// ********************************************************************
real Midpoint::operator()(int n){
real tnm,sum,del,ddel;
int it,j;
if(n==1){
x=0.5*(a+b); //set x
return (s=(b-a)*func()); //evaluate func()
}else{
114
for(it=1,j=1;j<n-1;j++) it *=3;
tnm=it;
del=(b-a)/(3.0*tnm);
ddel=del+del;
x=a+0.5*del;
sum=0.0;
for(j=1;j<=it;j++){
sum+=func(); //evaluate func()
x+=ddel; //set x
sum+=func(); //evaluate func()
x+=del; //set x
}
s=(s+(b-a)*sum/tnm)/3.0;
return s;
}
}
polint.C
#include <iostream.h>
#include <stdlib.h>
#include <math.h>
#include "exception.h"
typedef float real;
dif=fabs(x-xa[1]);
c=vector(1,n);
d=vector(1,n);
for(i=1;i<=n;i++){
if((dift=fabs(x-xa[i])) < dif){
ns=i;
dif=dift;
}
c[i]=ya[i];
d[i]=ya[i];
}
115
y=ya[ns--];
for(m=1;m<n;m++){
for(i=1;i<=n-m;i++){
ho=xa[i]-x;
hp=xa[i+m]-x;
w=c[i+1]-d[i];
if((den=ho-hp)==0.0)
{throw Exception("error in routine polint",__FILE__,__LINE__);}
den=w/den;
d[i]=hp*den;
c[i]=ho*den;
}
y += (dy=(2*ns < (n-m) ? c[ns+1] : d[ns--]));
}
delete [] d;
delete [] c;
}
#define NR_END 1
real *vector(int nl,int nh)
// allocate a real vector with subscript range v[nl .. nh]
{
real *v;
v=new real[nh-nl+1+NR_END];
if(!v)
{throw Exception("allocation failure in vector()", __FILE__,__LINE__);}
return v-nl+NR_END;
}
#undef NR_END
We have made template classes out of Romberg and Trapeze to allow for using dierent
integrators. We have made Integrator a base class, and the actual integrators are
derived from Integrator. We chose not to make Integrator an abstract class with pure
virtual functions since this costs overhead and doesn't really buy us anything. Notice
that in Romberg and Trapeze, the line
s=integrator(j)
calls the integration subroutine. integrator(j) does not overload a virtual function
in the base class Integrator. There is no function Integrator::operator()(int j).
Rather integrator is an instantiation of the template class SomeIntegrator. When
we replace the template type SomeIntegrator with a concrete type, like Romberg or
Trapeze, that concrete type had better have a member function operator()(int j).
The function being integrated (func), the variable of integration, and the limits of
integration are only needed by the integrators, and not by Romberg and Trapeze per se.
116
So we have made func, x, and the limits of integration data members of Integrator;
they are no longer data members of Romberg and Trapeze. But the constructors of
Romberg and Trapeze take these as arguments and pass them to Integrator.
testinteg.C
Here is the test program for our integration routines:
#include <iostream.h>
#include "integration.h"
int main() {
try
{
float a=0.0, b=1.5707963;
Func1 Testfunc; //call default constructor
Trapeze<Trapzd> Trap(Testfunc,Testfunc.x,a,b);
float integralt= Trap();
Romberg<Midpoint> RomTest(Testfunc,Testfunc.x,a,b);
float integral=RomTest();
cout << "C++ Trapezoidal Integration Result = " << integralt << endl;
cout << "C++ Romberg Integration Result = " << integral << endl;
Romberg<Trapzd> TRomX(FXY,FXY.x,a,b);
Romberg<Trapzd> TRom(TRomX,FXY.y,a,b);
integral=TRom();
cout << "C++ Romberg XY Integration Result with Trapzd integrator= "
<< integral << endl;
}
catch(Exception& x)
{
//use the built-in display member function
x.display(cerr);
}
117
return 0;
}
Notice that in the test program, we can easily do a double integral since Romberg
and Trapeze overload the pure virtual function virtual real operator()()=0 in class
RealFunction. In other words, Romberg and Trapeze are classes derived from RealFunction.
So Romberg IS_A RealFunction and Trapeze IS_A RealFunction. Their output is
completely acceptable as a function that can be integrated yet again.
118