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); }
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); }
/// <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)); }
internal IntegrationResult(UncertainValue estimate, int evaluationCount, IntegrationSettings settings) : base(evaluationCount) { Debug.Assert(settings != null); this.estimate = estimate; this.settings = settings; }
/// <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)); }
/// <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)); }
// 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)); }
// 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); } }
/// <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 /// ε = ∫<sub>0</sub><sup>δ</sup> x<sup>-1/2</sup> dx = 2 δ<sup>1/2</sup>, /// points within δ ∼ 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 ε ∼ 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 ε ∼ 10<sup>-16</sup> precision, we would need to /// evaluate the contributions of points within δ ∼ 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 = ∫<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 ∫<sub>0</sub><sup>√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/√x. Since /// ε = ∫<sub>0</sub><sup>δ</sup> x<sup>-1/2</sup> dx = 2 δ<sup>1/2</sup>, /// points within δ ∼ 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 ε ∼ 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 ε ∼ 10<sup>-16</sup> prescision, we would need to /// evaluate the contributions of points within δ ∼ 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 = ∫<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 ∫<sub>0</sub><sup>√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); }