import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')

########## Função degrau
def step():
    z = np.arange(-5, 5, .001)
    step_fn = np.vectorize(lambda z: 1.0 if z >= 0.0 else 0.0)
    step = step_fn(z)
    return z, step, "Função degrau"

def step_d():
    z = np.arange(-5, 5, .02)
    return z, [0]*len(z), "Derivada da função degrau"

########## Função logística
def logistic():
    z = np.arange(-10, 10, .1)
    sigma_fn = np.vectorize(lambda z: 1/(1+np.exp(-z)))
    sigma = sigma_fn(z)
    return z, sigma, "Função logística"

def logistic_d():
    z = np.arange(-10, 10, .1)
    sigma_fn = np.vectorize(lambda z: np.exp(-z)/((np.exp(-z) + 1)**2))
    sigma = sigma_fn(z)
    return z, sigma, "Derivada da função logística"

########## Função ReLU
def relu():
    z = np.arange(-2, 2, .01)
    zero = np.zeros(len(z))
    y = np.max([zero, z], axis=0)
    return z, y, "Função ReLU"

def relu_d():
    z = np.arange(-2, 2, .01)
    y = [1 if x > 0 else 0 for x in z]
    return z, y, "Derivada da função ReLU"

########## Função LeakyReLU
def lrelu(alpha=.2):
    z = np.arange(-2, 2, .01)
    y = [x if x > 0 else alpha*x for x in z]
    return z, y, "Função LeakyReLU (α = 0.2)"

def lrelu_d(alpha=.2):
    z = np.arange(-2, 2, .01)
    y = [1 if x > 0 else alpha for x in z]
    return z, y, "Derivada da função LeakyReLU (α = 0.2)"

########## Função tangente hiperbólica
def tanh():
    z = np.arange(-5, 5, .1)
    t = np.tanh(z)
    return z, t, "Função tangente hiperbólica"

def tanh_d():
    z = np.arange(-5, 5, .1)
    # sech(z) = 1 / cosh(z)
    t = 1/np.cosh(z)
    return z, t, "Derivada da função tangente hiperbólica"

########## Função tangente hiperbólica
def mish():
    z = np.arange(-5, 5, .1)
    t = z * np.tanh(np.log(1 + np.exp(z)))
    return z, t, "Função Mish"

def mish_d():
    z = np.arange(-5, 5, .1)
    t = ((2*np.exp(z)*z*(1+np.exp(z)))/((1+np.exp(z))**2+1)) - \
            (2*np.exp(z)*z*((1+np.exp(z))**2 - 1)*(1+np.exp(z)))/((1+np.exp(z))**2 + 1)**2 + \
                ((1+np.exp(z))**2 - 1)/((1+np.exp(z))**2 + 1)
    return z, t, "Derivada da função Mish"

########## Função APL
def apl_func(x,a,b):
    return max(0, x) + sum([a[i]*max(0, -x + b[i]) for i in range(len(a))])

def apl(a=[.2], b=[0]):
    z = np.arange(-5, 5, .01)
    t = [apl_func(x,a,b) for x in z]
    return z, t, "Função APL (a = " + a + "; b = " + b + ")"

def apl_d_a02_b0():
    z = np.arange(-5, 5, .01)
    t = [1 if x > 0 else -0.2 for x in z]
    return z, t, "Derivada da função APL (a = 0.2; b = 0)"

########## Função SHReLU
def shrelu(tau=0.2):
    z = np.arange(-5, 5, .01)
    t = [0.5*(x + np.sqrt(x**2 + tau**2)) for x in z]
    return z, t, "Função SHReLU (τ = %.2f)" % (tau)

def shrelu_d(tau=0.2):
    z = np.arange(-5, 5, .01)
    t = [0.5*((x/(np.sqrt(x**2 + tau**2))) + 1) for x in z]
    return z, t, "Derivada da função SHReLU (τ = %.2f)" % (tau)

########## Função BHSA
def h1_bhsa(l, tau, z):
    return np.sqrt( (l**2) * (z + (1/(2*l)))**2 + tau**2)

def h2_bhsa(l, tau, z):
    return np.sqrt( (l**2) * (z - (1/(2*l)))**2 + tau**2)

def bhsa(l=1, tau=0, z = None):
    # h1 = torch.sqrt( ((self.l**2) * (x + (1/(2*self.l)))**2) + self.t1**2 )
    # h2 = torch.sqrt( ((self.l**2) * (x - (1/(2*self.l)))**2) + self.t2**2 )
    if z is None: z = np.arange(-5, 5, .01)
    t = h1_bhsa(l, tau, z) - h2_bhsa(l, tau, z)
    return z, t, "Função BHSA (λ = %.2f; τ = %.2f)" % (l, tau)

def bhsa_d(l=1, tau=0):
    z = np.arange(-5, 5, .01)
    t = (l * (1 - 2 * l * z))/np.sqrt(4 * tau**2 + (1 - 2 * l * z)**2) + (l * (1 + 2 * l * z))/np.sqrt(4 * tau**2 + (1 + 2 * l * z)**2)
    return z, t, "Derivada da função BHSA (λ = %.2f; τ = %.2f)" % (l, tau)

########## Função BHAA
def bhaa(l=1, tau1=0, tau2=0):
    z = np.arange(-5, 5, .01)
    t = np.sqrt((l**2) * (z+(1/(2*l)))**2 + (tau1**2)) - np.sqrt((l**2) * (z-(1/(2*l)))**2 + (tau2**2))
    return z, t, "Função BHAA (λ = %.2f; τ1 = %.2f; τ2 = %.2f)" % (l, tau1, tau2)

def bhaa_d(l=1, tau1=0, tau2=0):
    z = np.arange(-5, 5, .01)
    t = (l * (1 - 2 * l * z))/np.sqrt((1 - 2 * l * z)**2 + 4 * tau2**2) + (l * (2 * l * z + 1))/np.sqrt((2 * l * z + 1)**2 + 4 * tau1**2)
    return z, t, "Derivada da função BHAA (λ = %.2f; τ1 = %.2f; τ2 = %.2f)" % (l, tau1, tau2)

def bhata(l=1,tau1=0,tau2=0):
    z, t, _ = bhaa(l, tau1, tau2)
    t[t > 1] = 1
    t[t < -1] = -1
    return z, t, "Função BHAA truncada (λ = %.2f; τ1 = %.2f; τ2 = %.2f)" % (l, tau1, tau2)

def func_bh_derivative_equal_to_zero(tau1, tau2, l):
    return [(tau1 - tau2)/(2*l*(tau1 + tau2)), (tau1 + tau2)/(2*l*(tau1 - tau2))]

def raw_func_bh_derivative(x, tau1, tau2, l):
    return (l * (1 - 2 * l * x))/np.sqrt(4 * tau2**2 + (1 - 2 * l * x)**2) + (l * (1 + 2 * l * x))/np.sqrt(4 * tau1**2 + (1 + 2 * l * x)**2)

def raw_func_bh(x, tau1, tau2, l):
    return (np.sqrt((l**2) * (x+(1/(2*l)))**2 + (tau1**2)) - np.sqrt((l**2) * (x-(1/(2*l)))**2 + (tau2**2)))

def bhana(l=1,tau1=0,tau2=0):
    z = np.arange(-5, 5, .001)
    result = raw_func_bh(z, tau1, tau2, l)
    func_points_edge_value = func_bh_derivative_equal_to_zero(tau1, tau2, l)
    func_points_edge_value = max(func_points_edge_value) if abs(tau1) > abs(tau2) else min(func_points_edge_value)
    func_edge_value = raw_func_bh(func_points_edge_value, tau1, tau2, l)

    if(abs(tau1) > abs(tau2)):
        result = (result - (-1))/(func_edge_value + 0.001 - (-1))
    elif(abs(tau2) > abs(tau1)):
        result = (result - func_edge_value)/(1 + 0.001 - func_edge_value)

    result = (result - 0.5) * 2

    return z, result, "Função BHANA (λ = %.2f; τ1 = %.2f; τ2 = %.2f)" % (l, tau1, tau2)

########## Função MiDA
def raw_softmax(x):
    return np.log(1 + np.exp(x))

def mida(l=1, tau=1):
    z = np.arange(-5, 5, .01)
    t = z * bhsa(l,tau,raw_softmax(z))[1]
    return z, t, "Função MiDA (λ = %.2f; τ = %.2f)" % (l, tau)

def mida_d(l=1, tau=1):
    z = np.arange(-5, 5, .01)
    delta = (l*np.exp(z)*z)/(2*(1+np.exp(z)))
    t = bhsa(l, tau, raw_softmax(z))[1] + delta * (((1 + 2*l*raw_softmax(z))/h1_bhsa(l, tau, raw_softmax(z))) + ((1-2*l*raw_softmax(z))/(h2_bhsa(l, tau, raw_softmax(z)))))
    return z, t, "Derivada da função MiDA (λ = %.2f; τ = %.2f)" % (l, tau)

#############################################################
def conf(ax, xlim=2, ylim=None, offsetx=0, offsety=0):
    if ylim is None: ylim = xlim
    ax.set_ylim([-ylim + offsety, ylim + offsety])
    ax.set_xlim([-xlim + offsetx, xlim + offsetx])
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.grid(True)
    ax.legend()

#############################################################
def step_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [step(), step_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax,xlim=1,offsety=.5)
    # fig.suptitle('Funções de ativação sigmoidais tradicionais')
    plt.show()

def logistic_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [logistic(), logistic_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax, xlim=4,ylim=1,offsety=.5)
    plt.show()

def tanh_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [tanh(), tanh_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax,ylim=1.5,offsety=.3)
    plt.show()

def mish_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [mish(), mish_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax,xlim=4,offsety=1.5)
    plt.show()

def relu_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [relu(), relu_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax, offsety=1,xlim=1.5)
    plt.show()

def lrelu_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [lrelu(), lrelu_d()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax, offsety=1,xlim=1.5)
    plt.show()

def apl_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    data, data_d = [apl(), apl_d_a02_b0()]
    ax.plot(data[0], data[1], label=data[2])
    ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax,offsety=1)
    plt.show()

def shrelu_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for tau in [0.05, 0.25, 1]:
        data, data_d = [shrelu(tau), shrelu_d(tau)]
        ax.plot(data[0], data[1], label=data[2])
        ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax, offsety=1,xlim=1.5)
    plt.show()

def bhsa_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for l, tau in [(1, 1), (.5, 1), (1, .5)]:
        data, data_d = [bhsa(l, tau), bhsa_d(l, tau)]
        ax.plot(data[0], data[1], label=data[2])
        ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax)
    plt.show()

def bhaa_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for l, tau1, tau2 in [(1, .5, .5), (1, .5, .1), (1, .1, .5)]:
        data, data_d = [bhaa(l, tau1, tau2), bhaa_d(l, tau1, tau2)]
        ax.plot(data[0], data[1], label=data[2])
        ax.plot(data_d[0], data_d[1], ls='dashed', lw=1, label=data_d[2])
    conf(ax)
    plt.show()

def mida_and_derivative():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for l, tau in [(1, 1), (.5, 1), (1, .5)]:
        data, data_d = [mida(l, tau), mida_d(l, tau)]
        ax.plot(data[0], data[1], label=data[2])
        ax.plot(data_d[0], data_d[1], ls='dashed', lw=1.25, label=data_d[2])
    conf(ax,xlim=3,offsety=1.5)
    plt.show()

def bhata_only():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for l, tau1, tau2 in [(1, .5, .1), (1, .1, .5)]:
        data = bhata(l, tau1, tau2)
        ax.plot(data[0], data[1], label=data[2])
    conf(ax)
    plt.show()

def bhana_only():
    fig, (ax) = plt.subplots(1, 1, figsize=(6,6))
    for l, tau1, tau2 in [(1, .75, .1), (1, .1, .75)]:
        data = bhana(l, tau1, tau2)
        ax.plot(data[0], data[1], label=data[2])
    conf(ax)
    plt.show()

# step_and_derivative()
# logistic_and_derivative()
# tanh_and_derivative()
# mish_and_derivative()
# relu_and_derivative()
# lrelu_and_derivative()
# apl_and_derivative()
# shrelu_and_derivative()
# bhsa_and_derivative()
# bhaa_and_derivative()
# mida_and_derivative()
# bhata_only()
bhana_only()


import numpy as np

def simpson(f, a, b, n):
    """Approximates the definite integral of f from a to b by
    the composite Simpson's rule, using n subintervals.
    From http://en.wikipedia.org/wiki/Simpson's_rule

    Args:
    f   : function to integrate
    a,b : boundaries
    n   : number of subintervals

    Return: integral
    """
    h = (b - a) / n
    i = np.arange(0,n)
    s = f(a) + f(b)
    s += 4 * np.sum( f( a + i[1::2] * h ) )
    s += 2 * np.sum( f( a + i[2:-1:2] * h ) )
    sum = s * h / 3
    return sum

def trapezoid(f, a, b, n):
    """Approximates the definite integral of f from a to b by
    the composite trapezoidal rule, using n subintervals.
    From http://en.wikipedia.org/wiki/Trapezoidal_rule

    Args:
    f   : function to integrate
    a,b : boundaries
    n   : number of subintervals

    Return: integral
    """
    h = (b - a) / n
    s = f(a) + f(b)
    i = np.arange(0,n)
    s += 2 * np.sum( f(a + i[1:] * h) )
    return s * h / 2

def adaptive_trapezoid(f, a, b, acc, output=False):
    """
    Uses the adaptive trapezoidal method to compute the definite integral
    of f from a to b to desired accuracy acc.

    Args:
    f      : function to integrate
    a,b    : boundaries
    acc    : desired accurarcy
    output : prints individual steps, default is False

    Return: integral
    """
    old_s = np.inf
    h = b - a
    n = 1
    s = (f(a) + f(b)) * 0.5
    if output == True :
        print ("N = " + str(n+1) + ",  Integral = " + str( h*s ))
    while abs(h * (old_s - s*0.5)) > acc :
        old_s = s
        for i in np.arange(n) :
            s += f(a + (i + 0.5) * h)
        n *= 2.
        h *= 0.5
        if output == True :
            print ("N = " + str(n) + ",  Integral = " + str( h*s ))
    return h * s


