From 067ec6fed3edf75ecf7987309e14051ffc332c3f Mon Sep 17 00:00:00 2001 From: diluculo Date: Wed, 11 Sep 2019 15:02:10 +0900 Subject: [PATCH] Add complex version of double-exponential integral --- .../IntegrationTests/IntegrationTest.cs | 70 ++++++- src/Numerics/Integrate.cs | 143 ++++++++++--- .../DoubleExponentialTransformation.cs | 20 ++ .../Integration/NewtonCotesTrapeziumRule.cs | 193 ++++++++++++++++++ 4 files changed, 392 insertions(+), 34 deletions(-) diff --git a/src/Numerics.Tests/IntegrationTests/IntegrationTest.cs b/src/Numerics.Tests/IntegrationTests/IntegrationTest.cs index e5e55ca2..1f600cfa 100644 --- a/src/Numerics.Tests/IntegrationTests/IntegrationTest.cs +++ b/src/Numerics.Tests/IntegrationTests/IntegrationTest.cs @@ -27,9 +27,10 @@ // OTHER DEALINGS IN THE SOFTWARE. // -using System; using MathNet.Numerics.Integration; using NUnit.Framework; +using System; +using System.Numerics; namespace MathNet.Numerics.UnitTests.IntegrationTests { @@ -59,7 +60,17 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests { return Math.Exp(-x / 5) * (2 + Math.Sin(2 * y)); } - + + /// + /// Test Function: f(x,y) = 1 / (1 + x^2) + /// + /// First input value. + /// Function result. + private static double TargetFunctionC(double x) + { + return 1 / (1 + x * x); + } + /// /// Test Function Start point. /// @@ -80,6 +91,16 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests /// private const double StopB = 1; + /// + /// Test Function Start point. + /// + private const double StartC = double.NegativeInfinity; + + /// + /// Test Function Stop point. + /// + private const double StopC = double.PositiveInfinity; + /// /// Target area square. /// @@ -90,6 +111,11 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests /// private const double TargetAreaB = 11.7078776759298776163; + /// + /// Target area. + /// + private const double TargetAreaC = Constants.Pi; + /// /// Test Integrate facade for simple use cases. /// @@ -119,6 +145,18 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests TargetAreaB, 1e-10, "Rectangle, Gauss-Legendre Order 22"); + + Assert.AreEqual( + TargetAreaC, + Integrate.DoubleExponential(TargetFunctionC, StartC, StopC), + 1e-5, + "Integral by substitution"); + + Assert.AreEqual( + TargetAreaC, + Integrate.DoubleExponential(TargetFunctionC, StartC, StopC, 1e-10), + 1e-10, + "Integral by substitution, Target 1e-10"); } /// @@ -326,7 +364,7 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests { Assert.AreEqual( expected, - Integrate.OnOpenInterval((x) => Math.Exp(-x * x / 2), a, b), + Integrate.DoubleExponential((x) => Math.Exp(-x * x / 2), a, b), 1e-10, "Integral e^(-x^2 /2) from {0} to {1}", a, b); } @@ -343,9 +381,33 @@ namespace MathNet.Numerics.UnitTests.IntegrationTests { Assert.AreEqual( expected, - factor * Integrate.OnOpenInterval((x) => 1 / (1 + x * x), a, b), + factor * Integrate.DoubleExponential((x) => 1 / (1 + x * x), a, b), 1e-10, "Integral sin(pi*x)/(pi*x) from -oo to oo"); } + + // integral_(-oo)^(oo) 1/(1 + j x^2) dx = -(-1)^(3/4) ¥ð + [TestCase(double.NegativeInfinity, double.PositiveInfinity, 2.2214414690791831235, -2.2214414690791831235)] + // integral_(0)^(oo) 1/(1 + j x^2) dx = -1/2 (-1)^(3/4) ¥ð + [TestCase(0, double.PositiveInfinity, 1.1107207345395915618, -1.1107207345395915618)] + // integral_(-oo)^(0) 1/(1 + j x^2) dx = -1/2 (-1)^(3/4) ¥ð + [TestCase(double.NegativeInfinity, 0, 1.1107207345395915618, -1.1107207345395915618)] + public void TestContourIntegralBySubstitution(double a, double b, double r, double i) + { + var expected = new Complex(r, i); + var actual = ContourIntegrate.DoubleExponential((x) => 1 / new Complex(1, x * x), a, b); + + Assert.AreEqual( + expected.Real, + actual.Real, + 1e-10, + "Integral e^(-x^2 /2) / (1 + j e^x) from {0} to {1}", a, b); + + Assert.AreEqual( + expected.Imaginary, + actual.Imaginary, + 1e-10, + "Integral e^(-x^2 /2) / (1 + j e^x) from {0} to {1}", a, b); + } } } diff --git a/src/Numerics/Integrate.cs b/src/Numerics/Integrate.cs index 461516e0..1a73d75a 100644 --- a/src/Numerics/Integrate.cs +++ b/src/Numerics/Integrate.cs @@ -28,6 +28,7 @@ // using System; +using System.Numerics; using MathNet.Numerics.Integration; namespace MathNet.Numerics @@ -50,15 +51,44 @@ namespace MathNet.Numerics return DoubleExponentialTransformation.Integrate(f, intervalBegin, intervalEnd, targetAbsoluteError); } + /// + /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// + /// The 2-dimensional analytic smooth function to integrate. + /// Where the interval starts for the first (inside) integral, exclusive and finite. + /// Where the interval ends for the first (inside) integral, exclusive and finite. + /// Where the interval starts for the second (outside) integral, exclusive and finite. + /// /// Where the interval ends for the second (outside) integral, exclusive and finite. + /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// Approximation of the finite integral in the given interval. + public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB, int order) + { + return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, order); + } + + /// + /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// + /// The 2-dimensional analytic smooth function to integrate. + /// Where the interval starts for the first (inside) integral, exclusive and finite. + /// Where the interval ends for the first (inside) integral, exclusive and finite. + /// Where the interval starts for the second (outside) integral, exclusive and finite. + /// /// Where the interval ends for the second (outside) integral, exclusive and finite. + /// Approximation of the finite integral in the given interval. + public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB) + { + return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, 32); + } + /// /// Approximation of the definite integral of an analytic smooth function by substitution. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. /// /// The analytic smooth function to integrate. - /// Where the interval starts, inclusive and finite. - /// Where the interval stops, inclusive and finite. + /// Where the interval starts. + /// Where the interval stops. /// The expected relative accuracy of the approximation. /// Approximation of the finite integral in the given interval. - public static double OnOpenInterval(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError = 1E-8) + public static double DoubleExponential(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError = 1E-8) { // Reference: // Formula used for variable subsitution from @@ -67,7 +97,7 @@ namespace MathNet.Numerics if (intervalBegin > intervalEnd) { - return -OnOpenInterval(f, intervalEnd, intervalBegin, targetAbsoluteError); + return -DoubleExponential(f, intervalEnd, intervalBegin, targetAbsoluteError); } // (-oo, oo) => [-1, 1] @@ -81,7 +111,7 @@ namespace MathNet.Numerics { return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); }; - return OnClosedInterval(u, -1d, 1d, targetAbsoluteError); + return DoubleExponentialTransformation.Integrate(u, -1, 1, targetAbsoluteError); } // [a, oo) => [0, 1] // @@ -95,7 +125,7 @@ namespace MathNet.Numerics { return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); }; - return OnClosedInterval(u, 0d, 1d, targetAbsoluteError); + return DoubleExponentialTransformation.Integrate(u, 0, 1, targetAbsoluteError); } // (-oo, b] => [-1, 0] // @@ -109,7 +139,7 @@ namespace MathNet.Numerics { return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); }; - return OnClosedInterval(u, -1d, 0d, targetAbsoluteError); + return DoubleExponentialTransformation.Integrate(u, -1, 0, targetAbsoluteError); } // [a, b] => [-1, 1] // @@ -122,37 +152,90 @@ namespace MathNet.Numerics { return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); }; - return OnClosedInterval(u, -1d, 1d, targetAbsoluteError); + return DoubleExponentialTransformation.Integrate(u, -1, 1, targetAbsoluteError); } } + } + /// + /// Numerical Contour Integration over a real variable, of a complex-valued function. + /// + public static class ContourIntegrate + { /// - /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. + /// Approximation of the definite integral of an analytic smooth complex function by substitution. When either or both limits are infinite, the integrand is assumed rapidly decayed to zero as x -> infinity. /// - /// The 2-dimensional analytic smooth function to integrate. - /// Where the interval starts for the first (inside) integral, exclusive and finite. - /// Where the interval ends for the first (inside) integral, exclusive and finite. - /// Where the interval starts for the second (outside) integral, exclusive and finite. - /// /// Where the interval ends for the second (outside) integral, exclusive and finite. - /// Defines an Nth order Gauss-Legendre rule. The order also defines the number of abscissas and weights for the rule. Precomputed Gauss-Legendre abscissas/weights for orders 2-20, 32, 64, 96, 100, 128, 256, 512, 1024 are used, otherwise they're calculated on the fly. + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts. + /// Where the interval stops. + /// The expected relative accuracy of the approximation. /// Approximation of the finite integral in the given interval. - public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB, int order) + public static Complex DoubleExponential(Func f, double intervalBegin, double intervalEnd, double targetAbsoluteError = 1E-8) { - return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, order); - } + // Reference: + // Formula used for variable subsitution from + // 1. Shampine, L. F. (2008). Vectorized adaptive quadrature in MATLAB. Journal of Computational and Applied Mathematics, 211(2), 131-140. + // 2. quadgk.m, GNU Octave - /// - /// Approximates a 2-dimensional definite integral using an Nth order Gauss-Legendre rule over the rectangle [a,b] x [c,d]. - /// - /// The 2-dimensional analytic smooth function to integrate. - /// Where the interval starts for the first (inside) integral, exclusive and finite. - /// Where the interval ends for the first (inside) integral, exclusive and finite. - /// Where the interval starts for the second (outside) integral, exclusive and finite. - /// /// Where the interval ends for the second (outside) integral, exclusive and finite. - /// Approximation of the finite integral in the given interval. - public static double OnRectangle(Func f, double invervalBeginA, double invervalEndA, double invervalBeginB, double invervalEndB) - { - return GaussLegendreRule.Integrate(f, invervalBeginA, invervalEndA, invervalBeginB, invervalEndB, 32); + if (intervalBegin > intervalEnd) + { + return -DoubleExponential(f, intervalEnd, intervalBegin, targetAbsoluteError); + } + + // (-oo, oo) => [-1, 1] + // + // integral_{-oo}^{oo} f(x) dx = integral_{-1}^{1} f(g(t)) g'(t) dt + // g(t) = t / (1 - t^2) + // g'(t) = (1 + t^2) / (1 - t^2)^2 + if (double.IsInfinity(intervalBegin) && double.IsInfinity(intervalEnd)) + { + Func u = (t) => + { + return f(t / (1 - t * t)) * (1 + t * t) / ((1 - t * t) * (1 - t * t)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, -1, 1, targetAbsoluteError); + } + // [a, oo) => [0, 1] + // + // integral_{a}^{oo} f(x) dx = integral_{0}^{oo} f(a + t^2) 2 t dt + // = integral_{0}^{1} f(a + g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 - s) + // g'(s) = 1 / (1 - s)^2 + else if (double.IsInfinity(intervalEnd)) + { + Func u = (s) => + { + return 2 * s * f(intervalBegin + (s / (1 - s)) * (s / (1 - s))) / ((1 - s) * (1 - s) * (1 - s)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, 0, 1, targetAbsoluteError); + } + // (-oo, b] => [-1, 0] + // + // integral_{-oo}^{b} f(x) dx = -integral_{-oo}^{0} f(b - t^2) 2 t dt + // = -integral_{-1}^{0} f(b - g(s)^2) 2 g(s) g'(s) ds + // g(s) = s / (1 + s) + // g'(s) = 1 / (1 + s)^2 + else if (double.IsInfinity(intervalBegin)) + { + Func u = (s) => + { + return -2 * s * f(intervalEnd - s / (1 + s) * (s / (1 + s))) / ((1 + s) * (1 + s) * (1 + s)); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, -1, 0, targetAbsoluteError); + } + // [a, b] => [-1, 1] + // + // integral_{a}^{b} f(x) dx = integral_{-1}^{1} f(g(t)) g'(t) dt + // g(t) = (b - a) * t * (3 - t^2) / 4 + (b + a) / 2 + // g'(t) = 3 / 4 * (b - a) * (1 - t^2) + else + { + Func u = (t) => + { + return f((intervalEnd - intervalBegin) / 4 * t * (3 - t * t) + (intervalEnd + intervalBegin) / 2) * 3 * (intervalEnd - intervalBegin) / 4 * (1 - t * t); + }; + return DoubleExponentialTransformation.ContourIntegrate(u, -1, 1, targetAbsoluteError); + } } } } diff --git a/src/Numerics/Integration/DoubleExponentialTransformation.cs b/src/Numerics/Integration/DoubleExponentialTransformation.cs index bd9ddd83..9072743c 100644 --- a/src/Numerics/Integration/DoubleExponentialTransformation.cs +++ b/src/Numerics/Integration/DoubleExponentialTransformation.cs @@ -29,6 +29,7 @@ using System; using System.Linq; +using System.Numerics; namespace MathNet.Numerics.Integration { @@ -63,6 +64,25 @@ namespace MathNet.Numerics.Integration targetRelativeError); } + /// + /// Approximate the integral by the double exponential transformation + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrate(Func f, double intervalBegin, double intervalEnd, double targetRelativeError) + { + return NewtonCotesTrapeziumRule.ContourIntegrateAdaptiveTransformedOdd( + f, + intervalBegin, intervalEnd, + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateAbcissas), + Enumerable.Range(0, NumberOfMaximumLevels).Select(EvaluateWeights), + 1.0, + targetRelativeError); + } + /// /// Compute the abscissa vector for a single level. /// diff --git a/src/Numerics/Integration/NewtonCotesTrapeziumRule.cs b/src/Numerics/Integration/NewtonCotesTrapeziumRule.cs index d82e08a7..b29037b1 100644 --- a/src/Numerics/Integration/NewtonCotesTrapeziumRule.cs +++ b/src/Numerics/Integration/NewtonCotesTrapeziumRule.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Numerics; using MathNet.Numerics.Properties; namespace MathNet.Numerics.Integration @@ -58,6 +59,23 @@ namespace MathNet.Numerics.Integration return (intervalEnd - intervalBegin)/2*(f(intervalBegin) + f(intervalEnd)); } + /// + /// Direct 2-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateTwoPoint(Func f, double intervalBegin, double intervalEnd) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + return (intervalEnd - intervalBegin) / 2 * (f(intervalBegin) + f(intervalEnd)); + } + /// /// Composite N-point approximation of the definite integral in the provided interval by the trapezium rule. /// @@ -92,6 +110,40 @@ namespace MathNet.Numerics.Integration return step*sum; } + /// + /// Composite N-point approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Number of composite subdivision partitions. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateComposite(Func f, double intervalBegin, double intervalEnd, int numberOfPartitions) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (numberOfPartitions <= 0) + { + throw new ArgumentOutOfRangeException(nameof(numberOfPartitions), Resources.ArgumentPositive); + } + + double step = (intervalEnd - intervalBegin) / numberOfPartitions; + + double offset = step; + Complex sum = 0.5 * (f(intervalBegin) + f(intervalEnd)); + for (int i = 0; i < numberOfPartitions - 1; i++) + { + // NOTE (ruegg, 2009-01-07): Do not combine intervalBegin and offset (numerical stability!) + sum += f(intervalBegin + offset); + offset += step; + } + + return step * sum; + } + /// /// Adaptive approximation of the definite integral in the provided interval by the trapezium rule. /// @@ -132,6 +184,47 @@ namespace MathNet.Numerics.Integration return sum; } + /// + /// Adaptive approximation of the definite integral in the provided interval by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, define don real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// The expected accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateAdaptive(Func f, double intervalBegin, double intervalEnd, double targetError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + int numberOfPartitions = 1; + double step = intervalEnd - intervalBegin; + Complex sum = 0.5 * step * (f(intervalBegin) + f(intervalEnd)); + for (int k = 0; k < 20; k++) + { + Complex midpointsum = 0; + for (int i = 0; i < numberOfPartitions; i++) + { + midpointsum += f(intervalBegin + ((i + 0.5) * step)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + numberOfPartitions *= 2; + + if (sum.AlmostEqualRelative(midpointsum, targetError)) + { + break; + } + } + + return sum; + } + + /// /// Adaptive approximation of the definite integral by the trapezium rule. /// @@ -230,5 +323,105 @@ namespace MathNet.Numerics.Integration return sum*linearSlope; } } + + /// + /// Adaptive approximation of the definite integral by the trapezium rule. + /// + /// The analytic smooth complex function to integrate, defined on the real domain. + /// Where the interval starts, inclusive and finite. + /// Where the interval stops, inclusive and finite. + /// Abscissa vector per level provider. + /// Weight vector per level provider. + /// First Level Step + /// The expected relative accuracy of the approximation. + /// Approximation of the finite integral in the given interval. + public static Complex ContourIntegrateAdaptiveTransformedOdd( + Func f, + double intervalBegin, double intervalEnd, + IEnumerable levelAbscissas, IEnumerable levelWeights, + double levelOneStep, double targetRelativeError) + { + if (f == null) + { + throw new ArgumentNullException(nameof(f)); + } + + if (levelAbscissas == null) + { + throw new ArgumentNullException(nameof(levelAbscissas)); + } + + if (levelWeights == null) + { + throw new ArgumentNullException(nameof(levelWeights)); + } + + double linearSlope = 0.5 * (intervalEnd - intervalBegin); + double linearOffset = 0.5 * (intervalEnd + intervalBegin); + targetRelativeError /= 5 * linearSlope; + + using (var abcissasIterator = levelAbscissas.GetEnumerator()) + using (var weightsIterator = levelWeights.GetEnumerator()) + { + double step = levelOneStep; + + // First Level + abcissasIterator.MoveNext(); + weightsIterator.MoveNext(); + double[] abcissasL1 = abcissasIterator.Current; + double[] weightsL1 = weightsIterator.Current; + + Complex sum = f(linearOffset) * weightsL1[0]; + for (int i = 1; i < abcissasL1.Length; i++) + { + sum += weightsL1[i] * (f((linearSlope * abcissasL1[i]) + linearOffset) + f(-(linearSlope * abcissasL1[i]) + linearOffset)); + } + + sum *= step; + + // Additional Levels + double previousDelta = double.MaxValue; + for (int level = 1; abcissasIterator.MoveNext() && weightsIterator.MoveNext(); level++) + { + double[] abcissas = abcissasIterator.Current; + double[] weights = weightsIterator.Current; + + Complex midpointsum = 0; + for (int i = 0; i < abcissas.Length; i++) + { + midpointsum += weights[i] * (f((linearSlope * abcissas[i]) + linearOffset) + f(-(linearSlope * abcissas[i]) + linearOffset)); + } + + midpointsum *= step; + sum = 0.5 * (sum + midpointsum); + step *= 0.5; + + double delta = Complex.Abs(sum - midpointsum); + + if (level == 1) + { + previousDelta = delta; + continue; + } + + double r = Math.Log(delta) / Math.Log(previousDelta); + previousDelta = delta; + + if (r > 1.9 && r < 2.1) + { + // convergence region + delta = Math.Sqrt(delta); + } + + if (sum.Real.AlmostEqualNormRelative(midpointsum.Real, delta, targetRelativeError) + && sum.Imaginary.AlmostEqualNormRelative(midpointsum.Imaginary, delta, targetRelativeError)) + { + break; + } + } + + return sum * linearSlope; + } + } } }