<reponame>zenmood/IndoorFarmWiz<filename>venv/lib/python3.7/site-packages/pba/dists.py<gh_stars>10-100
if __name__ is not None and "." in __name__:
    from .interval import Interval
else:
    from interval import Interval

if __name__ is not None and "." in __name__:
    from .pbox import Pbox
else:
    from pbox import Pbox

import scipy.stats as sps
import numpy as np
import itertools

dists = {
    'alpha' : sps.alpha,
    'anglit' : sps.anglit,
    'arcsine' : sps.arcsine,
    'argus' : sps.argus,
    'beta' : sps.beta,
    'betaprime' : sps.betaprime,
    'bradford' : sps.bradford,
    'burr' : sps.burr,
    'burr12' : sps.burr12,
    'cauchy' : sps.cauchy,
    'chi' : sps.chi,
    'chi2' : sps.chi2,
    'cosine' : sps.cosine,
    'crystalball' : sps.crystalball,
    'dgamma' : sps.dgamma,
    'dweibull' : sps.dweibull,
    'erlang' : sps.erlang,
    'expon' : sps.expon,
    'exponnorm' : sps.exponnorm,
    'exponweib' : sps.exponweib,
    'exponpow' : sps.exponpow,
    'f' : sps.f,
    'fatiguelife' : sps.fatiguelife,
    'fisk' : sps.fisk,
    'foldcauchy' : sps.foldcauchy,
    'foldnorm' : sps.foldnorm,
    # 'frechet_r' : sps.frechet_r,
    # 'frechet_l' : sps.frechet_l,
    'genlogistic' : sps.genlogistic,
    'gennorm' : sps.gennorm,
    'genpareto' : sps.genpareto,
    'genexpon' : sps.genexpon,
    'genextreme' : sps.genextreme,
    'gausshyper' : sps.gausshyper,
    'gamma' : sps.gamma,
    'gengamma' : sps.gengamma,
    'genhalflogistic' : sps.genhalflogistic,
    'geninvgauss' : sps.geninvgauss,
    'gilbrat' : sps.gilbrat,
    'gompertz' : sps.gompertz,
    'gumbel_r' : sps.gumbel_r,
    'gumbel_l' : sps.gumbel_l,
    'halfcauchy' : sps.halfcauchy,
    'halflogistic' : sps.halflogistic,
    'halfnorm' : sps.halfnorm,
    'halfgennorm' : sps.halfgennorm,
    'hypsecant' : sps.hypsecant,
    'invgamma' : sps.invgamma,
    'invgauss' : sps.invgauss,
    'invweibull' : sps.invweibull,
    'johnsonsb' : sps.johnsonsb,
    'johnsonsu' : sps.johnsonsu,
    'kappa4' : sps.kappa4,
    'kappa3' : sps.kappa3,
    'ksone' : sps.ksone,
    'kstwobign' : sps.kstwobign,
    'laplace' : sps.laplace,
    'levy' : sps.levy,
    'levy_l' : sps.levy_l,
    'levy_stable' : sps.levy_stable,
    'logistic' : sps.logistic,
    'loggamma' : sps.loggamma,
    'loglaplace' : sps.loglaplace,
    'lognorm' : sps.lognorm,
    'loguniform' : sps.loguniform,
    'lomax' : sps.lomax,
    'maxwell' : sps.maxwell,
    'mielke' : sps.mielke,
    'moyal' : sps.moyal,
    'nakagami' : sps.nakagami,
    'ncx2' : sps.ncx2,
    'ncf' : sps.ncf,
    'nct' : sps.nct,
    'norm' : sps.norm,
    'norminvgauss' : sps.norminvgauss,
    'pareto' : sps.pareto,
    'pearson3' : sps.pearson3,
    'powerlaw' : sps.powerlaw,
    'powerlognorm' : sps.powerlognorm,
    'powernorm' : sps.powernorm,
    'rdist' : sps.rdist,
    'rayleigh' : sps.rayleigh,
    'rice' : sps.rice,
    'recipinvgauss' : sps.recipinvgauss,
    'semicircular' : sps.semicircular,
    'skewnorm' : sps.skewnorm,
    't' : sps.t,
    'trapz' : sps.trapz,
    'triang' : sps.triang,
    'truncexpon' : sps.truncexpon,
    'truncnorm' : sps.truncnorm,
    'tukeylambda' : sps.tukeylambda,
    'uniform' : sps.uniform,
    'vonmises' : sps.vonmises,
    'vonmises_line' : sps.vonmises_line,
    'wald' : sps.wald,
    'weibull_min' : sps.weibull_min,
    'weibull_max' : sps.weibull_max,
    'wrapcauchy' : sps.wrapcauchy,
    'bernoulli' : sps.bernoulli,
    'betabinom' : sps.betabinom,
    'binom' : sps.binom,
    'boltzmann' : sps.boltzmann,
    'dlaplace' : sps.dlaplace,
    'geom' : sps.geom,
    'hypergeom' : sps.hypergeom,
    'logser' : sps.logser,
    'nbinom' : sps.nbinom,
    'planck' : sps.planck,
    'poisson' : sps.poisson,
    'randint' : sps.randint,
    'skellam' : sps.skellam,
    'zipf' : sps.zipf,
    'yulesimon' : sps.yulesimon
}

def __get_bounds(function_name = None,steps = 200,*args):

    # define support
    x = np.linspace(0.0001,0.9999,steps)

    #get bound arguments
    new_args = itertools.product(*args)

    bounds = []

    mean_hi = -np.inf
    mean_lo = np.inf
    var_lo = np.inf
    var_hi = 0

    for a in new_args:

        bounds.append(dists[function_name].ppf(x,*a))
        bmean, bvar = dists[function_name].stats(*a, moments = 'mv')

        if bmean < mean_lo:
            mean_lo = bmean
        if bmean > mean_hi:
            mean_hi = bmean
        if bvar > var_hi:
            var_hi = bvar
        if bvar < var_lo:
            var_lo = bvar

    Left = [min([b[i] for b in bounds]) for i in range(steps)]
    Right = [max([b[i] for b in bounds]) for i in range(steps)]

    var  = Interval(np.float64(var_lo),np.float64(var_hi))
    mean = Interval(np.float64(mean_lo),np.float64(mean_hi))

    Left = np.array(Left)
    Right = np.array(Right)

    return Left, Right, mean, var

