Ejemplo n.º 1
0
        internal static IntegrationSettings SetMultiIntegrationDefaults(IntegrationSettings original, int d)
        {
            IntegrationSettings settings = new IntegrationSettings();

            settings.RelativePrecision = (original.RelativePrecision < 0.0) ? Math.Pow(10.0, -(0.0 + 14.0 / d)) : original.RelativePrecision;
            settings.AbsolutePrecision = (original.AbsolutePrecision < 0.0) ? Math.Pow(10.0, -(1.0 + 14.0 / d)) : original.AbsolutePrecision;
            settings.EvaluationBudget  = (original.EvaluationBudget < 0.0) ? (int)Math.Round(Math.Pow(10.0, 9.0 - 8.0 / d)) : original.EvaluationBudget;
            settings.Listener          = original.Listener;
            return(settings);
        }
Ejemplo n.º 2
0
        internal static IntegrationSettings SetIntegrationDefaults(IntegrationSettings settings)
        {
            IntegrationSettings result = new IntegrationSettings();

            result.RelativePrecision = (settings.RelativePrecision < 0.0) ? 1.0E-14 : settings.RelativePrecision;
            result.AbsolutePrecision = (settings.AbsolutePrecision < 0.0) ? 1.0E-15 : settings.AbsolutePrecision;
            result.EvaluationBudget  = (settings.EvaluationBudget < 0) ? 5000 : settings.EvaluationBudget;
            result.Listener          = settings.Listener;
            return(result);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Estimates a multi-dimensional integral using the given evaluation settings.
        /// </summary>
        /// <param name="function">The function to be integrated.</param>
        /// <param name="volume">The volume over which to integrate.</param>
        /// <param name="settings">The integration settings.</param>
        /// <returns>A numerical estimate of the multi-dimensional integral.</returns>
        /// <remarks>
        /// <para>Note that the integration function must not attempt to modify the argument passed to it.</para>
        /// <para>Note that the integration volume must be a hyper-rectangle. You can integrate over regions with more complex boundaries by specifying the integration
        /// volume as a bounding hyper-rectangle that encloses your desired integration region, and returing the value 0 for the integrand outside of the desired integration
        /// region. For example, to find the volume of a unit d-sphere, you can integrate a function that is 1 inside the unit d-sphere and 0 outside it over the volume
        /// [-1,1]<sup>d</sup>. You can integrate over infinite volumes by specifing volume endpoints of <see cref="Double.PositiveInfinity"/> and/or
        /// <see cref="Double.NegativeInfinity"/>. Volumes with dimension greater than 15 are not currently supported.</para>
        /// <para>Integrals with hard boundaries (like our hyper-sphere volume problem) typically require more evaluations than integrals of smooth functions to achieve the same accuracy.
        /// Integrals with canceling positive and negative contributions also typically require more evaluations than integtrals of purely positive functions.</para>
        /// <para>Numerical multi-dimensional integration is computationally expensive. To make problems more tractable, keep in mind some rules of thumb:</para>
        /// <ul>
        ///   <li>Reduce the required accuracy to the minimum required. Alternatively, if you are willing to wait longer, increase the evaluation budget.</li>
        ///   <li>Exploit symmetries of the problem to reduce the integration volume. For example, to compute the volume of the unit d-sphere, it is better to
        ///   integrate over [0,1]<sup>d</sup> and multiply the result by 2<sup>d</sup> than to simply integrate over [-1,1]<sup>d</sup>.</li>
        ///   <li>Apply analytic techniques to reduce the dimension of the integral. For example, when computing the volume of the unit d-sphere, it is better
        ///   to do a (d-1)-dimesional integral over the function that is the height of the sphere in the dth dimension than to do a d-dimensional integral over the
        ///   indicator function that is 1 inside the sphere and 0 outside it.</li>
        /// </ul>
        /// </remarks>
        /// <exception cref="ArgumentException"><paramref name="function"/>, <paramref name="volume"/>, or <paramref name="settings"/> are null, or
        /// the dimension of <paramref name="volume"/> is larger than 15.</exception>
        /// <exception cref="NonconvergenceException">The prescribed accuracy could not be achieved with the given evaluation budget.</exception>
        public static IntegrationResult Integrate(Func <IList <double>, double> function, IList <Interval> volume, IntegrationSettings settings)
        {
            if (function == null)
            {
                throw new ArgumentNullException(nameof(function));
            }
            if (volume == null)
            {
                throw new ArgumentNullException(nameof(volume));
            }
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            // Get the dimension from the box
            int d = volume.Count;

            settings = SetMultiIntegrationDefaults(settings, d);

            // Translate the integration volume, which may be infinite, into a bounding box plus a coordinate transform.
            Interval[]            box = new Interval[d];
            CoordinateTransform[] map = new CoordinateTransform[d];
            for (int i = 0; i < d; i++)
            {
                Interval limits = volume[i];
                if (Double.IsInfinity(limits.RightEndpoint))
                {
                    if (Double.IsInfinity(limits.LeftEndpoint))
                    {
                        // -\infinity to +\infinity
                        box[i] = Interval.FromEndpoints(-1.0, +1.0);
                        map[i] = new TangentCoordinateTransform(0.0);
                    }
                    else
                    {
                        // a to +\infinity
                        box[i] = Interval.FromEndpoints(0.0, 1.0);
                        map[i] = new TangentCoordinateTransform(limits.LeftEndpoint);
                    }
                }
                else
                {
                    if (Double.IsInfinity(limits.LeftEndpoint))
                    {
                        // -\infinity to b
                        box[i] = Interval.FromEndpoints(-1.0, 0.0);
                        map[i] = new TangentCoordinateTransform(limits.RightEndpoint);
                    }
                    else
                    {
                        // a to b
                        box[i] = limits;
                        map[i] = new IdentityCoordinateTransform();
                    }
                }
            }

            // Use adaptive cubature for small dimensions, Monte-Carlo for large dimensions.

            MultiFunctor f = new MultiFunctor(function);

            f.IgnoreInfinity = true;
            f.IgnoreNaN      = true;

            UncertainValue estimate;

            if (d < 1)
            {
                throw new ArgumentException("The dimension of the integration volume must be at least 1.", "volume");
            }
            else if (d < 4)
            {
                IntegrationRegion r = new IntegrationRegion(box);
                estimate = Integrate_Adaptive(f, map, r, settings);
            }
            else if (d < 16)
            {
                estimate = Integrate_MonteCarlo(f, map, box, settings);
            }
            else
            {
                throw new ArgumentException("The dimension of the integrtion volume must be less than 16.", "volume");
            }

            // Sometimes the estimated uncertainty drops precipitiously. We will not report an uncertainty less than 3/4 of that demanded.
            double minUncertainty = Math.Max(0.75 * settings.AbsolutePrecision, Math.Abs(estimate.Value) * 0.75 * settings.RelativePrecision);

            if (estimate.Uncertainty < minUncertainty)
            {
                estimate = new UncertainValue(estimate.Value, minUncertainty);
            }

            return(new IntegrationResult(estimate, f.EvaluationCount, settings));
        }
Ejemplo n.º 4
0
 internal IntegrationResult(UncertainValue estimate, int evaluationCount, IntegrationSettings settings) : base(evaluationCount)
 {
     Debug.Assert(settings != null);
     this.estimate = estimate;
     this.settings = settings;
 }
Ejemplo n.º 5
0
 /// <summary>
 /// Evaluates a definite integral with the given evaluation settings.
 /// </summary>
 /// <param name="integrand">The function to be integrated.</param>
 /// <param name="range">The range of integration.</param>
 /// <param name="settings">The settings which control the evaluation of the integral.</param>
 /// <returns>The result of the integral.</returns>
 /// <exception cref="ArgumentNullException">The <paramref name="integrand"/> is <see langword="null"/>.</exception>
 /// <exception cref="NonconvergenceException">The maximum number of function evaluations was exceeded before the integral
 /// could be determined to the required precision.</exception>
 /// <remarks>
 /// <para>For information, see <see cref="Integrate(Func{double, double}, double, double, IntegrationSettings)"/>.</para>
 /// </remarks>
 public static IntegrationResult Integrate(Func <double, double> integrand, Interval range, IntegrationSettings settings)
 {
     return(Integrate(integrand, range.LeftEndpoint, range.RightEndpoint, settings));
 }
Ejemplo n.º 6
0
        /// <summary>
        /// Evaluates a definite integral.
        /// </summary>
        /// <param name="integrand">The function to be integrated.</param>
        /// <param name="range">The range of integration.</param>
        /// <returns>The result of the integral.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="integrand"/> is <see langword="null"/>.</exception>
        /// <exception cref="NonconvergenceException">The maximum number of function evaluations was exceeded before the integral
        /// could be determined to the required precision.</exception>
        /// <remarks>
        /// <para>By default, integrals are evaluated to a relative precision of about 10<sup>-14</sup>, about two digits short of full
        /// precision, or an absolute precision of about 10<sup>-16</sup>, using a budget of about 5000 evaluations.
        /// To specify different evaluation settings use
        /// <see cref="Integrate(Func{double, double}, Interval, IntegrationSettings)"/>.</para>
        /// <para>See <see cref="Integrate(Func{double, double}, Interval, IntegrationSettings)"/> for detailed remarks on
        /// numerical integration.</para>
        /// </remarks>
        public static IntegrationResult Integrate(Func <double, double> integrand, Interval range)
        {
            IntegrationSettings settings = new IntegrationSettings();

            return(Integrate(integrand, range, settings));
        }
Ejemplo n.º 7
0
        // the public API

        /// <summary>
        /// Evaluates a definite integral.
        /// </summary>
        /// <param name="integrand">The function to be integrated.</param>
        /// <param name="start">The lower integration endpoint.</param>
        /// <param name="end">The upper integration endpoint.</param>
        /// <returns>The result of the integral.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="integrand"/> is <see langword="null"/>.</exception>
        /// <exception cref="NonconvergenceException">The maximum number of function evaluations was exceeded before the integral
        /// could be determined to the required precision.</exception>
        /// <remarks>
        /// <para>By default, integrals are evaluated to a relative precision of about 10<sup>-14</sup>, about two digits short of full
        /// precision, or an absolute precision of about 10<sup>-16</sup>, using a budget of about 5000 evaluations.
        /// To specify different evaluation settings use
        /// <see cref="Integrate(Func{double, double}, double, double, IntegrationSettings)"/>.</para>
        /// <para>See <see cref="Integrate(Func{double, double}, double, double, IntegrationSettings)"/> for detailed remarks on
        /// numerical integration.</para>
        /// </remarks>
        public static IntegrationResult Integrate(Func <double, double> integrand, double start, double end)
        {
            IntegrationSettings settings = new IntegrationSettings();

            return(Integrate(integrand, start, end, settings));
        }
Ejemplo n.º 8
0
        // the drivers

        private static IntegrationResult Integrate_Adaptive(IAdaptiveIntegrator integrator, IntegrationSettings settings)
        {
            Debug.Assert(integrator != null);
            Debug.Assert(settings != null);

            LinkedList <IAdaptiveIntegrator> list = new LinkedList <IAdaptiveIntegrator>();

            list.AddFirst(integrator);

            int n = integrator.EvaluationCount;

            while (true)
            {
                // go through the intervals, adding estimates (and errors)
                // and noting which contributes the most error
                // keep track of the total value and uncertainty
                UncertainValue vTotal = new UncertainValue();

                // keep track of which node contributes the most error
                LinkedListNode <IAdaptiveIntegrator> maxNode = null;
                double maxError = 0.0;

                LinkedListNode <IAdaptiveIntegrator> node = list.First;
                while (node != null)
                {
                    IAdaptiveIntegrator i = node.Value;

                    UncertainValue v = i.Estimate;
                    vTotal += v;

                    if (v.Uncertainty > maxError)
                    {
                        maxNode  = node;
                        maxError = v.Uncertainty;
                    }

                    node = node.Next;
                }

                // Inform listeners of our latest result.
                if (settings.Listener != null)
                {
                    settings.Listener(new IntegrationResult(vTotal, n, settings));
                }

                // if our error is small enough, return
                double tol = settings.ComputePrecision(vTotal.Value);
                if (vTotal.Uncertainty <= tol)
                {
                    // Don't claim uncertainty significantly less than tol.
                    if (vTotal.Uncertainty < tol / 2.0)
                    {
                        vTotal = new UncertainValue(vTotal.Value, tol / 2.0);
                    }
                    return(new IntegrationResult(vTotal, n, settings));
                }

                // if our evaluation count is too big, throw
                if (n > settings.EvaluationBudget)
                {
                    throw new NonconvergenceException();
                }

                // Subdivide the interval with the largest error
                IEnumerable <IAdaptiveIntegrator> divisions = maxNode.Value.Divide();
                foreach (IAdaptiveIntegrator division in divisions)
                {
                    list.AddBefore(maxNode, division);
                    n += division.EvaluationCount;
                }
                list.Remove(maxNode);
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Evaluates a definite integral with the given evaluation settings.
        /// </summary>
        /// <param name="integrand">The function to be integrated.</param>
        /// <param name="start">The left integration endpoint.</param>
        /// <param name="end">The right integration endpoint.</param>
        /// <param name="settings">The settings which control the evaluation of the integral.</param>
        /// <returns>The result of the integral.</returns>
        /// <remarks>
        /// <para>To do integrals over infinite regions, simply set <paramref name="start"/> or <paramref name="end"/>
        /// to <see cref="System.Double.NegativeInfinity"/> or <see cref="System.Double.PositiveInfinity"/>.</para>
        /// <para>Our integrator handles smooth functions extremely efficiently. It handles integrands with
        /// discontinuities or kinks at the price of slightly more evaluations of the integrand.
        /// It can handle oscillatory functions, as long as cancelation between positive and negative regions
        /// is not too severe. It can integrate logarithmic and mild power-law singularities.</para>
        /// <para>Strong power-law singularities will cause the algorithm to fail with a <see cref="NonconvergenceException"/>.
        /// This is unavoidable for essentially any double-precision numerical integrator. Consider, for example,
        /// the integrable singularity x<sup>-1/2</sup>. Since
        /// &#x3B5; = &#x222B;<sub>0</sub><sup>&#x3B4;</sup> x<sup>-1/2</sup> dx = 2 &#x3B4;<sup>1/2</sup>,
        /// points within &#x3B4; &#x223C; 10<sup>-16</sup> of the end-points, which as a close as you can get to
        /// a point in double precision without being on top of it, contribute at the &#x3B5; &#x223C; 10<sup>-8</sup>
        /// level to our integral, well beyond limit that nearly-full double precision requires. Said differently,
        /// to know the value of the integral to &#x3B5; &#x223C; 10<sup>-16</sup> precision, we would need to
        /// evaluate the contributions of points within &#x3B4; &#x223C; 10<sup>-32</sup> of the endpoints,
        /// which is far closer than we can get.</para>
        /// <para>If you need to evaluate an integral with such a strong singularity, try to make an analytic
        /// change of variable to absorb the singularity before attempting numerical integration. For example,
        /// to evaluate I = &#x222B;<sub>0</sub><sup>b</sup> f(x) x<sup>-1/2</sup> dx, substitute y = x<sup>1/2</sup>
        /// to obtain I = 2 &#x222B;<sub>0</sub><sup>&#x221A;b</sup> f(y<sup>2</sup>) dy.</para>
        /// <para>To do multi-dimensional integrals, use
        /// <see cref="MultiFunctionMath.Integrate(Func{IReadOnlyList{double}, double}, IReadOnlyList{Interval}, IntegrationSettings)"/>.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">The <paramref name="integrand"/> is <see langword="null"/>.</exception>
        /// <exception cref="NonconvergenceException">The maximum number of function evaluations was exceeded before the integral
        /// could be determined to the required precision.</exception>
        public static IntegrationResult Integrate(Func <double, double> integrand, double start, double end, IntegrationSettings settings)
        {
            if (integrand == null)
            {
                throw new ArgumentNullException(nameof(integrand));
            }
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            // Deal with right-to-left integrals
            if (end < start)
            {
                IntegrationResult r = Integrate(integrand, end, start, settings);
                return(new IntegrationResult(-r.Estimate, r.EvaluationCount, r.Settings));
            }

            // Re-map infinite integrals to finite integrals
            if (Double.IsNegativeInfinity(start) && Double.IsPositiveInfinity(end))
            {
                // -\infty to +\infty
                // remap to (-\pi/2,\pi/2)
                Func <double, double> f1 = delegate(double t) {
                    double x = Math.Tan(t);
                    return(integrand(x) * (1.0 + x * x));
                };
                return(Integrate(f1, -Global.HalfPI, +Global.HalfPI, settings));
            }
            else if (Double.IsPositiveInfinity(end))
            {
                // finite to +\infty
                // remap to interval (-1,1)
                Func <double, double> f1 = delegate(double t) {
                    double q = 1.0 / (1.0 - t);
                    double x = start + (1.0 + t) * q;
                    return(integrand(x) * 2.0 * q * q);
                };
                return(Integrate(f1, -1.0, +1.0, settings));
            }
            else if (Double.IsNegativeInfinity(start))
            {
                // -\infty to finite
                // remap to interval (-1,1)
                Func <double, double> f1 = delegate(double t) {
                    double q = t + 1.0;
                    double x = end + (t - 1.0) / q;
                    return(integrand(x) * (2.0 / q / q));
                };
                return(Integrate(f1, -1.0, +1.0, settings));
            }

            // Fix settings.
            settings = SetIntegrationDefaults(settings);

            // normal integral over a finite range
            Debug.Assert(end >= start);
            IAdaptiveIntegrator integrator = new GaussKronrodIntegrator(integrand, Interval.FromEndpoints(start, end));
            IntegrationResult   result     = Integrate_Adaptive(integrator, settings);

            return(result);
        }
        private static UncertainValue Integrate_MonteCarlo(MultiFunctor f, CoordinateTransform[] map, IList <Interval> box, IntegrationSettings settings)
        {
            int d = box.Count;

            // Use a Sobol quasi-random sequence. This give us 1/N accuracy instead of 1/\sqrt{N} accuracy.
            //VectorGenerator g = new RandomVectorGenerator(d, new Random(314159265));
            VectorGenerator g = new SobolVectorGenerator(d);

            // Start with a trivial Lepage grid.
            // We will increase the grid size every few cycles.
            // My tests indicate that trying to increase every cycle or even every other cycle is too often.
            // This makes sense, because we have no reason to believe our new grid will be better until we
            // have substantially more evaluations per grid cell than we did for the previous grid.
            LePageGrid grid        = new LePageGrid(box, 1);
            int        refineCount = 0;

            // Start with a reasonable number of evaluations per cycle that increases with the dimension.
            int cycleCount = 8 * d;

            //double lastValue = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);

            // Each cycle consists of three sets of evaluations.
            // At first I did this with just two set and used the difference between the two sets as an error estimate.
            // I found that it was pretty common for that difference to be low just by chance, causing error underestimatation.
            double value1 = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);
            double value2 = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);
            double value3 = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);

            while (f.EvaluationCount < settings.EvaluationBudget)
            {
                // Take the largest deviation as the error.
                double value = (value1 + value2 + value3) / 3.0;
                double error = Math.Max(Math.Abs(value1 - value3), Math.Max(Math.Abs(value1 - value2), Math.Abs(value2 - value3)));
                Debug.WriteLine("{0} {1} {2}", f.EvaluationCount, value, error);

                if (settings.Listener != null)
                {
                    settings.Listener(new IntegrationResult(new UncertainValue(value, error), f.EvaluationCount, settings));
                }

                // Check for convergence.
                if ((error <= settings.AbsolutePrecision) || (error <= Math.Abs(value) * settings.RelativePrecision))
                {
                    return(new UncertainValue(value, error));
                }

                // Do more cycles. In order for new sets to be equal-sized, one of those must be at the current count and the next at twice that.
                double smallValue = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);
                cycleCount *= 2;
                double bigValue = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);

                // Combine all the cycles into new ones with twice the number of evaluations each.
                value1 = (value1 + value2) / 2.0;
                value2 = (value3 + smallValue) / 2.0;
                value3 = bigValue;

                //double currentValue = Integrate_MonteCarlo_Cycle(f, map, g, grid, cycleCount);
                //double error = Math.Abs(currentValue - lastValue);
                //double value = (currentValue + lastValue) / 2.0;



                //lastValue = value;

                // Increase the number of evaluations for the next cycle.
                //cycleCount *= 2;

                // Refine the grid for the next cycle.
                refineCount++;
                if (refineCount == 2)
                {
                    Debug.WriteLine("Replacing grid with {0} bins after {1} evaluations", grid.BinCount, grid.EvaluationCount);
                    grid        = grid.ComputeNewGrid(grid.BinCount * 2);
                    refineCount = 0;
                }

                if (f.EvaluationCount >= settings.EvaluationBudget)
                {
                    throw new NonconvergenceException();
                }
            }

            throw new NonconvergenceException();
        }
        /// <summary>
        /// Evaluates a definite integral with the given evaluation settings.
        /// </summary>
        /// <param name="integrand">The function to be integrated.</param>
        /// <param name="range">The range of integration.</param>
        /// <param name="settings">The settings which control the evaulation of the integal.</param>
        /// <returns>The result of the integral, which includes an estimated value and an estimated uncertainty of that value.</returns>
        /// <exception cref="ArgumentNullException">The <paramref name="integrand"/> is <see langword="null"/>.</exception>
        /// <exception cref="NonconvergenceException">The maximum number of function evaluations was exceeded before the integral
        /// could be determined to the required precision.</exception>
        /// <remarks>
        /// <para>To do integrals over infinite regions, simply set the lower bound of the <paramref name="range"/>
        /// to <see cref="System.Double.NegativeInfinity"/> or the upper bound to <see cref="System.Double.PositiveInfinity"/>.</para>
        /// <para>Our numerical integrator uses a Gauss-Kronrod rule that can integrate efficiently,
        /// combined with an adaptive strategy that limits function
        /// evaluations to those regions required to achieve the desired accuracy.</para>
        /// <para>Our integrator handles smooth functions extremely efficiently. It handles integrands with
        /// discontinuities, or discontinuities of derivatives, at the price of slightly more evaluations
        /// of the integrand. It can handle oscilatory functions, as long as not too many periods contribute
        /// significantly to the integral. It can integrate logarithmic and mild power-law singularities.</para>
        /// <para>Strong power-law singularities will cause the alrorighm to fail with a <see cref="NonconvergenceException"/>.
        /// This is unavoidable for essentially any double-precision numerical integrator. Consider, for example,
        /// the integrable singularity 1/&#x221A;x. Since
        /// &#x3B5; = &#x222B;<sub>0</sub><sup>&#x3B4;</sup> x<sup>-1/2</sup> dx = 2 &#x3B4;<sup>1/2</sup>,
        /// points within &#x3B4; &#x223C; 10<sup>-16</sup> of the end-points, which as a close as you can get to
        /// a point in double precision without being on top of it, contribute at the &#x3B5; &#x223C; 10<sup>-8</sup>
        /// level to our integral, well beyond limit that nearly-full double precision requires. Said differently,
        /// to know the value of the integral to &#x3B5; &#x223C; 10<sup>-16</sup> prescision, we would need to
        /// evaluate the contributions of points within &#x3B4; &#x223C; 10<sup>-32</sup> of the endpoints,
        /// which is far closer than we can get.</para>
        /// <para>If you need to evaluate an integral with such a strong singularity, make an analytic
        /// change of variable to absorb the singularity before attempting numerical integration. For example,
        /// to evaluate I = &#x222B;<sub>0</sub><sup>b</sup> f(x) x<sup>-1/2</sup> dx, substitute y = x<sup>1/2</sup>
        /// to obtain I = 2 &#x222B;<sub>0</sub><sup>&#x221A;b</sup> f(y<sup>2</sup>) dy.</para>
        /// <para>To do multi-dimensional integrals, use
        /// <see cref="MultiFunctionMath.Integrate(Func{IList{double}, double}, IList{Interval}, IntegrationSettings)"/>.
        /// </para>
        /// </remarks>
        public static IntegrationResult Integrate(Func <double, double> integrand, Interval range, IntegrationSettings settings)
        {
            if (integrand == null)
            {
                throw new ArgumentNullException(nameof(integrand));
            }
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            settings = SetIntegrationDefaults(settings);

            // remap infinite integrals to finite integrals

            if (Double.IsNegativeInfinity(range.LeftEndpoint) && Double.IsPositiveInfinity(range.RightEndpoint))
            {
                // -infinity to +infinity

                // remap to (-pi/2,pi/2)
                Func <double, double> f0 = integrand;
                Func <double, double> f1 = delegate(double t) {
                    double x = Math.Tan(t);
                    return(f0(x) * (1.0 + x * x));
                };
                Interval r1 = Interval.FromEndpoints(-Global.HalfPI, Global.HalfPI);

                return(Integrate(f1, r1, settings));
            }
            else if (Double.IsPositiveInfinity(range.RightEndpoint))
            {
                // finite to +infinity

                // remap to interval (-1,1)
                double a0 = range.LeftEndpoint;
                Func <double, double> f0 = integrand;
                Func <double, double> f1 = delegate(double t) {
                    double q = 1.0 - t;
                    double x = a0 + (1 + t) / q;
                    return(f0(x) * (2.0 / q / q));
                };
                Interval r1 = Interval.FromEndpoints(-1.0, 1.0);

                return(Integrate(f1, r1, settings));
            }
            else if (Double.IsNegativeInfinity(range.LeftEndpoint))
            {
                // -infinity to finite

                // remap to interval (-1,1)
                double b0 = range.RightEndpoint;
                Func <double, double> f0 = integrand;
                Func <double, double> f1 = delegate(double t) {
                    double q = t + 1.0;
                    double x = b0 + (t - 1.0) / q;
                    return(f0(x) * (2.0 / q / q));
                };
                Interval r1 = Interval.FromEndpoints(-1.0, 1.0);

                return(Integrate(f1, r1, settings));
            }

            // normal integral over a finitite range

            IAdaptiveIntegrator integrator = new GaussKronrodIntegrator(integrand, range);
            IntegrationResult   result     = Integrate_Adaptive(integrator, settings);

            return(result);
        }