import numpy as np

def coding_problem_32(exchange_matrix):
    """
    Suppose you are given a table of currency exchange rates, represented as a 2D array. Determine whether there is a
    possible arbitrage: that is, whether there is some sequence of trades you can make, starting with some amount A of
    any currency, so that you can end up with some amount greater than A of that currency.
    There are no transaction costs and you can trade fractional quantities.

    >>> em = [[1, 2, 3], [1./2, 1, 3./2], [1./3, 2./3, 1]]
    >>> coding_problem_32(em)
    True

    >>> em[0][2] = 2.98
    >>> coding_problem_32(em)
    False

    Note: idea is that given a single row in the currency exchange matrix, it is possible to generate the entire 2D
    array. Any difference between the given currency exchange matrix and the computed one implies the possibility of
    arbitration.

    For example, for five currencies and given the exchange rates from the first to the other 4 [b, c, d, e]:

      |  A   B   C   D   E
    --+------------------
    A |  1   b   c   d   e
    B | 1/b  1  c/b d/b e/b
    C | 1/c b/c  1  d/b e/c
    D | 1/d b/d c/d  1  e/d
    E | 1/e b/e c/e d/e  1

    Since floating point quantities are involved, we need to test for approximate equality.
    """
    em = np.array(exchange_matrix)  # ideally, we should test if the exchange_matrix is well-formed
    cem = np.vstack(tuple(em[0, :] / em[0, n] for n in range(len(em))))  # computed exchange_matrix
    return np.allclose(em, cem)

if __name__ == '__main__':

    import doctest
    doctest.testmod(verbose=True)


'''
Content under Creative Commons Attribution license CC-BY 4.0,
code under MIT license (c)2018 Sergio Rojas (srojas@usb.ve)

http://en.wikipedia.org/wiki/MIT_License
http://creativecommons.org/licenses/by/4.0/

Created on march, 2018
Last Modified on: may 15, 2018
'''

TheValues = [336.1, 346.67, 354.54, 359.9, 338.61, 348.16, 355.2, 360.,
339.32, 349.31, 355.36, 363.13, 350.56, 355.76, 342.62, 351.88,
356.85, 342.88, 351.99, 358.16, 343.44, 352.12, 358.3, 344.32,
352.47, 359.18, 345.03, 353.21, 359.56, 346.5]

TheValues.sort()
NumberOfValues = len(TheValues)

if NumberOfValues == 0:
   print(' List of values in empty ')
elif NumberOfValues == 1:
   TheMedian = TheValues[ 0 ]     # The only element in the list
elif (NumberOfValues % 2) == 1:   # Check if the number of values is odd
   temp = NumberOfValues//2
   TheMedian = TheValues[ temp ]
else:
   temp = NumberOfValues//2
   TheMedian = (TheValues[ temp ] + TheValues[ temp - 1 ])/2

print('The median = {0}'.format(TheMedian))
In [8]: import numpy as np

In [9]: np.median(TheValues)
Out[9]: 352.05500000000001


#coding=utf-8
# 计算图像文件 '正方形面积占比计算.JPG'中粉红色区域占总正方形面积的比例

# 思路：蒙特卡洛算方法：
# 假设正方形边长为1
# 以正方形的左下角定点为直角坐标系圆点，记圆点为A（0，0）
# 正方形右下角的点为B（1，0）
# 正方形左上角的点为C（0，1）
# 正方形上面的那条边的中点为D（0.5,1）
# 记AD与BC两线的交点，即为粉红色三角形的定点为P
# 直线AD的方程式为 y = 2*x
# 直线BC的方程为   y = 1-x
# 粉红的区域的可行域为满足 y < 2*x 且 y < 1-x 且 y >0，其中x的范围为 0～1
# 正方形区域的可行域为 0<x<1;0<y<1

import numpy as np
import matplotlib.pyplot as plt

def meng_num(n):
    col_X=[]; col_y=[]
    z=np.zeros(n)
    sum=0                       # 程序刚开始的时候，没有点落入粉红色区域的可行域
    for i in range(n):
        x=np.random.rand()      # 随机生成一个值为0～1的数即记为x坐标
        y=np.random.rand()      # 随机生成一个值为0～1的数即记为y坐标
        col_X.append(x)         # 收集生成的x，留着作图用
        col_y.append(y)         # 收集生成的y，留着作图用
        if y<2*x and y < 1-x:   # 若坐标（x，y）落入粉红区域,则进行计数
            sum +=1
            z[i]=1
    area_rate=sum/len(range(n+1))   # 则粉红色区域所占正方形的概率为落入粉红色区域的点数除以总生成的随机数n
    print('当随机生成 %i 个点的时候，面积占比为： %f ' %(n,area_rate))

    #以下是可视化输出，只是把生成的点投影到区域中
    x1 = np.linspace(0, 1 / 3, 1000)
    x2 = np.linspace(1 / 3, 1, 1000)
    x3 = np.linspace(0, 1, 1000)
    y1 = 2 * x1
    y2 = 1 - x2
    y3 = 0 * x1
    plt.plot(x1, y1, x2, y2, x3, y3)
    plt.xlim(0, 1)
    plt.ylim(0, 1)
    plt.scatter(col_X,col_y,c=z,cmap='rainbow')
    plt.title('when create %i random point' %n)
    plt.show()

for n in list([10,100,1000,10000,100000]):
    meng_num(n)


import sys

import numpy as np

def bayes_theorem(P_B_A, P_A, P_B_nA):
    """An implementation of Bayes theorem.
    Evaluate the probability of event $A$ given that $B$ is true.

    Parameters
    ----------
    P_B_A : array_like
        the likelihood of event $B$ occurring given that $A$ is true.
    P_A : array_like
        the likelihood of observing $A$.
    P_B_nA : array_like
        the likelihood of observing $B$ given $A$ is false.

    Returns
    -------
    P_A_B : float
        probability of event $A$ given that $B$ is true.

    Notes
    -----
    From wikipedia:
    [Bayes Theorem](https://en.wikipedia.org/wiki/Bayes%27_theorem)
    is described as in (1.):

    1. $$P(A|B) = \frac{P(B|A)P(A)}{P(B)} $$

    Where `A` and `B` are [events](https://en.wikipedia.org/wiki/Event_(probability_theory))
    and $P(B) \\neq 0$.

    - $P(A|B)$ is a [conditional probability](https://en.wikipedia.org/wiki/Conditional_probability):
      the likelihood of event $A$ occurring _given that_ $B$ is true.
    - $P(B|A)$ is also a conditional probability: the likelihood of event $B$ occurring
      given that $A$ is true.
    - $P(A)$ and $P(B)$ are the probabilities of observing $A$ and $B$ respectively;
      they are known as the [marginal probability](https://en.wikipedia.org/wiki/Marginal_distribution).
    - $A$ and $B$ must be different events.

    However it strikes me as obfuscating to only show the denominator as $P(B)$ as it must
    be developed in order to actually evaluate this equation, as follows in (2.):

    2. $$ P(B) = P(B|A)P(A) + P(B|¬A)P(¬A) $$

    Where:
    - $P(B|¬A)$ is a conditional probability;
        the likelihood of event $B$ occurring given that $A$ is false.
    - $P(¬A)$ is the probablity of not observing $A$.

    So we need a minimum of 3 numeric inputs to run this calculation:

    1. $P(B|A)$ the likelihood
    2. $P(A)$ the normalizing constant
    3. $P(B|¬A)$

    Example
    -------
    Retrieving a Special Publication of the GeolSoc (SPGS) from my bookshelf:
    Getting a SPGS book is the test (A);
    I have 11 SPGS books on my bookshelf and a total of 44 books on that shelf
    so:

    P(A) = 11 / 44 = 0.25
    P(B|A) = 1 - P(A) = 0.75
    P(B|¬A) = 1

    >>> python bayes_theorem.py 0.75 0.25 1
    P(A|B) = 0.200

    """
    P_B_A, P_A, P_B_nA = np.array(P_B_A), np.array(P_A), np.array(P_B_nA)
    return (P_B_A * P_A) / ((P_B_A * P_A) + P_B_nA * (1 - P_A))

if __name__ == "__main__":
    try:
        P_B_A_str, P_A_str, P_B_nA_str = sys.argv[1:]
        P_B_A, P_A, P_B_nA = float(P_B_A_str), float(P_A_str), float(P_B_nA_str)
        print(f'P(A|B) = {bayes_theorem(P_B_A, P_A, P_B_nA):.3f}')
    except ValueError:
        num_vals = int((len(sys.argv) - 1) / 3)
        vals = [float(val.strip('[],')) for val in sys.argv[1:]]
        P_B_A = vals[:num_vals]
        P_A = vals[num_vals:-num_vals]
        P_B_nA = vals[-num_vals:]
        print(f'P(A|B) = {bayes_theorem(P_B_A, P_A, P_B_nA)}')


# %% codecell
# create some x and y date
from matplotlib import pyplot as plt
x_list = [-4.0, -2.0, 3.0, 4.0, 5.0, 6.0]
y_list = list()
m = 2
b = 4

for x in x_list:
    y = (m*x) + b
    y_list.append(y)

plt.plot(x_list, y_list)

# %% codecell
def lin_model_single_ele(m, x, b):
    """ Returns a single y for a given x using a line """
    return (m*x) + b

x_list = [1, 2, 3, 4, 5, 6]
y_observed = [2.11, 3.87, 6.01, 7.93, 9.99, 12.12]

y_predicted = []
m = 2
b = 4

for x in x_list:
    y = lin_model_single_ele(m, x, b)
    y_predicted.append(y)

print(y_predicted[0])
# %% codecell
def calculate_residual(y_pred, y_obs):
    """ Returns the residual of either a point or array """
    return y_pred-y_obs

residuals = []
for i in range(0, len(y_predicted)):
    residual = calculate_residual(y_predicted[i], y_observed[i])
    residuals.append(residual)

print(residuals)
# %% codecell
from statistics import mean

def calculate_ssr(y_pred, y_obs, deci=4):
    """  Calculates the Sum of Squared Residuals using the observed and predicted y"""
    res = [calculate_residual(a_i, b_i) for a_i, b_i in zip(y_pred, y_obs)]
    return round(sum([d * d for d in res]), deci)

def calculate_tss(y_obs, deci=4):
	y_mean = mean(y_obs)
	delta = [y - y_mean for y in y_obs]
	return round(sum([d * d for d in delta]), deci)

def calculate_rsquared(ssr, tss, deci=4):
	return round(1-(ssr/tss), 4)
# %% codecell
tss = calculate_tss(y_observed)
ssr = calculate_ssr(y_observed, y_predicted)
r2 = calculate_rsquared(ssr, tss)

print('SSE: %s' % ssr)
print('R^2: %s' % r2)
# %% codecell
def fit_line(x_input, y_observed, m_max=5, c_max=5, print_output=True):

	best_model = {
		'm': None,
		'c': None,
		'SSR': None,
		'R^2': None
	}
	assert len(x_input) == len(y_observed), 'Input vectors have differing lengths'

	tss = calculate_tss(y_observed)

	for m in range(1, m_max):
		for c in range(0, c_max):
			y_model = list()
			for x_i in x_input:
				y_model.append(lin_model_single_ele(m, x_i, c))

			ssr = calculate_ssr(y_observed, y_model)
			r2 = calculate_rsquared(ssr, tss)
			if print_output:
				print('#####')
				print('using: m=%s and c=%s' % (m, c))
				print('actual: %s' % y_observed)
				print('model: %s' % y_model)
				print('SSR: %s' % ssr)
				print('R^2: %s' % r2)
				print('#####')

			if best_model['SSR'] is None or best_model['SSR'] > ssr:
				best_model['m'] = m
				best_model['c'] = c
				best_model['SSR'] = ssr
				best_model['R^2'] = r2

	return best_model

# %% codecell
my_model = fit_line(x_list, y_observed, print_output=False)
print('best model: %s' % my_model)
# %% codecell
from sklearn import linear_model
import numpy as np

lm = linear_model.LinearRegression()
X = np.array(x_list).reshape(-1, 1)
Y = np.array(y_observed).reshape(-1, 1)
model = lm.fit(X, Y)
print(model.coef_)
print(model.intercept_)
print(model.score(X, Y))

# %% codecell

# %% codecell
tss = calculate_tss(y_observed)
ssr = calculate_ssr(y_observed, y_predicted)
r2 = calculate_rsquared(ssr, tss)

print('SSE: %s' % ssr)
print('R^2: %s' % r2)
# %% codecell
def fit_line(x_input, y_observed, m_max=5, c_max=5, print_output=True):

	best_model = {
		'm': None,
		'c': None,
		'SSE': None,
		'R^2': None
	}
	assert len(x_input) == len(y_observed), 'Input vectors have differing lengths'

	tss = calculate_tss(y_observed)

	for m in range(1, m_max):
		for c in range(0, c_max):
			y_model = list()
			for x_i in x_input:
				y_i = (m*x_i) + c
				y_model.append(y_i)

			ssr = calculate_ssr(y_observed, y_model)
			r2 = calculate_rsquared(ssr, tss)
			if print_output:
				print('#####')
				print('using: m=%s and c=%s' % (m, c))
				print('actual: %s' % y_observed)
				print('model: %s' % y_model)
				print('SSE: %s' % ssr)
				print('R^2: %s' % r2)
				print('#####')

			if best_model['SSE'] is None or best_model['SSE'] > ssr:
				best_model['m'] = m
				best_model['c'] = c
				best_model['SSE'] = ssr
				best_model['R^2'] = r2

	return best_model

