private static UncertainValue Integrate_Adaptive(MultiFunctor f, CoordinateTransform[] map, IntegrationRegion r, EvaluationSettings settings)
        {
            // Create an evaluation rule
            GenzMalik75Rule rule = new GenzMalik75Rule(r.Dimension);

            // Use it on the whole region and put the answer in a linked list
            rule.Evaluate(f, map, r);
            LinkedList<IntegrationRegion> regionList = new LinkedList<IntegrationRegion>();
            regionList.AddFirst(r);

            // Iterate until convergence
            while (f.EvaluationCount < settings.EvaluationBudget) {

                // Add up value and errors in all regions.
                // While we are at it, take note of the region with the largest error.
                double value = 0.0;
                double error = 0.0;
                LinkedListNode<IntegrationRegion> regionNode = regionList.First;
                double maxError = 0.0;
                LinkedListNode<IntegrationRegion> maxErrorNode = null;
                while (regionNode != null) {
                    IntegrationRegion region = regionNode.Value;
                    value += region.Value;
                    error += region.Error;
                    //error += MoreMath.Sqr(region.Error);
                    if (region.Error > maxError) {
                        maxError = region.Error;
                        maxErrorNode = regionNode;
                    }
                    regionNode = regionNode.Next;
                }

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

                // Split the region with the largest error, and evaluate each subregion.
                IntegrationRegion maxErrorRegion = maxErrorNode.Value;
                regionList.Remove(maxErrorNode);
                IList<IntegrationRegion> subRegions = maxErrorRegion.Split(maxErrorRegion.SplitIndex);
                /*
                Countdown cnt = new Countdown(2);
                ThreadPool.QueueUserWorkItem((object state) => { rule.Evaluate(f, subRegions[0]); cnt.Signal(); });
                ThreadPool.QueueUserWorkItem((object state) => { rule.Evaluate(f, subRegions[1]); cnt.Signal(); });
                cnt.Wait();
                foreach (IntegrationRegion subRegion in subRegions) {
                    regionList.AddLast(subRegion);
                }
                */

                foreach (IntegrationRegion subRegion in subRegions) {
                    rule.Evaluate(f, map, subRegion);
                    regionList.AddLast(subRegion);
                }

            }

            throw new NonconvergenceException();
        }
