# Path: scratch/probability.py
# def normal_cdf(x: float, mu: float = 0, sigma: float = 1) -> float:
#     return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2
# 
# Path: scratch/probability.py
# def inverse_normal_cdf(p: float,
#                        mu: float = 0,
#                        sigma: float = 1,
#                        tolerance: float = 0.00001) -> float:
#     """Find approximate inverse using binary search"""
# 
#     # if not standard, compute standard and rescale
#     if mu != 0 or sigma != 1:
#         return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
# 
#     low_z = -10.0                      # normal_cdf(-10) is (very close to) 0
#     hi_z  =  10.0                      # normal_cdf(10)  is (very close to) 1
#     while hi_z - low_z > tolerance:
#         mid_z = (low_z + hi_z) / 2     # Consider the midpoint
#         mid_p = normal_cdf(mid_z)      # and the cdf's value there
#         if mid_p < p:
#             low_z = mid_z              # Midpoint too low, search above it
#         else:
#             hi_z = mid_z               # Midpoint too high, search below it
# 
#     return mid_z


from typing import Tuple
from scratch.probability import normal_cdf
from scratch.probability import inverse_normal_cdf
from typing import List
import math
import random
normal_probability_below = normal_cdf

# It's above the threshold if it's not below the threshold
def normal_probability_above(lo: float,
                             mu: float = 0,
                             sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is greater than lo."""
    return 1 - normal_cdf(lo, mu, sigma)

# It's between if it's less than hi, but not less than lo.
def normal_probability_between(lo: float,
                               hi: float,
                               mu: float = 0,
                               sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is between lo and hi."""
    return normal_cdf(hi, mu, sigma) - normal_cdf(lo, mu, sigma)

# It's outside if it's not between
def normal_probability_outside(lo: float,
                               hi: float,
                               mu: float = 0,
                               sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is not between lo and hi."""
    return 1 - normal_probability_between(lo, hi, mu, sigma)


def normal_upper_bound(probability: float,
                       mu: float = 0,
                       sigma: float = 1) -> float:
    """Returns the z for which P(Z <= z) = probability"""

# Path: scratch/probability.py
# def normal_cdf(x: float, mu: float = 0, sigma: float = 1) -> float:
#     return (1 + math.erf((x - mu) / math.sqrt(2) / sigma)) / 2
# 
# Path: scratch/probability.py
# def inverse_normal_cdf(p: float,
#                        mu: float = 0,
#                        sigma: float = 1,
#                        tolerance: float = 0.00001) -> float:
#     """Find approximate inverse using binary search"""
# 
#     # if not standard, compute standard and rescale
#     if mu != 0 or sigma != 1:
#         return mu + sigma * inverse_normal_cdf(p, tolerance=tolerance)
# 
#     low_z = -10.0                      # normal_cdf(-10) is (very close to) 0
#     hi_z  =  10.0                      # normal_cdf(10)  is (very close to) 1
#     while hi_z - low_z > tolerance:
#         mid_z = (low_z + hi_z) / 2     # Consider the midpoint
#         mid_p = normal_cdf(mid_z)      # and the cdf's value there
#         if mid_p < p:
#             low_z = mid_z              # Midpoint too low, search above it
#         else:
#             hi_z = mid_z               # Midpoint too high, search below it
# 
#     return mid_z


# Path: scratch/inference.py
from typing import Tuple
from scratch.probability import normal_cdf
from scratch.probability import inverse_normal_cdf
from typing import List
import math
import random

normal_probability_below = normal_cdf

# It's above the threshold if it's not below the threshold
def normal_probability_above(lo: float,
                             mu: float = 0,
                             sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is greater than lo."""
    return 1 - normal_cdf(lo, mu, sigma)

# It's between if it's less than hi, but not less than lo.
def normal_probability_between(lo: float,
                               hi: float,
                               mu: float = 0,
                               sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is between lo and hi."""
    return normal_cdf(hi, mu, sigma) - normal_cdf(lo, mu, sigma)

# It's outside if it's not between
def normal_probability_outside(lo: float,
                               hi: float,
                               mu: float = 0,
                               sigma: float = 1) -> float:
    """The probability that a N(mu, sigma) is not between lo and hi."""
    return 1 - normal_probability_between(lo, hi, mu, sigma)


def normal_upper_bound(probability: float,
                       mu: float = 0,
                       sigma: float = 1) -> float:
    """Returns the z for which P(Z <= z) = probability"""

    return inverse_normal_cdf(probability, mu, sigma)