# %% codecell
my_model = fit_line(x_list, y_observed, print_output=False)
print('best model: %s' % my_model)
# %% codecell
from sklearn import linear_model
import numpy as np

lm = linear_model.LinearRegression()
X = np.array(x).reshape(-1, 1)
Y = np.array(y).reshape(-1, 1)
model = lm.fit(X, Y)
print(lm.score(X, Y))


import sympy

##################################################################
# Function : factorize(a, n, factor_matrix)
# This function recursively divides a by n to get the largest
# multiple of n that is a divisor of a. It also checks if n is
# a prime factor because we only wanna account for prime factors
#
# In other words, repeatedly integer divides a by n, increasing
# the count of n as displayed in factor_matrix[n-1][1] by every
# time. The greatest divisor of a that is a multiple of only n
# is given by n ^ (factor_matrix[n-1][1])
#
# params : a - The number to be factored (int)
#        : n - The factor (int)
#        : factor_matrix - 2D matrix that holds the prime factors
#                          of a and the gcd of a that is a power
#                          of n.
#
# returns: nothing
#################################################################
def factorizer(a, n, factor_matrix):
    if a % n == 0 and sympy.isprime(n):  # replace with own isprime function
        factor_matrix[n-1][1] += 1
        factorizer(a//n, n, factor_matrix)
    return

##################################################################
# Function : gcd(x,y)
# Calculates the Greatest Common Divisor of x and y
#
# How the algorithm works:
# Find all the prime factors of x and y and their powers.
# GCD is set to 1 initially
# If any of x an y have common prime factors, multiply the GCD
# by the lower of the powers of the common prime factors.
#
# params : x - (int)
#        : y - (int)
#
# returns: (int) Greatest common divisor of x and y
#################################################################
def gcd(x, y):
    greatest_common_divisor = 1

########## Edge Case testing# #############
    if x == 0:
        raise ValueError("x cannot be 0")
    if y == 0:
        raise ValueError("y cannot be 0")

    if x < 0:
        x = abs(x)
    if y < 0:
        y = abs(y)

    if not isinstance(x, int):
        raise TypeError("x has to be an integer")
    if not isinstance(y, int):
        raise TypeError("y has to be an integer")

    # 1 is always a divisor of both x and y
    factor_matrix_x = [[1, 1]]     # matrix that contains the prime factors of x
    factor_matrix_y = [[1, 1]]     # matrix that contains the prime factors of y

    for i in range(2, x + 1):
        factor_matrix_x.append([i, 0])
        factorizer(x, i, factor_matrix_x)

    i = 2
    for i in range(2, y + 1):
        factor_matrix_y.append([i, 0])
        factorizer(y, i, factor_matrix_y)

    for i in range(0, len(factor_matrix_x) if len(factor_matrix_x) <= len(factor_matrix_y) else len(factor_matrix_y)):
        if factor_matrix_x[i][1] == 0 or factor_matrix_y[i][1] == 0:
            continue
        elif factor_matrix_x[i][1] <= factor_matrix_y[i][1]:
            greatest_common_divisor *= pow(factor_matrix_x[i][0], factor_matrix_x[i][1])
        else:
            greatest_common_divisor *= pow(factor_matrix_y[i][0], factor_matrix_y[i][1])

    return greatest_common_divisor


import numpy as np
from Exercise5.util import matrix_mult, transpose, matrix_vector_mult
from Exercise5.lu_decomposition import lu

def least_squares_fit(x, y, degree=10):
    """
        Computes the coefficients of the degree degree polynomial of the least squares fit.
Constant term
        first.

        Parameters
        ----------
        x : list
            The values of the independent variable.
        y : list
            The values of f(x) for every x, where x is the independent variable
        degree : int
            The degree of the polynomial model fitting the data.

        Returns
        -------
        c : list
            coefficients of interpolating polynomial
    """
    # construct A
    a = []
    for row in range(len(x)):
        a.append([])
        for column in range(degree):
            a[row].append(x[row]**column)
    # construct A^T * A
    a_transpose = transpose(a)
    a_transpose_a = matrix_mult(a_transpose, a)

    # solve (A^T * A) * x = A^T * y
    return lu(a_transpose_a, matrix_vector_mult(a_transpose, y))

def calculate_polynomial(c, x, degree=10):
    """
        Computes the value of the degree degree least squares fit polynomial.

        Parameters
        ----------
        c : list
            The coefficients of the degree degree polynomial. Constant term first.

        x : int
            The point at which the polynomial will be calculated
        degree : int
            The degree of the polynomial model that was used when fitting the data.

        Returns
        -------
        return_value : float
            The value of the interpolating polynomial at point x.
    """
    return_value = 0.0
    for i in range(degree):
        return_value += c[i] * (x ** i)
    return return_value

def custom_sin(value, d=10):
    """
        Approximates sin curve with least squares using d degree polynomial model.

        Parameters
        ----------
        value : float
            The point at which the sin will be approximated
        d : int
            The degree of the polynomial model fitting the data.

        Returns
        -------
        float
            The approximation value of the sin curve at point x.
    """
    x = [0.0, 0.65, 1.3, 1.9500000000000002, 2.6, 3.25, 3.9000000000000004, 4.55, 5.2, 2*np.pi]
    y = [0.0, 0.6051864057, 0.9635581854, 0.9289597150, 0.5155013718, -0.1081951345, -0.6877661591, -0.9868438585,
         -0.8834546557, 0]
    c = least_squares_fit(x, y, degree=d)

    value = value % (2*np.pi)
    return calculate_polynomial(c, value, degree=d)


def extgcd(a, b):
    """ 拡張ユークリッド互除法
    ax + by = gcd(a,b) の最小整数解 (gcd(a,b), x, y) を返す
    Args:
        a (int):
        b (int):
    Returns:
        Tuple[int, int, int]
    """
    u = y = 1
    v = x = 0
    while a:
        q = b // a
        x, u = u, x-q*u
        y, v = v, y-q*v
        b, a = a, b-q*a
    return b, x, y

    # 再帰版
    # if a == 0:
    #     return b, 0, 1
    # else:
    #     g, y, x = extgcd(b % a, a)
    #     return g, x - (b // a) * y, y

def chinese_reminder_theorem(q1, m1, q2, m2):
    """中国剰余定理 (CRT)

    x ≡ m1 (mod q1) ∧ x ≡ m2 (mod q2)
    <=> x ≡ m (mod q)
    となる (m. q) を返す
    解無しのとき (0, -1) を返す

    verify:
        https://atcoder.jp/contests/abc193/submissions/20558893

    Args:
        q1 (int): 除数
        m1 (int): あまり
        q2 (int): 除数
        m2 (int): あまり

    Returns:
        Tuple[int, int]: 除数、あまり
    """
    gcd_, p, q = extgcd(q1, q2)
    if (m2 - m1) % gcd_ != 0:
        return 0, -1
    q = q1 * (q2 // gcd_)  # q = lcm(q1, q2)
    tmp = (m2 - m1) // gcd_ * p % (q2 // gcd_)
    m = (m1 + q1 * tmp) % q
    return m, q

def modinv(a, mod=10**9+7):
    return pow(a, mod-2, mod)

def combination(n, r, mod=10**9+7):
    # nCr mod m
    # rがn/2に近いと非常に重くなる
    n1, r = n+1, min(r, n-r)
    numer = denom = 1
    for i in range(1, r+1):
        numer = numer * (n1-i) % mod
        denom = denom * i % mod
    return numer * pow(denom, mod-2, mod) % mod

def H(n, r, mod=10**9+7):
    # nHr mod m
    return combination(n+r-1, r, mod)

def combination_list(n, mod=10**9+7):
    # nCrをすべてのr(0<=r<=n)について求める
    # nC0, nC1, nC2, ... , nCn を求める
    lst = [1]
    for i in range(1, n+1):
        lst.append(lst[-1] * (n+1-i) % mod * pow(i, mod-2, mod) % mod)
    return lst

def make_modinv_list(n, mod=10**9+7):
    # 0 から n までの mod 逆元のリストを返す O(n)
    modinv = [0, 1]
    for i in range(2, n+1):
        modinv.append(mod - mod//i * modinv[mod%i] % mod)
    return modinv

class Combination:
    def __init__(self, n_max, mod=10**9+7):
        # O(n_max + log(mod))
        self.mod = mod
        f = 1
        self.fac = fac = [f]
        for i in range(1, n_max+1):
            f = f * i % mod
            fac.append(f)
        f = pow(f, mod-2, mod)
        self.facinv = facinv = [f]
        for i in range(n_max, 0, -1):
            f = f * i % mod
            facinv.append(f)
        facinv.reverse()

    def __call__(self, n, r):
        return self.fac[n] * self.facinv[r] % self.mod * self.facinv[n-r] % self.mod

    def C(self, n, r):
        if not 0 <= r <= n: return 0
        return self.fac[n] * self.facinv[r] % self.mod * self.facinv[n-r] % self.mod

    def P(self, n, r):
        if not 0 <= r <= n: return 0
        return self.fac[n] * self.facinv[n-r] % self.mod

    def H(self, n, r):
        if (n == 0 and r > 0) or r < 0: return 0
        return self.fac[n+r-1] * self.facinv[r] % self.mod * self.facinv[n-1] % self.mod

    # "n 要素" は区別できる n 要素
    # "k グループ" はちょうど k グループ

    def rising_factorial(self, n, r):  # 上昇階乗冪 n * (n+1) * ... * (n+r-1)
        return self.fac[n+r-1] * self.facinv[n-1] % self.mod

    def stirling_first(self, n, k):  # 第 1 種スターリング数  lru_cache を使うと O(nk)  # n 要素を k 個の巡回列に分割する場合の数
        if n == k: return 1
        if k == 0: return 0
        return (self.stirling_first(n-1, k-1) + (n-1)*self.stirling_first(n-1, k)) % self.mod

    def stirling_second(self, n, k):  # 第 2 種スターリング数 O(k + log(n))  # n 要素を区別のない k グループに分割する場合の数
        if n == k: return 1  # n==k==0 のときのため
        return self.facinv[k] * sum((-1)**(k-m) * self.C(k, m) * pow(m, n, self.mod) for m in range(1, k+1)) % self.mod

    def balls_and_boxes_3(self, n, k):  # n 要素を区別のある k グループに分割する場合の数  O(k + log(n))
        return sum((-1)**(k-m) * self.C(k, m) * pow(m, n, self.mod) for m in range(1, k+1)) % self.mod

    def bernoulli(self, n):  # ベルヌーイ数  lru_cache を使うと O(n**2 * log(mod))
        if n == 0: return 1
        if n % 2 and n >= 3: return 0  # 高速化
        return (- pow(n+1, self.mod-2, self.mod) * sum(self.C(n+1, k) * self.bernoulli(k) % self.mod for k in range(n))) % self.mod

    def faulhaber(self, k, n):  # べき乗和 0^k + 1^k + ... + (n-1)^k
        # bernoulli に lru_cache を使うと O(k**2 * log(mod))  bernoulli が計算済みなら O(k * log(mod))
        return pow(k+1, self.mod-2, self.mod) * sum(self.C(k+1, j) * self.bernoulli(j) % self.mod * pow(n, k-j+1, self.mod) % self.mod for j in range(k+1)) % self.mod

    def lah(self, n, k):  # n 要素を k 個の空でない順序付き集合に分割する場合の数  O(1)
        return self.C(n-1, k-1) * self.fac[n] % self.mod * self.facinv[k] % self.mod

    def bell(self, n, k):  # n 要素を k グループ以下に分割する場合の数  O(k**2 + k*log(mod))
        return sum(self.stirling_second(n, j) for j in range(1, k+1)) % self.mod

def make_prime_checker(n):
    # n までの自然数が素数かどうかを表すリストを返す  O(nloglogn)
    is_prime = [False, True, False, False, False, True] * (n//6+1)
    del is_prime[n+1:]
    is_prime[1:4] = False, True, True
    for i in range(5, int(n**0.5)+1):
        if is_prime[i]:
            is_prime[i*i::i] = [False] * (n//i-i+1)
    return is_prime

def prime_factorization(n):
    # 素因数分解
    i = 2
    table = []
    while i * i <= n:
        while n % i == 0:
            n //= i
            table.append(i)
        i += 1
    if n > 1:
        table.append(n)
    return table

def fast_prime_factorization(n):
    # 素因数分解（ロー法）  O(n^(1/4) polylog(n))
    from subprocess import Popen, PIPE
    return list(map(int, Popen(["factor", str(n)], stdout=PIPE).communicate()[0].split()[1:]))

def fast_prime_factorization_many(lst):
    # 素因数分解（ロー法、複数）
    from subprocess import Popen, PIPE
    res = Popen(["factor"] + list(map(str, lst)), stdout=PIPE).communicate()[0].split(b"\n")[:-1]
    return [list(map(int, r.split()[1:])) for r in res]

def miller_rabin(n):
    # 確率的素数判定（ミラーラビン素数判定法）
    # 素数なら確実に True を返す、合成数なら確率的に False を返す
    # True が返ったなら恐らく素数で、False が返ったなら確実に合成数である
    # 参考: http://tjkendev.github.io/procon-library/python/prime/probabilistic.html
    # 検証: https://yukicoder.me/submissions/381948
    primes = [2, 325, 9375, 28178, 450775, 9780504, 1795265022]  # 32bit: [2, 7, 61]
    if n==2: return True
    if n<=1 or n&1==0: return False
    d = m1 = n-1
    d //= d & -d
    for a in primes:
        if a >= n: return True
        t, y = d, pow(a, d, n)
        while t!=m1 and y!=1 and y!=m1:
            y = y * y % n
            t <<= 1
        if y!=m1 and t&1==0: return False
    return True

class Bit:
    # Binary Indexed Tree
    def __init__(self, n):
        self.size = n
        self.tree = [0]*(n+1)

    def __iter__(self):
        psum = 0
        for i in range(self.size):
            csum = self.sum(i+1)
            yield csum - psum
            psum = csum
        raise StopIteration()

    def __str__(self):  # O(nlogn)
        return str(list(self))

    def sum(self, i):
        # [0, i) の要素の総和を返す
        if not (0 <= i <= self.size): raise ValueError("error!")
        s = 0
        while i>0:
            s += self.tree[i]
            i -= i & -i
        return s

    def add(self, i, x):
        if not (0 <= i < self.size): raise ValueError("error!")
        i += 1
        while i <= self.size:
            self.tree[i] += x
            i += i & -i

    def __getitem__(self, key):
        if not (0 <= key < self.size): raise IndexError("error!")
        return self.sum(key+1) - self.sum(key)

    def __setitem__(self, key, value):
        # 足し算と引き算にはaddを使うべき
        if not (0 <= key < self.size): raise IndexError("error!")
        self.add(key, value - self[key])

class BitImos:
    """
    ・範囲すべての要素に加算
    ・ひとつの値を取得
    の2種類のクエリをO(logn)で処理
    """
    def __init__(self, n):
        self.bit = Bit(n+1)

    def add(self, s, t, x):
        # [s, t)にxを加算
        self.bit.add(s, x)
        self.bit.add(t, -x)

    def get(self, i):
        return self[i]

    def __getitem__(self, key):
        # 位置iの値を取得
        return self.bit.sum(key+1)

"""
# BITで転倒数を求められる
A = [3, 10, 1, 8, 5, 5, 1]
bit = Bit(max(A)+1)
ans = 0
for i, a in enumerate(A):
    ans += i - bit.sum(a+1)
    bit.add(a, 1)
print(ans)
"""

# 未検証
class Bit2:
    def __init__(self, n):
        self.bit0 = Bit(n)
        self.bit1 = Bit(n)

    def add(self, l, r, x):
        # [l, r) に x を足す
        self.bit0.add(l, -x * (l-1))
        self.bit1.add(l, x)
        self.bit0.add(r, x * (r-1))
        self.bit1.add(r, -x)

    def sum(self, l, r):
        res = 0
        res += self.bit0.sum(r) + self.bit1.sum(r) * (r-1)
        res -= self.bit0.sum(l) + self.bit1.sum(l) * (l-1)
        return res

def dijkstra(G, start):
    # ダイクストラ法
    from heapq import heappush, heappop
    N = len(G)
    inf = 1 << 62
    distances = [inf] * N
    distances[start] = 0
    q = [(0, start)]
    while q:
        dv, v = heappop(q)
        if distances[v] != dv:
            continue
        for u, cost in G[v]:
            du = dv + cost
            if du < distances[u]:
                distances[u] = du
                heappush(q, (du, u))
    return distances

def shortest_path_faster_algorithm(E, start):
    # ベルマンフォードの更新があるところだけ更新する感じのやつ
    # O(VE) だが実用上高速
    # E は隣接リスト
    # 検証: http://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=4005805#1
    # deque 版 (コーナーケースに強い？): http://judge.u-aizu.ac.jp/onlinejudge/review.jsp?rid=4005813
    inf = float("inf")  # 10**18 は良くない
    N = len(E)
    q = [start]
    distance = [inf] * N;  distance[start] = 0
    in_q = [0] * N;        in_q[start] = True
    times = [0] * N;       times[start] = 1
    while q:
        v = q.pop()
        in_q[v] = False
        dist_v = distance[v]
        for u, cost in E[v]:
            new_dist_u = dist_v + cost
            if distance[u] > new_dist_u:
                times[u] += 1
                if times[u] >= N:  # 負閉路検出
                    distance[u] = -inf
                else:
                    distance[u] = new_dist_u
                if not in_q[u]:
                    in_q[u] = True
                    q.append(u)
    return distance

class UnionFind:
    # 検証: https://atcoder.jp/contests/practice2/submissions/17594179
    def __init__(self, N):
        self.p = list(range(N))
        self.size = [1] * N

    def root(self, x):
        p = self.p
        while x != p[x]:
            x = p[x]
        return x

    def same(self, x, y):
        return self.root(x) == self.root(y)

    def unite(self, x, y):
        u = self.root(x)
        v = self.root(y)
        if u == v:
            return
        size = self.size
        if size[u] < size[v]:
            self.p[u] = v
            size[v] += size[u]
            size[u] = 0
        else:
            self.p[v] = u
            size[u] += size[v]
            size[v] = 0

    def count(self, x):
        return self.size[self.root(x)]

def fast_zeta_transform_superset(arr):
    # 上位集合の高速ゼータ変換  O(nlog(n))
    # fast_zeta_transform_superset([1]*8) => [8, 4, 4, 2, 4, 2, 2, 1]
    # 添字 and での畳み込みに使う
    n = len(arr)
    assert n & -n == n  # n は 2 冪
    for i in range(n.bit_length()-1):
        for s in range(n):
            if s>>i&1 == 0:
                arr[s] -= arr[s|1<<i]  # -= にすると逆変換
    return arr

def fast_zeta_transform_subset(arr):
    # 下位集合の高速ゼータ変換  O(nlog(n))
    # fast_zeta_transform_subset([1]*8) => [1, 2, 2, 4, 2, 4, 4, 8]
    # 添字 or での畳み込みに使う
    n = len(arr)
    assert n & -n == n  # n は 2 冪
    for i in range(n.bit_length()-1):
        for s in range(n):
            if s>>i&1:
                arr[s] += arr[s^1<<i]  # -= にすると逆変換
    return arr

# 倍数集合の高速ゼータ変換: https://atcoder.jp/contests/agc038/submissions/7671865

class SegmentTree(object):
    # 検証: https://atcoder.jp/contests/nikkei2019-2-qual/submissions/8434117
    # 参考: https://atcoder.jp/contests/abc014/submissions/3935971
    __slots__ = ["elem_size", "tree", "default", "op", "real_size"]

    def __init__(self, a, default, op):
        self.default = default
        self.op = op
        if hasattr(a, "__iter__"):
            self.real_size = len(a)
            self.elem_size = elem_size = 1 << (self.real_size-1).bit_length()
            self.tree = tree = [default] * (elem_size * 2)
            tree[elem_size:elem_size + self.real_size] = a
            for i in range(elem_size - 1, 0, -1):
                tree[i] = op(tree[i << 1], tree[(i << 1) + 1])
        elif isinstance(a, int):
            self.real_size = a
            self.elem_size = elem_size = 1 << (self.real_size-1).bit_length()
            self.tree = [default] * (elem_size * 2)

    def get_value(self, x: int, y: int) -> int:  # 半開区間
        l, r = x + self.elem_size, y + self.elem_size
        tree, result, op = self.tree, self.default, self.op
        while l < r:
            if l & 1:
                result = op(tree[l], result)
                l += 1
            if r & 1:
                r -= 1
                result = op(tree[r], result)
            l, r = l >> 1, r >> 1
        return result

    def set_value(self, i: int, value: int) -> None:
        k = self.elem_size + i
        op, tree = self.op, self.tree
        tree[k] = value
        while k > 1:
            k >>= 1
            tree[k] = op(tree[k << 1], tree[(k << 1) + 1])

    def get_one_value(self, i):
        return self.tree[i+self.elem_size]

    def debug(self):
        print(self.tree[self.elem_size:self.elem_size+self.real_size])

def manacher(S):
    # 最長回文 O(n)
    # R[i] := i 文字目を中心とする最長の回文の半径（自身を含む）
    # 偶数長の回文を検出するには "$a$b$a$a$b$" のようにダミーを挟む
    # 検証: https://atcoder.jp/contests/wupc2019/submissions/8665857
    # 左右で違う条件: https://atcoder.jp/contests/code-thanks-festival-2014-a-open/submissions/12911822
    c, r, n = 0, 0, len(S)  # center, radius, length
    R = [0]*n
    while c < n:
        while c-r >= 0 and c+r < n and S[c-r] == S[c+r]:
            r += 1
        R[c] = r
        d = 1  # distance from center
        while c-d >= 0 and c+d < n and d+R[c-d] < r:
            R[c+d] = R[c-d]
            d += 1
        c += d
        r -= d
    return R

def z_algorithm(S):
    # Z アルゴリズム  O(n)
    # Z[i] := S と S[i:] で prefix が何文字一致しているか
    # 検証: https://atcoder.jp/contests/arc055/submissions/14179788
    i, j, n = 1, 0, len(S)
    Z = [0] * n
    Z[0] = n
    while i < n:
        while i+j < n and S[j] == S[i+j]:
            j += 1
        if j == 0:
            i += 1
            continue
        Z[i] = j
        d = 1
        while i+d < n and d+Z[d] < j:
            Z[i+d] = Z[d]
            d += 1
        i += d
        j -= d
    return Z

# 最大流問題
from collections import deque
INF = float("inf")
TO = 0;  CAP = 1;  REV = 2
class Dinic:
    def __init__(self, N):
        self.N = N
        self.V = [[] for _ in range(N)]  # to, cap, rev
        # 辺 e = V[n][m] の逆辺は V[e[TO]][e[REV]]
        self.level = [0] * N

    def add_edge(self, u, v, cap):
        self.V[u].append([v, cap, len(self.V[v])])
        self.V[v].append([u, 0, len(self.V[u])-1])

    def add_edge_undirected(self, u, v, cap):  # 未検証
        self.V[u].append([v, cap, len(self.V[v])])
        self.V[v].append([u, cap, len(self.V[u])-1])

    def bfs(self, s: int) -> bool:
        self.level = [-1] * self.N
        self.level[s] = 0
        q = deque()
        q.append(s)
        while len(q) != 0:
            v = q.popleft()
            for e in self.V[v]:
                if e[CAP] > 0 and self.level[e[TO]] == -1:  # capが1以上で未探索の辺
                    self.level[e[TO]] = self.level[v] + 1
                    q.append(e[TO])
        return True if self.level[self.g] != -1 else False  # 到達可能

    def dfs(self, v: int, f) -> int:
        if v == self.g:
            return f
        for i in range(self.ite[v], len(self.V[v])):
            self.ite[v] = i
            e = self.V[v][i]
            if e[CAP] > 0 and self.level[v] < self.level[e[TO]]:
                d = self.dfs(e[TO], min(f, e[CAP]))
                if d > 0:  # 増加路
                    e[CAP] -= d  # cap を減らす
                    self.V[e[TO]][e[REV]][CAP] += d  # 反対方向の cap を増やす
                    return d
        return 0

    def solve(self, s, g):
        self.g = g
        flow = 0
        while self.bfs(s):  # 到達可能な間
            self.ite = [0] * self.N
            f = self.dfs(s, INF)
            while f > 0:
                flow += f
                f = self.dfs(s, INF)
        return flow

def lis(A: list):  # 最長増加部分列
    # original author: ikatakos
    # https://ikatakos.com/pot/programming_algorithm/dynamic_programming/longest_common_subsequence
    from bisect import bisect_left
    L = [A[0]]
    for a in A[1:]:
        if a > L[-1]:
            # Lの末尾よりaが大きければ増加部分列を延長できる
            L.append(a)
        else:
            # そうでなければ、「aより小さい最大要素の次」をaにする
            # 該当位置は、二分探索で特定できる
            L[bisect_left(L, a)] = a
    return len(L)

class NewtonInterpolation:
    # ニュートン補間  O(n^2)  n は次元
    # 具体的な係数は保持しない
    def __init__(self, X=(), Y=(), mod=10**9+7):
        self.mod = mod
        self.X = []
        self.C = []
        for x, y in zip(X, Y):
            self.add_constraint(x, y)

    def add_constraint(self, x, y):  # O(n)
        mod, X, C = self.mod, self.X, self.C
        numer, denom = y, 1
        for c, x_ in zip(C, X):
            numer -= denom * c
            denom = denom * (x - x_) % mod
        X.append(x)
        C.append(numer * pow(denom, mod-2, mod) % mod)

    def calc(self, x):
        mod, X, C = self.mod, self.X, self.C
        y = 0
        for c, x_ in zip(C[::-1], X[::-1]):
            y = (y * (x - x_) + c) % mod
        return y

def fast_lagrange_interpolation(Y, x, mod=10**9+7):
    # X = [0, 1, 2, ... , n] のラグランジュ補間  O(nlog(mod))  # n==len(Y)-1
    if 0 <= x < len(Y):
        return Y[x] % mod
    factorial, f, numer = [1], 1, x
    for x_ in range(1, len(Y)):
        f = f * x_ % mod
        factorial.append(f)
        numer = numer * (x - x_) % mod
    y = 0
    for x_, (y_, denom1, denom2) in enumerate(zip(Y, factorial, factorial[::-1])):
        y = (y_ * numer * pow((x-x_)*denom1*denom2, mod-2, mod) - y) % mod
    return y

def faulhaber(k, n, mod=10**9+7):  # べき乗和 0^k + 1^k + ... + (n-1)^k
    # n に関する k+1 次式になるので最初の k+2 項を求めれば多項式補間できる  O(k log(mod))
    s, Y = 0, [0]  # 第 0 項は 0
    for x in range(k+1):
        s += pow(x, k, mod)
        Y.append(s)
    return fast_lagrange_interpolation(Y, n, mod)

class RollingHash:
    # 未検証
    # 参考1:  http://tjkendev.github.io/procon-library/python/string/rolling_hash.html
    # 参考2:  https://ei1333.github.io/algorithm/rolling-hash.html
    BASE = 1000
    MOD = 1111111111111111111  # ≒10**18 素数  # 10**9くらいの素数2つ使うのとどちらが速い？
    def __init__(self, s):
        self.s = s
        self.n = n = len(s)
        BASE = RollingHash.BASE
        MOD = RollingHash.MOD
        self.h = h = [0]*(n+1)
        for i in range(n):
            h[i+1] = (h[i] * BASE + ord(s[i])) % MOD

    def get(self, l, r):  # [l, r)
        MOD = RollingHash.MOD
        return (self.h[r] - self.h[l]*pow(RollingHash.BASE, r-l, MOD)) % MOD

    @classmethod
    def connect(cls, h1, h2, h2len):
        return (h1 * pow(cls.BASE, h2len, cls.MOD) + h2) % cls.MOD

    def lcp(self, h2, l1, r1, l2, r2):  # 最長共通接頭辞
        # 区間の長さ N に対して O(logN)  # h2 は RollingHash オブジェクト
        # 自身の [l1, r1) と h2 の [l2, r2) の最長共通接頭辞の長さを返す
        length = min(r1-l1, r2-l2)
        ok, ng = 0, length+1
        while ng - ok > 1:
            c = ok + ng >> 1
            if self.get(l1, l1+c) == h2.get(l2, l2+c):
                ok = c
            else:
                ng = c
        return ok

def convolve(A, B):
    # 畳み込み (Numpy)  # 要素は整数
    # 3 つ以上の場合は一度にやった方がいい
    import numpy as np
    dtype = np.int64  # np.float128 は windows では動かない？
    fft, ifft = np.fft.rfft, np.fft.irfft
    a, b = len(A), len(B)
    if a==b==1:
        return np.array([A[0]*B[0]])
    n = a + b - 1  # 返り値のリストの長さ
    k = 1 << (n-1).bit_length()  # n 以上の最小の 2 冪
    AB = np.zeros((2, k), dtype=dtype)
    AB[0, :a] = A
    AB[1, :b] = B
    return np.rint(ifft(fft(AB[0]) * fft(AB[1]))).astype(np.int64)[:n]

def garner(A, M, mod):
    # Garner のアルゴリズム (NumPy)
    # 参考: https://math314.hateblo.jp/entry/2015/05/07/014908
    M.append(mod)
    coffs = [1] * len(M)
    constants = np.zeros((len(M),)+A[0].shape, dtype=np.int64)
    for i, (a, m) in enumerate(zip(A, M[:-1])):
        v = (a - constants[i]) * pow(coffs[i], m-2, m) % m
        for j, mm in enumerate(M[i+1:], i+1):
            constants[j] = (constants[j] + coffs[j] * v) % mm
            coffs[j] = coffs[j] * m % mm
    return constants[-1]

def convolve_mod(A, B, mod=10**9+7):
    # 任意 mod 畳み込み (NumPy)
    # 検証1: 注文の多い高橋商店 (TLE): https://atcoder.jp/contests/arc028/submissions/7467522
    # 検証2: [yosupo] Convolution (mod 1,000,000,007): https://judge.yosupo.jp/submission/12504

    #mods = [1000003, 1000033, 1000037, 1000039]  # 要素数が 10**3 以下の場合（誤差 6*2+3=15<16  復元 6*4=24>21=9*2+3）
    #mods = [100003, 100019, 100043, 100049, 100057]  # 要素数が10**5 以下の場合（誤差 5*2+5=15<16  復元 5*5=25>23=9*2+5）
    mods = [63097, 63103, 63113, 63127, 63131]  # 要素数が 5*10**5 以下の場合（誤差 4.8*2+5.7=15.3<16  復元 4.8*5=24>23.7=9*2+5.7  10**4.8=63096  10**5.7=501187）

    mods_np = np.array(mods, dtype=np.int32)
    fft, ifft = np.fft.rfft, np.fft.irfft
    a, b = len(A), len(B)
    if a == b == 1:
        return np.array([A[0] * B[0]]) % mod
    n = a + b - 1  # 畳み込みの結果の長さ
    k = 1 << (n - 1).bit_length()  # n 以上の最小の 2 冪
    AB = np.zeros((2, len(mods), k), dtype=np.int64)  # ここの dtype は fft 後の dtype に関係しない
    AB[0, :, :a] = A
    AB[1, :, :b] = B
    AB[:, :, :] %= mods_np[:, None]
    C = ifft(fft(AB[0]) * fft(AB[1]))[:, :n]
    C = ((C + 0.5) % mods_np[:, None]).astype(np.int64)
    return garner(C, mods, mod)

def scc(E, n_vertex):
    # 強連結成分分解 (NumPy, SciPy)  # E は [[a1, b1], [a2, b2], ... ] の形
    # 返り値は 強連結成分の数 と 各頂点がどの強連結成分に属しているか
    # numpy いらないのは https://tjkendev.github.io/procon-library/python/graph/scc.html
    import numpy as np
    from scipy.sparse import csr_matrix, csgraph
    A, B = np.array(E).T
    graph = csr_matrix((np.ones(len(E)), (A, B)), (n_vertex, n_vertex))
    n_components, labels = csgraph.connected_components(graph, connection='strong')
    return n_components, labels

def distribute(n, person, min, max, mode="even"):
    # n 個を person 人に分配する
    # 返り値は [[a (個), a 個もらう人数], ...]
    # 分配できないときは None を返す
    if person==0 and n==0:
        return []
    elif not min*person <= n <= max*person:
        return None
    elif mode=="even":
        q, m = divmod(n, person)
        if m==0:
            return [[q, person]]
        else:
            return [[q, person-m], [q+1, m]]
    elif mode=="greedy":
        if max==min:
            return [[max, person]]
        n -= min * person
        q, m = divmod(n, max-min)
        if m==0:
            return [[min, person-q], [max, q]]
        else:
            return [[min, person-1-q], [min+m, 1], [max, q]]
    else:
        raise ValueError("'mode' must be 'even' or 'greedy'.")

def xorshift(seed=123456789):  # 31 bit xorshift
    y = seed
    def randint(a, b):  # 閉区間
        nonlocal y
        y ^= (y & 0xffffff) << 7
        y ^= y >> 12
        return y % (b-a+1) + a
    return randint

"""
# 重み付き UnionFind https://atcoder.jp/contests/code-festival-2016-quala/submissions/8336387
# Trie https://atcoder.jp/contests/code-festival-2016-qualb/submissions/8335110
# 全方位木 dp https://atcoder.jp/contests/yahoo-procon2019-final-open/submissions/8664902
# 平方分割
#  I hate Shortest Path Problem https://atcoder.jp/contests/abc177/submissions/16384352
#  Replace Digits https://atcoder.jp/contests/abl/submissions/17049200
#  天下一数列にクエリを投げます https://atcoder.jp/contests/tenka1-2016-qualb/submissions/14415635
#  Range Affine Range Sum https://atcoder.jp/contests/practice2/submissions/17100361

A = csgraph.dijkstra(X, indices=0)

zip(*[iter(Ans)]*3)  # 3 個ずつ
zip(*[iter(map(int, sys.stdin.read().split()))]*4):

https://github.com/Lgeu/snippet/
import sys
input = sys.stdin.readline
C = np.frombuffer(buf.read(), dtype="S1").reshape(H, W+1)[:, :-1].T
"""


import pylab
import numpy

class GeneralRandom:
  """This class enables us to generate random numbers with an arbitrary
  distribution."""

  def __init__(self, x = pylab.arange(-1.0, 1.0, .01), p = None, Nrl = 1000):
    """Initialize the lookup table (with default values if necessary)
    Inputs:
    x = random number values
    p = probability density profile at that point
    Nrl = number of reverse look up values between 0 and 1"""
    if p == None:
      p = pylab.exp(-10*x**2.0)
    self.set_pdf(x, p, Nrl)

  def set_pdf(self, x, p, Nrl = 1000):
    """Generate the lookup tables.
    x is the value of the random variate
    pdf is its probability density
    cdf is the cumulative pdf
    inversecdf is the inverse look up table

    """

    self.x = x
    self.pdf = p/p.sum() #normalize it
    self.cdf = self.pdf.cumsum()
    self.inversecdfbins = Nrl
    self.Nrl = Nrl
    y = pylab.arange(Nrl)/float(Nrl)
    delta = 1.0/Nrl
    self.inversecdf = pylab.zeros(Nrl)
    self.inversecdf[0] = self.x[0]
    cdf_idx = 0
    for n in xrange(1,self.inversecdfbins):
      while self.cdf[cdf_idx] < y[n] and cdf_idx < Nrl:
        cdf_idx += 1
      self.inversecdf[n] = self.x[cdf_idx-1] + (self.x[cdf_idx] - self.x[cdf_idx-1]) * (y[n] - self.cdf[cdf_idx-1])/(self.cdf[cdf_idx] - self.cdf[cdf_idx-1])
      if cdf_idx >= Nrl:
        break
    self.delta_inversecdf = pylab.concatenate((pylab.diff(self.inversecdf), [0]))

  def random(self, N = 1000):
    """Give us N random numbers with the requested distribution"""

    idx_f = numpy.random.uniform(size = N, high = self.Nrl-1)
    idx = pylab.array([idx_f],'i')
    y = self.inversecdf[idx] + (idx_f - idx)*self.delta_inversecdf[idx]

    return y

  def plot_pdf(self):
    pylab.plot(self.x, self.pdf)

  def self_test(self, N = 1000):
    pylab.figure()
    #The cdf
    pylab.subplot(2,2,1)
    pylab.plot(self.x, self.cdf)
    #The inverse cdf
    pylab.subplot(2,2,2)
    y = pylab.arange(self.Nrl)/float(self.Nrl)
    pylab.plot(y, self.inversecdf)

    #The actual generated numbers
    pylab.subplot(2,2,3)
    y = self.random(N)
    p1, edges = pylab.histogram(y, bins = 50,
                                range = (self.x.min(), self.x.max()),
                                normed = True, new = True)
    x1 = 0.5*(edges[0:-1] + edges[1:])
    pylab.plot(x1, p1/p1.max())
    pylab.plot(self.x, self.pdf/self.pdf.max())


import itertools

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import streamlit as st

# From Python Cookbook
def erat2( ):
    D = {  }
    yield 2
    for q in itertools.islice(itertools.count(3), 0, None, 2):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            x = p + q
            while x in D or not (x&1):
                x += p
            D[x] = p

def get_primes_erat(n):
  return list(itertools.takewhile(lambda p: p<n, erat2()))

def get_prime_distribution(n):
    hist, bins = np.histogram(get_primes_erat(n), bins=np.linspace(1.5, n-0.5, n-1))
    prime_count = np.cumsum(hist)
    return prime_count

max_int = 1000
df = pd.DataFrame()
integers = list(range(2, max_int))
df['Integers'] = integers
df['Prime Counting Function'] = integers/np.log(integers)
df['Number of Primes'] = get_prime_distribution(max_int)

st.title("Prime Number Theorem")

quote = "_In number theory, the prime number theorem (PNT) describes the asymptotic distribution of the prime numbers among the positive integers. It formalizes the intuitive idea that primes become less common as they become larger by precisely quantifying the rate at which this occurs._"
st.markdown(quote)

fig, ax = plt.subplots()
ax.plot(df['Integers'], df['Prime Counting Function'], 'k-', label='Prime Counting Function')
ax.plot(df['Integers'], df['Number of Primes'], 'b-', label='Actual Number of Primes')
ax.legend()
ax.set_xlabel("n")

st.pyplot(fig)


# -*- coding: utf-8 -*-
"""
Created on Mon Nov 16 15:24:48 2015

@author: Hanna
"""
import numpy as np
import matplotlib.pyplot as plt

def featureNormalize(X):
    mu = np.mean(X,axis=0)
    X_norm = X-mu
    sigma = np.std(X_norm,axis=0)
    X_norm = X_norm/sigma

    return X_norm, mu, sigma

def hypothesis(theta,X):
    #works for any # of features

    return np.dot(X,theta)

def computeCost(X,y,theta):  # J(theta)
    #works for any # of features
    m = len(y)
    h = hypothesis(theta,X)

    return (np.dot((h-y).T,h-y))/(2.0*m)

def computeCostDeriv(X,y,theta):  # partial derivatives of J(theta)
    #works for any # of features
    m = len(y)
    h = hypothesis(theta,X)

    return np.dot(X.T,h-y)/m

def gradientDescent(X,y,theta,alpha,iterations):
    #works for any # of features

    #J(theta) as a function of # of iterations
    costHistory = np.zeros((iterations+1,1))  # for debugging
    costHistory[0,0] = computeCost(X,y,theta)  # for debugging

    #gradient descent
    for iter in range(0,iterations):
        theta = theta - alpha*computeCostDeriv(X,y,theta)
        #or, without separately calculating derivatives
        #theta = theta - np.dot(X.T,hypothesis(theta,X)-y)*alpha/len(y)

        costHistory[iter+1,0] = computeCost(X,y,theta)  # for debugging

    return theta, costHistory

def normalEquation(X,y):#analytical solution for theta which minimizes J(theta)
    #works for any # of features

    return np.dot(np.dot(np.linalg.inv(np.dot(X.T,X)),X.T),y)

if __name__ == '__main__':

    data = np.genfromtxt("ex1data2.txt", delimiter=',')
    X, y = data[:,:2], data[:,2:]
    m = np.shape(y)[0]  # number of examples in training set
    n = np.shape(X)[1]  # number of features

    #normalize features
    #print(X[0:10,:])  # first ten examples of original data
    X, mu, sigma = featureNormalize(X)
    #print(X[0:10,:])  # first ten examples of normalized data

    #multivariate linear regression
    X = np.hstack((np.ones((m,1)),X))  # add intercept column

    #try for many alphas to select optimal learning rate
    alphas = [1.0,0.3,0.1,0.03,0.01]
    iterations = 100  # 500
    #vector of # of iterations
    iter_num = np.ones((iterations+1,1))
    for i in range(0,iterations+1):
        iter_num[i,0] = i
    #plot of J(theta) vs # of iterations for alphas
    plt.figure()
    for alpha in alphas:
        theta = np.zeros((n+1,1))  # initial guess for theta
        theta, costHistory = gradientDescent(X,y,theta,alpha,iterations)
        plt.plot(iter_num[:,0],costHistory[:,0])
    plt.legend(['alpha=1.0', 'alpha=0.3', 'alpha=0.1', 'alpha=0.03', 'alpha=0.01'], loc='upper right')
    plt.ylabel('cost function')
    plt.xlabel('# of iterations')
    plt.savefig("cost_vs_iter_vs_alpha.pdf")
    plt.show()

    #select alpha = 0.3 seens to converge at 50 iterations
    alpha = 0.3
    theta = np.zeros((n+1,1))  # initial guess for theta
    theta, costHistory = gradientDescent(X,y,theta,alpha,iterations)

    # prediction for 1650 sq feet 3 bedroom house
    test = np.array([[1650.0, 3.0]])
    #normalize
    test = (test-mu)/sigma
    #add intercept
    test = np.hstack((np.ones((1,1)),test))
    predict = hypothesis(theta,test)
    print("prediction for 1650 sq feet 3 bedroom house is",predict)

    #normal equation
    #do not need to normalize data
    X, y = data[:,0:2], data[:,2:3]
    X = np.hstack((np.ones((m,1)),X))  # add intercept column
    theta = normalEquation(X,y)
    # prediction for 1650 sq feet 3 bedroom house with added intercept
    test = np.array([[1.0,1650.0, 3.0]])
    predict = hypothesis(theta,test)
    print("prediction from normal equation for 1650 sq feet 3 bedroom house is",predict)

    #results: gradient descent:[[ 293081.47339913]], normal eq:[[ 293081.4643349]]
    #if use 500 iterations: gradient descent:[[ 293081.4643349]] coincides with normal eq


# Exercise 6: Scatterplot

# generate list of numbers for height
y = [5, 5.5, 5, 5.5, 6, 6.5, 6, 6.5, 7, 5.5, 5.25, 6, 5.25]
print(y)

# create a list of numbers for weight
x = [100, 150, 110, 140, 140, 170, 168, 165, 180, 125, 115, 155, 135]
print(x)

# create histogram
import matplotlib.pyplot as plt
plt.scatter(x, y) # generate scatterplot
plt.xlabel('Weight') # label x-axis
plt.ylabel('Height') # label y-axis
plt.show() # print plot

# calculate pearson correlations
from scipy.stats import pearsonr
correlation_coeff, p_value = pearsonr(x, y)
print(correlation_coeff)

# Set up some logic
if correlation_coeff == 1.00:
    title = 'There is a perfect positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff >= 0.8:
    title = 'There is a very strong, positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff >= 0.6:
    title = 'There is a strong, positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff >= 0.4:
    title = 'There is a moderate, positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff >= 0.2:
    title = 'There is a weak, positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff > 0:
    title = 'There is a very weak, positive linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff == 0:
    title = 'There is no linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff <= -0.8:
    title = 'There is a very strong, negative linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff <= -0.6:
    title = 'There is a strong, negative linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff <= -0.4:
    title = 'There is a moderate, negative linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
elif correlation_coeff <= -0.2:
    title = 'There is a weak, negative linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
else:
    title = 'There is a very weak, negative linear relationship (r = {0:0.2f}).'.format(correlation_coeff)
print(title)

# Use title as title
import matplotlib.pyplot as plt
plt.scatter(x, y) # generate scatterplot
plt.xlabel('Weight') # label x-axis
plt.ylabel('Height') # label y-axis
plt.title(title) # set programmatic title
plt.show() # print plot


# Weekly Code           #2
# LQR                   Version 1.0.0       Date: 9/7/2019
# solving a inverted pendulum using lqr ref. below the same using matlab
# http://ctms.engin.umich.edu/CTMS/index.php?example=InvertedPendulum&section=SystemModeling
# Future Improvements:  1) add visualization
#                       2) animation and simulation
#                       3) comparison with various Q and R values.

import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg

M = 0.5
m = 0.3
b = 0.1
I = 0.006
g = 9.8
l = 0.3

p = I*(M+m) + M*m*l**2
# state space representation
A = np.array([[0, 1, 0, 0],
              [0, -(I+m*l**2)*b/p, (m**2*g*l**2)/p, 0],
              [0, 0, 0, 1],
              [0, -(m*l*b)/p, m*g*l*(M+m)/p, 0]])
B = np.array([[0],
     [(I+m*l**2)/p],
     [0],
     [m*l/p]])
C = np.array([[1, 0, 0, 0],
     [0, 0, 1, 0]])
D = np.array([[0],
     [0]])
A_size = A.size/2
# print("The poles of A are: ")
print(A)
# [w, v] = np.linalg.eig(A)
# print(w)
# We see that the system is unstable, which is expected.
# We check for controllability
AB = np.matmul(A, B)
A2B = np.matmul(A, AB)
A3B = np.matmul(A, A2B)
A4B = np.matmul(A, A3B)

# forming the controllability matrix
co = np.zeros((4, 4), float)
# filling the controllability matrix
for i in range(4):
    co[i, 0] = B[i]
    co[i, 1] = AB[i]
    co[i, 2] = A2B[i]
    co[i, 3] = A3B[i]
# print(co)
print(" The rank of ctrb = {}".format(np.linalg.matrix_rank(co)))
# we see system is controllable

# we select the R and Q matrices as follows
Q = np.matmul(C.T, C)
R = np.array([[1]])

# now we solve the countinous time ricatti equation
X = np.array(linalg.solve_continuous_are(A, B, Q, R))
print(X)
# computing the LQR Gain
K = np.linalg.inv(R)@(B.T@X)
print("K = {}".format(K))
# computing eigen values
[w, v] = np.linalg.eig(A-B@K)
print("The eigen values of the closed loop systems are \n {}".format(w))
# We see that all eigen value are in LHP
# we check from MATLAB the answer we recieve matches.


from statistics import mean
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import style
import random

style.use('fivethirtyeight')

def slope_of_reg(x_val: np.array, y_val: np.array):
    assert isinstance(x_val, np.ndarray)
    assert isinstance(y_val, np.ndarray)
    num = (mean(x_val) * mean(y_val)) - (mean(x_val * y_val))
    dem =( mean(x_val)**2) - (mean(x_val **2))
    m = num/dem
    b = mean(y_val) - m * mean(x_val)
    return m, b

def squared_error(y_orig, y_hat):
    return np.sum((y_orig - y_hat)**2)

def R_squared(y_orig, y_hat):
    y_mean = [mean(y_orig) for y in y_orig]
    squared_error_reg = squared_error(y_orig, y_hat)
    squared_error_mean = squared_error(y_orig, y_mean)
    r_squared = 1 - (squared_error_reg/ squared_error_mean)
    return r_squared

def create_dataset(n_points, variance, step, correlation=False):
    val = 1
    ys = []
    for i in range(n_points):
        y = val + random.randrange(-variance, variance)
        ys.append(y)
        if correlation and correlation == 'pos':
            val += step
        elif correlation and correlation == 'neg':
            val -= step
    xs = [i for i in range(len(ys))]
    x = np.array(xs)
    y = np.array(ys)
    return x, y

x, y = create_dataset(40, 80, 2, 'neg')

m, b = slope_of_reg(x, y)
reg_line = [(m * x) + b for x in x]
r = R_squared(y, reg_line)
print(r)

plt.plot(x, y, 'o')
plt.plot(x, reg_line)
plt.show()


import numpy as np
import pandas as pd

def amortization(per, rate, pv,
    date=pd.to_datetime('today').strftime("%Y/%m/%d"), add=0, sf=False):
    '''
    Returns two tables: monthly changes and summary over entire duration
    of loan.

    Parameters
    ----------
    per: scalar
        Number of total months.
    rate: scalar
        Percent annual interest rate.
    pv: scalar
        Principal amount loaned.
    date: string, optional
        Date entered as 'YYYYMMDD'.
    add: scalar, optional
        Additional payment towards principal given each month.

    Returns
    -------
    (df, sm) : (DataFrame, Series)
        df is a DataFrame with monthly breakdown of payments. sm is a Series
        table of summary of monthly over entire time of loan.

    Examples
    --------
    >>> import brindle as bd
    >>> import pandas as pd
    >>> df, sm = bd.amortization(72, 2.99, 35000, add=50)
    >>> pd.concat([df.head(2), df.tail(2)])
                 Date  Payment  Principal  Interest  Extra   Balance
        N
        0  2018-11-01     0.00       0.00      0.00      0  35000.00
        1  2018-12-01   581.62     492.74     88.88     50  34507.26
        65 2024-04-01   581.62     579.79      1.83     50    163.50
        66 2024-05-01   163.92     163.50      0.42     50      0.00

    >>> sm
        Payoff      2024-05-01 00:00:00
        Months                       66
        Rate                       2.99
        Payment                  531.62
        Extra                        50
        Interest                2969.22
        dtype: object

    >>> df1, sm1 = bd.amortization(72, 4.99, 22000, '20181026')
    >>> df2, sm2 = bd.amortization(72, 4.99, 22000, '20181026', add=100)
    >>> df3, sm3 = bd.amortization(72, 3.99, 22000, '20181026')
    >>> df4, sm4 = bd.amortization(72, 3.99, 22000, '20181026', add=50)
    >>> pd.DataFrame([sm, sm1, sm2, sm3])
              Payoff  Months  Rate  Payment  Extra  Interest
        0 2024-10-01      71  4.99   354.21      0   3504.11
        1 2023-06-01      55  4.99   354.21    100   2619.74
        2 2024-10-01      71  3.99   344.09      0   2775.79
        3 2024-01-01      62  3.99   344.09     50   2378.05
    '''
    # Generator to fill up DataFrame details.
    def calc(per, dates, pv, rate, pay, add, days):
        for i, (dt, dy) in enumerate(zip(dates, days)):
            if i == 0:
                yield dt, 0, 0, 0, 0, pv
            else:
                interest = round(pv * rate * dy / 100 / 365, 2)
                pr = min(pv, pay + add - interest)
                pv -= pr
                yield dt, pr + interest, pr, interest, add, pv
            if pv <= 0: break

    # Create time series DataFrame table of loan details.
    dates = pd.date_range(date, periods=per+1, freq='MS')
    days = np.array(np.array(np.diff(dates), dtype='timedelta64[D]'), dtype=int)
    pay = round(-np.pmt(rate / 100 / 12, per, pv), 2)
    cols = ['Date', 'Payment', 'Principal', 'Interest', 'Extra', 'Balance']
    df = pd.DataFrame(list(calc(per, dates, pv, rate, pay, add, days)),
        columns=cols)
    df.index.name = 'N'

    # Create summary table of loan details.
    index = ['Payoff', 'Months', 'Rate', 'Payment', 'Extra', 'Interest']
    sm = pd.Series([df['Date'].iloc[-1], len(df) - 1, rate, pay, add,
        np.sum(df['Interest'])], index=index)
    return df, sm


import json
import numpy as np
from scipy.optimize import minimize

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def cost(theta, X, y, lmbda):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid(X * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid(X * theta.T)))
    reg = lmbda * np.sum(np.power(theta[:,1:theta.shape[1]], 2))
    return (np.sum(first - second) + reg) / (2 * len(X))

def gradient(theta, X, y, lmbda):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)

    parameters = int(theta.ravel().shape[1])
    error = sigmoid(X * theta.T) - y
    grad = ((X.T * error) / len(X)).T + ((lmbda / len(X)) * theta)

    # intercept gradient is not regularized
    grad[0, 0] = np.sum(np.multiply(error, X[:,0])) / len(X)

    return np.array(grad).ravel()

def one_vs_all(X, y, num_labels, lmbda):
    rows = X.shape[0]
    params = X.shape[1]

    all_theta = np.zeros((num_labels, params + 1))
    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    for i in range(1, num_labels + 1):
        theta = np.zeros(params + 1)
        y_i = np.array([1 if label == i else 0 for label in y])
        y_i = np.reshape(y_i, (rows, 1))
        fmin = minimize(fun=cost, x0=theta, args=(X, y_i, lmbda), method='TNC', jac=gradient)
        all_theta[i-1,:] = fmin.x

    return all_theta

def predict_all(X, all_theta):
    rows = X.shape[0]
    params = X.shape[1]
    num_labels = all_theta.shape[0]

    X = np.insert(X, 0, values=np.ones(rows), axis=1)

    X = np.matrix(X)
    all_theta = np.matrix(all_theta)

    h = sigmoid(X * all_theta.T)

    scores = [ {"like": score.item(0,0), "dislike": score.item(0,1)} for score in h]
    return scores

def train_and_predict(trainer, tuning_params):
    num_classifications = 2

    for lmbda in tuning_params:
        print "\ncalculating theta for lambda param: {}".format(lmbda)
        all_theta = one_vs_all(trainer.X_train, trainer.Y, num_classifications, lmbda)
        trainer.timer.interval('Calculated theta')

        print "predicting"
        hypothesis = predict_all(trainer.X_test, all_theta)
        trainer.timer.interval('Made prediction')
        trainer.store_predict(hypothesis, lmbda)


#ヒルベルト行列のcondition numberを求める

import scipy.linalg as linalg
import numpy as np
from numpy import linalg as LA

def calc_hilbert_condition(n):
    A = np.zeros((n,n))
    for i in range(n):
        for j in range(n):
            A[i][j] = 1/(i+j+1)
    print("matrix size =", n)
    print("condition number is", LA.cond(A, 2))
if __name__ == "__main__":
    calc_hilbert_condition(3)
    calc_hilbert_condition(6)
    calc_hilbert_condition(9)


import numpy as np
import math

def sum__(x, f=lambda x: x):
	acc = 0.
	for item in x:
		acc += f(item)
	return acc
def mean(x):
	n = x.shape[0]
	acc = 0.
	for item in x:
		acc += item
	return acc/n

def	variance(x):
	u = mean(x)
	return sum__(x,lambda x: (x - u)**2) / x.shape[0]

def std(x):
	return math.sqrt(variance(x))

def dot(x, y):
	if (x is None or y is None
	or x.shape[0] != y.shape[0]):
		return None
	return sum__(x * y, lambda x:x)

def mat_vec_prod(x, y):
	if (x.shape[1], 1) != y.shape:
		return None
	res = np.zeros((x.shape[0],1))
	f = 0
	for row in x:
		row = row.reshape((row.shape[0],1))
		res[f] = dot(row, y)
		f += 1
	return res

def mat_mat_prod(x, y):
	if (x.shape[0] != y.shape[1]):
		return None
	res = np.zeros((x.shape[0],y.shape[1]))
	print(x.shape," ",y.shape)
	for i in range(x.shape[0]):
		for j in range(y.shape[1]):
			res[i][j] = dot(x[i],y[:,j])
	return res

def mse(y, y_hat):
	if y.shape != y_hat.shape:
		return None
	#return sum__(y_hat - y,lambda x: x**2)
	acc = 0.
	for elem1,elem2 in zip(y,y_hat):
		acc += (elem1 - elem2)**2
	return acc / y.shape[0]

def vec_mse(y, y_hat):
	y = y.reshape((y.shape[0],1))
	y_hat = y_hat.reshape((y_hat.shape[0],1))
	# print(y.shape)
	return float(dot(y_hat - y,y_hat - y) / y_hat.shape[0])

def reshape(x):
	x = x.reshape(x.shape[0], 1)
	return x
def linear_mse(x, y, theta):
	theta = reshape(theta)
	y = reshape(y)
	hypothes = mat_vec_prod(x,theta)
	return float(mse(hypothes, y))

def	vec_linear_mse(x, y, theta):
	y = reshape(y)
	theta = reshape(theta)
	hypothes = mat_vec_prod(x, theta)
	return float(dot(hypothes - y,hypothes - y) / x.shape[0])
# print(vec_linear_mse(X, Y, W))

# def	gradient(x, y, theta):
# 	y = reshape(y)
# 	theta = reshape(theta)
# 	hypothes = mat_vec_prod(x, theta)
# 	# scalar = float(sum__(hypothes - y) / x.shape[0])
# 	grad =  np.zeros(theta.shape)
# 	for j in range(theta.shape[0]):
# 		for i in range(x.shape[0]):
# 			grad[j] = sum__((hypothes[i] - y[i]) * x[i])
# 	return grad

def vec_gradient(x, y, theta):
	y = reshape(y)
	theta = reshape(theta)
	return dot(x, mat_vec_prod(x, theta) - y) / x.shape[0]

# print(gradient(X, Y, Z))
# print(vec_gradient(X, Y, Z))


import math
import numpy as np
import scipy.io
import matplotlib.pyplot as plt

# Load yalefaces.mat data
M = scipy.io.loadmat('yalefaces.mat')["M"]

# Flatten each 2x2 matrix to a col vector and find the mean
M_flattened = np.zeros((1024, 2414))
sum = np.zeros((1024, 1))
for i in range(2414):
    M_flattened[:, i] = np.reshape(M[:, :, i], (1, 1024))
    # Sum x vectors to find mean
    for j in range(1024):
        sum[j] += M_flattened[j, i]

x_mean = sum / 2414

# Create X with all 2414 centred image vectors
X = np.zeros((1024, 2414))
for i in range(1024):
    for j in range(2414):
        X[i][j] = M_flattened[i][j] - x_mean[i]

C = X.dot(np.transpose(X))

# Compute eigenvector/eigenvalue pairs for C
eigenvalues, eigenvectors = np.linalg.eig(C)

# Sort the eigenvalues
idx = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:,idx]

# Plot log(eigenvalues) over j
j = range(1, 1025)
log_eigenvalues = np.log10(eigenvalues)
plt.plot(j, log_eigenvalues)
plt.title('log(eigenvalues)')
plt.xlabel('j')
plt.ylabel('log(eigenvalues)')

# Reshape eigenvectors to get eigenfaces
eigenfaces = np.zeros((32, 32, 1024))
for i in range(1024):
    eigenfaces[:, :, i] = np.reshape(eigenvectors[:, i], (32, 32))

# Plot eigenfaces of largest 10 eigenvalues
fig_highest_eigenvalues, (f1, f2, f3, f4, f5, f6, f7, f8, f9, f10) = plt.subplots(1, 10)
f1.imshow(eigenfaces[:,:,0])
f2.imshow(eigenfaces[:,:,1])
f3.imshow(eigenfaces[:,:,2])
f4.imshow(eigenfaces[:,:,3])
f5.imshow(eigenfaces[:,:,4])
f6.imshow(eigenfaces[:,:,5])
f7.imshow(eigenfaces[:,:,6])
f8.imshow(eigenfaces[:,:,7])
f9.imshow(eigenfaces[:,:,8])
f10.imshow(eigenfaces[:,:,9])

# Plot eigenfaces of smallest 10 eigenvalues
fig_lowest_eigenvalues, (f11, f12, f13, f14, f15, f16, f17, f18, f19, f20) = plt.subplots(1, 10)
f11.imshow(eigenfaces[:,:,1014])
f12.imshow(eigenfaces[:,:,1015])
f13.imshow(eigenfaces[:,:,1016])
f14.imshow(eigenfaces[:,:,1017])
f15.imshow(eigenfaces[:,:,1018])
f16.imshow(eigenfaces[:,:,1019])
f17.imshow(eigenfaces[:,:,1020])
f18.imshow(eigenfaces[:,:,1021])
f19.imshow(eigenfaces[:,:,1022])
f20.imshow(eigenfaces[:,:,1023])

# Function that returns projection of vector onto subspace of B(j)
def projection_onto_B(x_mean, j):
    alpha = np.zeros((j, 1))
    proj = np.zeros((1024, 1))
    for i in range(j):
        # Add projection of vector onto jth vector in subspace
        alpha[i, 0] = (np.inner(x_mean, eigenvectors[:, i])/np.inner(eigenvectors[:, i], eigenvectors[:, i]))
        eigenvector = eigenvectors[:, i]
        # Add projection component
        for k in range(1024):
            proj[k] = proj[k] + (alpha[i, 0] * eigenvector)[k]
    return alpha, proj

j_values = [2, 2**2, 2**3, 2**4, 2**5, 2**6, 2**7, 2**8, 2**9, 2**10]

# Project image J(1) onto B
y = np.zeros((1024, 10))
fig_y = plt.figure(4)
# Loop through j values
for i in range(10):
    # Project X[:, 0] onto subspace v(1) to v(j) to get y_mean(i, j)
    projection = projection_onto_B(X[:, 0], j_values[i])[1]
    for j in range(1024):
        y[j, i] = projection[j]
    # Add x_mean vector to y_mean(i,j) to get y(i,j)
    for j in range(1024):
        y[j, i] += x_mean[j]

    # Reshape y and plot
    y_plot = np.reshape(y[:, i], (32, 32))
    plot = fig_y.add_subplot(3, 10, i + 1)
    plot.imshow(y_plot)

# Project image J(1076) onto B
for i in range(10):
    # Project X[:, 1075] onto subspace v(1) to v(j) to get y_mean(i, j)
    projection = projection_onto_B(X[:, 1075], j_values[i])[1]
    for j in range(1024):
        y[j, i] = projection[j]
    # Add x_mean vector to y_mean(i,j) to get y(i,j)
    for j in range(1024):
        y[j, i] += x_mean[j]

    # Reshape y and plot
    y_plot = np.reshape(y[:, i], (32, 32))
    plot = fig_y.add_subplot(3, 10, i + 11)
    plot.imshow(y_plot)

# Project image J(2043) onto B
for i in range(10):
    # Project X[:, 2042] onto subspace v(1) to v(j) to get y_mean(i, j)
    projection = projection_onto_B(X[:, 2042], j_values[i])[1]
    for j in range(1024):
        y[j, i] = projection[j]
    # Add x_mean vector to y_mean(i,j) to get y(i,j)
    for j in range(1024):
        y[j, i] += x_mean[j]

    # Reshape y and plot
    y_plot = np.reshape(y[:, i], (32, 32))
    plot = fig_y.add_subplot(3, 10, i + 21)
    plot.imshow(y_plot)

# Project images in set I onto B(25)
image_set = [0, 1, 6, 2042, 2043, 2044]
coeffs = np.zeros((25, 6))
for i in range(6):
    alphas = projection_onto_B(X[:, image_set[i]], 25)[0]
    # Store alpha values into coeffs
    for j in range(25):
        coeffs[j, i] = alphas[j, 0]

# Tabulate Euclidean distances between the pairwise c(i) vectors
distances = np.zeros((6, 6))
for i in range(6):
    for j in range(i + 1, 6):
        x = coeffs[:, i]
        y = coeffs[:, j]
        distances[i, j] = np.linalg.norm(x - y)

plt.show()
print("Done!")


import numpy as np
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams["figure.dpi"] = 100
mpl.rcParams["text.usetex"] = True
mpl.rc("font", **{"family": "sans-serif"})
params = {"text.latex.preamble": r"\usepackage{amsmath}"}
plt.rcParams.update(params)

sns.set_theme()

# Q7
# Multivariate Normal Distribution

points = 100000
mu = np.zeros(2)

mv_normal = (
    lambda cov: np.random.default_rng().multivariate_normal(mu, cov, points).T
)

def title(part, cov, mj=None, mn=None):
    t = (f"Part {part} | Covariance=$\\begin{{array}}{{cc}} {cov[0][0]}"
        f" & {cov[0][1]} \\\\ {cov[1][0]} & {cov[1][1]} \\end{{array}}$\n")

    if mj is not None and mn is not None:
        t += f" Minor Axis: {2*mn:.3},\nMajor Axis: {2*mj:.3}"
    return t

# Part A/B

fig, ax = plt.subplots(
    nrows=3, ncols=2, figsize=(10, 20), sharex=True, sharey=True
)

for c in range(1, 4):
    cov = c * np.identity(2)
    x, y = mv_normal(cov)
    mn, mj = max(abs(x)), max(abs(y))
    ax[c - 1, 0].scatter(x, y)
    ax[c - 1, 0].set_title(title("A", cov, mn=mn, mj=mj), x=1.5, y=0.5)

for c in range(1, 4):
    cov = c * np.diagflat([1, 2])
    x, y = mv_normal(cov)
    mn, mj = max(abs(x)), max(abs(y))
    s = ax[c - 1, 1].scatter(x, y)
    ax[c - 1, 1].set_title(title("B", cov, mn=mn, mj=mj), x=1.5, y=0.5)

for ax_ in ax.flatten():
    ax_.set_aspect("equal")
    ax_.set_xticks([])
    ax_.set_yticks([])

fig.tight_layout()
plt.show()
plt.close()

"""
As value of covariance matrix increases the radius of the circle/ellipse formed
    appears to be increasing.

In part A and part B, since the Σ matrix is a diagonal matrix.

The diagonal matrix's elements on main diagonal would specify the "variance"
    in the x and y direction.

We can see this is a perfectly symmetric curve in all the dimensions (also seen in the plots above).
"""

# Part C

cov = lambda a: np.array([[15, a], [a, 15]])

l = [1, 5, 8, 10, 14]

fig, ax = plt.subplots(
    nrows=len(l), ncols=2, figsize=(10, 5 * len(l)), sharex=True, sharey=True
)

for index, a in enumerate(l):
    mat = cov(a), cov(-a)
    x, y = mv_normal(mat[0])
    ax[index, 0].scatter(x, y)
    ax[index, 0].set_title(title("C", mat[0]), x=1.7, y=0.5)

    x, y = mv_normal(mat[1])
    ax[index, 1].scatter(x, y)
    ax[index, 1].set_title(title("C", mat[1]), x=1.7, y=0.5)

for ax_ in ax.flatten():
    ax_.set_aspect("equal")
    ax_.set_xticks([])
    ax_.set_yticks([])

fig.tight_layout()
plt.show()
plt.close()

"""
Here mean is still zero, although now covariance is a symmetric matrix.

We can see as magnitude of off diagonal elements increase the points
start to scatter in a rotated direction, and along that direction lies there maximum variance.

For negative elements, the direction is also opposite (as correlation will be negative).

This will also hold true for more dimensions.

This property of variance & direction of ellipse made by
multivariate normal distribution does not appear to be
for other distribution, although for a large number of
samples distributions will approximate to be like normal distribution
(due to CLT) hence it is applicable there.

"""


""""
The goal of this module is to implement all algorithms and numerical
methods needed to solve the Task 1 from the coding homeworks in the
Machine Learning course on coursera.com.
"""
from typing import Tuple

import numpy as np

def hypothesis_function(x: np.ndarray, theta: np.ndarray) -> np.ndarray:
    """
    Calculate the hypothesis function. The hypothesis function is the linear regression function.

    Args:
      x:
        Input dataset. A matrix with m rows and n columns.
      theta:
        The parameters for the regression function. An n+1-element vector.

    Returns:
        A dot product of matrix x and vector theta.
    """
    return np.dot(x, theta)

def compute_cost(x: np.ndarray, y: np.ndarray, theta: np.ndarray = None) -> np.float64:
    """
    Computes the cost of using theta as the parameter for linear regression to fit the data points in x and y.
    The cost function is the sum of the squares of the differences between the predicted and actual values.
    The cost function is minimized by gradient descent.

    Args:
      x:
        Input dataset. A matrix with m rows and n columns.
      y:
        The values corresponding to the input dataset. An m-element vector.
      theta:
        The parameters for the regression function. An n+1-element vector.

    Returns:
        The cost of using theta as the parameter for linear regression to fit the data points in x and y.
    """
    if theta is None:
        theta = np.zeros((x.shape[1], 1))

    m = y.size
    a = (hypothesis_function(x, theta) - y).T
    b = hypothesis_function(x, theta) - y
    j = (1.0 / (2 * m) * np.dot(a, b))[0][0]
    return j

def gradient_descent(
    x: np.ndarray,
    y: np.ndarray,
    theta: np.ndarray = None,
    num_iter: int = 2000,
    alpha: float = 0.01,
) -> Tuple[list, list]:
    """
    Performs gradient descent to learn theta. The function returns the theta and the cost history.
    Theta is a vector of parameters for the regression function. The cost history is a list of the
    cost values at each iteration.  The function will not return anything if the number of iterations
    is less than 1. The function will not return anything if the learning rate is less than 0.

    Args:
      x:
        Input dataset. A matrix with m rows and n columns.
      y:
        The values corresponding to the input dataset. An m-element vector.
      theta:
        The parameters for the regression function. An n+1-element vector.
      num_iter:
        The number of steps in gradient descent algorithm.
      alpha:
        The learning rate.

    Returns:
      A tuple consisting of:
      - A list of theta values after each iteration.
      - A list of the cost function values after each iteration.
    """
    if theta is None:
        theta = np.zeros((x.shape[1], 1))

    initial_theta = theta
    m = y.size
    costs = []
    theta_history = []

    for _ in range(num_iter):
        costs.append(compute_cost(x, y, theta))
        theta_history.append(list(theta[:, 0]))

        for i in range(len(theta)):
            theta[i] -= (alpha / m) * np.sum(
                (hypothesis_function(x, initial_theta) - y)
                * np.array(x[:, i]).reshape(m, 1)
            )

    return theta_history, costs

def normalize_features(x: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    A preprocessing step that is typically performed when dealing with learning algorithms.
    The features are normalized by substracting the mean and dividing by the standard deviation.

    Args:
      x:
        Input dataset. A matrix with m rows and n columns.

    Returns:
      A tuple consisting of:
      - A vector of means.
      - A vector of standard deviations.
    """
    means = [np.mean(x[:, 0])]
    stds = [np.std(x[:, 0])]

    for i in range(1, x.shape[1]):
        means.append(np.mean(x[:, i]))
        stds.append(np.std(x[:, i]))
        x[:, i] = (x[:, i] - means[-1]) / stds[-1]

    return np.array(means), np.array(stds)

def predict_using_normalized_features(
    means: np.ndarray,
    stds: np.ndarray,
    theta: np.ndarray,
    house_size: int,
    num_bedrooms: int,
) -> np.ndarray:
    """
    Make a prediction of value for given house size and number of bedrooms using provided theta
    vector. Normalize the y values by substring the means and dividing by standard deviations.

    Args:
      means:
        3-element vector of feature means.
      stds:
        3-element vector of feature stds.
      theta:
        The parameters for the regression function. An n+1-element vector.
      house_size:
        A house size for which the prediction should be made.
      num_bedrooms:
        A number of bedrooms for which the prediction should be made.

    Returns:
        Predicted price of the house with the given size and number of bedrooms.
    """
    y = [house_size, num_bedrooms]
    y = [(y[i] - means[i + 1]) / stds[i + 1] for i in range(len(y))]
    y.insert(0, 1)
    y = np.array(y)

    return hypothesis_function(y, theta)

def predict_from_normal_equation(
    x: np.ndarray, y: np.ndarray, house_size: int, num_bedrooms: int
) -> np.ndarray:
    """
    Using the normal equations, computes the closed-form solution to linear regression.

    Args:
      x:
        Input dataset. A matrix with m rows and n columns.
      y:
        The values corresponding to the input dataset. An m-element vector.
      house_size:
        A house size for which the prediction should be made.
      num_bedrooms:
        A number of bedrooms for which the prediction should be made.

    Returns:
      Predicted price of the house with the given size and number of bedrooms.
    """

    def norm_eq(_x: np.ndarray, _y: np.ndarray) -> np.ndarray:
        """
        Implementation of the normal equation.

        Args:
          _x:
            Input dataset. A matrix with m rows and n columns.
          _y:
            The values corresponding to the input dataset. An m-element vector.

        Returns:
            A theta vector for given _x and _y matrices.
        """
        return np.dot(np.dot(np.linalg.inv(np.dot(_x.T, _x)), _x.T), _y)

    return hypothesis_function(np.array([1, house_size, num_bedrooms]), norm_eq(x, y))


import numpy as np
from math import pi,cos,sin,tan,atan,ceil
import operator as op
from cmath import exp
def linear_conv(x,h):

              N=len(x)+len(h)-1
              x1=np.zeros((N))
              h1=np.zeros((N))
              m=len(x)
              n=len(h)
              y=np.zeros((N))
              for i in range(m):
                            x1[i]=x[i]
              for i in range(n):
                            h1[i]=h[i]
              for i in range(N):
                            for j in range(i+1):
                                          y[i]=y[i]+ x1[j]*h1[i-j]
              return y

def circular_conv(x,h):
              N=max(len(x),len(h))
              y=np.zeros((N))
              x1=np.zeros((N))
              h1=np.zeros((N))
              for i in range(len(x)):
                            x1[i]=x[i]
              for i in range(len(h)):
                            h1[i]=h[i]
              for i in range(N):
                            for j in range(N):
                                          y[i]=y[i]+x1[j]*h1[op.mod((i-j),N)]

              return y

def sampling_theorem():
              Rt=float(input('Enter the resolution of analog signal'))
              Ns= int(1/Rt)
              t=[Rt*t1 for t1 in range(Ns)]
              fm=int(input('enter the fundamental frequency'))
              xt=[cos(2*pi*fm*Rt*t1) for t1 in range(Ns)]
              fs=int(input('enter the sampling frequency'))
              Ts=(1/fs)
              N=fs
              n=[n1 for n1 in range(N)]
              xn=[cos(2*pi*fm*n1*Ts) for n1 in range(N)]
              xr=np.zeros((len(xt)))
              tr=0
              for t1 in range(Ns):
                            for n2 in range(N):
                                          if((pi*(tr-n2*Ts)/Ts)==0):
                                                        xr[t1]=xr[t1]+xn[n2]
                                          else:
                                                        xr[t1]=xr[t1]+xn[n2]*(sin(pi*(tr-n2*Ts)/Ts))/((pi*(tr-n2*Ts))/Ts)
                            tr=tr+Rt
              return t,xt,n,xn,xr
def fft(x):
        N=len(x)
        X=np.zeros((N),'complex')
        for k in range(N):
            for n in range(N):
                X[k]=X[k] + x[n]*exp(-1j*2*pi*k*n/N)
        return X

def auto_correlation(x):
        x1=x[::-1]
        N=len(x)+len(x1)-1
        x11=np.zeros((N))
        h1=np.zeros((N))
        m=len(x)
        n=len(x1)
        y=[0]*N
        for i in range(m):
            x11[i]=x[i]
        for i in range(n):
            h1[i]=x1[i]
        for i in range(N):
            for j in range(i+1):
                y[i]=y[i]+ x11[j]*h1[i-j]
        return y

def cross_correlation(x,h):
        h1=h[::-1]
        N=len(x)+len(h)-1
        x11=np.zeros((N))
        h11=np.zeros((N))
        m=len(x)
        n=len(h)
        y=np.zeros((N))
        for i in range(m):
            x11[i]=x[i]
        for i in range(n):
            h11[i]=h1[i]
        for i in range(N):
            for j in range(i+1):
                y[i]=y[i]+ x11[j]*h11[i-j]
        return y

def filter(b,a,x):
              N=len(x)
              b1=np.zeros((N))
              a1=np.zeros((N))
              nr=np.zeros((N))
              dr=np.zeros((N))
              y=np.zeros((N))
              if(np.size(a)==1):
                            for i in range(len(b)):
                                          b1[i]=b[i]
                            for i in range(N):
                                          for j in range(i+1):
                                                        y[i]=y[i]+b1[j]*x[i-j]
              else:

                            for i in range(len(b)):
                                          b1[i]=b[i]
                            for i in range(len(a)):
                                          a1[i]=a[i]
                            for i in range(N):
                                          for j in range(i+1):
                                                        nr[i]=nr[i]+b1[j]*x[i-j]
                                          for j in range(i+1):
                                                        dr[i]=dr[i]-a1[j]*y[i-j]
                                          y[i]=nr[i]+dr[i]
              return y

def fir_lpf(N,wc,win,freq_resolution):
              w=np.zeros((N))
              if win=='hamm':
                            for n in range(N):
                                          w[n]=0.54-0.46*cos((2*pi*n)/(N-1))
              elif win=='hann':
                            for n in range(N):
                                          w[n]=0.5-0.5*cos((2*pi*n)/(N-1))
              else:
                            for n in range(N):
                                          w[n]= 1

              hd=np.zeros((N))
              h=np.zeros((N))
              alp=(N-1)/2
              for n in range(N):
                            if n==alp:
                                          hd[n]=wc/pi
                            else:
                                          hd[n]=sin(wc*(n-alp))/(pi*(n-alp))
              for n in range(N):
                            h[n]=hd[n]*w[n]
              N1=np.ceil((2*pi)/(freq_resolution))+1
              H=np.zeros(int(N1),'complex')
              w2=-pi
              t1=np.zeros(int(N1))
              i=0
              for w1 in range(int(N1)):
                            for n in range(N):
                                          H[w1]=H[w1]+h[n]*exp(-1j*w2*n)
                            t1[i]=w2
                            w2=w2+freq_resolution
                            i=i+1
              return h,t1,H
def fir_hpf(N,wc,win,freq_resolution):
              w=np.zeros((N))
              if win=='hamm':
                            for n in range(N):
                                          w[n]=0.54-0.46*cos((2*pi*n)/(N-1))
              elif win=='hann':
                            for n in range(N):
                                          w[n]=0.5-0.5*cos((2*pi*n)/(N-1))
              else:
                            for n in range(N):
                                          w[n]= 1

              hd=np.zeros((N))
              h=np.zeros((N))
              alp=(N-1)/2
              for n in range(N):
                            if n==alp:
                                          hd[n]=(pi-wc)/pi
                            else:
                                          hd[n]= -sin(wc*(n-alp))/(pi*(n-alp))
              for n in range(N):
                            h[n]=hd[n]*w[n]
              N1=np.ceil((2*pi)/(freq_resolution))+1
              H=np.zeros(int(N1),'complex')
              w2=-pi
              t1=np.zeros(int(N1))
              i=0
              for w1 in range(int(N1)):
                            for n in range(N):
                                          H[w1]=H[w1]+h[n]*exp(-1j*w2*n)
                            t1[i]=w2
                            w2=w2+freq_resolution
                            i=i+1
              return h,t1,H
def buttord(fp,fs,ap1,as1,F):
              T=1/F
              wp=2*pi*fp/F
              ws=2*pi*fs/F
              Wp=2*F*tan(wp/2)
              Ws=2*F*tan(ws/2)
              nr= 10**(ap1/10)-1
              dr=  10**(as1/10)-1
              N= np.log10((nr/dr))/(2*np.log10(Wp/Ws))
              N=ceil(N)
              if(ap1>10):
                            Wc= (Ws)/((10**(as1/10)-1)**(1/(2*N)))
              else:
                            Wc= (Wp)/((10**(ap1/10)-1)**(1/(2*N)))
              wc= 2*atan((Wc*T)/2)
              return N,wc/pi


import numpy as np
import random

def four_way_slice(m, n):
    m11 = m[:n // 2, :n // 2]
    m12 = m[:n // 2, n // 2:]
    m21 = m[n // 2:, :n // 2]
    m22 = m[n // 2:, n // 2:]

    return m11, m12, m21, m22

def multiply_Strassen_pad(a, b, n, n_min):
    if n <= n_min:
        return np.matmul(a, b)

    a11, a12, a21, a22 = four_way_slice(a, n)
    b11, b12, b21, b22 = four_way_slice(b, n)

    p1 = multiply_Strassen_pad(a11 + a22, b11 + b22, n // 2, n_min)
    p2 = multiply_Strassen_pad(a21 + a22, b11, n // 2, n_min)
    p3 = multiply_Strassen_pad(a11, b12 - b22, n // 2, n_min)
    p4 = multiply_Strassen_pad(a22, b21 - b11, n // 2, n_min)
    p5 = multiply_Strassen_pad(a11 + a12, b22, n // 2, n_min)
    p6 = multiply_Strassen_pad(a21 - a11, b11 + b12, n // 2, n_min)
    p7 = multiply_Strassen_pad(a12 - a22, b21 + b22, n // 2, n_min)

    c11 = p1 + p4 - p5 + p7
    c12 = p3 + p5
    c21 = p2 + p4
    c22 = p1 + p3 - p2 + p6

    c1 = np.concatenate([c11, c12], axis=1)
    c2 = np.concatenate([c21, c22], axis=1)
    return np.concatenate([c1, c2], axis=0)

def multiply_Strassen(a, b, n, n_min):
    assert a.shape[0] == a.shape[1], "a must be a square matrix"
    assert b.shape[0] == b.shape[1], "b must be a square matrix"
    assert a.shape[0] == b.shape[0], "a and b must have the same size"

    size = a.shape[0]
    pad_size = 1
    while pad_size < size:
        pad_size *= 2

    a_padded = np.zeros((pad_size, pad_size))
    a_padded[:size, :size] = a

    b_padded = np.zeros((pad_size, pad_size))
    b_padded[:size, :size] = b

    result = multiply_Strassen_pad(a_padded, b_padded, pad_size, n_min)

    return result[:size, :size]

def generate_random_matrix(size):
    matrix = []
    for i in range(0, size):
        current_line = []
        for j in range(0, size):
            current_line.append(random.random() * 10)
        matrix.append(current_line)
    return np.array(matrix)

if __name__ == '__main__':
    size = 100

    a = generate_random_matrix(size)

    b = generate_random_matrix(size)

    our_result = multiply_Strassen(a, b, size, 2)
    np_result = np.matmul(a, b)

    norm_matrix = our_result - np_result

    #print(multiply_Strassen(a, b, size, 2))
    #print(np.matmul(a, b))
    print("Norm =", np.linalg.norm(norm_matrix))