def lognormal(mean, var, steps = 200):

    x = np.linspace(0,1,steps)

    if mean.__class__.__name__ != 'Interval':
        mean = Interval(mean,mean)
    if var.__class__.__name__ != 'Interval':
        var = Interval(var,var)

    bound0 = sps.lognorm.ppf(x, mean.left, var.left)
    bound1 = sps.lognorm.ppf(x, mean.right, var.left)
    bound2 = sps.lognorm.ppf(x, mean.left, var.right)
    bound3 = sps.lognorm.ppf(x, mean.right, var.right)

    Left = [min(bound0[i],bound1[i],bound2[i],bound3[i]) for i in range(steps)]
    Right = [max(bound0[i],bound1[i],bound2[i],bound3[i]) for i in range(steps)]

    Left = np.array(Left)
    Right = np.array(Right)
    return Pbox(
        Left,
        Right,
        steps = steps,
        shape='lognormal',
        mean_left=mean.left,
        mean_right=mean.right,
        var_left=var.left,
        var_right=var.right)
lognorm = lognormal

def alpha(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('alpha',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'alpha',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def anglit(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('anglit',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'anglit',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def arcsine(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('arcsine',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'arcsine',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def argus(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('argus',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'argus',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def beta(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('beta',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'beta',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def betaprime(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('betaprime',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'betaprime',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def bradford(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('bradford',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'bradford',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def burr(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('burr',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'burr',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def burr12(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('burr12',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'burr12',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def cauchy(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('cauchy',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'cauchy',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def chi(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('chi',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'chi',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def chi2(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('chi2',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'chi2',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def cosine(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('cosine',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'cosine',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def crystalball(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('crystalball',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'crystalball',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def dgamma(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('dgamma',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'dgamma',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def dweibull(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('dweibull',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'dweibull',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def erlang(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('erlang',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'erlang',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def expon(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('expon',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'expon',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def exponnorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('exponnorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'exponnorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def exponweib(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('exponweib',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'exponweib',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def exponpow(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('exponpow',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'exponpow',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def f(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('f',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'f',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def fatiguelife(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('fatiguelife',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'fatiguelife',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def fisk(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('fisk',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'fisk',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def foldcauchy(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('foldcauchy',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'foldcauchy',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def foldnorm(mu,s, steps = 200):

    x = np.linspace(0.0001,0.9999,steps)
    if mu.__class__.__name__ != 'Interval':
        mu = Interval(mu)
    if s.__class__.__name__ != 'Interval':
        s = Interval(s)

    new_args = [
        [mu.lo()/s.lo(),0,s.lo()],
        [mu.hi()/s.lo(),0,s.lo()],
        [mu.lo()/s.hi(),0,s.hi()],
        [mu.hi()/s.hi(),0,s.hi()]
    ]

    bounds = []

    mean_hi = -np.inf
    mean_lo = np.inf
    var_lo = np.inf
    var_hi = 0

    for a in new_args:

        bounds.append(sps.foldnorm.ppf(x,*a))
        bmean, bvar = sps.foldnorm.stats(*a, moments = 'mv')

        if bmean < mean_lo:
            mean_lo = bmean
        if bmean > mean_hi:
            mean_hi = bmean
        if bvar > var_hi:
            var_hi = bvar
        if bvar < var_lo:
            var_lo = bvar

    Left = [min([b[i] for b in bounds]) for i in range(steps)]
    Right = [max([b[i] for b in bounds]) for i in range(steps)]

    var  = Interval(np.float64(var_lo),np.float64(var_hi))
    mean = Interval(np.float64(mean_lo),np.float64(mean_hi))

    Left = np.array(Left)
    Right = np.array(Right)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'foldnorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

# def frechet_r(*args, steps = 200):
#     args = list(args)
#     for i in range(0,len(args)):
#         if args[i].__class__.__name__ != 'Interval':
#             args[i] = Interval(args[i])

#     Left, Right, mean, var = __get_bounds('frechet_r',steps,*args)

#     return Pbox(
#           Left,
#           Right,
#           steps      = steps,
#           shape      = 'frechet_r',
#           mean_left  = mean.left,
#           mean_right = mean.right,
#           var_left   = var.left,
#           var_right  = var.right
#           )

# def frechet_l(*args, steps = 200):
#     args = list(args)
#     for i in range(0,len(args)):
#         if args[i].__class__.__name__ != 'Interval':
#             args[i] = Interval(args[i])

#     Left, Right, mean, var = __get_bounds('frechet_l',steps,*args)

#     return Pbox(
#           Left,
#           Right,
#           steps      = steps,
#           shape      = 'frechet_l',
#           mean_left  = mean.left,
#           mean_right = mean.right,
#           var_left   = var.left,
#           var_right  = var.right
#           )

def genlogistic(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('genlogistic',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'genlogistic',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gennorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gennorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gennorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def genpareto(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('genpareto',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'genpareto',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def genexpon(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('genexpon',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'genexpon',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def genextreme(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('genextreme',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'genextreme',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gausshyper(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gausshyper',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gausshyper',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gamma(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gamma',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gamma',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gengamma(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gengamma',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gengamma',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def genhalflogistic(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('genhalflogistic',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'genhalflogistic',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def geninvgauss(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('geninvgauss',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'geninvgauss',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gilbrat(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gilbrat',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gilbrat',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gompertz(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gompertz',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gompertz',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gumbel_r(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gumbel_r',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gumbel_r',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def gumbel_l(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('gumbel_l',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'gumbel_l',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def halfcauchy(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('halfcauchy',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'halfcauchy',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def halflogistic(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('halflogistic',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'halflogistic',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def halfnorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('halfnorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'halfnorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def halfgennorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('halfgennorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'halfgennorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def hypsecant(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('hypsecant',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'hypsecant',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def invgamma(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('invgamma',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'invgamma',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def invgauss(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('invgauss',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'invgauss',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def invweibull(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('invweibull',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'invweibull',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def johnsonsb(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('johnsonsb',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'johnsonsb',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def johnsonsu(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('johnsonsu',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'johnsonsu',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def kappa4(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('kappa4',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'kappa4',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def kappa3(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('kappa3',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'kappa3',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def ksone(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('ksone',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'ksone',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def kstwobign(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('kstwobign',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'kstwobign',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def laplace(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('laplace',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'laplace',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def levy(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('levy',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'levy',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def levy_l(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('levy_l',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'levy_l',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def levy_stable(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('levy_stable',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'levy_stable',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def logistic(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('logistic',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'logistic',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def loggamma(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('loggamma',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'loggamma',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def loglaplace(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('loglaplace',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'loglaplace',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def lognorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('lognorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'lognorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def loguniform(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('loguniform',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'loguniform',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def lomax(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('lomax',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'lomax',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def maxwell(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('maxwell',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'maxwell',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def mielke(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('mielke',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'mielke',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def moyal(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('moyal',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'moyal',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def nakagami(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('nakagami',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'nakagami',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def ncx2(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('ncx2',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'ncx2',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def ncf(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('ncf',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'ncf',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def nct(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('nct',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'nct',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def norm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('norm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'norm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def norminvgauss(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('norminvgauss',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'norminvgauss',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def pareto(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('pareto',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'pareto',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def pearson3(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('pearson3',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'pearson3',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def powerlaw(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('powerlaw',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'powerlaw',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def powerlognorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('powerlognorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'powerlognorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def powernorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('powernorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'powernorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def rdist(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('rdist',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'rdist',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def rayleigh(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('rayleigh',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'rayleigh',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def rice(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('rice',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'rice',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def recipinvgauss(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('recipinvgauss',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'recipinvgauss',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def semicircular(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('semicircular',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'semicircular',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def skewnorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('skewnorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'skewnorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def t(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('t',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 't',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def trapz(a,b,c,d , steps = 200):
    if a.__class__.__name__ != 'Interval':
        a = Interval(a)
    if b.__class__.__name__ != 'Interval':
        b = Interval(b)
    if c.__class__.__name__ != 'Interval':
        c = Interval(c)
    if d.__class__.__name__ != 'Interval':
        d = Interval(d)

    x = np.linspace(0.0001,0.9999,steps)
    left = sps.trapz.ppf(x,b.lo()/d.lo(),c.lo()/d.lo(),a.lo(),d.lo()-a.lo())
    right = sps.trapz.ppf(x,b.hi()/d.hi(),c.hi()/d.hi(),a.hi(),d.hi()-a.hi())

    return Pbox(
          left,
          right,
          steps      = steps,
          shape      = 'trapz'
          )

def triang(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('triang',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'triang',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def truncexpon(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('truncexpon',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'truncexpon',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def truncnorm(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('truncnorm',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'truncnorm',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def tukeylambda(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('tukeylambda',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'tukeylambda',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def uniform(a, b, steps = 200):

    x = np.linspace(0,1,steps)

    if a.__class__.__name__ != 'Interval':
        a = Interval(a,a)
    if b.__class__.__name__ != 'Interval':
        b = Interval(b,b)

    Left = np.linspace(a.left,b.left)
    Right = np.linspace(a.right,b.right)

    mean = 0.5 * (a+b)
    var = ((b-a)**2 )/12

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'uniform',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def vonmises(*args, steps = Pbox.STEPS):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('vonmises',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'vonmises',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def vonmises_line(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('vonmises_line',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'vonmises_line',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def wald(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('wald',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'wald',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def weibull_min(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('weibull_min',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'weibull_min',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def weibull_max(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('weibull_max',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'weibull_max',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def wrapcauchy(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('wrapcauchy',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'wrapcauchy',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def bernoulli(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('bernoulli',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'bernoulli',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def betabinom(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('betabinom',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'betabinom',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def binom(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('binom',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'binom',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def boltzmann(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('boltzmann',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'boltzmann',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def dlaplace(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('dlaplace',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'dlaplace',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def geom(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('geom',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'geom',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def hypergeom(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('hypergeom',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'hypergeom',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def logser(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('logser',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'logser',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def nbinom(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('nbinom',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'nbinom',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def planck(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('planck',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'planck',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def poisson(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('poisson',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'poisson',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def randint(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('randint',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'randint',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def skellam(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('skellam',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'skellam',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def zipf(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('zipf',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'zipf',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

def yulesimon(*args, steps = 200):
    args = list(args)
    for i in range(0,len(args)):
        if args[i].__class__.__name__ != 'Interval':
            args[i] = Interval(args[i])

    Left, Right, mean, var = __get_bounds('yulesimon',steps,*args)

    return Pbox(
          Left,
          Right,
          steps      = steps,
          shape      = 'yulesimon',
          mean_left  = mean.left,
          mean_right = mean.right,
          var_left   = var.left,
          var_right  = var.right
          )

### Other distributions
def KM(k,m,steps = 200):
    return beta(Interval(k,k+1),Interval(m,m+1),steps = steps)

def KN(k,n,steps = 200):
    return KM(k,n-k,steps=steps)

### Alternate names
normal = norm
N = normal
unif = uniform
U = uniform


import numpy as np
import itertools
import raam
import pickle
from raam import features
from scipy import cluster
from warnings import warn

def degradation(fun,charge,discharge):
    """
    Constructs a capacity degradation function from given parameters.

    The functions are D- and D+: the anti-derivatives. See the paper for more details

    Parameters
    ----------
    fun : {"polynomial"}
        Type of the function
    charge : object
        List of parameters for the function.
        polynomial: (charge,discharge)
            parameters are the coefficients with low to high order
    discharge : object
        List of parameters for the function.
        polynomial: (charge,discharge)
            parameters are the coefficients with low to high order

    Returns
    -------
    out : function (x1,x2)
        x1: initial level
        x2: new level
    """
    if fun == 'polynomial':
        charge_a = np.array(charge)
        discharge_a = np.array(discharge)
        def polynomial(x1,x2):
            if x1 <= x2:    # charge
                # intentional x2 - x1
                return np.polynomial.polynomial.polyval(x2,charge_a) - np.polynomial.polynomial.polyval(x1,charge_a)
            else:           # discharge
                # intentional: x1 - x2
                return np.polynomial.polynomial.polyval(x1,discharge_a) - np.polynomial.polynomial.polyval(x2,discharge_a)
        return polynomial
    else:
        raise ValueError('Incorrect function type: "%s"' % funtype)

DefaultConfiguration = {
    "price_buy" : [1.2,2.1,3.3],
    "price_sell" : [1,2,3],
    "price_probabilities" : np.array([[0.8, 0.1, 0.1],[0.1, 0.8, 0.1],[0.1, 0.1, 0.8]]),
    "initial_capacity" : 1,
    "initial_inventory" : 0.5,
    "degradation" : {"fun":"polynomial","charge":[0,0,0.01],"discharge":[0.01,0.02,0.01]},
    "capacity_cost" : 1,
    "change_capacity" : True,
    "charge_limit" : 1, # limit on charge in a single step
    "discharge_limit" : 1  # limit on discharge in a single step, absolute value
    }

class Simulator(raam.Simulator):
    """
    Simulates the evolution of the inventory, the total capacity, and the price
    levels. The prices come from a Markov chain process.

    The initial state is generated from an expectation state

    State (tuple):
        - inventory
        - capacity
        - priceindex

    Action: charge change
        This is not an index but an absolute value of the change in charge

    Parameters
    ----------
    config : dict
        Configuration. See DefaultConfiguration for an example
    discount : float, optional
        Discount factor
    action_cnt : int, optional
        Number of ticks for discretizing actions.
    inventory_cnt : int, optional
        Number of ticks for discretizing inventory states.
    capacity_cnt : int, optional
        Discretization set for storage capacity states.
        This step must be fine enough to capture the small
        change in capacities
    """

    def __init__(self,config,discount=0.9999,action_cnt=20,inventory_cnt=100,\
                    capacity_cnt=100):
        self._discount = discount
        self._action_cnt = action_cnt
        self._inventory_cnt = inventory_cnt
        self._capacity_cnt = capacity_cnt

        self.degradation = degradation(**config['degradation'])
        self.initial_capacity = config['initial_capacity']
        self.price_buy = config['price_buy']
        self.price_sell = config['price_sell']
        self.price_probabilities = config['price_probabilities']
        self.capacity_cost = config['capacity_cost']
        self.change_capacity = config['change_capacity']
        self.initial_inventory = config['initial_inventory']

        if 'charge_limit' not in config:
            warn('No charge_limit in config, using 1')
        if 'discharge_limit' not in config:
            warn('No discharge_limit in config, using 1')
        self.charge_limit = config['charge_limit'] if 'charge_limit' in config else 1
        self.discharge_limit = config['discharge_limit'] if 'discharge_limit' in config else 1

        # state and the distributions
        self._all_states = None
        self._initial_distribution = None

        assert np.all(np.array(self.price_buy) >= 0)
        assert np.all(np.array(self.price_sell) >= 0)
        assert len(self.price_buy) == len(self.price_sell)
        assert self.price_probabilities.shape[0] == self.price_probabilities.shape[1] == len(self.price_sell)
        assert np.max(np.abs(np.sum(self.price_probabilities,1) - 1)) < 0.01
        assert np.all(np.array(self.price_probabilities) >= 0)
        assert self.capacity_cost >= 0
        assert type(self.change_capacity) is bool
        assert self.initial_inventory <= self.initial_capacity and self.initial_inventory >= 0

    @property
    def discount(self):
        return self._discount

    def get_stateindex(self, decstate):
        """
        Finds the index of the state in the list returned by all_states
        """
        # lazy initialization
        if self._all_states is None:
            self.all_states()

        return self._aggindex2stateindex[self._state_aggregation.classify(decstate)]

    def all_states(self):
        """
        Returns all states (quantized according to the parameters provided in the constructor)

        There is no iteration over capacities if self.change_capacity = False and it is
        fixed to be 0.

        Important: Use self.get_stateindex() to get index of a state instead of searching this list.
        It is much more efficient.

        Returns
        -------
        out : np.ndarray
            List of all states
        """

        # lazy initialization
        if self._all_states is None:

            pricecount = len(self.price_buy)

            # if the capacity does not change, then aggregate the capacity dimension to only one value
            if self.change_capacity:
                # make sure that the centers of price clusters are integer numbers
                self._state_aggregation = raam.features.GridAggregation(\
                         ((0,self.initial_capacity), (0,self.initial_capacity), (-0.5,pricecount-0.5)), \
                         (self._inventory_cnt, self._capacity_cnt, pricecount)  )
            else:
                self._state_aggregation = raam.features.GridAggregation(\
                         ((0,self.initial_capacity), (self.initial_capacity-0.1,self.initial_capacity+0.1), (-0.5,pricecount-0.5)), \
                         (self._inventory_cnt, 1, pricecount)  )

            centers = list(self._state_aggregation.centers())

            # select only states that have smaller inventory than capacity,
            # and construct the mapping from aggregation index to the index in all_states
            self._all_states, stateindex2aggindex = \
                zip(*( (s,i) for i,s in enumerate(centers) \
                        if s[0] <= s[1]))

            # invert the aggregation index
            self._aggindex2stateindex = [None] * len(centers)
            for si,ai in enumerate(stateindex2aggindex):
                self._aggindex2stateindex[ai] = si

        return self._all_states

    def initial_distribution(self):
        """
        Returns initial distributions over states returned be all_states

        Returns
        -------
        out : np.ndarray
            Initial distribution
        """

        # lazy initialization
        if self._initial_distribution is None:
            from scipy import cluster

            allstates = self.all_states()
            initialstate = self.initstates().__next__()

            init_index = self.get_stateindex(initialstate)

            distribution = np.zeros(len(allstates))
            distribution[init_index] = 1.0

            self._initial_distribution = distribution

        return self._initial_distribution

    def all_transitions_continuous(self, decstate, action):
        """
        Returns all transitions and probabilities for the given state and action.

        The returned states are continuous and are not quantized according to
        self._all_states()

        Returns
        -------
        out : list
            Sequence of tuples: (nextstate, probability, reward)
        """

        inventory, capacity, priceindex = decstate
        priceindex = int(priceindex)

        assert(inventory >= 0 and inventory <= capacity)

        # determine buy and sell prices
        pricesell = self.price_sell[priceindex]
        pricebuy = self.price_buy[priceindex]

        # trim action based on the current inventory
        action = max(action, - inventory)
        action = min(action, capacity - inventory)

        # update the next inventory based on the action
        ninventory = inventory + action

        # compute capacity loss
        capacity_loss = self.degradation(inventory / capacity, ninventory / capacity) * capacity
        assert capacity_loss >= -1e-10, 'Cannot have negative capacity loss'

        if self.change_capacity:
            ncapacity = max(0,capacity - capacity_loss)
            ninventory = min(ninventory, ncapacity)
        else:
            ncapacity = capacity

        # compute the reward for the transition
        reward = - (pricebuy if action >= 0 else pricesell) * action
        reward -= capacity_loss * self.capacity_cost

        # sample the next price index
        return (((ninventory,ncapacity,npriceindex),probability,reward) \
                for npriceindex, probability in \
                    enumerate(self.price_probabilities[priceindex,:]) if probability > 0)

    def all_transitions(self, stateindex, actionindex):
        """
        Returns all transitions and probabilities for the given state and action.

        The returned states are continuous and are not quantized according to
        self._all_states()

        Parameters
        ----------
        stateindex : int
            Index of the state in the list returned by all_states
        actionindex : int
            Index of the action in the list returned by actions

        Returns
        -------
        out : sequence
            Sequence of tuples: (nextstate, probability, reward)
        """
        allstates = self.all_states()
        decstate = allstates[stateindex]

        allactions = self.actions(decstate)
        action = allactions[actionindex]

        # map transitions to the state indexes
        return [(self.get_stateindex(s),p,r)
            for s,p,r in
                self.all_transitions_continuous(decstate, action)]

    def transition(self,decstate,action):
        """
        Represents a transition from a state.

        Charging over the available capacity, or discharging below empty is not possible.
        Any action that attempts to do that is automatically limited to the capacity.

        Parameters
        ----------
        decstate : state
            inventory,capacity,priceindex
        action : float
            change in charge (this is a float value, not the index)

        Returns
        -------
        out : expectation state
            inventory,capacity,reward
        """
        #TODO: replace by a call to all_transitions
        inventory, capacity, priceindex = decstate
        assert inventory >= 0 and inventory <= capacity

        # determine buy and sell prices
        pricesell = self.price_sell[priceindex]
        pricebuy = self.price_buy[priceindex]

        # trim action based on the current inventory
        action = max(action, - inventory)
        action = min(action, capacity - inventory)

        # update the next inventory based on the action
        ninventory = inventory + action

        # compute capacity loss
        capacity_loss = self.degradation(inventory / capacity, ninventory / capacity) * capacity
        assert capacity_loss >= -1e-10, 'Cannot have negative capacity loss'

        if self.change_capacity:
            ncapacity = max(0,capacity - capacity_loss)
            ninventory = min(ninventory, ncapacity)
        else:
            ncapacity = capacity

        # compute the reward for the transition
        reward = - (pricebuy if action >= 0 else pricesell) * action
        reward -= capacity_loss * self.capacity_cost

        # sample the next price index
        pricecount = self.price_probabilities.shape[1]
        npriceindex = np.random.choice(\
            np.arange(pricecount,dtype=int), \
            p=self.price_probabilities[priceindex,:])

        return (reward,(ninventory,ncapacity,npriceindex))

    def actions(self, state):
        """
        List of applicable actions in the state. Relative change
        in capacity
        """
        inventory, capacity, _ = state

        discharge_floor = max(-inventory,-self.discharge_limit)
        charge_ceil = min(capacity - inventory,self.charge_limit)

        return np.linspace(discharge_floor, charge_ceil, self._action_cnt)

    def initstates(self):
        """ The initial state is given by the configuration and the 1st state of the
        price process. """
        return itertools.repeat( (self.initial_inventory,self.initial_capacity,0) )

    def price_levels(self):
        """ Returns the number of price states in the Markov model """
        return self.price_probabilities.shape[0]

class Features:
    """
    Suitable features for inventory management
    """
    linear = (features.piecewise_linear(None), features.piecewise_linear(None))

## Threshold policy definitions
def threshold_policy(lowers, uppers, simulator):
    """
    Construct a threshold policy with different thresholds for different price
    indexes.

    Assumes that the capacity of the battery does not change.

    Lower is the lower inventory target, and upper is the upper inventory target

    Parameters
    ----------
    lowers : list
        List of lower thresholds
    uppers : list
        List of upper thresholds
    simulator : inventory.Simulator
        Simulator of the inventory problem (used to determine available actions)
    """
    assert len(lowers) == len(uppers)
    assert np.min(uppers - lowers) >= -1e-4

    def policy(state):
        inventory,capacity,priceindex = state

        # compute the target charge change
        if inventory < lowers[priceindex]:
            target = lowers[priceindex] - inventory # the target charge change
        elif inventory > uppers[priceindex]:
            target = uppers[priceindex] - inventory # the target charge change
        else:
            # if it is between the thresholds, then there is no change
            target = 0

        # find the closest (discretized) action
        actions = simulator.actions(state)
        actionindex = np.argmin(np.abs(actions - target))
        return actions[actionindex]

    return policy

## Threshold Optimization Functions

import math
import random

def _eval_dimchange(sim,lowers,uppers,dim,l,u,horizon,runs):
    """ Evaluates the dimension change impact """
    dim_lowers = lowers.copy()
    dim_uppers = uppers.copy()

    dim_lowers[dim] = l
    dim_uppers[dim] = u

    policy = raam.examples.inventory.threshold_policy(dim_lowers, dim_uppers, sim)

    # Common random numbers for the evaluation!
    np.random.seed(0)
    random.seed(0)

    samples = sim.simulate(horizon,policy,runs)

    print('.', end='')
    return samples.statistics(sim.discount)['mean_return']

def optimize_jointly(sim,step=0.1,horizon=600,runs=5):
    """
    Jointly optimizes uppen and lower thresholds for charging and discharging for
    each dimension.

    It can be shown (a publication pending) that this method will compute
    the optimal solution when there is no degradation in the battery.
    """

    values = [(l,u) for l in np.arange(0,1+step/2,step) for u in np.arange(l,1+step/2,step) ]

    # copy the lower and upper bounds
    lowers = np.zeros(len(sim.price_buy))    # lower thresholds
    uppers = np.ones(len(sim.price_buy))     # upper thresholds

    for iteration in range(10):
        print('Lowers', lowers)
        print('Uppers', uppers)

        for dimension in range(len(sim.price_sell)):
            print('Dimension', dimension)
            returns = [_eval_dimchange(sim,lowers,uppers,dimension,l,u,horizon,runs) for (l,u) in values]

            maxindex = np.argmax(returns)

            print('\n', returns[maxindex])
            l,u = values[maxindex]
            lowers[dimension] = l
            uppers[dimension] = u

    print('Lowers', lowers)
    print('Uppers', uppers)

def optimize_independently(sim,step=0.1,horizon=600,runs=5):
    """
    Optimizes the upper and lower thresholds independently. It is not clear
    that this method actually computes the optimal policy
    """

    epsilon = 1e-6                  # small value to deal with numertical issues

    # copy the lower and upper bounds
    lowers = 0.5*np.ones(len(sim.price_buy))    # lower thresholds
    uppers = 0.5*np.ones(len(sim.price_buy))     # upper thresholds

    for iteration in range(10):
        print('Lowers', lowers)
        print('Uppers', uppers)

        weight = 1.0 / math.sqrt(iteration + 1)

        for dimension in range(len(sim.price_sell)):
            print('Dimension', dimension)

            print('   lowers')
            values = np.arange(0,1+_epsilon,step)
            if len(values) > 0:
                returns = [_eval_dimchange(sim,lowers,uppers,dimension,\
                            l,max(l,uppers[dimension]),horizon,runs)\
                             for l in values]
                maxindex = np.argmax(returns)
                l = values[maxindex]
                lowers[dimension] = weight * l + (1-weight)*lowers[dimension]
                uppers[dimension] = max(uppers[dimension],lowers[dimension])
                assert lowers[dimension] <= uppers[dimension]

                print('\n',returns[maxindex])

            print('\n   uppers')
            values = np.arange(0,1+_epsilon,step)
            if len(values) > 0:
                returns = [_eval_dimchange(sim,lowers,uppers,dimension,\
                            min(lowers[dimension],u),u,horizon,runs) \
                            for u in values]
                maxindex = np.argmax(returns)
                u = values[maxindex]
                uppers[dimension] = weight*u + (1-weight)*uppers[dimension]
                lowers[dimension] = min(lowers[dimension],uppers[dimension])
                assert lowers[dimension] <= uppers[dimension]

                print('\n',returns[maxindex])

    print('Lowers', lowers)
    print('Uppers', uppers)

## Plotting functions

def plot_degradation(degrad, ex_inventories = [0.1,0.5,0.9],delta=None):
    """
    Plots the degradation function for examples of the current inventory

    Parameters
    ----------
    degrad : fun
        Degradation function, the output of :fun:`degradation`
    ex_inventories : list, optional
        List of example inventories to use for plotting
    delta : dict
        Two delta functions (the derivative of the degradation)
    """

    import matplotlib.pyplot as pp

    x = np.linspace(0,1,100)

    #ax1 = pp.subplot()

    for ei in ex_inventories:
        y = np.array([degrad(ei, ix) for ix in x])
        pp.plot(100*x,100*y,label="$d(x,y-x)$,$x=%2.0f\\%%$" % (100*ei))

    #ax2 = ax1.twinx()

    if delta is not None:
        pp.plot(100*x, 100*delta['charge'](x), '.', label='$\\delta_+$')
        pp.plot(100*x, 100*delta['discharge'](x), '.', label='$\\delta_-$')

    pp.xlabel('New State of Charge (%): $y$')
    pp.ylabel('Capacity Loss (%): $d(x,y-x)$')

    pp.legend(loc=9)
    pp.grid()


<gh_stars>100-1000
# Copyright (c) 2020 Graphcore Ltd. All rights reserved.
import numpy as np
import scipy as sp
from scipy import sparse
import os
import ctypes
import popart

so_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                       "../../custom_ops.so")
ctypes.cdll.LoadLibrary(so_path)

"""
This example presents the extension of the sparse_softmax_subblock_demo.py example to multiple inputs
In NLP the attention matrix will have dims [batch_size, heads, n_sequence, n_sequence]
Where each batch item and head may have a different sparsity pattern.

In this example there are two attention heads each with a different mask (and number of active blocks)
"""

# INPUT DATA
n_windows = 2
n_sequence = 256
window_size = n_sequence//n_windows
blocksize = [16, 16]

def type1_mask(window_size, n_windows, blocksize_x):
    # Add the sparsity for the first attention head (autoregressive windows)
    auto_mask = sp.sparse.tril(np.ones([window_size, window_size]), k = 0)
    summary_mask = sp.sparse.lil_matrix((window_size, window_size))
    summary_mask[:, window_size-blocksize_x:] = 1
    global_mask = sp.sparse.kron(sp.sparse.tril(np.ones([n_windows, n_windows]), k = -1), summary_mask)
    global_mask = (global_mask + sp.sparse.kron(sp.sparse.eye(n_windows), auto_mask)).sign()
    return global_mask

def type2_mask(n_sequence, blocksize_x):
    # Local mask attends to local block plus one backward (a bit like reformer)
    # Autoregressive block on diagonal
    A = np.expand_dims(sp.sparse.tril(np.ones((blocksize_x, blocksize_x)), 0).toarray(), 0)
    A = A.repeat(n_sequence//blocksize_x, axis = 0)
    mask = sp.sparse.block_diag(A)

    # Add full blocks on the -1 diagonal
    C = sp.sparse.dia_matrix((np.ones((1, n_sequence)), [-1]), shape=[n_sequence//blocksize_x]*2)
    mask += sp.sparse.kron(C, np.ones((blocksize_x, blocksize_x)))
    return mask

def mask_to_blocks(global_mask, blocksize):
    # Get the block sparse format
    bsr = sp.sparse.bsr_matrix(global_mask, blocksize = blocksize)
    bsr.eliminate_zeros()  # need to call this to eliminate blocks of all zeros

    # The dense blocks
    blocks = np.reshape(bsr.data, [bsr.data.shape[0], -1])
    blocks = sp.float32(list(blocks))

    # Dense mask for each active block
    mask_data = np.array([[[1]]]*len(bsr.indices))
    active_mask = sp.sparse.bsr_matrix((mask_data, bsr.indices, bsr.indptr)).toarray()
    active_mask = list(active_mask.flatten())
    return blocks, active_mask

# Get the two attention patterns
head1_blocks, head1_sparsity = mask_to_blocks(type1_mask(window_size, n_windows, blocksize[0]), blocksize)
head2_blocks, head2_sparsity = mask_to_blocks(type2_mask(n_sequence, blocksize[0]), blocksize)

def concat(h1, h2):
    out = np.concatenate((h1, h2), 0)
    out = np.tile(out, [2, 1])
    return out

# Build a matrix which is [2, 2, 256, 256] (B, H, S, S)
matrix_dims = [2, 2, n_sequence, n_sequence]
input_blocks = concat(head1_blocks, head2_blocks)
sparsity = np.tile([*head1_sparsity, *head2_sparsity], 2)
# There are 4 groups in total (B*H)
group_sizes = np.tile([len(head1_blocks), len(head2_blocks)], 2)
# note that group_sizes are equal to [80, 31, 80, 31]

# #### MODEL CREATION ####
builder = popart.Builder()
logits = np.array(list(input_blocks), dtype = sp.float32)
logits = builder.addInitializedInputTensor(logits, "logits")

probs = builder.customOp(opName = "BsSoftmax",
                         opVersion = 1,
                         domain = "ai.graphcore",
                         inputs = [logits],
                         attributes = {
                          "matrixDims": matrix_dims,
                          "blockSize": blocksize,
                          "sparsity": sparsity.tolist(),
                          "groupSizes": group_sizes.tolist(),
                          "subBlockMaskPerGroup": "[ZeroUpperTriangle, ZeroUpperTriangle, ZeroUpperTriangle, ZeroUpperTriangle]"
                         })[0]
dlogits = popart.reservedGradientPrefix() + logits  # the gradient tensor's name
upstream_grad = popart.reservedGradientPrefix() + probs  # the gradient tensor's name

# Make some blocks to regress agains just so there are gradients
expected_tokens = np.zeros_like(input_blocks) + np.eye(16).flatten()
expected_tokens = -sp.float32(np.array(list(expected_tokens)))  # negative sign for negative logprob
expected_tokens = builder.aiOnnx.constant(expected_tokens, 'expected_tokens')

pbias = builder.aiOnnx.constant(np.zeros([1, input_blocks.shape[-1]], dtype=np.float32)+1e-6, 'pbias')
biased_probs = builder.aiOnnx.add([probs, pbias])
logprobs = builder.aiOnnx.log([biased_probs])

out = builder.aiOnnx.mul([logprobs, expected_tokens])
loss = builder.aiGraphcore.l1loss([out], 1.0)

# Describe how to run the model
anchor_desc = {probs: popart.AnchorReturnType("ALL"), dlogits: popart.AnchorReturnType("ALL"), upstream_grad: popart.AnchorReturnType("ALL")}
dataFlow = popart.DataFlow(1, anchor_desc)

session = popart.TrainingSession(fnModel = builder.getModelProto(),
                                 loss = loss,
                                 deviceInfo = popart.DeviceManager().acquireAvailableDevice(1),
                                 optimizer = popart.ConstSGD(0.01),
                                 dataFlow = dataFlow)

# Compile graph
session.prepareDevice()

# Create buffers to receive results from the execution
anchors = session.initAnchorArrays()

# TRAINING
session.weightsFromHost()
stepio = popart.PyStepIO({}, anchors)
session.run(stepio)
print("Mean max grad of each row: ", np.mean(np.max(anchors[dlogits].reshape([-1, *blocksize]), axis = -1)))


"""
Utility functions needed to compute the HP4-EDA descriptors

@author: <NAME> (<EMAIL>)
"""
# ------------------------------------------------------------------------------------------------- #
#Import the needed library.
import numpy as np
import glob, re
from scipy.spatial.distance import pdist, squareform

# ------------------------------------------------------------------------------------------------- #
#Generic Function that returns a SORTED list of ALL filenames in a given directory(path), in ascending order.
def sort_directoryfiles(path, ext):
    '''
    INPUTS:
		(i) path: (Path to the directory containing files. Example: 'c:/ekpo/dataset/')
		(ii) ext: (Extension of the files we intend to sort. Example: '.txt')
    OUTPUTS:
		(i) sortedf: (List of ONLY filenames in 'path'. Example: '1.txt, 2.txt, ..., 9.txt, 10.txt, 11.txt ..., 99.txt, 100.txt,')

    AUTHOR: <NAME> (<EMAIL>)
    '''
    filepaths = glob.glob1(path,'*'+ ext)
    sortedf = sorted(filepaths, key=lambda x:float(re.findall("(\d+)",x)[0]))
    sortedpath = []
    for i in np.arange(0, len(sortedf)):
        fullpath = path + sortedf[i]
        sortedpath.append(fullpath)
    return sortedpath

# ------------------------------------------------------------------------------------------------- #
def compare_allDescriptors_EMD(allDescriptors, outputdir, save_matrix_as = True):
	'''
	PURPOSE:
	To compute the EMD (Wasserstein Distance) between a set of shape-descriptors.

	INPUTS:
		(i) allDescriptors: An [M x K] matrix of all descriptors, where M is the total number of models/objects in the database, and K is the length of each descriptor.
		(ii) outputdir: Location or Directory, where the output to this function would be saved in. E.g: outputdir = "c:/myPyGraphics/2018_Research_Implementations/5_May_2018/ekpoMayImplementations/"
		(iii) save_matrix_as: Default(None): If 'save_matrix_as' is given, This MUST be 'STRING' input, and this function adds the '.matrix' extension. E.g: "jaccardDistanceMatrix_spbLSD.txt"
	OUTPUT:
		(i) Dist_matrix: An N x N matrix, where the ij entry is the wasserstein_distance between the shape-descriptor for point cloud i and point cloud j.
	AUTHOR: <NAME> (<EMAIL>)
	'''
	pwdist = pdist(allDescriptors, wasserstein_distance)
	Dist_matrix = squareform(pwdist)
	if save_matrix_as:
		descrpath = outputdir + save_matrix_as + ".matrix"
		np.savetxt(descrpath, Dist_matrix, fmt='%f')

	return Dist_matrix
# ------------------------------------------------------------------------------------------------- #

def compare_allDescriptors_kld(allDescriptors, outputdir, save_matrix_as = True):
	'''
	PURPOSE:
	To compute the Kullback Leibner Divergence Similarity/Distance between a set of shape-descriptors.

	INPUTS:
	allDescriptors: An MxK matrix of all descriptors, where M is the total number of models(3D meshes) in the database, and K is the length of each descriptor.
	outputdir: Location or Directory, where the output to this function would be saved in. E.g: outputdir = "c:/myPyGraphics/2018_Research_Implementations/5_May_2018/ekpoMayImplementations/"
	save_matrix_as: Default(None): If 'save_matrix_as' is given, This MUST be 'STRING' input, and this function adds '.txt' extension. E.g: "kldDistanceMatrix_spbLSD.txt"

	OUTPUT:
	Dist_matrix: An N x N matrix, where the ij entry is the KLD distance between the shape-descriptor for point cloud i and point cloud j.
	'''
	pw_dist = pdist(allDescriptors, kullback_divergence)
	Dist_matrix = squareform(pw_dist)
	if save_matrix_as:
		descrpath = outputdir + save_matrix_as + ".matrix"
		np.savetxt(descrpath, Dist_matrix, fmt='%f')

	return Dist_matrix


#IMPORTING LIBRARIES
import numpy as np
import pandas as pd
import os
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from statistics import mean
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
from scipy import stats
import random
from matplotlib import rcParams
import re
import matplotlib.pyplot as plt
import seaborn as sns
import tensorflow as tf
import tensorflowjs as tfjs
from tensorflow.keras import models, regularizers
import cv2
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D,MaxPool2D,Dropout,Flatten,Dense,BatchNormalization
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import EarlyStopping
from main_module import get_acc,model,split  #IMPORTING FROM main_module.py
import warnings

#######################CONFIG_ONLY########################################

#SETTING UP SOME CONFIG
warnings.filterwarnings("ignore")
pd.pandas.set_option('display.max_columns',None)
pd.pandas.set_option('display.max_rows',None)

#CHECKING TF VERSIONS
print("tf version : {}".format(tf.__version__)) #IN MY CASE ITS 2.3+
print("tfjs version : {}".format(tfjs.__version__)) #IN MY CASE ITS 2.7.0

#SEEDING EVERYTHING
def seed_everything(seed):
    np.random.seed(seed)
    tf.random.set_seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    os.environ['TF_DETERMINISTIC_OPS'] = '1'
    os.environ['TF_KERAS'] = '1'

SEED = 42
seed_everything(SEED)

width = 128
height = 128

#######################CONFIG_ONLY########################################

#FOR GENERATING DATA ON THE FLY
datagen = ImageDataGenerator(rescale=1/255.0, validation_split=0.2)

trainDatagen = datagen.flow_from_directory(directory='../input/cell-images-for-detecting-malaria/cell_images/cell_images/',
                                           target_size=(width,height),
                                           class_mode = 'binary',
                                           batch_size = 16,
                                           subset='training')

valDatagen = datagen.flow_from_directory(directory='../input/cell-images-for-detecting-malaria/cell_images/cell_images/',
                                           target_size=(width,height),
                                           class_mode = 'binary',
                                           batch_size = 16,
                                           subset='validation')

#MODEL ARCHITECTURE
model = Sequential()
model.add(Conv2D(16,(3,3),activation='relu',input_shape=(128,128,3)))
model.add(MaxPool2D(2,2))
model.add(Dropout(0.2))

model.add(Conv2D(32,(3,3),activation='relu'))
model.add(MaxPool2D(2,2))
model.add(Dropout(0.3))

model.add(Conv2D(64,(3,3),activation='relu'))
model.add(MaxPool2D(2,2))
model.add(Dropout(0.3))

model.add(Flatten())
model.add(Dense(64,activation='relu'))
model.add(Dropout(0.5))

model.add(Dense(1,activation='sigmoid'))
model.summary()

model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy'])

#ADDING EARLYSTOP
early_stop = EarlyStopping(monitor='val_loss',patience=2)

# FITTING DATA TO THE MODEL
history = model.fit_generator(generator = trainDatagen,
                             steps_per_epoch = len(trainDatagen),
                              epochs =20,
                              validation_data = valDatagen,
                              validation_steps=len(valDatagen),
                             callbacks=[early_stop])

tfjs.converters.save_keras_model(model, 'malaria_model')