I have tried my hand with implementing simple quadrature formulas in C++.
Definite integral: $$\int_a^b f(x) dx$$ Domain of integration \$[a, b]\$ divided into \$n\$ intervals of equal length \$h = (b - a) / n\$.
Midpoint of interval \$h_i = b_i - a_i\$ is $$x_i = a + h / 2 + (i - 1) h$$ with \$i = 1, \dots, n \$.
Newton-Cotes quadrature formulas implemented in the code:
- Midpoint rule (constant approx) $$F_M = h \sum_{i=1}^n f(x_i)$$
- Trapezoidal rule (linear approx) $$F_T = \frac{h}{2} \left(f(a) + f(b) \right) + h \sum_{i=1}^{n-1} f(a_i)$$
- Simpson's rule (quadratic approx) $$F_S = \frac{h}{6} \left(f(a) + f(b) \right) + \frac{h}{3} \sum_{i=1}^{n-1} f(a_i) + 2 \frac{h}{3} \sum_{i=1}^{n} f(x_i) $$
I decided to go with a parametrized factory pattern that has no subclasses involved.
I employ \$ f(x) = \sqrt{x} \exp{(-x)} \$ as test function, to be integrated between \$a = 1 \$ and \$b = 3\$.
I wonder whether the #include "NewtonCotesFormulas.cpp"
at the end of the NewtonCotesFormulas.h
header file could be somehow avoided. I used it because without I'd get linking errors.
Here is the code.
main.cpp:
#include <iostream>
#include <cmath>
#include "NewtonCotesFormulas.h"
#include "NewtonCotesFactory.h"
using namespace std;
double func(double x) {
return sqrt(x) * exp(-x);
}
int main()
{
cout << "Choose method:\n"
<< "Midpoint numeric quadrature -----> (1)\n"
<< "Trapezoidal numeric quadrature --> (2)\n"
<< "Simpson's numeric quadrature ----> (3)\n" << endl;
// //user input code:
// int choice;
// cin >> choice;
// while (choice < 1 || choice > 3) {
// cout << "Pick again a number from 1 to 3\n";
// cin >> choice;
// }
NewtonCotesFactory creator;
// alternative to user input, print all results at once:
for (int choice : {1,2,3}) {
NewtonCotesFormulas* rule = creator.createQuadrature(choice, 1, 3, 0.000001);
double result = rule->printConvergenceValues(4, &func);
cout << "Final result within tolerance: " << result << endl << endl;
delete rule;
}
return 0;
}
NewtonCotesFormulas.h:
#ifndef NEWTONCOTESFORMULAS_H_INCLUDED
#define NEWTONCOTESFORMULAS_H_INCLUDED
#include <string>
class NewtonCotesFormulas {
public:
NewtonCotesFormulas(double, double, double, std::string);
virtual ~NewtonCotesFormulas();
virtual double computeIntegral(int, double (*func)(double)) = 0;
double convergeToTol(int, double (*func)(double));
double printConvergenceValues(int, double (*func)(double));
protected:
double inf, sup;
double tol;
std::string method_name;
};
class Midpoint: public NewtonCotesFormulas {
public:
Midpoint(double, double, double);
~Midpoint(){};
double computeIntegral(int, double (*func)(double)) override;
};
class Trapezoidal: public NewtonCotesFormulas {
public:
Trapezoidal(double, double, double);
~Trapezoidal(){};
double computeIntegral(int, double (*func)(double)) override;
};
class Simpsons: public NewtonCotesFormulas {
public:
Simpsons(double, double, double);
~Simpsons(){};
double computeIntegral(int, double (*func)(double)) override;
};
#include "NewtonCotesFormulas.cpp"
#endif // NEWTONCOTESFORMULAS_H_INCLUDED
NewtonCotesFormulas.cpp:
#include <iomanip>
#include "NewtonCotesFormulas.h"
using namespace std;
NewtonCotesFormulas::NewtonCotesFormulas(double a, double b, double inp_tol, string name):
inf(a), sup(b), tol(inp_tol), method_name(name) {}
NewtonCotesFormulas::~NewtonCotesFormulas() {}
double NewtonCotesFormulas::convergeToTol(int intervals, double (*func)(double)) {
double old_integral = this->computeIntegral(intervals, func);
intervals *= 2;
double new_integral = this->computeIntegral(intervals, func);
double difference = abs(new_integral - old_integral);
while(difference > tol) {
cout << setprecision(8) << string(9 - to_string(intervals).size(), ' ') << // blank padding used to format output table
intervals << string(3,' ') << fixed << new_integral << string(3,' ') << scientific << difference << endl;
old_integral = new_integral;
intervals *= 2;
new_integral = this->computeIntegral(intervals, func);
difference = abs(new_integral - old_integral);
}
cout << setprecision(8) << fixed << string(9 - to_string(intervals).size(), ' ') <<
intervals << string(3,' ') << new_integral << string(3,' ') << scientific << difference << endl;
return new_integral;
}
double NewtonCotesFormulas::printConvergenceValues(int intervals, double (*func)(double)) {
cout << method_name << " method, with tolerance " << tol << "\n";
cout << "intervals" << string(5,' ') << "integral " << string(9,' ') << "tol\n";
cout << setprecision(8) << string(9 - to_string(intervals).size(), ' ') <<
intervals << string(3,' ') << this->computeIntegral(intervals, func) <<
string(3,' ') << fixed << 0.0 << endl;
double result = this->convergeToTol(intervals, func);
return result;
}
Midpoint::Midpoint(double a, double b, double inp_tol):
NewtonCotesFormulas(a, b, inp_tol, "Midpoint"){}
Trapezoidal::Trapezoidal(double a, double b, double inp_tol):
NewtonCotesFormulas(a, b, inp_tol, "Trapezoidal"){}
Simpsons::Simpsons(double a, double b, double inp_tol):
NewtonCotesFormulas(a, b, inp_tol, "Simpson's"){}
double Midpoint::computeIntegral(int intervals, double (*func)(double)) {
double interval_width = (sup - inf) / intervals;
double result = 0;
for (int i = 1; i <= intervals; ++i)
result += func(inf + (i - 0.5) * interval_width);
return result * interval_width;
}
double Trapezoidal::computeIntegral(int intervals, double (*func)(double)) {
double interval_width = (sup - inf) / intervals;
double result = (func(inf) + func(sup)) / 2.;
for (int i = 1; i <= intervals - 1; ++i)
result += func(inf + i * interval_width);
return result * interval_width;
}
double Simpsons::computeIntegral(int intervals, double (*func)(double)) {
double interval_width = (sup - inf) / intervals;
double result = (func(inf) + func(sup)) / 6;
for (int i = 1; i <= intervals - 1; ++i)
result += func(inf + i * interval_width) / 3 + 2 * func(inf + (i - 0.5) * interval_width) / 3;
result += 2 * func(inf + (intervals - 0.5) * interval_width) / 3;
return result * interval_width;
}
NewtonCotesFactory.h:
#ifndef NEWTONCOTESFACTORY_H_INCLUDED
#define NEWTONCOTESFACTORY_H_INCLUDED
#include <string>
#include <stdexcept>
#include "NewtonCotesFormulas.h"
class NewtonCotesFactory { // Parametrized factory, no children
public:
virtual NewtonCotesFormulas* createQuadrature(int choice, double a, double b, double inp_tol) const {
if (choice == 1)
return new Midpoint(a, b, inp_tol);
else if (choice == 2)
return new Trapezoidal(a, b, inp_tol);
else if (choice == 3)
return new Simpsons(a, b, inp_tol);
else
throw std::runtime_error("Pick either choice = 1, 2, or 3");
return 0; // never gets here
}
virtual ~NewtonCotesFactory(){}
};
#endif // NEWTONCOTESFACTORY_H_INCLUDED
Results look fine:
Choose method:
Midpoint numeric quadrature -----> (1)
Trapezoidal numeric quadrature --> (2)
Simpson's numeric quadrature ----> (3)
Midpoint method, with tolerance 1e-06
intervals integral tol
4 0.40715731 0.00000000
8 0.40807542 9.18106750e-04
16 0.40829709 2.21674991e-04
32 0.40835199 5.49009778e-05
64 0.40836569 1.36924160e-05
128 0.40836911 3.42104469e-06
256 0.40836996 8.55132348e-07
Final result within tolerance: 4.08369962e-01
Trapezoidal method, with tolerance 1.00000000e-06
intervals integral tol
4 4.10757439e-01 0.00000000
8 0.40895737 1.80006398e-03
16 0.40851640 4.40978614e-04
32 0.40840674 1.09651812e-04
64 0.40837937 2.73754169e-05
128 0.40837253 6.84150045e-06
256 0.40837082 1.71022788e-06
512 0.40837039 4.27547766e-07
Final result within tolerance: 4.08370390e-01
Simpson's method, with tolerance 1.00000000e-06
intervals integral tol
4 4.08357353e-01 0.00000000
8 0.40836940 1.20498403e-05
16 0.40837019 7.90455982e-07
Final result within tolerance: 4.08370194e-01
Process returned 0 (0x0) execution time : 0.063 s
Press any key to continue.