View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.numbers.examples.jmh.gamma;
19  
20  import java.util.SplittableRandom;
21  import java.util.concurrent.ThreadLocalRandom;
22  import java.util.concurrent.TimeUnit;
23  import java.util.function.DoubleSupplier;
24  import java.util.function.DoubleUnaryOperator;
25  import java.util.function.Supplier;
26  import java.util.stream.DoubleStream;
27  import org.apache.commons.numbers.core.Precision;
28  import org.apache.commons.numbers.fraction.ContinuedFraction;
29  import org.apache.commons.numbers.gamma.Erf;
30  import org.apache.commons.numbers.gamma.Erfc;
31  import org.apache.commons.numbers.gamma.InverseErf;
32  import org.apache.commons.numbers.gamma.InverseErfc;
33  import org.apache.commons.numbers.gamma.LogGamma;
34  import org.openjdk.jmh.annotations.Benchmark;
35  import org.openjdk.jmh.annotations.BenchmarkMode;
36  import org.openjdk.jmh.annotations.Fork;
37  import org.openjdk.jmh.annotations.Measurement;
38  import org.openjdk.jmh.annotations.Mode;
39  import org.openjdk.jmh.annotations.OutputTimeUnit;
40  import org.openjdk.jmh.annotations.Param;
41  import org.openjdk.jmh.annotations.Scope;
42  import org.openjdk.jmh.annotations.Setup;
43  import org.openjdk.jmh.annotations.State;
44  import org.openjdk.jmh.annotations.Warmup;
45  import org.openjdk.jmh.infra.Blackhole;
46  
47  /**
48   * Executes a benchmark to estimate the speed of error function operations.
49   */
50  @BenchmarkMode(Mode.AverageTime)
51  @OutputTimeUnit(TimeUnit.NANOSECONDS)
52  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
53  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
54  @State(Scope.Benchmark)
55  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
56  public class ErfPerformance {
57      /** The threshold value for returning the extreme value. */
58      private static final double EXTREME_VALUE_BOUND = 40;
59      /** Commons Numbers 1.0 implementation. */
60      private static final String IMP_NUMBERS_1_0 = "Numbers 1.0";
61      /** Commons Numbers 1.1 implementation. */
62      private static final String IMP_NUMBERS_1_1 = "Boost";
63      /** Uniform numbers in the appropriate domain of the function. */
64      private static final String NUM_UNIFORM = "uniform";
65      /** Uniform numbers in the domain of the error function result, [1, 1] or [0, 2]. */
66      private static final String NUM_INVERSE_UNIFORM = "inverse uniform";
67      /** Message prefix for an unknown parameter. */
68      private static final String UNKNOWN = "unknown parameter: ";
69      /** Message prefix for a erf domain error. */
70      private static final String ERF_DOMAIN_ERROR = "erf domain error: ";
71      /** Message prefix for a erf domain error. */
72      private static final String ERFC_DOMAIN_ERROR = "erfc domain error: ";
73  
74      /**
75       * The seed for random number generation. Ensures the same numbers are generated
76       * for each implementation of the function.
77       *
78       * <p>Note: Numbers will not be the same for the error function and complementary error
79       * function. The domain is shifted from [-1, 1] to [0, 2]. However number generation
80       * may wish to target specific regions of the domain where there is limited precision
81       * as {@code |x| -> 1} or high precision as {@code |x| -> 0}.
82       */
83      private static final long SEED = ThreadLocalRandom.current().nextLong();
84  
85      /**
86       * Contains an array of numbers.
87       */
88      public abstract static class NumberData {
89          /** The size of the data. */
90          @Param({"1000"})
91          private int size;
92  
93          /** The numbers. */
94          private double[] numbers;
95  
96          /**
97           * Gets the size of the array of numbers.
98           *
99           * @return the size
100          */
101         public int getSize() {
102             return size;
103         }
104 
105         /**
106          * Gets the numbers.
107          *
108          * @return the numbers
109          */
110         public double[] getNumbers() {
111             return numbers;
112         }
113 
114         /**
115          * Create the numbers.
116          */
117         @Setup
118         public void setup() {
119             numbers = createNumbers(new SplittableRandom(SEED));
120         }
121 
122         /**
123          * Creates the numbers.
124          *
125          * @param rng Random number generator.
126          * @return the random numbers
127          * @see #getSize()
128          */
129         protected abstract double[] createNumbers(SplittableRandom rng);
130     }
131 
132     /**
133      * Contains an array of numbers. This is used to test the JMH overhead for calling
134      * a function on each number in the array.
135      */
136     @State(Scope.Benchmark)
137     public static class BaseData extends NumberData {
138         /** {@inheritDoc} */
139         @Override
140         protected double[] createNumbers(SplittableRandom rng) {
141             return rng.doubles().limit(getSize()).toArray();
142         }
143     }
144 
145     /**
146      * Contains an array of numbers and the method to compute the error function.
147      */
148     public abstract static class FunctionData extends NumberData {
149 
150         /** The function. */
151         private DoubleUnaryOperator function;
152 
153         /**
154          * The implementation of the function.
155          */
156         @Param({IMP_NUMBERS_1_0, IMP_NUMBERS_1_1})
157         private String implementation;
158 
159         /**
160          * Gets the implementation.
161          *
162          * @return the implementation
163          */
164         public String getImplementation() {
165             return implementation;
166         }
167 
168         /**
169          * Gets the function.
170          *
171          * @return the function
172          */
173         public DoubleUnaryOperator getFunction() {
174             return function;
175         }
176 
177         /**
178          * Create the numbers and the function.
179          */
180         @Override
181         @Setup
182         public void setup() {
183             super.setup();
184             function = createFunction();
185             verify();
186         }
187 
188         /**
189          * Creates the function from the implementation name.
190          *
191          * @return the inverse error function
192          * @see #getImplementation()
193          */
194         protected abstract DoubleUnaryOperator createFunction();
195 
196         /**
197          * Verify the numbers for the function. This is called after the numbers and function
198          * have been created.
199          *
200          * @see #getNumbers()
201          * @see #getFunction()
202          */
203         protected abstract void verify();
204     }
205 
206     /**
207      * Contains an array of numbers in the range for the error function.
208      */
209     @State(Scope.Benchmark)
210     public static class ErfData extends FunctionData {
211         /** The type of the data. */
212         @Param({NUM_UNIFORM, NUM_INVERSE_UNIFORM})
213         private String type;
214 
215         /** {@inheritDoc} */
216         @Override
217         protected double[] createNumbers(SplittableRandom rng) {
218             DoubleSupplier generator;
219             if (NUM_INVERSE_UNIFORM.equals(type)) {
220                 // p range: [-1, 1)
221                 // The final value is generated using the inverse erf function.
222                 generator = () -> InverseErf.value(makeSignedDouble(rng));
223             } else if (NUM_UNIFORM.equals(type)) {
224                 // range [-6, 6)
225                 // Note: Values are not distinguishable from +/-1 when |x| > 6
226                 generator = () -> makeSignedDouble(rng) * 6;
227             } else {
228                 throw new IllegalStateException(UNKNOWN + type);
229             }
230             return DoubleStream.generate(generator).limit(getSize()).toArray();
231         }
232 
233         /** {@inheritDoc} */
234         @Override
235         protected DoubleUnaryOperator createFunction() {
236             final String impl = getImplementation();
237             if (IMP_NUMBERS_1_0.equals(impl)) {
238                 return ErfPerformance::erf;
239             } else if (IMP_NUMBERS_1_1.equals(impl)) {
240                 return Erf::value;
241             } else {
242                 throw new IllegalStateException(UNKNOWN + impl);
243             }
244         }
245 
246         /** {@inheritDoc} */
247         @Override
248         protected void verify() {
249             final DoubleUnaryOperator function = getFunction();
250             final double relativeEps = 1e-6;
251             for (final double x : getNumbers()) {
252                 final double p = function.applyAsDouble(x);
253                 assert -1 <= p & p <= 1 : ERF_DOMAIN_ERROR + p;
254 
255                 // Implementations may not compute a round-trip
256                 // to a suitable accuracy as:
257                 // |p| -> 0 : x -> 0
258                 // |p| -> 1 : x -> +/-big
259                 if (p < 1e-10 || Math.abs(p - 1) < 1e-10) {
260                     continue;
261                 }
262                 assertEquals(x, InverseErf.value(p), Math.abs(x) * relativeEps,
263                     () -> getImplementation() + " inverse erf " + p);
264             }
265         }
266     }
267 
268     /**
269      * Contains an array of numbers in the range for the complementary error function.
270      */
271     @State(Scope.Benchmark)
272     public static class ErfcData extends FunctionData {
273         /** The type of the data. */
274         @Param({NUM_UNIFORM, NUM_INVERSE_UNIFORM})
275         private String type;
276 
277         /** {@inheritDoc} */
278         @Override
279         protected double[] createNumbers(SplittableRandom rng) {
280             DoubleSupplier generator;
281             if (NUM_INVERSE_UNIFORM.equals(type)) {
282                 // q range: [0, 2)
283                 // The final value is generated using the inverse erfc function.
284                 generator = () -> InverseErfc.value(rng.nextDouble() * 2);
285             } else if (NUM_UNIFORM.equals(type)) {
286                 // range [-6, 28)
287                 // Note: Values are not distinguishable from 2 when x < -6
288                 // Shift the range [-17, 17) to [-6, 28)
289                 generator = () -> makeSignedDouble(rng) * 17 + 11;
290             } else {
291                 throw new IllegalStateException(UNKNOWN + type);
292             }
293             return DoubleStream.generate(generator).limit(getSize()).toArray();
294         }
295 
296         /** {@inheritDoc} */
297         @Override
298         protected DoubleUnaryOperator createFunction() {
299             final String impl = getImplementation();
300             if (IMP_NUMBERS_1_0.equals(impl)) {
301                 return ErfPerformance::erfc;
302             } else if (IMP_NUMBERS_1_1.equals(impl)) {
303                 return Erfc::value;
304             } else {
305                 throw new IllegalStateException(UNKNOWN + impl);
306             }
307         }
308 
309         /** {@inheritDoc} */
310         @Override
311         protected void verify() {
312             final DoubleUnaryOperator function = getFunction();
313             final double relativeEps = 1e-6;
314             for (final double x : getNumbers()) {
315                 final double q = function.applyAsDouble(x);
316                 assert 0 <= q && q <= 2 : ERFC_DOMAIN_ERROR + q;
317 
318                 // Implementations may not compute a round-trip
319                 // to a suitable accuracy as:
320                 // q -> 0 : x -> big
321                 // |q| -> 1 : x -> 0
322                 // q -> 2 : x -> -big
323                 if (q < 1e-10 || Math.abs(q - 1) < 1e-10 || q > 2 - 1e-10) {
324                     continue;
325                 }
326                 assertEquals(x, InverseErfc.value(q), Math.abs(x) * relativeEps,
327                     () -> getImplementation() + " inverse erfc " + q);
328             }
329         }
330     }
331 
332     /**
333      * Contains an array of numbers in the range [-1, 1] for the inverse error function.
334      */
335     @State(Scope.Benchmark)
336     public static class InverseErfData extends FunctionData {
337         /**
338          * The type of the data.
339          */
340         @Param({NUM_UNIFORM})
341         private String type;
342 
343         /** {@inheritDoc} */
344         @Override
345         protected double[] createNumbers(SplittableRandom rng) {
346             DoubleSupplier generator;
347             if (NUM_UNIFORM.equals(type)) {
348                 // range [-1, 1)
349                 generator = () -> makeSignedDouble(rng);
350             } else {
351                 throw new IllegalStateException(UNKNOWN + type);
352             }
353             return DoubleStream.generate(generator).limit(getSize()).toArray();
354         }
355 
356         /** {@inheritDoc} */
357         @Override
358         protected DoubleUnaryOperator createFunction() {
359             final String impl = getImplementation();
360             if (IMP_NUMBERS_1_0.equals(impl)) {
361                 return ErfPerformance::inverseErf;
362             } else if (IMP_NUMBERS_1_1.equals(impl)) {
363                 return InverseErf::value;
364             } else {
365                 throw new IllegalStateException(UNKNOWN + impl);
366             }
367         }
368 
369         /** {@inheritDoc} */
370         @Override
371         protected void verify() {
372             final DoubleUnaryOperator function = getFunction();
373             final double relativeEps = 1e-12;
374             for (final double x : getNumbers()) {
375                 assert -1 <= x && x <= 1 : ERF_DOMAIN_ERROR + x;
376 
377                 // Implementations may not compute a round-trip
378                 // to a suitable accuracy as:
379                 // |x| -> 0 : t -> 0
380                 // |x| -> 1 : t -> +/-big
381                 if (x < 1e-10 || Math.abs(x - 1) < 1e-10) {
382                     continue;
383                 }
384                 final double t = function.applyAsDouble(x);
385                 assertEquals(x, Erf.value(t), Math.abs(x) * relativeEps,
386                     () -> getImplementation() + " erf " + t);
387             }
388         }
389     }
390 
391     /**
392      * Contains an array of numbers in the range [0, 2] for the inverse complementary error function.
393      */
394     @State(Scope.Benchmark)
395     public static class InverseErfcData extends FunctionData {
396         /**
397          * The type of the data.
398          */
399         @Param({NUM_UNIFORM})
400         private String type;
401 
402         /** {@inheritDoc} */
403         @Override
404         protected double[] createNumbers(SplittableRandom rng) {
405             DoubleSupplier generator;
406             if (NUM_UNIFORM.equals(type)) {
407                 // range [0, 2)
408                 generator = () -> rng.nextDouble() * 2;
409             } else {
410                 throw new IllegalStateException(UNKNOWN + type);
411             }
412             return DoubleStream.generate(generator).limit(getSize()).toArray();
413         }
414 
415         /** {@inheritDoc} */
416         @Override
417         protected DoubleUnaryOperator createFunction() {
418             final String impl = getImplementation();
419             if (IMP_NUMBERS_1_0.equals(impl)) {
420                 return ErfPerformance::inverseErfc;
421             } else if (IMP_NUMBERS_1_1.equals(impl)) {
422                 return InverseErfc::value;
423             } else {
424                 throw new IllegalStateException(UNKNOWN + impl);
425             }
426         }
427 
428         /** {@inheritDoc} */
429         @Override
430         protected void verify() {
431             final DoubleUnaryOperator function = getFunction();
432             final double relativeEps = 1e-12;
433             for (final double x : getNumbers()) {
434                 assert 0 <= x && x <= 2 : ERFC_DOMAIN_ERROR + x;
435 
436                 // Implementations may not compute a round-trip
437                 // to a suitable accuracy as:
438                 // x -> 0 : t -> big
439                 // |x| -> 1 : t -> 0
440                 // x -> 2 : t -> -big
441                 if (x < 1e-10 || Math.abs(x - 1) < 1e-10 || x > 2 - 1e-10) {
442                     continue;
443                 }
444                 final double t = function.applyAsDouble(x);
445                 assertEquals(x, Erfc.value(t), Math.abs(x) * relativeEps,
446                     () -> getImplementation() + " erfc " + t);
447             }
448         }
449     }
450 
451     /**
452      * Make a signed double in the range [-1, 1).
453      *
454      * @param rng Random generator
455      * @return u in [-1, 1)
456      */
457     private static double makeSignedDouble(SplittableRandom rng) {
458         // As per o.a.c.rng.core.utils.NumberFactory.makeDouble(long) but using a signed
459         // shift of 10 in place of an unsigned shift of 11.
460         // Use the upper 54 bits on the assumption they are more random.
461         // The sign bit is maintained by the signed shift.
462         // The next 53 bits generates a magnitude in the range [0, 2^53) or [-2^53, 0).
463         return (rng.nextLong() >> 10) * 0x1.0p-53;
464     }
465 
466     /**
467      * Returns the inverse complementary error function.
468      *
469      * <p>This is the implementation in Commons Numbers 1.0.
470      *
471      * @param x Value in [0, 2].
472      * @return t such that {@code x = erfc(t)}
473      */
474     private static double inverseErfc(final double x) {
475         return inverseErf(1 - x);
476     }
477 
478     /**
479      * Returns the inverse error function.
480      *
481      * <p>This implementation is described in the paper:
482      * <a href="http://people.maths.ox.ac.uk/gilesm/files/gems_erfinv.pdf">Approximating
483      * the erfinv function</a> by Mike Giles, Oxford-Man Institute of Quantitative Finance,
484      * which was published in GPU Computing Gems, volume 2, 2010.
485      * The source code is available <a href="http://gpucomputing.net/?q=node/1828">here</a>.
486      * </p>
487      *
488      * <p>This is the implementation in Commons Numbers 1.0.
489      *
490      * @param x Value in [-1, 1].
491      * @return t such that {@code x = erf(t)}
492      */
493     private static double inverseErf(final double x) {
494         // Beware that the logarithm argument must be
495         // computed as (1 - x) * (1 + x),
496         // it must NOT be simplified as 1 - x * x as this
497         // would induce rounding errors near the boundaries +/-1
498         double w = -Math.log((1 - x) * (1 + x));
499         double p;
500 
501         if (w < 6.25) {
502             w -= 3.125;
503             p =  -3.6444120640178196996e-21;
504             p =   -1.685059138182016589e-19 + p * w;
505             p =   1.2858480715256400167e-18 + p * w;
506             p =    1.115787767802518096e-17 + p * w;
507             p =   -1.333171662854620906e-16 + p * w;
508             p =   2.0972767875968561637e-17 + p * w;
509             p =   6.6376381343583238325e-15 + p * w;
510             p =  -4.0545662729752068639e-14 + p * w;
511             p =  -8.1519341976054721522e-14 + p * w;
512             p =   2.6335093153082322977e-12 + p * w;
513             p =  -1.2975133253453532498e-11 + p * w;
514             p =  -5.4154120542946279317e-11 + p * w;
515             p =    1.051212273321532285e-09 + p * w;
516             p =  -4.1126339803469836976e-09 + p * w;
517             p =  -2.9070369957882005086e-08 + p * w;
518             p =   4.2347877827932403518e-07 + p * w;
519             p =  -1.3654692000834678645e-06 + p * w;
520             p =  -1.3882523362786468719e-05 + p * w;
521             p =    0.0001867342080340571352 + p * w;
522             p =  -0.00074070253416626697512 + p * w;
523             p =   -0.0060336708714301490533 + p * w;
524             p =      0.24015818242558961693 + p * w;
525             p =       1.6536545626831027356 + p * w;
526         } else if (w < 16.0) {
527             w = Math.sqrt(w) - 3.25;
528             p =   2.2137376921775787049e-09;
529             p =   9.0756561938885390979e-08 + p * w;
530             p =  -2.7517406297064545428e-07 + p * w;
531             p =   1.8239629214389227755e-08 + p * w;
532             p =   1.5027403968909827627e-06 + p * w;
533             p =   -4.013867526981545969e-06 + p * w;
534             p =   2.9234449089955446044e-06 + p * w;
535             p =   1.2475304481671778723e-05 + p * w;
536             p =  -4.7318229009055733981e-05 + p * w;
537             p =   6.8284851459573175448e-05 + p * w;
538             p =   2.4031110387097893999e-05 + p * w;
539             p =   -0.0003550375203628474796 + p * w;
540             p =   0.00095328937973738049703 + p * w;
541             p =   -0.0016882755560235047313 + p * w;
542             p =    0.0024914420961078508066 + p * w;
543             p =   -0.0037512085075692412107 + p * w;
544             p =     0.005370914553590063617 + p * w;
545             p =       1.0052589676941592334 + p * w;
546             p =       3.0838856104922207635 + p * w;
547         } else if (w < Double.POSITIVE_INFINITY) {
548             w = Math.sqrt(w) - 5;
549             p =  -2.7109920616438573243e-11;
550             p =  -2.5556418169965252055e-10 + p * w;
551             p =   1.5076572693500548083e-09 + p * w;
552             p =  -3.7894654401267369937e-09 + p * w;
553             p =   7.6157012080783393804e-09 + p * w;
554             p =  -1.4960026627149240478e-08 + p * w;
555             p =   2.9147953450901080826e-08 + p * w;
556             p =  -6.7711997758452339498e-08 + p * w;
557             p =   2.2900482228026654717e-07 + p * w;
558             p =  -9.9298272942317002539e-07 + p * w;
559             p =   4.5260625972231537039e-06 + p * w;
560             p =  -1.9681778105531670567e-05 + p * w;
561             p =   7.5995277030017761139e-05 + p * w;
562             p =  -0.00021503011930044477347 + p * w;
563             p =  -0.00013871931833623122026 + p * w;
564             p =       1.0103004648645343977 + p * w;
565             p =       4.8499064014085844221 + p * w;
566         } else if (w == Double.POSITIVE_INFINITY) {
567             // this branch does not appears in the original code, it
568             // was added because the previous branch does not handle
569             // x = +/-1 correctly. In this case, w is positive infinity
570             // and as the first coefficient (-2.71e-11) is negative.
571             // Once the first multiplication is done, p becomes negative
572             // infinity and remains so throughout the polynomial evaluation.
573             // So the branch above incorrectly returns negative infinity
574             // instead of the correct positive infinity.
575             p = Double.POSITIVE_INFINITY;
576         } else {
577             // this branch does not appears in the original code, it
578             // occurs when the input is NaN or not in the range [-1, 1].
579             return Double.NaN;
580         }
581 
582         return p * x;
583     }
584 
585     /**
586      * Returns the complementary error function.
587      *
588      * <p>This implementation computes erfc(x) using the
589      * {@link RegularizedGamma.Q#value(double, double, double, int) regularized gamma function},
590      * following <a href="http://mathworld.wolfram.com/Erf.html">Erf</a>, equation (3).
591      *
592      * <p>This is the implementation in Commons Numbers 1.0.
593      *
594      * @param x Value in [0, 2].
595      * @return t such that {@code x = erfc(t)}
596      */
597     private static double erfc(final double x) {
598         if (Math.abs(x) > EXTREME_VALUE_BOUND) {
599             return x > 0 ? 0 : 2;
600         }
601         final double ret = RegularizedGamma.Q.value(0.5, x * x, 1e-15, 10000);
602         return x < 0 ? 2 - ret : ret;
603     }
604 
605     /**
606      * Returns the error function.
607      *
608      * <p>This implementation computes erf(x) using the
609      * {@link RegularizedGamma.P#value(double, double, double, int) regularized gamma function},
610      * following <a href="http://mathworld.wolfram.com/Erf.html"> Erf</a>, equation (3)
611      *
612      * <p>This is the implementation in Commons Numbers 1.0.
613      *
614      * @param x Value in [-1, 1].
615      * @return t such that {@code x = erf(t)}
616      */
617     private static double erf(final double x) {
618         if (Math.abs(x) > EXTREME_VALUE_BOUND) {
619             return x > 0 ? 1 : -1;
620         }
621         final double ret = RegularizedGamma.P.value(0.5, x * x, 1e-15, 10000);
622         return x < 0 ? -ret : ret;
623     }
624 
625     /**
626      * <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
627      * Regularized Gamma functions</a>.
628      *
629      * <p>This is the Commons Numbers 1.0 implementation. Later versions of
630      * RegularizedGamma changed to compute using more than the continued fraction
631      * representation (Q) or lower gamma series representation (P).
632      *
633      * <p>The ContinuedFraction and LogGamma class use the current version
634      * and are not preserved from Commons Numbers 1.0.
635      */
636     private static final class RegularizedGamma {
637         /** Private constructor. */
638         private RegularizedGamma() {
639             // intentionally empty.
640         }
641 
642         /**
643          * \( P(a, x) \) <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
644          * regularized Gamma function</a>.
645          *
646          * Class is immutable.
647          */
648         static final class P {
649             /** Prevent instantiation. */
650             private P() {}
651 
652             /**
653              * Computes the regularized gamma function \( P(a, x) \).
654              *
655              * The implementation of this method is based on:
656              * <ul>
657              *  <li>
658              *   <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
659              *   Regularized Gamma Function</a>, equation (1)
660              *  </li>
661              *  <li>
662              *   <a href="http://mathworld.wolfram.com/IncompleteGammaFunction.html">
663              *   Incomplete Gamma Function</a>, equation (4).
664              *  </li>
665              *  <li>
666              *   <a href="http://mathworld.wolfram.com/ConfluentHypergeometricFunctionoftheFirstKind.html">
667              *   Confluent Hypergeometric Function of the First Kind</a>, equation (1).
668              *  </li>
669              * </ul>
670              *
671              * @param a Argument.
672              * @param x Argument.
673              * @param epsilon Tolerance in continued fraction evaluation.
674              * @param maxIterations Maximum number of iterations in continued fraction evaluation.
675              * @return \( P(a, x) \).
676              * @throws ArithmeticException if the continued fraction fails to converge.
677              */
678             static double value(double a,
679                                 double x,
680                                 double epsilon,
681                                 int maxIterations) {
682                 if (Double.isNaN(a) ||
683                     Double.isNaN(x) ||
684                     a <= 0 ||
685                     x < 0) {
686                     return Double.NaN;
687                 } else if (x == 0) {
688                     return 0;
689                 } else if (x >= a + 1) {
690                     // Q should converge faster in this case.
691                     return 1 - RegularizedGamma.Q.value(a, x, epsilon, maxIterations);
692                 } else {
693                     // Series.
694                     double n = 0; // current element index
695                     double an = 1 / a; // n-th element in the series
696                     double sum = an; // partial sum
697                     while (Math.abs(an / sum) > epsilon &&
698                            n < maxIterations &&
699                            sum < Double.POSITIVE_INFINITY) {
700                         // compute next element in the series
701                         n += 1;
702                         an *= x / (a + n);
703 
704                         // update partial sum
705                         sum += an;
706                     }
707                     if (n >= maxIterations) {
708                         throw new ArithmeticException("Max iterations exceeded: " + maxIterations);
709                     } else if (Double.isInfinite(sum)) {
710                         return 1;
711                     } else {
712                         // Ensure result is in the range [0, 1]
713                         final double result = Math.exp(-x + (a * Math.log(x)) - LogGamma.value(a)) * sum;
714                         return result > 1.0 ? 1.0 : result;
715                     }
716                 }
717             }
718         }
719 
720         /**
721          * Creates the \( Q(a, x) \equiv 1 - P(a, x) \) <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
722          * regularized Gamma function</a>.
723          *
724          * Class is immutable.
725          */
726         static final class Q {
727             /** Prevent instantiation. */
728             private Q() {}
729 
730             /**
731              * Computes the regularized gamma function \( Q(a, x) = 1 - P(a, x) \).
732              *
733              * The implementation of this method is based on:
734              * <ul>
735              *  <li>
736              *   <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">
737              *   Regularized Gamma Function</a>, equation (1).
738              *  </li>
739              *  <li>
740              *   <a href="http://functions.wolfram.com/GammaBetaErf/GammaRegularized/10/0003/">
741              *   Regularized incomplete gamma function: Continued fraction representations
742              *   (formula 06.08.10.0003)</a>
743              *  </li>
744              * </ul>
745              *
746              * @param a Argument.
747              * @param x Argument.
748              * @param epsilon Tolerance in continued fraction evaluation.
749              * @param maxIterations Maximum number of iterations in continued fraction evaluation.
750              * @throws ArithmeticException if the continued fraction fails to converge.
751              * @return \( Q(a, x) \).
752              */
753             static double value(final double a,
754                                 double x,
755                                 double epsilon,
756                                 int maxIterations) {
757                 if (Double.isNaN(a) ||
758                     Double.isNaN(x) ||
759                     a <= 0 ||
760                     x < 0) {
761                     return Double.NaN;
762                 } else if (x == 0) {
763                     return 1;
764                 } else if (x < a + 1) {
765                     // P should converge faster in this case.
766                     return 1 - RegularizedGamma.P.value(a, x, epsilon, maxIterations);
767                 } else {
768                     final ContinuedFraction cf = new ContinuedFraction() {
769                             /** {@inheritDoc} */
770                             @Override
771                             protected double getA(int n, double x) {
772                                 return n * (a - n);
773                             }
774 
775                             /** {@inheritDoc} */
776                             @Override
777                             protected double getB(int n, double x) {
778                                 return ((2 * n) + 1) - a + x;
779                             }
780                         };
781 
782                     return Math.exp(-x + (a * Math.log(x)) - LogGamma.value(a)) /
783                         cf.evaluate(x, epsilon, maxIterations);
784                 }
785             }
786         }
787     }
788 
789     /**
790      * Assert the values are equal to the given epsilon, else throw an AssertionError.
791      *
792      * @param x the x
793      * @param y the y
794      * @param eps the max epsilon for equality
795      * @param msg the message upon failure
796      */
797     static void assertEquals(double x, double y, double eps, Supplier<String> msg) {
798         if (!Precision.equalsIncludingNaN(x, y, eps)) {
799             throw new AssertionError(msg.get() + ": " + x + " != " + y);
800         }
801     }
802 
803     /**
804      * Apply the function to all the numbers.
805      *
806      * @param numbers Numbers.
807      * @param fun Function.
808      * @param bh Data sink.
809      */
810     private static void apply(double[] numbers, DoubleUnaryOperator fun, Blackhole bh) {
811         for (int i = 0; i < numbers.length; i++) {
812             bh.consume(fun.applyAsDouble(numbers[i]));
813         }
814     }
815 
816     /**
817      * Identity function. This can be used to measure the JMH overhead of calling a function
818      * on an array of numbers.
819      *
820      * @param z Number.
821      * @return the number
822      */
823     private static double identity(double z) {
824         return z;
825     }
826 
827     // Benchmark methods.
828     // Benchmarks use function references to perform different operations on the numbers.
829 
830     /**
831      * Baseline the JMH overhead for all the benchmarks that evaluate a function of
832      * an array of numbers. All other methods are expected to be slower than this.
833      *
834      * @param numbers Numbers.
835      * @param bh Data sink.
836      */
837     @Benchmark
838     public void baseline(BaseData numbers, Blackhole bh) {
839         apply(numbers.getNumbers(), ErfPerformance::identity, bh);
840     }
841 
842     /**
843      * Benchmark the error function.
844      *
845      * @param data Test data.
846      * @param bh Data sink.
847      */
848     @Benchmark
849     public void erf(ErfData data, Blackhole bh) {
850         apply(data.getNumbers(), data.getFunction(), bh);
851     }
852 
853     /**
854      * Benchmark the complementary error function.
855      *
856      * @param data Test data.
857      * @param bh Data sink.
858      */
859     @Benchmark
860     public void erfc(ErfcData data, Blackhole bh) {
861         apply(data.getNumbers(), data.getFunction(), bh);
862     }
863 
864     /**
865      * Benchmark the inverse error function.
866      *
867      * @param data Test data.
868      * @param bh Data sink.
869      */
870     @Benchmark
871     public void inverseErf(InverseErfData data, Blackhole bh) {
872         apply(data.getNumbers(), data.getFunction(), bh);
873     }
874 
875     /**
876      * Benchmark the inverse complementary error function.
877      *
878      * @param data Test data.
879      * @param bh Data sink.
880      */
881     @Benchmark
882     public void inverseErfc(InverseErfcData data, Blackhole bh) {
883         apply(data.getNumbers(), data.getFunction(), bh);
884     }
885 }