Erf.java

  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.  *      https://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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.special;

  22. import org.hipparchus.CalculusFieldElement;
  23. import org.hipparchus.Field;
  24. import org.hipparchus.util.FastMath;

  25. /**
  26.  * This is a utility class that provides computation methods related to the
  27.  * error functions.
  28.  *
  29.  */
  30. public class Erf {

  31.     /**
  32.      * The number {@code X_CRIT} is used by {@link #erf(double, double)} internally.
  33.      * This number solves {@code erf(x)=0.5} within 1ulp.
  34.      * More precisely, the current implementations of
  35.      * {@link #erf(double)} and {@link #erfc(double)} satisfy:<br>
  36.      * {@code erf(X_CRIT) < 0.5},<br>
  37.      * {@code erf(Math.nextUp(X_CRIT) > 0.5},<br>
  38.      * {@code erfc(X_CRIT) = 0.5}, and<br>
  39.      * {@code erfc(Math.nextUp(X_CRIT) < 0.5}
  40.      */
  41.     private static final double X_CRIT = 0.4769362762044697;

  42.     /**
  43.      * Default constructor.  Prohibit instantiation.
  44.      */
  45.     private Erf() {}

  46.     /**
  47.      * Returns the error function.
  48.      *
  49.      * \[
  50.      * \mathrm{erf}(x) = \frac{2}{\sqrt{\pi}} \int_{t=0}^x e^{-t^2}dt
  51.      * \]
  52.      *
  53.      * <p>This implementation computes erf(x) using the
  54.      * {@link Gamma#regularizedGammaP(double, double, double, int) regularized gamma function},
  55.      * following <a href="http://mathworld.wolfram.com/Erf.html"> Erf</a>, equation (3)</p>
  56.      *
  57.      * <p>The value returned is always between -1 and 1 (inclusive).
  58.      * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from
  59.      * either 1 or -1 as a double, so the appropriate extreme value is returned.
  60.      * </p>
  61.      *
  62.      * @param x the value.
  63.      * @return the error function erf(x)
  64.      * @throws org.hipparchus.exception.MathIllegalStateException
  65.      * if the algorithm fails to converge.
  66.      * @see Gamma#regularizedGammaP(double, double, double, int)
  67.      */
  68.     public static double erf(double x) {
  69.         if (FastMath.abs(x) > 40) {
  70.             return x > 0 ? 1 : -1;
  71.         }
  72.         final double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000);
  73.         return x < 0 ? -ret : ret;
  74.     }

  75.     /**
  76.      * Returns the error function.
  77.      *
  78.      * \[
  79.      * \mathrm{erf}(x) = \frac{2}{\sqrt{\pi}} \int_{t=0}^x e^{-t^2}dt
  80.      * \]
  81.      *
  82.      * <p>This implementation computes erf(x) using the
  83.      * {@link Gamma#regularizedGammaP(double, double, double, int) regularized gamma function},
  84.      * following <a href="http://mathworld.wolfram.com/Erf.html"> Erf</a>, equation (3)</p>
  85.      *
  86.      * <p>The value returned is always between -1 and 1 (inclusive).
  87.      * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from
  88.      * either 1 or -1 as a double, so the appropriate extreme value is returned.
  89.      * </p>
  90.      *
  91.      * @param <T> type of the field elements
  92.      * @param x the value.
  93.      * @return the error function erf(x)
  94.      * @throws org.hipparchus.exception.MathIllegalStateException
  95.      * if the algorithm fails to converge.
  96.      * @see Gamma#regularizedGammaP(double, double, double, int)
  97.      */
  98.     public static <T extends CalculusFieldElement<T>> T erf(T x) {
  99.         final Field<T> field = x.getField();
  100.         final T one = field.getOne();

  101.         if (FastMath.abs(x.getReal()) > 40) {
  102.             return x.getReal() > 0 ? one : one.negate();
  103.         }
  104.         final T ret = Gamma.regularizedGammaP(one.newInstance(0.5), x.square(), 1.0e-15, 10000);
  105.         return x.getReal() < 0 ? ret.negate() : ret;
  106.     }


  107.     /**
  108.      * Returns the complementary error function.
  109.      *
  110.      * \[
  111.      * \mathrm{erfc}(x) =  \frac{2}{\sqrt{\pi}} \int_{t=x}^\infty e^{-t^2}dt = 1 - \mathrm{erf}
  112.      *
  113.      * <p>This implementation computes erfc(x) using the
  114.      * {@link Gamma#regularizedGammaQ(double, double, double, int) regularized gamma function},
  115.      * following <a href="http://mathworld.wolfram.com/Erf.html"> Erf</a>, equation (3).</p>
  116.      *
  117.      * <p>The value returned is always between 0 and 2 (inclusive).
  118.      * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from
  119.      * either 0 or 2 as a double, so the appropriate extreme value is returned.
  120.      * </p>
  121.      *
  122.      * @param x the value
  123.      * @return the complementary error function erfc(x)
  124.      * @throws org.hipparchus.exception.MathIllegalStateException
  125.      * if the algorithm fails to converge.
  126.      * @see Gamma#regularizedGammaQ(double, double, double, int)
  127.      */
  128.     public static double erfc(double x) {
  129.         if (FastMath.abs(x) > 40) {
  130.             return x > 0 ? 0 : 2;
  131.         }
  132.         final double ret = Gamma.regularizedGammaQ(0.5, x * x, 1.0e-15, 10000);
  133.         return x < 0 ? 2 - ret : ret;
  134.     }

  135.     /**
  136.      * Returns the complementary error function.
  137.      *
  138.      * \[
  139.      * erfc(x) = \frac{2}{\sqrt{\pi}} \int_x^\infty e^{-t^2}dt = 1 - erf(x)
  140.      * \]
  141.      *
  142.      * <p>This implementation computes erfc(x) using the
  143.      * {@link Gamma#regularizedGammaQ(double, double, double, int) regularized gamma function}, following <a
  144.      * href="http://mathworld.wolfram.com/Erf.html"> Erf</a>, equation (3).</p>
  145.      *
  146.      * <p>The value returned is always between 0 and 2 (inclusive).
  147.      * If {@code abs(x) > 40}, then {@code erf(x)} is indistinguishable from either 0 or 2 as a double, so the
  148.      * appropriate extreme value is returned. <b>This implies that the current implementation does not allow the use of
  149.      * {@link org.hipparchus.dfp.Dfp Dfp} with extended precision.</b>
  150.      * </p>
  151.      *
  152.      * @param x the value
  153.      * @param <T> type of the field elements
  154.      *
  155.      * @return the complementary error function erfc(x)
  156.      *
  157.      * @throws org.hipparchus.exception.MathIllegalStateException if the algorithm fails to converge.
  158.      * @see Gamma#regularizedGammaQ(double, double, double, int)
  159.      */
  160.     public static <T extends CalculusFieldElement<T>> T erfc(T x) {
  161.         final Field<T> field = x.getField();
  162.         final T        zero  = field.getZero();
  163.         final T        one   = field.getOne();

  164.         if (FastMath.abs(x.getReal()) > 40) {
  165.             return x.getReal() > 0 ? zero : one.newInstance(2.);
  166.         }
  167.         final T ret = Gamma.regularizedGammaQ(one.newInstance(0.5), x.square(), 1.0e-15, 10000);
  168.         return x.getReal() < 0 ? ret.negate().add(2.) : ret;
  169.     }

  170.     /**
  171.      * Returns the difference between erf(x1) and erf(x2).
  172.      * <p>
  173.      * The implementation uses either erf(double) or erfc(double)
  174.      * depending on which provides the most precise result.
  175.      *
  176.      * @param x1 the first value
  177.      * @param x2 the second value
  178.      * @return erf(x2) - erf(x1)
  179.      */
  180.     public static double erf(double x1, double x2) {
  181.         if(x1 > x2) {
  182.             return -erf(x2, x1);
  183.         }

  184.         return
  185.         x1 < -X_CRIT ?
  186.             x2 < 0.0 ?
  187.                 erfc(-x2) - erfc(-x1) :
  188.                 erf(x2) - erf(x1) :
  189.             x2 > X_CRIT && x1 > 0.0 ?
  190.                 erfc(x1) - erfc(x2) :
  191.                 erf(x2) - erf(x1);
  192.     }

  193.     /**
  194.      * Returns the difference between erf(x1) and erf(x2).
  195.      * <p>
  196.      * The implementation uses either erf(double) or erfc(double)
  197.      * depending on which provides the most precise result.
  198.      *
  199.      * @param x1 the first value
  200.      * @param x2 the second value
  201.      * @param <T> type of the field elements
  202.      *
  203.      * @return erf(x2) - erf(x1)
  204.      */
  205.     public static <T extends CalculusFieldElement<T>> T erf(T x1, T x2) {

  206.         if (x1.getReal() > x2.getReal()) {
  207.             return erf(x2, x1).negate();
  208.         }

  209.         return
  210.                 x1.getReal() < -X_CRIT ?
  211.                         x2.getReal() < 0.0 ?
  212.                                 erfc(x2.negate()).subtract(erfc(x1.negate())) :
  213.                                 erf(x2).subtract(erf(x1)) :
  214.                         x2.getReal() > X_CRIT && x1.getReal() > 0.0 ?
  215.                                 erfc(x1).subtract(erfc(x2)) :
  216.                                 erf(x2).subtract(erf(x1));
  217.     }

  218.     /**
  219.      * Returns the inverse erf.
  220.      * <p>
  221.      * This implementation is described in the paper:
  222.      * <a href="http://people.maths.ox.ac.uk/gilesm/files/gems_erfinv.pdf">Approximating
  223.      * the erfinv function</a> by Mike Giles, Oxford-Man Institute of Quantitative Finance,
  224.      * which was published in GPU Computing Gems, volume 2, 2010.
  225.      * The source code is available <a href="http://gpucomputing.net/?q=node/1828">here</a>.
  226.      * </p>
  227.      * @param x the value
  228.      * @return t such that x = erf(t)
  229.      */
  230.     public static double erfInv(final double x) {

  231.         // beware that the logarithm argument must be
  232.         // computed as (1.0 - x) * (1.0 + x),
  233.         // it must NOT be simplified as 1.0 - x * x as this
  234.         // would induce rounding errors near the boundaries +/-1
  235.         double w = - FastMath.log((1.0 - x) * (1.0 + x));
  236.         double p;

  237.         if (w < 6.25) {
  238.             w -= 3.125;
  239.             p =  -3.6444120640178196996e-21;
  240.             p =   -1.685059138182016589e-19 + p * w;
  241.             p =   1.2858480715256400167e-18 + p * w;
  242.             p =    1.115787767802518096e-17 + p * w;
  243.             p =   -1.333171662854620906e-16 + p * w;
  244.             p =   2.0972767875968561637e-17 + p * w;
  245.             p =   6.6376381343583238325e-15 + p * w;
  246.             p =  -4.0545662729752068639e-14 + p * w;
  247.             p =  -8.1519341976054721522e-14 + p * w;
  248.             p =   2.6335093153082322977e-12 + p * w;
  249.             p =  -1.2975133253453532498e-11 + p * w;
  250.             p =  -5.4154120542946279317e-11 + p * w;
  251.             p =    1.051212273321532285e-09 + p * w;
  252.             p =  -4.1126339803469836976e-09 + p * w;
  253.             p =  -2.9070369957882005086e-08 + p * w;
  254.             p =   4.2347877827932403518e-07 + p * w;
  255.             p =  -1.3654692000834678645e-06 + p * w;
  256.             p =  -1.3882523362786468719e-05 + p * w;
  257.             p =    0.0001867342080340571352 + p * w;
  258.             p =  -0.00074070253416626697512 + p * w;
  259.             p =   -0.0060336708714301490533 + p * w;
  260.             p =      0.24015818242558961693 + p * w;
  261.             p =       1.6536545626831027356 + p * w;
  262.         } else if (w < 16.0) {
  263.             w = FastMath.sqrt(w) - 3.25;
  264.             p =   2.2137376921775787049e-09;
  265.             p =   9.0756561938885390979e-08 + p * w;
  266.             p =  -2.7517406297064545428e-07 + p * w;
  267.             p =   1.8239629214389227755e-08 + p * w;
  268.             p =   1.5027403968909827627e-06 + p * w;
  269.             p =   -4.013867526981545969e-06 + p * w;
  270.             p =   2.9234449089955446044e-06 + p * w;
  271.             p =   1.2475304481671778723e-05 + p * w;
  272.             p =  -4.7318229009055733981e-05 + p * w;
  273.             p =   6.8284851459573175448e-05 + p * w;
  274.             p =   2.4031110387097893999e-05 + p * w;
  275.             p =   -0.0003550375203628474796 + p * w;
  276.             p =   0.00095328937973738049703 + p * w;
  277.             p =   -0.0016882755560235047313 + p * w;
  278.             p =    0.0024914420961078508066 + p * w;
  279.             p =   -0.0037512085075692412107 + p * w;
  280.             p =     0.005370914553590063617 + p * w;
  281.             p =       1.0052589676941592334 + p * w;
  282.             p =       3.0838856104922207635 + p * w;
  283.         } else if (!Double.isInfinite(w)) {
  284.             w = FastMath.sqrt(w) - 5.0;
  285.             p =  -2.7109920616438573243e-11;
  286.             p =  -2.5556418169965252055e-10 + p * w;
  287.             p =   1.5076572693500548083e-09 + p * w;
  288.             p =  -3.7894654401267369937e-09 + p * w;
  289.             p =   7.6157012080783393804e-09 + p * w;
  290.             p =  -1.4960026627149240478e-08 + p * w;
  291.             p =   2.9147953450901080826e-08 + p * w;
  292.             p =  -6.7711997758452339498e-08 + p * w;
  293.             p =   2.2900482228026654717e-07 + p * w;
  294.             p =  -9.9298272942317002539e-07 + p * w;
  295.             p =   4.5260625972231537039e-06 + p * w;
  296.             p =  -1.9681778105531670567e-05 + p * w;
  297.             p =   7.5995277030017761139e-05 + p * w;
  298.             p =  -0.00021503011930044477347 + p * w;
  299.             p =  -0.00013871931833623122026 + p * w;
  300.             p =       1.0103004648645343977 + p * w;
  301.             p =       4.8499064014085844221 + p * w;
  302.         } else {
  303.             // this branch does not appears in the original code, it
  304.             // was added because the previous branch does not handle
  305.             // x = +/-1 correctly. In this case, w is positive infinity
  306.             // and as the first coefficient (-2.71e-11) is negative.
  307.             // Once the first multiplication is done, p becomes negative
  308.             // infinity and remains so throughout the polynomial evaluation.
  309.             // So the branch above incorrectly returns negative infinity
  310.             // instead of the correct positive infinity.
  311.             p = Double.POSITIVE_INFINITY;
  312.         }

  313.         return p * x;

  314.     }

  315.     /**
  316.      * Returns the inverse erf.
  317.      * <p>
  318.      * This implementation is described in the paper:
  319.      * <a href="http://people.maths.ox.ac.uk/gilesm/files/gems_erfinv.pdf">Approximating
  320.      * the erfinv function</a> by Mike Giles, Oxford-Man Institute of Quantitative Finance,
  321.      * which was published in GPU Computing Gems, volume 2, 2010.
  322.      * The source code is available <a href="http://gpucomputing.net/?q=node/1828">here</a>.
  323.      * </p>
  324.      * @param <T> type of the filed elements
  325.      * @param x the value
  326.      * @return t such that x = erf(t)
  327.      */
  328.     public static <T extends CalculusFieldElement<T>> T erfInv(final T x) {
  329.         final T one = x.getField().getOne();

  330.         // beware that the logarithm argument must be
  331.         // computed as (1.0 - x) * (1.0 + x),
  332.         // it must NOT be simplified as 1.0 - x * x as this
  333.         // would induce rounding errors near the boundaries +/-1
  334.         T w = one.subtract(x).multiply(one.add(x)).log().negate();
  335.         T p;

  336.         if (w.getReal() < 6.25) {
  337.             w = w.subtract(3.125);
  338.             p = one.newInstance(-3.6444120640178196996e-21);
  339.             p = p.multiply(w).add(-1.685059138182016589e-19);
  340.             p = p.multiply(w).add(1.2858480715256400167e-18);
  341.             p = p.multiply(w).add(1.115787767802518096e-17);
  342.             p = p.multiply(w).add(-1.333171662854620906e-16);
  343.             p = p.multiply(w).add(2.0972767875968561637e-17);
  344.             p = p.multiply(w).add(6.6376381343583238325e-15);
  345.             p = p.multiply(w).add(-4.0545662729752068639e-14);
  346.             p = p.multiply(w).add(-8.1519341976054721522e-14);
  347.             p = p.multiply(w).add(2.6335093153082322977e-12);
  348.             p = p.multiply(w).add(-1.2975133253453532498e-11);
  349.             p = p.multiply(w).add(-5.4154120542946279317e-11);
  350.             p = p.multiply(w).add(1.051212273321532285e-09);
  351.             p = p.multiply(w).add(-4.1126339803469836976e-09);
  352.             p = p.multiply(w).add(-2.9070369957882005086e-08);
  353.             p = p.multiply(w).add(4.2347877827932403518e-07);
  354.             p = p.multiply(w).add(-1.3654692000834678645e-06);
  355.             p = p.multiply(w).add(-1.3882523362786468719e-05);
  356.             p = p.multiply(w).add(0.0001867342080340571352);
  357.             p = p.multiply(w).add(-0.00074070253416626697512);
  358.             p = p.multiply(w).add(-0.0060336708714301490533);
  359.             p = p.multiply(w).add(0.24015818242558961693);
  360.             p = p.multiply(w).add(1.6536545626831027356);
  361.         }
  362.         else if (w.getReal() < 16.0) {
  363.             w = w.sqrt().subtract(3.25);
  364.             p = one.newInstance(2.2137376921775787049e-09);
  365.             p = p.multiply(w).add(9.0756561938885390979e-08);
  366.             p = p.multiply(w).add(-2.7517406297064545428e-07);
  367.             p = p.multiply(w).add(1.8239629214389227755e-08);
  368.             p = p.multiply(w).add(1.5027403968909827627e-06);
  369.             p = p.multiply(w).add(-4.013867526981545969e-06);
  370.             p = p.multiply(w).add(2.9234449089955446044e-06);
  371.             p = p.multiply(w).add(1.2475304481671778723e-05);
  372.             p = p.multiply(w).add(-4.7318229009055733981e-05);
  373.             p = p.multiply(w).add(6.8284851459573175448e-05);
  374.             p = p.multiply(w).add(2.4031110387097893999e-05);
  375.             p = p.multiply(w).add(-0.0003550375203628474796);
  376.             p = p.multiply(w).add(0.00095328937973738049703);
  377.             p = p.multiply(w).add(-0.0016882755560235047313);
  378.             p = p.multiply(w).add(0.0024914420961078508066);
  379.             p = p.multiply(w).add(-0.0037512085075692412107);
  380.             p = p.multiply(w).add(0.005370914553590063617);
  381.             p = p.multiply(w).add(1.0052589676941592334);
  382.             p = p.multiply(w).add(3.0838856104922207635);
  383.         }
  384.         else if (!w.isInfinite()) {
  385.             w = w.sqrt().subtract(5.0);
  386.             p = one.newInstance(-2.7109920616438573243e-11);
  387.             p = p.multiply(w).add(-2.5556418169965252055e-10);
  388.             p = p.multiply(w).add(1.5076572693500548083e-09);
  389.             p = p.multiply(w).add(-3.7894654401267369937e-09);
  390.             p = p.multiply(w).add(7.6157012080783393804e-09);
  391.             p = p.multiply(w).add(-1.4960026627149240478e-08);
  392.             p = p.multiply(w).add(2.9147953450901080826e-08);
  393.             p = p.multiply(w).add(-6.7711997758452339498e-08);
  394.             p = p.multiply(w).add(2.2900482228026654717e-07);
  395.             p = p.multiply(w).add(-9.9298272942317002539e-07);
  396.             p = p.multiply(w).add(4.5260625972231537039e-06);
  397.             p = p.multiply(w).add(-1.9681778105531670567e-05);
  398.             p = p.multiply(w).add(7.5995277030017761139e-05);
  399.             p = p.multiply(w).add(-0.00021503011930044477347);
  400.             p = p.multiply(w).add(-0.00013871931833623122026);
  401.             p = p.multiply(w).add(1.0103004648645343977);
  402.             p = p.multiply(w).add(4.8499064014085844221);
  403.         }
  404.         else {
  405.             // this branch does not appear in the original code, it
  406.             // was added because the previous branch does not handle
  407.             // x = +/-1 correctly. In this case, w is positive infinity
  408.             // and as the first coefficient (-2.71e-11) is negative.
  409.             // Once the first multiplication is done, p becomes negative
  410.             // infinity and remains so throughout the polynomial evaluation.
  411.             // So the branch above incorrectly returns negative infinity
  412.             // instead of the correct positive infinity.
  413.             p = one.multiply(Double.POSITIVE_INFINITY);
  414.         }

  415.         return p.multiply(x);

  416.     }

  417.     /**
  418.      * Returns the inverse erfc.
  419.      * @param x the value
  420.      * @return t such that x = erfc(t)
  421.      */
  422.     public static double erfcInv(final double x) {
  423.         return erfInv(1 - x);
  424.     }

  425.     /**
  426.      * Returns the inverse erfc.
  427.      * @param x the value
  428.      * @param <T> type of the field elements
  429.      * @return t such that x = erfc(t)
  430.      */
  431.     public static <T extends CalculusFieldElement<T>> T erfcInv(final T x) {
  432.         return erfInv(x.negate().add(1));
  433.     }

  434. }