Example #2
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));
        }
        public void Evaluate(MultiFunctor f, CoordinateTransform[] map, IntegrationRegion r)
        {
            if (map == null) {
                Evaluate(f, r);
                return;
            }

            // Evaluation at origin. Keep a vector of origin coordinates and jacobian values.
            double[] x0 = new double[d];
            double[] x = new double[d];
            double[] v0 = new double[d];
            double[] v = new double[d];
            for (int i = 0; i < x.Length; i++) {
                x0[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, 0.0), out v0[i]);
                x[i] = x0[i];
                v[i] = v0[i];
            }
            double f0 = Jacobian(v) * f.Evaluate(x);
            double I7 = w70 * f0;
            double I5 = w50 * f0;

            // near off-one-axis evaluations
            double f1 = 0.0;
            for (int i = 0; i < d; i++) {
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a1), out v[i]);
                f1 += Jacobian(v) * f.Evaluate(x);
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a1), out v[i]);
                f1 += Jacobian(v) * f.Evaluate(x);
                x[i] = x0[i];
                v[i] = v0[i];
            }
            I7 += w71 * f1;
            I5 += w51 * f1;

            // far off-one-axis evaluations
            int sIndex = 0; double sMax = 0.0;
            double f2 = 0.0;
            for (int i = 0; i < d; i++) {
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a2), out v[i]);
                double fp2 = Jacobian(v) * f.Evaluate(x);
                f2 += fp2;
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a2), out v[i]);
                double fm2 = Jacobian(v) * f.Evaluate(x);
                f2 += fm2;
                x[i] = x0[i];
                v[i] = v0[i];

                double s = Math.Abs(fm2 + fp2 - 2.0 * f0);
                if (s > sMax) {
                    sMax = s;
                    sIndex = i;
                } else if ((s == sMax) && (r.CoordinateWidth(i) > r.CoordinateWidth(sIndex))) {
                    sIndex = i;
                }
            }
            I7 += w72 * f2;
            I5 += w52 * f2;

            // far off-two-axis evaluations
            double f3 = 0.0;
            for (int i = 0; i < d; i++) {
                for (int j = 0; j < i; j++) {
                    // ++
                    x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a3), out v[i]);
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a3), out v[j]);
                    f3 += Jacobian(v) * f.Evaluate(x);
                    // +-
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, -a3), out v[j]);
                    f3 += Jacobian(v) * f.Evaluate(x);
                    // --
                    x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a3), out v[i]);
                    f3 += Jacobian(v) * f.Evaluate(x);
                    // -+
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a3), out v[j]);
                    f3 += Jacobian(v) * f.Evaluate(x);
                    x[i] = x0[i];
                    x[j] = x0[j];
                    v[i] = v0[i];
                    v[j] = v0[j];
                }
            }
            I7 += w73 * f3;
            I5 += w53 * f3;

            // mid off-all-axis evaluations
            // We need all 2^d permutations of + and - in each position.
            // So that we only need to change one component each time, we proceed in Gray code order.
            // We use a bit-vector to keep track of which component is + (0) and which is - (1).
            int state = 0;
            for (int j = 0; j < d; j++) {
                x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a4), out v[j]);
            }
            double f4 = Jacobian(v) * f.Evaluate(x);
            for (int i = 0; i < (td - 1); i++) {
                int j = GrayFlipIndex(i);
                int mask = 1 << j;
                state = state ^ mask;
                x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, ((state & mask) > 0) ? -a4 : a4), out v[j]);
                f4 += Jacobian(v) * f.Evaluate(x);
            }
            I7 += w74 * f4;

            double V = r.Volume();

            r.Value = V * I7;
            r.Error = V * Math.Abs(I7 - I5);
            r.SplitIndex = sIndex;
        }
        public void Evaluate(MultiFunctor f, IntegrationRegion r)
        {
            // evaluation at origin
            // Keep a vector of origin coordinates, we will often re-set components to them.
            double[] x0 = new double[d];
            double[] x = new double[d];
            for (int i = 0; i < x.Length; i++) {
                x0[i] = r.MapCoordinateFromSymmetricUnitInterval(i, 0.0);
                x[i] = x0[i];
            }
            double f0 = f.Evaluate(x);
            double I7 = w70 * f0;
            double I5 = w50 * f0;

            // near off-one-axis evaluations
            double f1 = 0.0;
            for (int i = 0; i < d; i++) {
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a1);
                f1 += f.Evaluate(x);
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a1);
                f1 += f.Evaluate(x);
                x[i] = x0[i];
            }
            I7 += w71 * f1;
            I5 += w51 * f1;

            // far off-one-axis evaluations
            // while doing this, determine along which direction we will split, if asked
            int sIndex = 0; double sMax = 0.0;
            double f2 = 0.0;
            for (int i = 0; i < d; i++) {
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a2);
                double fp2 = f.Evaluate(x);
                f2 += fp2;
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a2);
                double fm2 = f.Evaluate(x);
                f2 += fm2;
                x[i] = x0[i];
                // One way to view fm2 + fp2 - 2 f0 is as numerical 2nd derivative
                // Another way is as difference of f0 from value predicted by linear interpolation from fm2 and fp2
                double s = Math.Abs(fm2 + fp2 - 2.0 * f0);
                if (s > sMax) {
                    // Use the direction of largest difference for future splits.
                    sMax = s;
                    sIndex = i;
                } else if ((s == sMax) && (r.CoordinateWidth(i) > r.CoordinateWidth(sIndex))) {
                    // If two directions both have the same difference, pick the one with the larger width to split.
                    // This may seem like an unimportant corner case, but it turns out to be critical to convergence.
                    // Without this clause we get into cycles in which we split along the same direction forever.
                    // For example, given the 3D watson integral of [1 - \cos(x) \cos(y) \cos(z)]^{-1} over [0,\pi]^3,
                    // all directions have zero deficit from starting point because one of the cosines is always zero,
                    // and without this branch we split along the x-axis forever.
                    sIndex = i;
                }
            }
            I7 += w72 * f2;
            I5 += w52 * f2;
            r.SplitIndex = sIndex;

            // far off-two-axis evaluations
            double f3 = 0.0;
            for (int i = 0; i < d; i++) {
                for (int j = 0; j < i; j++) {
                    // ++
                    x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a3);
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a3);
                    f3 += f.Evaluate(x);
                    // +-
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, -a3);
                    f3 += f.Evaluate(x);
                    // --
                    x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a3);
                    f3 += f.Evaluate(x);
                    // -+
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a3);
                    f3 += f.Evaluate(x);
                    x[i] = x0[i];
                    x[j] = x0[j];
                }
            }
            I7 += w73 * f3;
            I5 += w53 * f3;

            // mid off-all-axis evaluations
            // We need all 2^d permutations of + and - in each position.
            // So that we only need to change one component each time, we proceed in Gray code order.
            // We use a bit-vector to keep track of which component is + (0) and which is - (1).
            int state = 0;
            for (int j = 0; j < d; j++) {
                x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a4);
            }
            double f4 = f.Evaluate(x);
            for (int i = 0; i < (td - 1); i++) {
                int j = GrayFlipIndex(i);
                int mask = 1 << j;
                state = state ^ mask;
                x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, ((state & mask) > 0) ? -a4 : a4);
                f4 += f.Evaluate(x);
            }
            I7 += w74 * f4;

            double V = r.Volume();

            r.Value = V * I7;
            r.Error = V * Math.Abs(I7 - I5);
        }
        public void Evaluate(MultiFunctor f, CoordinateTransform[] map, IntegrationRegion r)
        {
            if (map == null)
            {
                Evaluate(f, r);
                return;
            }

            // Evaluation at origin. Keep a vector of origin coordinates and jacobian values.
            double[] x0 = new double[d];
            double[] x  = new double[d];
            double[] v0 = new double[d];
            double[] v  = new double[d];
            for (int i = 0; i < x.Length; i++)
            {
                x0[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, 0.0), out v0[i]);
                x[i]  = x0[i];
                v[i]  = v0[i];
            }
            double f0 = Jacobian(v) * f.Evaluate(x);
            double I7 = w70 * f0;
            double I5 = w50 * f0;

            // near off-one-axis evaluations
            double f1 = 0.0;

            for (int i = 0; i < d; i++)
            {
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a1), out v[i]);
                f1  += Jacobian(v) * f.Evaluate(x);
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a1), out v[i]);
                f1  += Jacobian(v) * f.Evaluate(x);
                x[i] = x0[i];
                v[i] = v0[i];
            }
            I7 += w71 * f1;
            I5 += w51 * f1;

            // far off-one-axis evaluations
            int    sIndex = 0; double sMax = 0.0;
            double f2 = 0.0;

            for (int i = 0; i < d; i++)
            {
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a2), out v[i]);
                double fp2 = Jacobian(v) * f.Evaluate(x);
                f2  += fp2;
                x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a2), out v[i]);
                double fm2 = Jacobian(v) * f.Evaluate(x);
                f2  += fm2;
                x[i] = x0[i];
                v[i] = v0[i];

                double s = Math.Abs(fm2 + fp2 - 2.0 * f0);
                if (s > sMax)
                {
                    sMax   = s;
                    sIndex = i;
                }
                else if ((s == sMax) && (r.CoordinateWidth(i) > r.CoordinateWidth(sIndex)))
                {
                    sIndex = i;
                }
            }
            I7 += w72 * f2;
            I5 += w52 * f2;

            // far off-two-axis evaluations
            double f3 = 0.0;

            for (int i = 0; i < d; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    // ++
                    x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, a3), out v[i]);
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a3), out v[j]);
                    f3  += Jacobian(v) * f.Evaluate(x);
                    // +-
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, -a3), out v[j]);
                    f3  += Jacobian(v) * f.Evaluate(x);
                    // --
                    x[i] = map[i].Transform(r.MapCoordinateFromSymmetricUnitInterval(i, -a3), out v[i]);
                    f3  += Jacobian(v) * f.Evaluate(x);
                    // -+
                    x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a3), out v[j]);
                    f3  += Jacobian(v) * f.Evaluate(x);
                    x[i] = x0[i];
                    x[j] = x0[j];
                    v[i] = v0[i];
                    v[j] = v0[j];
                }
            }
            I7 += w73 * f3;
            I5 += w53 * f3;

            // mid off-all-axis evaluations
            // We need all 2^d permutations of + and - in each position.
            // So that we only need to change one component each time, we proceed in Gray code order.
            // We use a bit-vector to keep track of which component is + (0) and which is - (1).
            int state = 0;

            for (int j = 0; j < d; j++)
            {
                x[j] = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, a4), out v[j]);
            }
            double f4 = Jacobian(v) * f.Evaluate(x);

            for (int i = 0; i < (td - 1); i++)
            {
                int j    = GrayFlipIndex(i);
                int mask = 1 << j;
                state = state ^ mask;
                x[j]  = map[j].Transform(r.MapCoordinateFromSymmetricUnitInterval(j, ((state & mask) > 0) ? -a4 : a4), out v[j]);
                f4   += Jacobian(v) * f.Evaluate(x);
            }
            I7 += w74 * f4;

            double V = r.Volume();

            r.Value      = V * I7;
            r.Error      = V * Math.Abs(I7 - I5);
            r.SplitIndex = sIndex;
        }
        public void Evaluate(MultiFunctor f, IntegrationRegion r)
        {
            // evaluation at origin
            // Keep a vector of origin coordinates, we will often re-set components to them.
            double[] x0 = new double[d];
            double[] x  = new double[d];
            for (int i = 0; i < x.Length; i++)
            {
                x0[i] = r.MapCoordinateFromSymmetricUnitInterval(i, 0.0);
                x[i]  = x0[i];
            }
            double f0 = f.Evaluate(x);
            double I7 = w70 * f0;
            double I5 = w50 * f0;

            // near off-one-axis evaluations
            double f1 = 0.0;

            for (int i = 0; i < d; i++)
            {
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a1);
                f1  += f.Evaluate(x);
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a1);
                f1  += f.Evaluate(x);
                x[i] = x0[i];
            }
            I7 += w71 * f1;
            I5 += w51 * f1;

            // far off-one-axis evaluations
            // while doing this, determine along which direction we will split, if asked
            int    sIndex = 0; double sMax = 0.0;
            double f2 = 0.0;

            for (int i = 0; i < d; i++)
            {
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a2);
                double fp2 = f.Evaluate(x);
                f2  += fp2;
                x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a2);
                double fm2 = f.Evaluate(x);
                f2  += fm2;
                x[i] = x0[i];
                // One way to view fm2 + fp2 - 2 f0 is as numerical 2nd derivative
                // Another way is as difference of f0 from value predicted by linear interpolation from fm2 and fp2
                double s = Math.Abs(fm2 + fp2 - 2.0 * f0);
                if (s > sMax)
                {
                    // Use the direction of largest difference for future splits.
                    sMax   = s;
                    sIndex = i;
                }
                else if ((s == sMax) && (r.CoordinateWidth(i) > r.CoordinateWidth(sIndex)))
                {
                    // If two directions both have the same difference, pick the one with the larger width to split.
                    // This may seem like an unimportant corner case, but it turns out to be critical to convergence.
                    // Without this clause we get into cycles in which we split along the same direction forever.
                    // For example, given the 3D watson integral of [1 - \cos(x) \cos(y) \cos(z)]^{-1} over [0,\pi]^3,
                    // all directions have zero deficit from starting point because one of the cosines is always zero,
                    // and without this branch we split along the x-axis forever.
                    sIndex = i;
                }
            }
            I7          += w72 * f2;
            I5          += w52 * f2;
            r.SplitIndex = sIndex;

            // far off-two-axis evaluations
            double f3 = 0.0;

            for (int i = 0; i < d; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    // ++
                    x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, a3);
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a3);
                    f3  += f.Evaluate(x);
                    // +-
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, -a3);
                    f3  += f.Evaluate(x);
                    // --
                    x[i] = r.MapCoordinateFromSymmetricUnitInterval(i, -a3);
                    f3  += f.Evaluate(x);
                    // -+
                    x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a3);
                    f3  += f.Evaluate(x);
                    x[i] = x0[i];
                    x[j] = x0[j];
                }
            }
            I7 += w73 * f3;
            I5 += w53 * f3;

            // mid off-all-axis evaluations
            // We need all 2^d permutations of + and - in each position.
            // So that we only need to change one component each time, we proceed in Gray code order.
            // We use a bit-vector to keep track of which component is + (0) and which is - (1).
            int state = 0;

            for (int j = 0; j < d; j++)
            {
                x[j] = r.MapCoordinateFromSymmetricUnitInterval(j, a4);
            }
            double f4 = f.Evaluate(x);

            for (int i = 0; i < (td - 1); i++)
            {
                int j    = GrayFlipIndex(i);
                int mask = 1 << j;
                state = state ^ mask;
                x[j]  = r.MapCoordinateFromSymmetricUnitInterval(j, ((state & mask) > 0) ? -a4 : a4);
                f4   += f.Evaluate(x);
            }
            I7 += w74 * f4;

            double V = r.Volume();

            r.Value = V * I7;
            r.Error = V * Math.Abs(I7 - I5);
        }
        private static UncertainValue Integrate_Adaptive(MultiFunctor f, CoordinateTransform[] map, IntegrationRegion r, EvaluationSettings settings)
        {
            // Create an evaluation rule
            GenzMalik75Rule rule = new GenzMalik75Rule(r.Dimension);

            // Use it on the whole region and put the answer in a linked list
            rule.Evaluate(f, map, r);
            LinkedList <IntegrationRegion> regionList = new LinkedList <IntegrationRegion>();

            regionList.AddFirst(r);

            // Iterate until convergence
            while (f.EvaluationCount < settings.EvaluationBudget)
            {
                // Add up value and errors in all regions.
                // While we are at it, take note of the region with the largest error.
                double value = 0.0;
                double error = 0.0;
                LinkedListNode <IntegrationRegion> regionNode = regionList.First;
                double maxError = 0.0;
                LinkedListNode <IntegrationRegion> maxErrorNode = null;
                while (regionNode != null)
                {
                    IntegrationRegion region = regionNode.Value;
                    value += region.Value;
                    error += region.Error;
                    //error += MoreMath.Sqr(region.Error);
                    if (region.Error > maxError)
                    {
                        maxError     = region.Error;
                        maxErrorNode = regionNode;
                    }
                    regionNode = regionNode.Next;
                }

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

                // Split the region with the largest error, and evaluate each subregion.
                IntegrationRegion maxErrorRegion = maxErrorNode.Value;
                regionList.Remove(maxErrorNode);
                IList <IntegrationRegion> subRegions = maxErrorRegion.Split(maxErrorRegion.SplitIndex);

                /*
                 * Countdown cnt = new Countdown(2);
                 * ThreadPool.QueueUserWorkItem((object state) => { rule.Evaluate(f, subRegions[0]); cnt.Signal(); });
                 * ThreadPool.QueueUserWorkItem((object state) => { rule.Evaluate(f, subRegions[1]); cnt.Signal(); });
                 * cnt.Wait();
                 * foreach (IntegrationRegion subRegion in subRegions) {
                 *  regionList.AddLast(subRegion);
                 * }
                 */

                foreach (IntegrationRegion subRegion in subRegions)
                {
                    rule.Evaluate(f, map, subRegion);
                    regionList.AddLast(subRegion);
                }
            }

            throw new NonconvergenceException();
        }