Contents
- Introduction
- Theory
- First Part of Code
- Antiderivative Method - Other Implementations
- Testing of Different Implementations of Antiderivate Method
- Usage of Antiderivative Method
- Multiple Integration
- 2D Integrals Numerical Evaluation
- 3D Integrals Numerical Evaluation
- Results
- References
- History
Introduction
The functional programming is widely used and discussed. My first experience with it is still fresh. During my studies, I read a famous article http://www.codeproject.com/Articles/375166/Functional-programming-in-Csharp which made some "clicks" in my mind.
Because I did some experiments with numerical integration and I found articles http://www.codeproject.com/Articles/95073/Using-Nested-Lambda-Functions-to-Implement-Numeric and http://www.codeproject.com/Articles/334308/Numerical-Integration-with-Simpsons-Rule, I decided to try something similar but in a slightly different way.
Even if the presented functions / methods are fully functional, they are not optimal and probably you will be able to find faster implementations. But still here remains some usable concept.
Because I really like the extension, the whole code is using it. For more explanation on extension methods, you can look at http://www.codeproject.com/Articles/261639/Extension-Methods-in-NET or http://msdn.microsoft.com/en-us/library/bb383977.aspx.
Theory
As mentioned above, the article is focused on numerical integration. Theory about integrals can be easily learned at wiki pages http://en.wikipedia.org/wiki/Integral and http://en.wikipedia.org/wiki/Antiderivative.
The equation (image) was taken from http://en.wikipedia.org/wiki/Integral.
In C# language, we want to develop a function which will be formally declared as:
public static Func<double, double> Antiderivative(this Func<double, double> df)
so the user will be able to call something like:
Func<double, double> cos = Math.Cos;
Func<double, double> sin = cos.Antiderivative();
First Part of Code
The first method which I implemented was Antiderivative function in form which is very close to form introduced in code strip above. For implementation, I used Runge-Kutta method which is the general equivalent of Simpson rule.
public static double eps = 1e-16;
public static Func<double, double> Antiderivative
(this Func<double, double> df, double x0, double f0, double stepSize)
{
double currentx = x0;
double currentf = f0;
double currentdf = df(x0);
return (x) =>
{
if (x < x0)
throw new ArgumentException("Cannot compute");
if (currentx > x)
{
currentx = x0;
currentf = f0;
currentdf = df(x0);
}
double newLimit = x - stepSize;
while (currentx < newLimit)
{
double k1 = stepSize * currentdf; double k2 = stepSize * df(currentx + 0.5 * stepSize);
double k3 = k2; currentdf = df(currentx + stepSize);
double k4 = stepSize * currentdf;
currentf = currentf + 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
currentx = currentx + stepSize;
}
double lastStepSize = x - currentx;
if (lastStepSize > eps)
{
currentf = currentf + lastStepSize * df(currentx);
currentx = x;
}
return currentf;
};
}
Antiderivative Method - Other Implementations
Thanks to Marc Clifton (see discussion part bellow article) I included other possible implementations of Antiderivate method.
Firstly the implementation where I avoid use currentx
, currentf
and currentdf
. Full code of AntiderivativePure
method:
public static Func<double, double> AntiderivativePure
(this Func<double, double> df, double x0, double f0, double stepSize)
{
return (x) =>
{
if (x < x0)
throw new ArgumentException("Cannot compute");
double currentx = x0;
double currentf = f0;
double currentdf = df(x0);
double newLimit = x - stepSize;
while (currentx < newLimit)
{
double k1 = stepSize * currentdf; double k2 = stepSize * df(currentx + 0.5 * stepSize);
double k3 = k2; currentdf = df(currentx + stepSize);
double k4 = stepSize * currentdf;
currentf = currentf + 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
currentx = currentx + stepSize;
}
double lastStepSize = x - currentx;
if (lastStepSize > eps)
currentf = currentf + lastStepSize * df(currentx);
return currentf;
};
}
As an second implementation the recursive implementation will be shown. Notice the expression
return df.AntiderivativeRecursive(x0, f0, stepSize)(x - stepSize) + delta;
Full code of AntiderivativeRecursive
method:
public static Func<double, double> AntiderivativeRecursive
(this Func<double, double> df, double x0, double f0, double stepSize)
{
return (x) =>
{
if (x < x0)
throw new ArgumentException("Cannot compute");
if (x - x0 < stepSize)
{
double cstep = x - x0;
double k1 = cstep * df(x0);
double k2 = cstep * df(x0 + 0.5 * cstep);
double k3 = k2; double k4 = cstep * df(x0 + cstep);
double currentf = f0 + 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
return currentf;
}
else
{
double k1 = stepSize * df(x - stepSize);
double k2 = stepSize * df(x - 0.5 * stepSize);
double k3 = k2; double k4 = stepSize * df(x);
double delta = 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
return df.AntiderivativeRecursive(x0, f0, stepSize)(x - stepSize) + delta;
}
};
}
And as last implementation (at least at this stage) AntiderivativeEx
method:
public static Func<double, double> AntiderivativeEx
(this Func<double, double> df, double x0, double f0, double stepSize)
{
return (x) => df.AntiderivativeExH(x0, f0, stepSize)(x, df(x));
}
private static Func<double, double, double> AntiderivativeExH
(this Func<double, double> df, double x0, double f0, double stepSize)
{
return (x, dfx) =>
{
if (x < x0)
throw new ArgumentException("Cannot compute");
if (x - x0 < stepSize)
{
double cstep = x - x0;
double k1 = cstep * df(x0);
double k2 = cstep * df(x0 + 0.5 * cstep);
double k3 = k2; double k4 = cstep * dfx; double currentf = f0 + 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
return currentf;
}
else
{
double dfxmh = df(x - stepSize);
double k1 = stepSize * dfxmh;
double k2 = stepSize * df(x - 0.5 * stepSize);
double k3 = k2; double k4 = stepSize * dfx;
double delta = 1 / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4);
return df.AntiderivativeExH(x0, f0, stepSize)(x - stepSize, dfxmh) + delta;
}
};
}
This last implementation uses function Func<double, double, double>
which can accommodate already calculated value of dfxm = df(x - stepSize)
. Once the value is computed, it is shared with other evaluations (see last expression df.AntiderivativeExH(x0, f0, stepSize)(x - stepSize, dfxmh)
in code above).
Testing of Different Implementations of Antiderivate Method
Of course there are differences in implementation and a good question is how big an impact on performance those differences will have? To be able to answer this question, I prepared a test where two aspects - call count and time consumption - will be observed.
The test code is as follows:
private void AntiderivativePerformanceTest()
{
int calls = 0;
Func<double, double> cos = (x) =>
{
calls++;
return Math.Cos(x);
};
Func<double, double> sin = cos.Antiderivative(0, 0, 0.01);
Func<double, double> sinB = cos.AntiderivativePure(0, 0, 0.01);
Func<double, double> sinC = cos.AntiderivativeRecursive(0, 0, 0.01);
Func<double, double> sinD = cos.AntiderivativeEx(0, 0, 0.01);
int cycles = 1000;
calls = 0;
int start = Environment.TickCount;
for (int i = 0; i < cycles; i++)
for (double x = 0; x < Math.PI; x = x + 0.1)
{
double sinValueI = sin(x);
}
int elapsed = Environment.TickCount - start;
Msg(string.Format("Antiderivative: {0}ms, calls: {1}", elapsed, calls));
calls = 0;
start = Environment.TickCount;
for (int i = 0; i < cycles; i++)
for (double x = 0; x < Math.PI; x = x + 0.1)
{
double sinValueI = sinB(x);
}
elapsed = Environment.TickCount - start;
Msg(string.Format("AntiderivativePure: {0}ms, calls: {1}", elapsed, calls));
calls = 0;
start = Environment.TickCount;
for (int i = 0; i < cycles; i++)
for (double x = 0; x < Math.PI; x = x + 0.1)
{
double sinValueI = sinC(x);
}
elapsed = Environment.TickCount - start;
Msg(string.Format("AntiderivativeRecursive: {0}ms, calls: {1}", elapsed, calls));
calls = 0;
start = Environment.TickCount;
for (int i = 0; i < cycles; i++)
for (double x = 0; x < Math.PI; x = x + 0.1)
{
double sinValueI = sinD(x);
}
elapsed = Environment.TickCount - start;
Msg(string.Format("AntiderivativeEx: {0}ms, calls: {1}", elapsed, calls));
}
All implementations are used for evaluation of 3200 values of integral function. Results of this tests are given below:
Antiderivative: 46ms, calls: 642999
AntiderivativePure: 750ms, calls: 9974000
AntiderivativeRecursive: 1625ms, calls: 14919000
AntiderivativeEx: 1235ms, calls: 9978000
As we can read, Antiderivative
method is fastes (46ms) and does least calls of df
function (643k). If we avoid recursive implementation and use of state variables (currentx
, currentf
and currentdf
), we get 750ms and nearly one million of calls (0.997M).
There are two implementations with use of recursive calls AntiderivativeRecursive
and AntiderivativeEx
. AntiderivativeEx
is faster (1235ms) and does less call (0.998M) than AntiderivativeRecursive
. Both aspects are better because of optimization of df
function calls. This optimization is done via sharing of derivation values between recursive calls as you can see in appropriate code (AntiderivativeExH
method).
Usage of Antiderivative Method
For a test of Antiderivative
method, I choose test with sin(x) and cos(x).
private void AntiderivativeTest()
{
Func<double, double> cos = Math.Cos;
Func<double, double> sin = cos.Antiderivative(0, 0, 0.01);
for (double x = 0; x < Math.PI; x = x + 0.1)
{
double sinValueI = sin(x);
double sinValue = Math.Sin(x);
double delta = sinValue - sinValueI;
Msg(string.Format("x = {0:F6}, Math.Sin(x) = {1:F6}, sin(x) = {2:F6}, delta = {3:F6}", x, sinValue, sinValueI, delta));
}
}
The result of method is:
x = 0.000000, Math.Sin(x) = 0.000000, sin(x) = 0.000000, delta = 0.000000
x = 0.100000, Math.Sin(x) = 0.099833, sin(x) = 0.099833, delta = 0.000000
x = 0.200000, Math.Sin(x) = 0.198669, sin(x) = 0.198669, delta = 0.000000
x = 0.300000, Math.Sin(x) = 0.295520, sin(x) = 0.295520, delta = 0.000000
x = 0.400000, Math.Sin(x) = 0.389418, sin(x) = 0.389418, delta = 0.000000
x = 0.500000, Math.Sin(x) = 0.479426, sin(x) = 0.479426, delta = 0.000000
x = 0.600000, Math.Sin(x) = 0.564642, sin(x) = 0.564642, delta = 0.000000
x = 0.700000, Math.Sin(x) = 0.644218, sin(x) = 0.644218, delta = 0.000000
x = 0.800000, Math.Sin(x) = 0.717356, sin(x) = 0.717356, delta = 0.000000
x = 0.900000, Math.Sin(x) = 0.783327, sin(x) = 0.783327, delta = 0.000000
x = 1.000000, Math.Sin(x) = 0.841471, sin(x) = 0.841471, delta = 0.000000
x = 1.100000, Math.Sin(x) = 0.891207, sin(x) = 0.891207, delta = 0.000000
x = 1.200000, Math.Sin(x) = 0.932039, sin(x) = 0.932039, delta = 0.000000
x = 1.300000, Math.Sin(x) = 0.963558, sin(x) = 0.963558, delta = 0.000000
x = 1.400000, Math.Sin(x) = 0.985450, sin(x) = 0.985450, delta = 0.000000
x = 1.500000, Math.Sin(x) = 0.997495, sin(x) = 0.997495, delta = 0.000000
x = 1.600000, Math.Sin(x) = 0.999574, sin(x) = 0.999574, delta = 0.000000
x = 1.700000, Math.Sin(x) = 0.991665, sin(x) = 0.991665, delta = 0.000000
x = 1.800000, Math.Sin(x) = 0.973848, sin(x) = 0.973848, delta = 0.000000
x = 1.900000, Math.Sin(x) = 0.946300, sin(x) = 0.946300, delta = 0.000000
x = 2.000000, Math.Sin(x) = 0.909297, sin(x) = 0.909297, delta = 0.000000
x = 2.100000, Math.Sin(x) = 0.863209, sin(x) = 0.863209, delta = 0.000000
x = 2.200000, Math.Sin(x) = 0.808496, sin(x) = 0.808496, delta = 0.000000
x = 2.300000, Math.Sin(x) = 0.745705, sin(x) = 0.745705, delta = 0.000000
x = 2.400000, Math.Sin(x) = 0.675463, sin(x) = 0.675463, delta = 0.000000
x = 2.500000, Math.Sin(x) = 0.598472, sin(x) = 0.598472, delta = 0.000000
x = 2.600000, Math.Sin(x) = 0.515501, sin(x) = 0.515501, delta = 0.000000
x = 2.700000, Math.Sin(x) = 0.427380, sin(x) = 0.427380, delta = 0.000000
x = 2.800000, Math.Sin(x) = 0.334988, sin(x) = 0.334988, delta = 0.000000
x = 2.900000, Math.Sin(x) = 0.239249, sin(x) = 0.239249, delta = 0.000000
x = 3.000000, Math.Sin(x) = 0.141120, sin(x) = 0.141120, delta = 0.000000
x = 3.100000, Math.Sin(x) = 0.041581, sin(x) = 0.041581, delta = 0.000000
Once we get a method for creating antiderivate function, we can use it for numerical solution of definite integrals:
The equation (image) was taken from http://en.wikipedia.org/wiki/Integral.
So the method in C# can look like:
public static double IntervalEval(this Func<double, double> f, double from, double to)
{
double l = f(from);
double h = f(to);
return h - l;
}
public static double Integral(this Func<double, double> fx, double a, double b, double stepSize)
{
return fx.Antiderivative(a, 0, stepSize).IntervalEval(a, b);
}
Interval
method is here for simplification of interval evaluation of equation right side. Notice that body of Integral
method consists of just one line.
At this stage of implementation, the question "What about 2D integrals?" will appear.
Multiple Integration
Multiple integration is discussed on Wikipedia, see http://en.wikipedia.org/wiki/Integral#Extensions - Multiple integration or http://en.wikipedia.org/wiki/Multiple_integral. A very common form of multiple integration is 2D and 3D integrals which can be used for area or volume calculations.
2D Integrals Numerical Evaluation
2D integrals can be computed by usage of the next equation:
The equation (image) was taken from http://en.wikipedia.org/wiki/Multiple_integral.
public static Func<double, double> FixateX(this Func<double, double, double> fxy, double x)
{
return (y) => fxy(x, y);
}
public static double Integral(this Func<double, double, double> fxy,
double lowX, double highX, Func<double, double> lowY, Func<double, double> highY, double stepSize)
{
Func<double, double> helperFunc =
(cx) => fxy.FixateX(cx).Integral(lowY(cx), highY(cx), stepSize);
return helperFunc.Integral(lowX, highX, stepSize);
}
FixateX
method is here for simplification. The implementation can be shortened if this general method is omitted. Even if the body of Integral
method can be typed on one line, the two expression implementation is much easier to understand (maybe one more line can be optimal).
Test for this method can look like:
private double Circle(double r)
{
Func<double, double, double> fxy = (x, y) => 1;
Func<double, double> yboundLow = (x) => -Math.Sqrt(r * r - x * x);
Func<double, double> yboundHigh = (x) => Math.Sqrt(r * r - x * x);
double result = 0;
result = fxy.Integral(-r, r, yboundLow, yboundHigh, 0.01);
return result;
}
private void I2DTest()
{
Msg("Circle = " + Circle(1));
Msg("Expected : " + Math.PI);
}
Result of I2DTest
is:
Circle = 3.14143024919302
Expected : 3.14159265358979
What now 3D integration? Of course! Let's do it!
3D Integrals Numerical Evaluation
For 3D integrals, we can use a very similar equation (compare with 2D):
The equation was taken from http://en.wikipedia.org/wiki/Multiple_integral.
C# implementation can be this:
public static Func<double, double, double> FixateX
(this Func<double, double, double, double> fxyz, double x)
{
return (y, z) => fxyz(x, y, z);
}
public static double Integral(this Func<double, double, double, double> fxyz,
double lowX, double highX, Func<double, double> lowY, Func<double,
double> highY, Func<double, double, double> lowZ, Func<double,
double, double> highZ, double stepSize)
{
Func<double, double> helperFunc =
(cx) => fxyz.FixateX(cx).Integral(lowY(cx),
highY(cx), lowZ.FixateX(cx), highZ.FixateX(cx), stepSize);
return helperFunc.Integral(lowX, highX, stepSize);
}
FixateX
method if here for simplification. The implementation can be shorted if this general method is omitted. Even if the body of Integral
method can be typed on one line, the two expression implementation is much easier to understand (maybe one more line can be optimal). Really the same words like in 2D...
Test for this method can look like:
private double Sphere(double r)
{
Func<double, double, double, double> fxyz = (x, y, z) => 1;
Func<double, double> yboundLow = (x) => -Math.Sqrt(r * r - x * x);
Func<double, double> yboundHigh = (x) => Math.Sqrt(r * r - x * x);
Func<double, double, double> zboundLow = (x, y) => -Math.Sqrt(r * r - x * x - y * y);
Func<double, double, double> zboundHigh = (x, y) => Math.Sqrt(r * r - x * x - y * y);
double result = 0;
result = fxyz.Integral(-r, r, yboundLow, yboundHigh, zboundLow, zboundHigh, 0.01);
return result;
}
private void I3DTest()
{
Msg("Sphere = " + Sphere(1).ToString());
Msg("Expected : " + 4 * Math.PI / 3);
}
Result of I3DTest
is:
Sphere = 4.18857816178346
Expected : 4.18879020478639
Results
In the Introduction part, there was a warning about optimality of implemented algorithms. Yes, this implementation is not optimal in case of evaluation speed. But why not sometime create something "smooth". This work helped me to become more familiar with functional programming.
References
History
- 04-Jun-2014 First implementation (from scratch)
- 11-Jun-2014 Update according discussion (and fixed bug - improved accuracy)