Module toleranceinterval.hk

Expand source code
# -- coding: utf-8 --
# MIT License
#
# Copyright (c) 2019 Charles Jekel
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from sympy import Symbol, Integral, factorial
from sympy import gamma, hyper, exp_polar, I, pi, log
from scipy.special import betainc, betaincinv
import numpy as np
from warnings import warn


class HansonKoopmans(object):

    def __init__(self, p, g, n, j, method='secant', max_iter=200,
                 tol=1e-5, step_size=1e-4):
        r"""
        An object to solve for the Hanson-Koopmans bound.

        Solve the Hanson-Koopmans [1] bound for any percentile, confidence
        level, and number of samples.  This assumes the lowest value is the
        first order statistic, but you can specify the index of the second
        order statistic as j.

        Parameters
        ----------
        p : float
            Percentile where p < 0.5 and p > 0.
        g : float
            Confidence level where g > 0. and g < 1.
        n : int
            Number of samples.
        j : int
            Index of the second value to use for the second order statistic.
        method : string, optional
            Which rootfinding method to use to solve for the Hanson-Koopmans
            bound. Default is method='secant' which appears to converge
            quickly. Other choices include 'newton-raphson' and 'halley'.
        max_iter : int, optional
            Maximum number of iterations for the root finding method.
        tol : float, optional
            Tolerance for the root finding method to converge.
        step_size : float, optional
            Step size for the secant solver. Default step_size = 1e-4.

        Attributes
        ----------
        b : float_like
            Hanson-Koopmans bound.
        un_conv : bool
            Unconvergence status. If un_conv, then the method did not converge.
        count : int
            Number of iterations used in the root finding method.
        fall_back : bool
            Whether to fall back to the traditional non-parametric method.

        Raises
        ------
        ValueError
            Incorrect input, or unable to comptue the Hanson-Koopmans bound.

        References
        ----------
        [1] Hanson, D. L., & Koopmans, L. H. (1964). Tolerance Limits for
            the Class of Distributions with Increasing Hazard Rates. Ann. Math.
            Statist., 35(4), 1561–1570. https://doi.org/10.1214/aoms/1177700380

        [2] Vangel, M. G. (1994). One-sided nonparametric tolerance limits.
            Communications in Statistics - Simulation and Computation, 23(4),
            1137–1154. https://doi.org/10.1080/03610919408813222

        """
        self.max_iter = max_iter
        self.tol = tol
        self.step_size = step_size
        # create a dummy variable v
        self.v = Symbol('v', nonzero=True, rational=True, positive=True)
        # check that p, g, n, j are valid
        if not (p < 0.5 and p > 0.):
            self.invalid_value(p, 'p')
        else:
            self.p = p
        if not (g > 0. and g < 1.):
            self.invalid_value(g, 'g')
        else:
            self.g = g
        self.n = int(n)
        if not (j < n and j > -1):
            self.invalid_value(j, 'j')
        else:
            self.j = int(j)
        # compute the stuff that doesn't depend on b
        self.constant_vales()
        # compute b = 1
        pi_B_1 = self.piB(0)  # remember that b - 1 = B; b = B + 1
        if pi_B_1 >= self.g:
            self.fall_back = True
            # raise ValueError('b = 1, defer to traditional methods...')
            # raise RunTimeWarning?
        else:
            self.fall_back = False
            b_guess = self.vangel_approx(p=float(self.p))
            # print(float(b_guess))
            if np.isnan(b_guess):
                raise RuntimeError('Bad Vangel Approximation is np.nan')
            elif b_guess <= 0:
                b_guess = 1e-2
                # print(b_guess)
            self.b_guess = b_guess
            if method == 'secant':
                B, status, count = self.secant_solver(b_guess - 1.)
            elif method == 'newton-raphson':
                B, status, count = self.nr_solver(b_guess - 1.)
            elif method == 'halley':
                B, status, count = self.halley_solver(b_guess - 1.)
            else:
                raise ValueError(str(method) + ' is not a valid method!')

            self.b = B + 1.
            self.un_conv = status
            self.count = count
            if self.un_conv:
                war = 'HansonKoopmans root finding method failed to converge!'
                warn(war, RuntimeWarning)
            # This should raise RuntimeError if not converged!

    def invalid_value(self, value, variable):
        err = str(value) + ' was not a valid value for ' + variable
        raise ValueError(err)

    def constant_vales(self):
        self.nj = self.n-self.j-1
        self.A = factorial(self.n) / (factorial(self.nj) *
                                      factorial(self.j-1))
        # compute the left integral
        int_left = (self.p*self.p**self.j*gamma(self.j + 1) *
                    hyper((-self.nj, self.j + 1),
                          (self.j + 2,),
                          self.p*exp_polar(2*I*pi)) /
                    (self.j*gamma(self.j + 2)))
        self.int_left = int_left.evalf()  # evaluates to double precision

    def piB(self, B):
        int_right_exp = (self.v**self.j*(1 - self.v)**self.nj/self.j
                         - (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                         self.v**(B/(B + 1)) + self.v)**self.j/self.j)
        int_right = Integral(int_right_exp, (self.v, self.p, 1)).evalf()
        return (self.int_left + int_right)*self.A

    def dpiB(self, B):
        d_int_right_exp_B = (self.v**self.j*(1 - self.v)**self.nj/self.j -
                             (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                             self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                             log(self.v) + self.p**(1/(B + 1))*self.v **
                             (B / (B + 1))*log(self.p)/(B + 1)**2 + self.v) **
                             self.j / self.j)
        d_int_right = Integral(d_int_right_exp_B, (self.v, self.p, 1)).evalf()
        return d_int_right*self.A

    def d2piB2(self, B):
        d2_int_r_B = (-(1 - self.v)**self.nj*(-self.p**(1/(B + 1))*self.v **
                      (B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1))*log(self.v) +
                      self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                      (B + 1)**2 + self.v)**self.j*(-self.p**(1/(B + 1)) *
                      self.v**(B/(B + 1))*(2*B/(B + 1)**3 - 2/(B + 1)**2) *
                      log(self.v) - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      (-B/(B + 1)**2 + 1/(B + 1))**2*log(self.v)**2 + 2 *
                      self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      (-B/(B + 1)**2 + 1/(B + 1))
                      * log(self.p)*log(self.v)/(B + 1)**2 - 2 *
                      self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                      (B + 1)**3 - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      log(self.p)**2/(B + 1)**4)/(-self.p**(1/(B + 1)) *
                      self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                      log(self.v) + self.p**(1/(B + 1))*self.v **
                      (B/(B + 1)) *
                      log(self.p)/(B + 1)**2 + self.v))
        d2_int_right = Integral(d2_int_r_B, (self.v, self.p, 1)).evalf()
        return d2_int_right*self.A

    def vangel_approx(self, n=None, i=None, j=None, p=None, g=None):
        if n is None:
            n = self.n
        if i is None:
            i = 1
        if j is None:
            j = self.j+1
        if p is None:
            p = self.p
        if g is None:
            g = self.g
        betatmp = betainc(j, n-j+1, p)
        a = g - betatmp
        b = 1.0 - betatmp
        q = betaincinv(i, j-i, a/b)
        return np.log(((p)*(n+1))/j) / np.log(q)

    def secant_solver(self, B_guess, max_iter=None, tol=None, step_size=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        if step_size is None:
            step_size = self.step_size
        count = 0
        f = self.piB(B_guess) - self.g
        f1 = self.piB(B_guess + step_size) - self.g
        dfdx = (f1 - f) / step_size
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            f1 = self.piB(B_guess + step_size) - self.g
            dfdx = (f1 - f) / step_size
            B_next = B_guess - (f/dfdx)
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

    def nr_solver(self, B_guess, max_iter=None, tol=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        count = 0
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            dfdx = self.dpiB(B_guess)
            B_next = B_guess - (f/dfdx)
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

    def halley_solver(self, B_guess, max_iter=None, tol=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        count = 0
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        d2fdx2 = self.d2piB2(B_guess)
        B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            dfdx = self.dpiB(B_guess)
            d2fdx2 = self.d2piB2(B_guess)
            B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

Classes

class HansonKoopmans (p, g, n, j, method='secant', max_iter=200, tol=1e-05, step_size=0.0001)

An object to solve for the Hanson-Koopmans bound.

Solve the Hanson-Koopmans [1] bound for any percentile, confidence level, and number of samples. This assumes the lowest value is the first order statistic, but you can specify the index of the second order statistic as j.

Parameters

p : float
Percentile where p < 0.5 and p > 0.
g : float
Confidence level where g > 0. and g < 1.
n : int
Number of samples.
j : int
Index of the second value to use for the second order statistic.
method : string, optional
Which rootfinding method to use to solve for the Hanson-Koopmans bound. Default is method='secant' which appears to converge quickly. Other choices include 'newton-raphson' and 'halley'.
max_iter : int, optional
Maximum number of iterations for the root finding method.
tol : float, optional
Tolerance for the root finding method to converge.
step_size : float, optional
Step size for the secant solver. Default step_size = 1e-4.

Attributes

b : float_like
Hanson-Koopmans bound.
un_conv : bool
Unconvergence status. If un_conv, then the method did not converge.
count : int
Number of iterations used in the root finding method.
fall_back : bool
Whether to fall back to the traditional non-parametric method.

Raises

ValueError
Incorrect input, or unable to comptue the Hanson-Koopmans bound.

References

[1] Hanson, D. L., & Koopmans, L. H. (1964). Tolerance Limits for the Class of Distributions with Increasing Hazard Rates. Ann. Math. Statist., 35(4), 1561–1570. https://doi.org/10.1214/aoms/1177700380

[2] Vangel, M. G. (1994). One-sided nonparametric tolerance limits. Communications in Statistics - Simulation and Computation, 23(4), 1137–1154. https://doi.org/10.1080/03610919408813222

Expand source code
class HansonKoopmans(object):

    def __init__(self, p, g, n, j, method='secant', max_iter=200,
                 tol=1e-5, step_size=1e-4):
        r"""
        An object to solve for the Hanson-Koopmans bound.

        Solve the Hanson-Koopmans [1] bound for any percentile, confidence
        level, and number of samples.  This assumes the lowest value is the
        first order statistic, but you can specify the index of the second
        order statistic as j.

        Parameters
        ----------
        p : float
            Percentile where p < 0.5 and p > 0.
        g : float
            Confidence level where g > 0. and g < 1.
        n : int
            Number of samples.
        j : int
            Index of the second value to use for the second order statistic.
        method : string, optional
            Which rootfinding method to use to solve for the Hanson-Koopmans
            bound. Default is method='secant' which appears to converge
            quickly. Other choices include 'newton-raphson' and 'halley'.
        max_iter : int, optional
            Maximum number of iterations for the root finding method.
        tol : float, optional
            Tolerance for the root finding method to converge.
        step_size : float, optional
            Step size for the secant solver. Default step_size = 1e-4.

        Attributes
        ----------
        b : float_like
            Hanson-Koopmans bound.
        un_conv : bool
            Unconvergence status. If un_conv, then the method did not converge.
        count : int
            Number of iterations used in the root finding method.
        fall_back : bool
            Whether to fall back to the traditional non-parametric method.

        Raises
        ------
        ValueError
            Incorrect input, or unable to comptue the Hanson-Koopmans bound.

        References
        ----------
        [1] Hanson, D. L., & Koopmans, L. H. (1964). Tolerance Limits for
            the Class of Distributions with Increasing Hazard Rates. Ann. Math.
            Statist., 35(4), 1561–1570. https://doi.org/10.1214/aoms/1177700380

        [2] Vangel, M. G. (1994). One-sided nonparametric tolerance limits.
            Communications in Statistics - Simulation and Computation, 23(4),
            1137–1154. https://doi.org/10.1080/03610919408813222

        """
        self.max_iter = max_iter
        self.tol = tol
        self.step_size = step_size
        # create a dummy variable v
        self.v = Symbol('v', nonzero=True, rational=True, positive=True)
        # check that p, g, n, j are valid
        if not (p < 0.5 and p > 0.):
            self.invalid_value(p, 'p')
        else:
            self.p = p
        if not (g > 0. and g < 1.):
            self.invalid_value(g, 'g')
        else:
            self.g = g
        self.n = int(n)
        if not (j < n and j > -1):
            self.invalid_value(j, 'j')
        else:
            self.j = int(j)
        # compute the stuff that doesn't depend on b
        self.constant_vales()
        # compute b = 1
        pi_B_1 = self.piB(0)  # remember that b - 1 = B; b = B + 1
        if pi_B_1 >= self.g:
            self.fall_back = True
            # raise ValueError('b = 1, defer to traditional methods...')
            # raise RunTimeWarning?
        else:
            self.fall_back = False
            b_guess = self.vangel_approx(p=float(self.p))
            # print(float(b_guess))
            if np.isnan(b_guess):
                raise RuntimeError('Bad Vangel Approximation is np.nan')
            elif b_guess <= 0:
                b_guess = 1e-2
                # print(b_guess)
            self.b_guess = b_guess
            if method == 'secant':
                B, status, count = self.secant_solver(b_guess - 1.)
            elif method == 'newton-raphson':
                B, status, count = self.nr_solver(b_guess - 1.)
            elif method == 'halley':
                B, status, count = self.halley_solver(b_guess - 1.)
            else:
                raise ValueError(str(method) + ' is not a valid method!')

            self.b = B + 1.
            self.un_conv = status
            self.count = count
            if self.un_conv:
                war = 'HansonKoopmans root finding method failed to converge!'
                warn(war, RuntimeWarning)
            # This should raise RuntimeError if not converged!

    def invalid_value(self, value, variable):
        err = str(value) + ' was not a valid value for ' + variable
        raise ValueError(err)

    def constant_vales(self):
        self.nj = self.n-self.j-1
        self.A = factorial(self.n) / (factorial(self.nj) *
                                      factorial(self.j-1))
        # compute the left integral
        int_left = (self.p*self.p**self.j*gamma(self.j + 1) *
                    hyper((-self.nj, self.j + 1),
                          (self.j + 2,),
                          self.p*exp_polar(2*I*pi)) /
                    (self.j*gamma(self.j + 2)))
        self.int_left = int_left.evalf()  # evaluates to double precision

    def piB(self, B):
        int_right_exp = (self.v**self.j*(1 - self.v)**self.nj/self.j
                         - (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                         self.v**(B/(B + 1)) + self.v)**self.j/self.j)
        int_right = Integral(int_right_exp, (self.v, self.p, 1)).evalf()
        return (self.int_left + int_right)*self.A

    def dpiB(self, B):
        d_int_right_exp_B = (self.v**self.j*(1 - self.v)**self.nj/self.j -
                             (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                             self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                             log(self.v) + self.p**(1/(B + 1))*self.v **
                             (B / (B + 1))*log(self.p)/(B + 1)**2 + self.v) **
                             self.j / self.j)
        d_int_right = Integral(d_int_right_exp_B, (self.v, self.p, 1)).evalf()
        return d_int_right*self.A

    def d2piB2(self, B):
        d2_int_r_B = (-(1 - self.v)**self.nj*(-self.p**(1/(B + 1))*self.v **
                      (B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1))*log(self.v) +
                      self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                      (B + 1)**2 + self.v)**self.j*(-self.p**(1/(B + 1)) *
                      self.v**(B/(B + 1))*(2*B/(B + 1)**3 - 2/(B + 1)**2) *
                      log(self.v) - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      (-B/(B + 1)**2 + 1/(B + 1))**2*log(self.v)**2 + 2 *
                      self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      (-B/(B + 1)**2 + 1/(B + 1))
                      * log(self.p)*log(self.v)/(B + 1)**2 - 2 *
                      self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                      (B + 1)**3 - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                      log(self.p)**2/(B + 1)**4)/(-self.p**(1/(B + 1)) *
                      self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                      log(self.v) + self.p**(1/(B + 1))*self.v **
                      (B/(B + 1)) *
                      log(self.p)/(B + 1)**2 + self.v))
        d2_int_right = Integral(d2_int_r_B, (self.v, self.p, 1)).evalf()
        return d2_int_right*self.A

    def vangel_approx(self, n=None, i=None, j=None, p=None, g=None):
        if n is None:
            n = self.n
        if i is None:
            i = 1
        if j is None:
            j = self.j+1
        if p is None:
            p = self.p
        if g is None:
            g = self.g
        betatmp = betainc(j, n-j+1, p)
        a = g - betatmp
        b = 1.0 - betatmp
        q = betaincinv(i, j-i, a/b)
        return np.log(((p)*(n+1))/j) / np.log(q)

    def secant_solver(self, B_guess, max_iter=None, tol=None, step_size=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        if step_size is None:
            step_size = self.step_size
        count = 0
        f = self.piB(B_guess) - self.g
        f1 = self.piB(B_guess + step_size) - self.g
        dfdx = (f1 - f) / step_size
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            f1 = self.piB(B_guess + step_size) - self.g
            dfdx = (f1 - f) / step_size
            B_next = B_guess - (f/dfdx)
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

    def nr_solver(self, B_guess, max_iter=None, tol=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        count = 0
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            dfdx = self.dpiB(B_guess)
            B_next = B_guess - (f/dfdx)
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

    def halley_solver(self, B_guess, max_iter=None, tol=None):
        if max_iter is None:
            max_iter = self.max_iter
        if tol is None:
            tol = self.tol
        count = 0
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        d2fdx2 = self.d2piB2(B_guess)
        B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
        un_conv = np.abs(B_next - B_guess) > tol
        while un_conv and count < max_iter:
            B_guess = B_next
            f = self.piB(B_guess) - self.g
            dfdx = self.dpiB(B_guess)
            d2fdx2 = self.d2piB2(B_guess)
            B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
            un_conv = np.abs(B_next - B_guess) > tol
            count += 1
        return B_next, un_conv, count

Methods

def constant_vales(self)
Expand source code
def constant_vales(self):
    self.nj = self.n-self.j-1
    self.A = factorial(self.n) / (factorial(self.nj) *
                                  factorial(self.j-1))
    # compute the left integral
    int_left = (self.p*self.p**self.j*gamma(self.j + 1) *
                hyper((-self.nj, self.j + 1),
                      (self.j + 2,),
                      self.p*exp_polar(2*I*pi)) /
                (self.j*gamma(self.j + 2)))
    self.int_left = int_left.evalf()  # evaluates to double precision
def d2piB2(self, B)
Expand source code
def d2piB2(self, B):
    d2_int_r_B = (-(1 - self.v)**self.nj*(-self.p**(1/(B + 1))*self.v **
                  (B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1))*log(self.v) +
                  self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                  (B + 1)**2 + self.v)**self.j*(-self.p**(1/(B + 1)) *
                  self.v**(B/(B + 1))*(2*B/(B + 1)**3 - 2/(B + 1)**2) *
                  log(self.v) - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                  (-B/(B + 1)**2 + 1/(B + 1))**2*log(self.v)**2 + 2 *
                  self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                  (-B/(B + 1)**2 + 1/(B + 1))
                  * log(self.p)*log(self.v)/(B + 1)**2 - 2 *
                  self.p**(1/(B + 1))*self.v**(B/(B + 1))*log(self.p) /
                  (B + 1)**3 - self.p**(1/(B + 1))*self.v**(B/(B + 1)) *
                  log(self.p)**2/(B + 1)**4)/(-self.p**(1/(B + 1)) *
                  self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                  log(self.v) + self.p**(1/(B + 1))*self.v **
                  (B/(B + 1)) *
                  log(self.p)/(B + 1)**2 + self.v))
    d2_int_right = Integral(d2_int_r_B, (self.v, self.p, 1)).evalf()
    return d2_int_right*self.A
def dpiB(self, B)
Expand source code
def dpiB(self, B):
    d_int_right_exp_B = (self.v**self.j*(1 - self.v)**self.nj/self.j -
                         (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                         self.v**(B/(B + 1))*(-B/(B + 1)**2 + 1/(B + 1)) *
                         log(self.v) + self.p**(1/(B + 1))*self.v **
                         (B / (B + 1))*log(self.p)/(B + 1)**2 + self.v) **
                         self.j / self.j)
    d_int_right = Integral(d_int_right_exp_B, (self.v, self.p, 1)).evalf()
    return d_int_right*self.A
def halley_solver(self, B_guess, max_iter=None, tol=None)
Expand source code
def halley_solver(self, B_guess, max_iter=None, tol=None):
    if max_iter is None:
        max_iter = self.max_iter
    if tol is None:
        tol = self.tol
    count = 0
    f = self.piB(B_guess) - self.g
    dfdx = self.dpiB(B_guess)
    d2fdx2 = self.d2piB2(B_guess)
    B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
    un_conv = np.abs(B_next - B_guess) > tol
    while un_conv and count < max_iter:
        B_guess = B_next
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        d2fdx2 = self.d2piB2(B_guess)
        B_next = B_guess - ((2*f*dfdx) / (2*(dfdx**2) - (f*d2fdx2)))
        un_conv = np.abs(B_next - B_guess) > tol
        count += 1
    return B_next, un_conv, count
def invalid_value(self, value, variable)
Expand source code
def invalid_value(self, value, variable):
    err = str(value) + ' was not a valid value for ' + variable
    raise ValueError(err)
def nr_solver(self, B_guess, max_iter=None, tol=None)
Expand source code
def nr_solver(self, B_guess, max_iter=None, tol=None):
    if max_iter is None:
        max_iter = self.max_iter
    if tol is None:
        tol = self.tol
    count = 0
    f = self.piB(B_guess) - self.g
    dfdx = self.dpiB(B_guess)
    B_next = B_guess - (f/dfdx)
    un_conv = np.abs(B_next - B_guess) > tol
    while un_conv and count < max_iter:
        B_guess = B_next
        f = self.piB(B_guess) - self.g
        dfdx = self.dpiB(B_guess)
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        count += 1
    return B_next, un_conv, count
def piB(self, B)
Expand source code
def piB(self, B):
    int_right_exp = (self.v**self.j*(1 - self.v)**self.nj/self.j
                     - (1 - self.v)**self.nj*(-self.p**(1/(B + 1)) *
                     self.v**(B/(B + 1)) + self.v)**self.j/self.j)
    int_right = Integral(int_right_exp, (self.v, self.p, 1)).evalf()
    return (self.int_left + int_right)*self.A
def secant_solver(self, B_guess, max_iter=None, tol=None, step_size=None)
Expand source code
def secant_solver(self, B_guess, max_iter=None, tol=None, step_size=None):
    if max_iter is None:
        max_iter = self.max_iter
    if tol is None:
        tol = self.tol
    if step_size is None:
        step_size = self.step_size
    count = 0
    f = self.piB(B_guess) - self.g
    f1 = self.piB(B_guess + step_size) - self.g
    dfdx = (f1 - f) / step_size
    B_next = B_guess - (f/dfdx)
    un_conv = np.abs(B_next - B_guess) > tol
    while un_conv and count < max_iter:
        B_guess = B_next
        f = self.piB(B_guess) - self.g
        f1 = self.piB(B_guess + step_size) - self.g
        dfdx = (f1 - f) / step_size
        B_next = B_guess - (f/dfdx)
        un_conv = np.abs(B_next - B_guess) > tol
        count += 1
    return B_next, un_conv, count
def vangel_approx(self, n=None, i=None, j=None, p=None, g=None)
Expand source code
def vangel_approx(self, n=None, i=None, j=None, p=None, g=None):
    if n is None:
        n = self.n
    if i is None:
        i = 1
    if j is None:
        j = self.j+1
    if p is None:
        p = self.p
    if g is None:
        g = self.g
    betatmp = betainc(j, n-j+1, p)
    a = g - betatmp
    b = 1.0 - betatmp
    q = betaincinv(i, j-i, a/b)
    return np.log(((p)*(n+1))/j) / np.log(q)