Exemple #1
0
def tanh_sinh_lr(f_left, f_right, alpha, eps, max_steps=10):
    '''Integrate a function `f` between `a` and `b` with accuracy `eps`. The
    function `f` is given in terms of two functions

        * `f_left(s) = f(a + s)`, i.e., `f` linearly scaled such that
          `f_left(0) = a`, `f_left(b-a) = b`,

        * `f_right(s) = f(b - s)`, i.e., `f` linearly scaled such that
          `f_right(0) = b`, `f_left(b-a) = a`.

    Implemented are Bailey's enhancements plus a few more tricks.

    David H. Bailey, Karthik Jeyabalan, and Xiaoye S. Li,
    Error function quadrature,
    Experiment. Math., Volume 14, Issue 3 (2005), 317-329,
    <https://projecteuclid.org/euclid.em/1128371757>.

    David H. Bailey,
    Tanh-Sinh High-Precision Quadrature,
    2006,
    <http://www.davidhbailey.com/dhbpapers/dhb-tanh-sinh.pdf>.
    '''
    num_digits = int(-mp.log10(eps) + 1)
    mp.dps = num_digits

    alpha2 = alpha / mp.mpf(2)

    # What's a good initial step size `h`?
    # The larger `h` is chosen, the fewer points will be part of the
    # evaluation. However, we don't want to choose the step size too large
    # since that means less accuracy for the quadrature overall. The idea would
    # then be too choose `h` such that it is just large enough for the first
    # tanh-sinh-step to contain only one point, the midpoint. The expression
    #
    #    j = mp.ln(-2/mp.pi * mp.lambertw(-tau/h/2, -1)) / h
    #
    # hence needs to just smaller than 1. (Ideally, one would actually like to
    # get `j` from the full tanh-sinh formula, but the above approximation is
    # good enough.) One gets
    #
    #    0 = pi/2 * exp(h) - h - ln(h) - ln(pi/tau)
    #
    # for which there is no analytic solution. One can, however, approximate
    # it. Since pi/2 * exp(h) >> h >> ln(h) (for `h` large enough), one can
    # either forget about both h and ln(h) to get
    #
    #     h0 = ln(2/pi * ln(pi/tau))
    #
    # or just scratch ln(h) to get
    #
    #     h1 = ln(tau/pi) - W_{-1}(-tau/2).
    #
    # Both of these suggestions underestimate and `j` will be too large. An
    # approximation that overestimates is obtained by replacing `ln(h)` by `h`,
    #
    #     h2 = 1/2 - log(sqrt(pi/tau)) - W_{-1}(-sqrt(exp(1)*pi*tau) / 4).
    #
    # Application of Newton's method will improve all of these approximations
    # and will also always overestimate such that `j` won't exceed 1 in the
    # first step. Nice!
    # TODO since we're doing Newton iterations anyways, use a more accurate
    #      representation for j, and consequently for h
    h = _solve_expx_x_logx(eps**2, tol=1.0e-10)

    last_error_estimate = None

    success = False
    for level in range(max_steps + 1):
        # We would like to calculate the weights until they are smaller than
        # tau, i.e.,
        #
        #     h * pi/2 * cosh(h*j) / cosh(pi/2 * sinh(h*j))**2 < tau.
        #
        # (TODO Newton on this expression to find tau?)
        #
        # To streamline the computation, j is estimated in advance. The only
        # assumption we're making is that h*j>>1 such that exp(-h*j) can be
        # neglected. With this, the above becomes
        #
        #     tau > h * pi/2 * exp(h*j)/2 / cosh(pi/2 * exp(h*j)/2)**2
        #
        # and further
        #
        #     tau > h * pi * exp(h*j) / exp(pi/2 * exp(h*j)).
        #
        # Calling z = - pi/2 * exp(h*j), one gets
        #
        #     tau > -2*h*z * exp(z)
        #
        # This inequality is fulfilled exactly if z = W(-tau/h/2) with W being
        # the (-1)-branch of the Lambert-W function IF exp(1)*tau < 2*h (which
        # we can assume since `tau` will generally be small). We finally get
        #
        #     j > ln(-2/pi * W(-tau/h/2)) / h.
        #
        # We do require j to be positive, so -2/pi * W(-tau/h/2) > 1. This
        # translates to the slightly stricter requirement
        #
        #     tau * exp(pi/2) < pi * h,
        #
        # i.e., h needs to be about 1.531 times larger than tau (not only 1.359
        # times as the previous bound suggested).
        #
        # Note further that h*j is ever decreasing as h decreases.
        assert eps**2 * mp.exp(mp.pi / 2) < mp.pi * h
        j = int(mp.ln(-2 / mp.pi * mp.lambertw(-eps**2 / h / 2, -1)) / h)

        # At level 0, one only takes the midpoint, for all greater levels every
        # other point. The value estimation is later completed with the
        # estimation from the previous level which.
        if level == 0:
            t = [0]
        else:
            t = h * numpy.arange(1, j + 1, 2)

        sinh_t = mp.pi / 2 * numpy.array(list(map(mp.sinh, t)))
        cosh_t = mp.pi / 2 * numpy.array(list(map(mp.cosh, t)))
        cosh_sinh_t = numpy.array(list(map(mp.cosh, sinh_t)))

        # y = alpha/2 * (1 - x)
        # x = [mp.tanh(v) for v in u2]
        exp_sinh_t = numpy.array(list(map(mp.exp, sinh_t)))

        y0 = alpha2 / exp_sinh_t / cosh_sinh_t
        y1 = -alpha2 * cosh_t / cosh_sinh_t**2

        weights = -h * y1

        fly = numpy.array([f_left[0](yy) for yy in y0])
        fry = numpy.array([f_right[0](yy) for yy in y0])

        lsummands = fly * weights
        rsummands = fry * weights

        # Perform the integration.
        if level == 0:
            # The root level only contains one node, the midpoint; function
            # values of f_left and f_right are equal here. Deliberately take
            # lsummands here.
            value_estimates = list(lsummands)
        else:
            value_estimates.append(
                # Take the estimation from the previous step and half the step
                # size. Fill the gaps with the sum of the values of the current
                # step.
                value_estimates[-1] / 2 + mp.fsum(lsummands) +
                mp.fsum(rsummands))

        # error estimation
        if 1 in f_left and 2 in f_left:
            assert 1 in f_right and 2 in f_right
            error_estimate = _error_estimate1(h, sinh_t, cosh_t, cosh_sinh_t,
                                              y0, y1, fly, fry, f_left,
                                              f_right, alpha,
                                              last_error_estimate)
            last_error_estimate = error_estimate
        else:
            error_estimate = _error_estimate2(eps, value_estimates, lsummands,
                                              rsummands)

        if abs(error_estimate) < eps:
            success = True
            break

        h /= 2

    assert success
    return value_estimates[-1], error_estimate
Exemple #2
0
def myExtendLambert(oneMPFdata):
    return mp.lambertw(oneMPFdata, k=-1).real