import numpy as np
import math
from sklearn import metrics

def NMI(A,B):
    # len(A) should be equal to len(B)
    total = len(A)
    A_ids = set(A)
    B_ids = set(B)

    #Mutual information
    MI = 0
    eps = 1.4e-45
    for idA in A_ids:
        for idB in B_ids:
            idAOccur = np.where(A==idA)
            idBOccur = np.where(B==idB)
            idABOccur = np.intersect1d(idAOccur,idBOccur)
            px = len(idAOccur[0]) / total
            py = len(idBOccur[0]) / total
            pxy = len(idABOccur) / total
            MI = MI + pxy * math.log(pxy / (px * py) + eps)
    # Normalized Mutual information
    Hx = 0
    for idA in A_ids:
        idAOccurCount = 1.0*len(np.where(A==idA)[0])
        Hx = Hx - (idAOccurCount/total)*math.log(idAOccurCount/total+eps)
    Hy = 0
    for idB in B_ids:
        idBOccurCount = 1.0*len(np.where(B==idB)[0])
        Hy = Hy - (idBOccurCount/total)*math.log(idBOccurCount/total+eps)
    MIhat = 2.0*MI/(Hx+Hy)
    return MIhat

def H(x : np.ndarray):
    where = x > 0.0001
    entropy = x[where] * np.log(x[where])
    return - np.sum(entropy)

def my_NMI(A, B):
    length = len(A)
    set_A = set(A)
    set_B = set(B)

    p_a = np.array([np.sum(A == i) / length for i in set_A])
    p_b = np.array([np.sum(B == i) / length for i in set_B])

    H_a = H(p_a)
    H_b = H(p_b)

    eps = 1e-10
    MI = 0
    for i, x in enumerate(set_A):
        a_equals_x = np.where(A == x)
        for j, y in enumerate(set_B):

            b_equals_y = np.where(B == y)

            joint = np.intersect1d(a_equals_x, b_equals_y)
            joint_p = len(joint) / length
            MI += joint_p * math.log(joint_p / (p_a[i] * p_b[j]) + eps)

    NMI = 2 * MI / (H_a + H_b)
    return NMI

if __name__ == '__main__':
    A = np.array([1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3])
    B = np.array([1,2,1,1,1,1,1,2,2,2,2,3,1,1,3,3,3])

    # print(metrics.normalized_mutual_info_score(A, B))
    print(my_NMI(A, B))
    # print(NMI(A, B))


from scipy import stats
import matplotlib.pyplot as plt
import numpy as np
import jax.scipy.stats as jstats
from jax import grad

# various beta distribution shapes
x = np.linspace(0.0, 1.0, 100)
plt.plot(x, stats.beta.pdf(x, a=0.5, b=0.5), label=f'a={0.5}, b={0.5}')
plt.plot(x, stats.beta.pdf(x, a=1.0, b=1.0), label=f'a={1.0}, b={1.0}')
plt.plot(x, stats.beta.pdf(x, a=10.0, b=10.0), label=f'a={10.0}, b={10.0}')
plt.plot(x, stats.beta.pdf(x, a=5.0, b=20.0), label=f'a={5.0}, b={20.0}')
plt.plot(x, stats.beta.pdf(x, a=20.0, b=5.0), label=f'a={20.0}, b={5.0}')
plt.ylabel('Beta PDF')
plt.xlabel('Action')
plt.legend()
plt.show()

# action distribution
plt.plot(x, stats.beta.pdf(x, a=2.0, b=2.0), label=f'a={2.0}, b={2.0}')
plt.axvline(0.1, color='red')
plt.legend()
plt.xlabel('Action')
plt.ylabel('Beta PDF')
plt.show()

# beta distribution at x=0.1 for varying values of a
a = np.linspace(0.1, 2.0, 100)
pdf_at_x = [stats.beta.pdf(x=0.1, a=a, b=2.0) for a in a]
plt.plot(a, pdf_at_x)
plt.xlabel('a')
plt.ylabel('Beta PDF @ x=0.1, b=2.0')
plt.xlim((0.1, 2.0))
plt.show()

# modified beta distribution to increase the density at x=0.1
plt.plot(x, stats.beta.pdf(x, a=2.0, b=2.0), label=f'a={2.0}, b={2.0}')
plt.plot(x, stats.beta.pdf(x, a=0.6, b=2.0), label=f'a={0.6}, b={2.0}')
plt.axvline(0.1, color='red')
plt.legend()
plt.xlabel('Action')
plt.ylabel('Beta PDF')
plt.show()

# 1. Define the function for which we want a gradient.
def jax_beta_pdf(
        x: float,
        a: float,
        b: float
):
    return jstats.beta.pdf(x=x, a=a, b=b, loc=0.0, scale=1.0)

# 2. Ask JAX for the gradient with respect to the second argument (shape parameter a).
jax_beta_pdf_grad = grad(jax_beta_pdf, argnums=1)

# 3. Calculate the gradient that we want.
print(f'{jax_beta_pdf_grad(0.1, 2.0, b=2.0)}')

import numpy,from scipy,import jax,from jax


"""Bracketing Methods"""
import math as m

"""Bisection Method with error""" """ROOT FINDING"""

def f(x):
    return x**3 - 4*(x*2) + 10

x_lower = 200
x_upper = -300
counter = 0
error = 0.000001
while True:
    counter +=1
    x_mid = (x_lower+x_upper)/2
    if f(x_lower)*f(x_upper)>0:
        print('The upper and lower limits have not been proper')
        break
    if f(x_lower)*f(x_mid)<0:
        x_upper = x_mid
    if f(x_lower)*f(x_mid)>0:
        x_lower = x_mid
    if -error <= f(x_lower)*f(x_mid) <= error:
        print(f'We have found the root at x = {x_mid} after {counter} iterations')
        break

"""False Position Method""" """ROOT FINDING""" """Similar Triangle method for finding the roots"""
MAX_ITER = 1000000
a = 200
b = -300

# The function is x^3 - x^2 + 2
def func(x):
    return (x**3 - 4*(x*2) + 10)
# Prints root of func(x) in interval [a, b]
def regulaFalsi(a,b):
    if func(a) * func(b) >= 0:
        print("You have not assumed right a and b")
        return -1
    c = a
    # Initialize result
    for i in range(MAX_ITER):
        # Find the point that touches x axis
        c = (a * func(b) - b * func(a))/ (func(b) - func(a))
        # Check if the above found point is root
        if func(c) == 0:
            break
        # Decide the side to repeat the steps
        elif func(c) * func(a) < 0:
            b = c
        else:
            a = c
    print("The value of root is : " , '%.4f' %c)
    return
regulaFalsi(a,b)

"""Modified false Position Method""" """ROOT FINDING"""

def modregulaFalsi(a,b):
    if func(a) * func(b) >= 0:
        print("You have not assumed right a and b")
        return -1
    c = a
   # Initialize result
    for i in range(MAX_ITER):
    # Find the point that touches x axis
        c1 = (a * 0.5*func(b) - b * func(a))/(0.5*func(b) - func(a))
        c2 = (a * func(b) - b * 0.5* func(a))/(func(b) - 0.5* func(a))
        if func(c1) < func(c2):
            c = c1
        else:
            c = c2
        # Check if the above found point is root
        if func(c) == 0:
            break
        elif func(c) * func(a) < 0:
            b = c
        else:
            a = c
            print("The value of root is : " , '%.4f' %c)
            break

modregulaFalsi(a,b)

"""Incremental Search Techniques for finding loads of roots""" """ROOT FINDING"""

def naive_root(f, x_guess, tolerance, step_size):
    steps_taken = 0
    while abs(f(x_guess)) > tolerance:
        if f(x_guess) > 0:
            x_guess -= step_size
        elif f(x_guess) < 0:
            x_guess += step_size
        else:
            return x_guess
        steps_taken += 1
    return x_guess, steps_taken

f = lambda x: x**2 - 20
root, steps = naive_root(f, x_guess=4.5, tolerance=.01, step_size=.001)
print ("root is:", root)
print ("steps taken:", steps)

"""Open Root Finding Methods""" """ROOT FINDING"""

def f(x):
    return x*x*x + x*x -1
# Re-writing f(x)=0 to x = g(x)
def g(x):
    return 1/m.sqrt(1+x)
# Implementing Fixed Point Iteration Method
def fixedPointIteration(x0, e, N):
    print('\n\n*** FIXED POINT ITERATION ***')
    step = 1
    flag = 1
    condition = True
    while condition:
        x1 = g(x0)
        print('Iteration-%d, x1 = %0.6f and f(x1) = %0.6f' % (step, x1, f(x1)))
        x0 = x1
        step = step + 1
        if step > N:
            flag=0
            break
        condition = abs(f(x1)) > e
        if flag==1:
            print('\nRequired root is: %0.8f' % x1)
        else:
            print('\nNot Convergent.')

# Input Section
x0 = 1
e = 0.01
N = 1000

fixedPointIteration(x0, e, N)

"""Newton Raphson""" """ROOT FINDING""" """BEST TO USE BUT HAS PROBLEMS WITH DERIVATIVE IF EQUATION IS DIFFICULT"""

def newton(f,Df,x0,epsilon,max_iter):
    """Solution of f(x)=0 by Newton's method.
    Parameters
    ----------
    f : function for which we are searching for a solution
    f(x)=0.
    Df : Derivative of f(x).
    x0 : Initial guess for a solution f(x)=0.
    epsilon : number
    Stopping criteria is abs(f(x)) < epsilon.
    max_iter : integer
    Maximum number of iterations
    Examples
    --------
    >>> f = lambda x: x**2 - x - 1
    >>> Df = lambda x: 2*x - 1
    >>> newton(f,Df,1,1e-8,10)
    Found solution after 5 iterations.
    1.618033988749989  """
    xn = x0
    for n in range(0,max_iter):
        fxn = f(xn)
    if abs(fxn) < epsilon:
        print('Found solution after',n,'iterations.')
    return xn
    Dfxn = Df(xn)
    if Dfxn == 0:
        print('Zero derivative. No solution found.')
    return None
    xn = xn - fxn/Dfxn
    print('Exceeded maximum iterations. No solution found.')
    return None

f = lambda x: x**4 - x - 1
df= lambda x: 4*x**3 - 1
x0=1
epsilon=0.001
max_iter=100
solution = newton(f,df,x0,epsilon,max_iter)
print(f'solution {solution}')

"""Secant Technique"""

def secant(f,a,b,N):
    if f(a)*f(b) >= 0:
        print("Secant method fails.")
        return None
    a_n = a
    b_n = b
    for n in range(1,N+1):
        m_n = a_n - f(a_n)*(b_n -a_n)/(f(b_n) - f(a_n))
        f_m_n = f(m_n)
    if f(a_n)*f_m_n < 0:
        a_n = a_n
        b_n = m_n
    elif f(b_n)*f_m_n < 0:
        a_n = m_n
        b_n = b_n
    elif f_m_n == 0:
        print("Found exact solution.")
        return m_n
    else:
        print("Secant method fails.")
        return None
    return a_n - f(a_n)*(b_n - a_n)/(f(b_n) - f(a_n))

f = lambda x: x**4 - x - 1
solution = secant(f,1,2,25)
print(f'Secant Method solutions: {solution}')

"""Modified Secant Technique"""

def secant_method(func, x0, alpha=1.0, tol=1E-9, maxit=200):
    """
    Uses the secant method to find f(x)=0.
    INPUTS
    * f : function f(x)
    * x0 : initial guess for x
    * alpha : relaxation coefficient:
    modifies Secant step size
    * tol : convergence tolerance
    * maxit : maximum number of iteration, default=200
    """
    x, xprev = x0, 1.001*x0
    f, fprev = x**4 - x - 1, xprev**4 - xprev - 1
    rel_step = 2.0 *tol
    k = 0
    rel_step = abs(x-xprev)/abs(x)
    # Full secant step
    dx = -f/(f - fprev)*(x - xprev)
    while (abs(f) > tol) and (rel_step) > tol and (k<maxit):
        if x == 0:
            x = 1
        rel_step = abs(x-xprev)/abs(x)
    # Full secant step
        dx = -f/(f - fprev)*(x - xprev)
    # Update `xprev` and `x` simultaneously
        xprev, x = x, x + alpha*dx
    # Update `fprev` and `f`:
        fprev, f = f, func(x)
        k += 1
        # print('{0:10d} {1:12.5f} {2:12.5f}{3:12.5f} {4:12.5f}'\.format(k, xprev, fprev, rel_step, alpha*dx))
    return x
funcT = lambda x: x**4 - x - 1
solution = secant_method(funcT,1,alpha=1.0,tol=1E-9,maxit=200)
print(f'Modified Secant Method solutions: {solution}')

"""Inverse Quadratic Interpolation"""

def inverse_quadratic_interpolation(f, x0, x1, x2, max_iter=20,
    tolerance=1e-5):
    steps_taken = 0
    while steps_taken < max_iter and abs(x1-x0) > tolerance: # last guess and new guess are v close
        fx0 = f(x0)
        fx1 = f(x1)
        fx2 = f(x2)
        L0 = (x0 * fx1 * fx2) / ((fx0 - fx1) * (fx0 - fx2))
        L1 = (x1 * fx0 * fx2) / ((fx1 - fx0) * (fx1 - fx2))
        L2 = (x2 * fx1 * fx0) / ((fx2 - fx0) * (fx2 - fx1))
        new = L0 + L1 + L2
        x0, x1, x2 = new, x0, x1
        steps_taken += 1
    return x0, steps_taken
f = lambda x: x**2 - 20
root, steps = inverse_quadratic_interpolation(f, 4.3, 4.4, 4.5)
print ("Inverse Quadratic Interpolation root is:", root)
print ("steps taken:", steps)

"""Brent Method"""
def f_01 ( x ):
  import numpy as np
  value = np.sin ( x ) - 0.5 * x
  return value

def zero ( a, b, machep, t, f ):

#*****************************************************************************80
#
## ZERO seeks the root of a function F(X) in an interval [A,B].
#
#  Discussion:
#
#    The interval [A,B] must be a change of sign interval for F.
#    That is, F(A) and F(B) must be of opposite signs.  Then
#    assuming that F is continuous implies the existence of at least
#    one value C between A and B for which F(C) = 0.
#
#    The location of the zero is determined to within an accuracy
#    of 6 * MACHEPS * abs ( C ) + 2 * T.
#
#    Thanks to Thomas Secretin for pointing out a transcription error in the
#    setting of the value of P, 11 February 2013.
#
#  Licensing:
#
#    This code is distributed under the GNU LGPL license.
#
#  Modified:
#
#    03 December 2016
#
#  Author:
#
#    Original FORTRAN77 version by Richard Brent
#    Python version by John Burkardt
#
#  Reference:
#
#    Richard Brent,
#    Algorithms for Minimization Without Derivatives,
#    Dover, 2002,
#    ISBN: 0-486-41998-3,
#    LC: QA402.5.B74.
#
#  Parameters:
#
#    Input, real A, B, the endpoints of the change of sign interval.
#
#    Input, real MACHEP, an estimate for the relative machine
#    precision.
#
#    Input, real T, a positive error tolerance.
#
#    Input, real value = F ( x ), the name of a user-supplied
#    function which evaluates the function whose zero is being sought.
#
#    Output, real VALUE, the estimated value of a zero of
#    the function F.
#

#
#  Make local copies of A and B.
#
  sa = a
  sb = b
  fa = f ( sa )
  fb = f ( sb )

  c = sa
  fc = fa
  e = sb - sa
  d = e

  while ( True ):
    if ( abs ( fc ) < abs ( fb ) ):
      sa = sb
      sb = c
      c = sa
      fa = fb
      fb = fc
      fc = fa
    tol = 2.0 * machep * abs ( sb ) + t
    m = 0.5 * ( c - sb )
    if ( abs ( m ) <= tol or fb == 0.0 ):
      break
    if ( abs ( e ) < tol or abs ( fa ) <= abs ( fb ) ):
      e = m
      d = e
    else:
      s = fb / fa
      if ( sa == c ):
        p = 2.0 * m * s
        q = 1.0 - s
      else:
        q = fa / fc
        r = fb / fc
        p = s * ( 2.0 * m * q * ( q - r ) - ( sb - sa ) * ( r - 1.0 ) )
        q = ( q - 1.0 ) * ( r - 1.0 ) * ( s - 1.0 )
      if ( 0.0 < p ):
        q = - q
      else:
        p = - p
      s = e
      e = d
      if ( 2.0 * p < 3.0 * m * q - abs ( tol * q ) and p < abs ( 0.5 * s * q ) ):
        d = p / q
      else:
        e = m
        d = e
    sa = sb
    fa = fb
    if ( tol < abs ( d ) ):
      sb = sb + d
    elif ( 0.0 < m ):
      sb = sb + tol
    else:
      sb = sb - tol
    fb = f ( sb )
    if ( ( 0.0 < fb and 0.0 < fc ) or ( fb <= 0.0 and fc <= 0.0 ) ):
      c = sa
      fc = fa
      e = sb - sa
      d = e
  value = sb
  return value

def zero_test ( ):

#*****************************************************************************80
#
## ZERO_TEST tests the Brent zero finding routine on all test functions.
#
#  Licensing:
#
#    This code is distributed under the GNU LGPL license.
#
#  Modified:
#
#    03 December 2016
#
#  Author:
#
#    John Burkardt
#
  import numpy as np

  print ( '' )
  print ( 'ZERO_TEST' )
  print ( '  ZERO seeks a root X of a function F()' )
  print ( '  in an interval [A,B].' )

  eps = 2.220446049250313E-016
  machep = np.sqrt ( eps )
  t = 10.0 * np.sqrt ( eps )
  a = 1.0
  b = 2.0
  x = zero ( a, b, machep, t, f_01 )
  print ( '' )
  print ( '  f_01(x) = sin ( x ) - x / 2' )
  print ( '  f_01(%g) = %g' % ( x, f_01 ( x ) ) )

def timestamp ( ):

#*****************************************************************************80
#
## TIMESTAMP prints the date as a timestamp.
#
#  Licensing:
#
#    This code is distributed under the GNU LGPL license.
#
#  Modified:
#
#    06 April 2013
#
#  Author:
#
#    John Burkardt
#
#  Parameters:
#
#    None
#
  import time

  t = time.time ( )
  print ( time.ctime ( t ) )

  return None

def timestamp_test ( ):

#*****************************************************************************80

  import platform

  print ( '' )
  print ( 'TIMESTAMP_TEST:' )
  print ( '  Python version: %s' % ( platform.python_version ( ) ) )
  print ( '  TIMESTAMP prints a timestamp of the current date and time.' )
  print ( '' )

  timestamp ( )
#
#  Terminate.
#
  print ( '' )
  print ( 'TIMESTAMP_TEST:' )
  print ( '  Normal end of execution.' )
  return

if ( __name__ == '__main__' ):
  timestamp ( )
  zero_test ( )
  timestamp ( )

"""Ralston - Rabinowitz for Multiple Roots"""

function = lambda x: (x-3)*(x-1)*(x-1)
derivative = lambda x: 3*x**2-10*x+7
second_derivative = lambda x: 6*x -10

def u(x):
    return function(x)/derivative(x)

def derivative_u(x):
    return (derivative(x)**2-function(x)*second_derivative(x))/(derivative(x)**2)

def Rolston_Rabinowitz(f,Df,x0,epsilon,max_iter):
    """Solution of f(x)=0 by Newton's method.
    Parameters
    ----------
    f : function for which we are searching for a solution
    f(x)=0.
    Df : Derivative of f(x).
    x0 : Initial guess for a solution f(x)=0.
    epsilon : number
    Stopping criteria is abs(f(x)) < epsilon.
    max_iter : integer
    Maximum number of iterations
    Examples
    --------
    >>> f = lambda x: x**2 - x - 1
    >>> Df = lambda x: 2*x - 1
    >>> newton(f,Df,1,1e-8,10)
    Found solution after 5 iterations.
    1.618033988749989  """
    xn = x0
    for n in range(0,max_iter):
        fxn = f(xn)
    if abs(fxn) < epsilon:
        print('Found solution after',n,'iterations.')
        return xn
    Dfxn = Df(xn)
    if Dfxn == 0:
        print('Zero derivative. No solution found.')
        return None
    xn = xn - u(xn)/(derivative_u(xn))
    print('Exceeded maximum iterations. No solution found.')
    return None

x0=1
epsilon=0.01
max_iter=100
solution = newton(function,derivative,x0,epsilon,max_iter)
print(f'The solution of Rolstion using derivatives is: {solution}')


"""Binomial pricing of options"""
import numpy as np
# pylint: disable=invalid-name

def call_option(S, K):
    return max(S - K, 0)

def put_option(S, K):
    return max(K - S, 0)

def risk_neutral_prob(r, U, D, d_t=0):
    return (np.exp(r * d_t) - D) / (U - D)

def option_price(p, C_u, C_d, r, d_t):
    return (p * C_u + (1 - p) * C_d) * np.exp(-r * d_t)

def main():
    S0 = 100
    K = 105
    r = 0.03
    U = 1.07
    D = 1 / U
    d_t = 1 / 12.0
    M = 6
    p = risk_neutral_prob(r, U, D, d_t)
    print(f'p={p}')
    C_u = put_option(S0*U, K)
    C_d = put_option(S0*D, K)
    C0 = option_price(p, C_u, C_d, r, d_t)

    # Stock price lattice
    S = []
    for i in range(M, 0, -1):
        Ss = []
        for j in range(i + 1):
            Ss.append(S0 * U ** (i - j) * D ** j)
        print(Ss)
        S.append(Ss)
    print([S0])
    S.append([S0])

    # EUR option lattice
    C = []
    C.append(put_option(S0 * U ** (M - j) * D ** j, K) for j in range(M))
    for i in range(M, 0, -1):
        Cs = []
        for j in range(i + 1):
            C0 = put_option(S0 * U ** (i - j) * D ** j, K)
            C1 = option_price(
                p,
                C[-1][j],
                C[-1][j],
                r, (M - i) * d_t)
            Cs.append(C0)
        print(Cs)
        C.append(Cs)
    print([option_price(p, put_option(U * S0, K), put_option(D * S0, K), r, M * d_t)])

if __name__ == '__main__':
    main()

"""
# init vars
r = 0.02
d_t = 1.0 / 12
S0 = 10.0
K = 9.0
U = 13 / S0
D = 7.5 / S0
p = risk_neutral_prob(r, U, D, d_t)
MODE = 'call'

if MODE == 'call':
    # Call option
    C_u = call_option(13, K)
    C_d = call_option(7.5, K)
elif MODE == 'put':
    # Put option
    C_u = put_option(13, K)
    C_d = put_option(7.5, K)
else:
    raise ValueError(f'Unknown MODE {MODE}')

C0 = (p * C_u + (1 - p) * C_d) * np.exp(-r * d_t)
print(f'U={U}')
print(f'D={D}')
print(f'p={p}')
print(f'C0={C0}')
"""


from math import sqrt
from itertools import izip
from numpy import mean

from py_variance_std import t_percentile

def calc_slope(r, sdy, sdx): return r * (float(sdy)/sdx)

def line_fitting(x_arr, y_arr):
    """
    using straight line y = mx + c;
    m(of a sample data points) = Covariance(X,Y)/Covariance(X,X) =
    E[(X - E(X))(Y - E(Y))]/E[(X - E(X))^2]
    Another way: Look at calc_slope given STD Y and STD X and r
    """
    xbar = mean(x_arr)
    ybar = mean(y_arr)
    xsqr_bar = mean([i**2 for i in x_arr])
    xybar = mean([i*j for i,j in izip(x_arr, y_arr)])
    #calcuate the slope m
    m = (xbar*ybar - xybar)/(xbar**2 - xsqr_bar)
    #calculate the y intercept
    c = ybar - m*xbar
    return ybar,m,xbar,c

def trace_line(x_arr, y_arr, x_start = 0):
    y, m, x, c = line_fitting(x_arr, y_arr)
    return [(i, (m*i)+c) for i in [x_start]+list(x_arr)]

def line_error(**params):
    """
    The least squares estimates represent the minimum value;
    http://www.pmean.com/10/LeastSquares.html
    params: x_arr, y_arr, m,c
    """
    if 'x_arr' in params and 'y_arr' in params:
        if ('m' in params and 'c' in params):
            m,c = params['m'], params['c']
        else:
            y, m, x, c = line_fitting(params['x_arr'], params['y_arr'])
        #return difference magnitude between y,actual - y,calculated/predicted
        return [(yi - ((m*xi)+c))**2 for yi,xi in izip(params['y_arr'], params['x_arr'])]

def std_error_y_estimate(n, y_line_error_var):
    """
    To construct a confidence interval for the slope of the regression line, we need to know the standard error of the sampling distribution of the slope;

    n: total samples in x or y;
    y_line_error_var: sum(line_error(**params))

    df = n-2 since two variables while calculating linear regression.
    #calculate \summ(yi - y_cap)^2 variance
    line_error_var = line_error(**params)
    """
    return sqrt(float(y_line_error_var)/(n-2))

def x_line_std(x_arr):
    xbar = mean(x_arr)
    return sqrt(sum([(xi - xbar)**2 for xi in x_arr]))

def std_error_linear(se_y, x_line_std):
    """
    se_y: from std_error_y_estimate(n, y_line_error_var)
    #calculate x - xbar variance and then STD
    xbar = mean(x_arr)
    x_line_std: x_line_std(x_arr, xbar)
    """
    return se_y/x_line_std

def find_std_err_linear(x_arr, y_arr, n_sample):
    #Find SE of SEy/SEx
    #find descriptive params
    ybar,m,xbar,c = line_fitting(x_arr, y_arr)
    #find error in x
    se_x = x_line_std(x_arr)
    #find error in y
    y_line_error = sum(line_error(**dict(x_arr=x_arr, y_arr=y_arr, m=m, c=c)))
    se_y = std_error_y_estimate(n_sample, y_line_error)
    #return standard error
    return std_error_linear(se_y, se_x)

def r_squared(x_arr, y_arr):
    """
    Literally Trying to do sqrt() of scipy.stats import pearsonr val
    using functions in this module: linear_regression.py.

    Also called Coefficient of Determination.
    It simply means total_variation_line: How much the best fit line is
    "fit" Or Away from the scattered points. High value means good fit.
    How much % is explained by the Fitted Line.
    High R^2 = good model, probably profitable,
    Low R^2 = bad model, probably dangerous
    """
    y, m, x, c = line_fitting(x_arr, y_arr)
    total_var_y = ([(i-y)**2 for i in y_arr])  #(y-ybar)^2
    #print sum(total_var_y)
    #\summ(yi - mxi * c)^2/\summ(yi - ybar)^2
    variation_not_by_line = float(sum(line_error(x_arr=x_arr, y_arr=y_arr, m=m, c=c)))/sum(total_var_y)
    #R sqaured
    return 1 - variation_not_by_line #total variation in x, variation in line

def calc_tscore_from_r(r2,n):
    """
    Hypothesis Testing if relationship is due to sampling error.
    r: coefficient of determination
    n: number of elements in a sample
    Returns: t score
    For looking at critical t val and comparing the t score,
    df = n-2 since there are 2 variables for correlation under test.
    """
    return sqrt(r2*float(n-2)/(1 - r2))

def calc_p_from_tval_from_r(r,n, one_tailed= 0 ):
    return t_percentile(calc_tscore_from_r(r,n), n-2, one_tailed= one_tailed)

def margin_error_linear(tscore, se): return tscore * se

def ci_linear(slope, tscore, se):
    margin_error = margin_error_linear(tscore, se)
    return (slope - margin_error, slope + margin_error)


# Question 3 Lab Assignment 2
# AB Satyaprakash - 180123062

# imports ----------------------------------------------------------------------
from math import exp, factorial
import numpy as np
from sympy import *

# functions --------------------------------------------------------------------

def f(x):
    return exp(-(x**2))

def makePoly(x0, x1):
    return np.array([x0, x1])

def secondLagrange(x0, x1, x2):
    # The given function here is exp(-x^2)
    f0, f1, f2 = f(x0), f(x1), f(x2)

    # Represents the numerator polynomial part in the form of an np array
    nl0 = np.polymul(makePoly(1, -x1), makePoly(1, -x2))
    nl1 = np.polymul(makePoly(1, -x0), makePoly(1, -x2))
    nl2 = np.polymul(makePoly(1, -x0), makePoly(1, -x1))

    # Constant portion to be multiplied with the polynomial
    cl0 = f0/((x0-x1)*(x0-x2))
    cl1 = f1/((x1-x0)*(x1-x2))
    cl2 = f2/((x2-x0)*(x2-x1))

    # multiplying the constants
    nl0 = nl0*cl0
    nl1 = nl1*cl1
    nl2 = nl2*cl2

    px = nl0+nl1+nl2
    return px

def derivative(x0, n):
    x = symbols('x')
    f = exp(-x*x)
    fn = f.diff(x, n)
    fn = lambdify(x, fn)
    return fn(x0)

def maxError(nodes, x):
    err = 1
    for n in nodes:
        err *= (x-n)
    err /= factorial(len(nodes))
    a = min(nodes)
    b = max(nodes)
    l = np.linspace(a, b, 250)
    ret = 0
    for z in l:
        der = derivative(z, len(nodes))
        ret = max(ret, abs(err*der))
    return ret

# ------------------------------------------------------------------------------
# Lagrange form of interpolating polynomial P2(x) at the nodes x0 = −1, x1 = 0 and x2 = 1
x0, x1, x2 = -1, 0, 1
px = secondLagrange(x0, x1, x2)
print('The Lagrange form of interpolating polynomial P2(x) is: \n {}\n'.format(np.poly1d(px)))

# The value of P2(0.9)
val = 0.9
print('The value of P2(0.9) is {}'.format(np.polyval(px, val)))
print('The value of P2(0.9) rounded to 6 decimal places is {}'.format(round(np.polyval(px, val), 6)))

# The true value of f(0.9)
print('The true value of f(0.9) is {}'.format(f(0.9)))

# The max error in this calculation
print('The max error in this calculation is {}'.format(maxError([x0, x1, x2], val)))

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 2/Code/q3.py


import numpy as np
import matplotlib.pyplot as mp

# Interpolates the function f using the left rectangle method on the range [a, b] with a precision of epsilon
# Returns the integral of f on the range [a, b], if track_efficiency = False
# Returns the integral of f on the range [a, b] and the dictionnary containing the value of each step, otherwise
def rectangle(f, a, b, epsilon, track_efficiency = False):
	n = 1
	# Initial integral for n = 1
	curIntegration = (b-a) * f(a)

	if track_efficiency:
		interalArray = {}
		interalArray[1] = curIntegration

	while n == 1 or np.abs(lastIntegration - curIntegration) > epsilon:
		lastIntegration = curIntegration # Save the previous value

		n *= 2
		h = (b-a)/n

		# Compute the new integral value
		sum = 0
		for i in range(1, n, 2):
			sum += f(a + h*i)

		curIntegration = h * sum + lastIntegration / 2

		if track_efficiency:
			interalArray[n] = curIntegration

	if track_efficiency:
		return curIntegration, interalArray
	else:
		return curIntegration

# Interpolates the function f using the midpoint rectangle method on the range [a, b] with a precision of epsilon
# Returns the integral of f on the range [a, b], if track_efficiency = False
# Returns the integral of f on the range [a, b] and the dictionnary containing the value of each step, otherwise
def midpoint(f, a, b, epsilon, track_efficiency = False):
	n = 1
	# Initial integral for n = 1
	curIntegration = (b-a) * f((a+b)/2)

	if track_efficiency:
		interalArray = {}
		interalArray[1] = curIntegration

	while n == 1 or np.abs(lastIntegration - curIntegration) > epsilon:
		lastIntegration = curIntegration # Save the previous value

		n *= 3
		h = (b-a)/n

		# Compute the new integral value
		sum = 0
		for i in range(0, n, 3):
			sum += f(a + h*i + h/2) + f(a + h*(i + 2) + h/2)

		curIntegration = h * sum + lastIntegration / 3

		if track_efficiency:
			interalArray[n] = curIntegration

	if track_efficiency:
		return curIntegration, interalArray
	else:
		return curIntegration

# Interpolates the function f using the given method on the range [a, b] with a precision of epsilon
# Returns the integral of f on the range [a, b], if track_efficiency = False
# Returns the integral of f on the range [a, b] and the dictionnary containing the value of each step, otherwise
def integrate(method, f, a, b, epsilon, track_efficiency = False):
	return method(f, a,  b, epsilon, track_efficiency)

def test_integral():
	# f(x) = x² + 2
	f = lambda x: x*x + 2
	a = 0
	b = 1
	epsilon = 1e-4
	expected = 7/3
	print("f(x) = x² + 2")
	print("Expected:", expected)

	integralLeft, interalLeftArray = integrate(rectangle, f, a, b, epsilon, True)
	print("--Left rectangle method--")
	print("Computed integral:", integralLeft)
	print("Relative error:", np.abs(expected - integralLeft) / expected)

	integralMid, interalMidArray = integrate(midpoint, f, a, b, epsilon, True)
	print("--Middle rectangle method--")
	print("Computed integral:", integralMid)
	print("Relative error:", np.abs(expected - integralMid) / expected)

	mp.plot(list(interalLeftArray.keys()), np.abs([expected] * len(interalLeftArray) - np.array(list(interalLeftArray.values()))), label = "Left rectangle", marker='.', linewidth = 1.0)
	mp.plot(list(interalMidArray.keys()), np.abs([expected] * len(interalMidArray) - np.array(list(interalMidArray.values()))), label = "Middle rectangle", marker='.', linewidth = 1.0)
	mp.xscale('log')
	mp.yscale('log')
	mp.legend()
	mp.title("Absolute error of the left rectangle and the midpoint methods\non the function $f(x) = x^2 + 2$ on the range [0, 1] with epsilon = $10^{-4}$")
	mp.xlabel("n, the number of calls of f")
	mp.ylabel("Absolute error of the computed integral value")
	mp.show()

if __name__ == "__main__":
	test_integral()


# import modules
import numpy as np

'''
# Description.
determine the area of a simple polygon whose vertices are described by
ordered pairs in the plane based on Gauss's area formula.

# Input(s).
1xn vector with x-coordinates of the polygon (xCoord).

1xn vector with y-coordinates of the polygon (yCoord).

# Output(s):
polygon area (area)

# Example1: by giving next values
xCoord = np.arange(1,10)
yCoord = xCoord**2

is obtained: area = 84.0

---
 area = polyarea(xCoord, yCoord)
'''
def polyarea(xCoord, yCoord):
    area = 0.5*np.abs(np.dot(xCoord, np.roll(yCoord, 1))-\
        np.dot(yCoord, np.roll(xCoord, 1)))
    return area
'''
BSD 2 license.

Copyright (c) 2016, Universidad Nacional de Colombia, Ludger O.
   Suarez-Burgoa and Exneyder Andrés Montoya Araque.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''


# Graphical Solutions
## Introduction to Linear Programming

#Import some required packages.
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Graphical solution is limited to linear programming models containing only two decision variables (can be used with three variables but only with great difficulty).

Graphical methods provide a picture of how a solution for a linear programming problem is obtained.

## Product mix problem - Beaver Creek Pottery Company
How many bowls and mugs should be produced to maximize profits given labor and materials constraints?

Product resource requirements and unit profit:

Decision Variables:

$x_{1}$ = number of bowls to produce per day

$x_{2}$ = number of mugs to produce per day

Profit (Z)  Mazimization

Z = 40$x_{1}$ + 50$x_{2}$

Labor Constraint Check

1$x_{1}$ + 2$x_{2}$ <= 40

Clay (Physicial Resource) Constraint Check

4$x_{1}$ + 3$x_{2}$ <= 120

Negative Production Constaint Check

$x_{1}$ > 0

$x_{2}$ > 0

#Create an Array X2 from 0 to 60, and it should have a length of 61.
x2 = np.linspace(0, 60, 61)

#This is the same as starting your Excel Spreadsheet with incrementing X2
x2

#Labor Constraint Check
# 1x1 + 2x2 <= 40
#x1 = 40 - 2*x2
c1 =  40 - 2*x2
c1

#Clay (Physicial Resource) Constraint Check
#4x1 + 3x2 <= 120
#x1 = (120 - 3*x2)/4
c2  = (120 - 3*x2)/4
c2

#Calculate the minimum of X1 you can make per the 2 different constraints.
ct = np.minimum(c1,c2)
ct

#remove those valuese that don't follow non-negativity constraint.
ct= ct[0:21]
x2= x2[0:21] #Shape of array must be the same.
ct

#Calculate the profit from the constrained
profit = 40*ct+50*x2
profit

# Make plot for the labor constraint
plt.plot(c1, x2, label=r'1$x_{1}$ + 2$x_{2}$ <= 40')
plt.xlim((0, 60))
plt.ylim((0, 60))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.fill_between(c1, x2, color='grey', alpha=0.5)

#Graph Resource Constraint
plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ <= 120')
plt.xlim((0, 60))
plt.ylim((0, 60))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.fill_between(c2, x2, color='grey', alpha=0.5)

# Make plot for the combined constraints.
plt.plot(c1, x2, label=r'1$x_{1}$ + 2$x_{2}$ <= 40')
plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ <= 120')
#plt.plot(ct, x2, label=r'min(x$x_{1}$)')
plt.xlim((0, 60))
plt.ylim((0, 60))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.fill_between(ct, x2,  color='grey', alpha=0.5)

Our solution must be in in lies somewhere in the grey feasible region in the graph above. However, according to the fundamental theorum of Linear programming we know it is at a vertex.

"In mathematical optimization, the fundamental theorem of linear programming states, in a weak formulation, that the maxima and minima of a linear function over a convex polygonal region occur at the region's corners. Further, if an extreme value occurs at two corners, then it must also occur everywhere on the line segment between them."

- [Wikipedia](https://en.wikipedia.org/wiki/Fundamental_theorem_of_linear_programming)

#This returns the index position of the maximum value
max_value = np.argmax(profit)
max_value

#Calculate The max Profit that is made.
profit_answer=profit[max_value]
profit_answer

# Verify all constraints are integers
x2_answer = x2[max_value]
x2_answer

# Verify all constraints are integers
ct_answer = ct[max_value]
ct_answer

## Q1 Challenge

What if the profit function is:

Z = 70$x_{1}$ + 20$x_{2}$

Find the optimal solution using Python. Assign the answers to:

q1_profit_answer
q1_x1_answer
q1_x2_answer


import numpy as np

def L2Loss(y_predicted, y_ground_truth, reduction="None"):
    """returns l2 loss between two arrays

    :param y_predicted: array of predicted values
    :type y_predicted: ndarray
    :param y_ground_truth: array of ground truth values
    :type y_ground_truth: ndarray
    :param reduction: reduction mode, defaults to "mean"
    :type reduction: str, optional
    :return: l2-loss
    :rtype: scalar if reduction is sum or mean, else ndarray
    """
    # Calculate the difference array
    difference = y_predicted - y_ground_truth
    # Raise every difference value to the power of 2
    squared_difference = np.multiply(difference, difference)
    # L2 distance is the reduced form of the squared difference array
    if reduction == "sum":
        # Reduction can be done by summing up all the values in the difference array (this is known as "L2-Loss")
        l2_distance = np.sum(squared_difference)
        return l2_distance
    elif reduction == "mean":
        # Reduction can also be done by taking the mean (this is known as "Mean Squared Error")
        mean_squared_error = np.mean(squared_difference)
        return mean_squared_error
    elif reduction == "None":
        return squared_difference
    else:
        print('ValueError: reduction should be "sum" / "mean" / "None"')

def main():
    print("Initializing predicted and ground truth arrays:\n")
    print('(NOTE: Enter the values in a space-separated format. Ex: "5.36 1.02 2.03")')
    y_predicted = [
        float(item) for item in input("Enter the predicted values: ").split()
    ]
    y_ground_truth = [
        float(item)
        for item in input("Enter the corresponding ground truth values: ").split()
    ]
    assert len(y_predicted) == len(
        y_ground_truth
    ), "Number of predicted values {} and ground truth {} values should match".format(
        len(y_predicted), len(y_ground_truth)
    )
    y_predicted = np.array(y_predicted)
    y_ground_truth = np.array(y_ground_truth)
    reduction = str(input('Enter the reduction mode: "sum" / "mean" / "None": '))
    loss = L2Loss(y_predicted, y_ground_truth, reduction=reduction)
    print("L2-Loss with {}-reduction: {}".format(reduction, loss))

if __name__ == "__main__":
    main()


import numpy as np

from scipy.optimize import linprog

def ex1():
    c = np.array([-2, -3])
    A = np.array([[1, 1], [1, 2], [-1, 1]])
    b = np.array([3, 4, 1])

    x1_bounds = (0, None)
    x2_bounds = (0, None)

    res = linprog(c, A, b, bounds=(x1_bounds, x2_bounds), method='simplex')
    print("Ex1")
    print(res)

def ex4():
    A = np.array([[1, -1], [-4, 1]])
    b = np.array([1, 4])

    c_a = np.array([1, 0])
    c_b = np.array([-2, 1])
    c_c = np.array([8, -2])

    x1_bounds = (0, None)
    x2_bounds = (0, None)

    res_a = linprog(c_a, A, b, bounds=(x1_bounds, x2_bounds), method='simplex')
    res_b = linprog(c_b, A, b, bounds=(x1_bounds, x2_bounds), method='simplex')
    res_c = linprog(c_c, A, b, bounds=(x1_bounds, x2_bounds), method='simplex')

    print("Ex4a")
    print(res_a)
    print("Ex4b")
    print(res_b)
    print("Ex4c")
    print(res_c)

def ex5():
    c = np.array([1, 1, 4])
    A = np.array([[1, -1, -1], [2, -3, -3]])
    b = np.array([1, 2])

    x1_bounds = (0, None)
    x2_bounds = (0, None)
    x3_bounds = (0, None)

    res = linprog(c, A_eq=A, b_eq=b, bounds=(x1_bounds, x2_bounds, x3_bounds), method='simplex')
    print("Ex5")
    print(res)

def ex6():
    c = np.array([1, 2])

    A_eq= np.array([[2, -5]])
    b_eq = np.array([4])

    x1_bounds = (0, None)
    x2_bounds = (0, None)

    A = np.array([[-1, 2]])
    b = np.array([-6])

    res = linprog(c, A_ub=A, b_ub=b, A_eq=A_eq, b_eq=b_eq, bounds=(x1_bounds, x2_bounds), method='simplex')
    print("Ex6 a = 2")
    print(res)

    A = np.array([[-1, 3]])
    b = np.array([-6])

    res = linprog(c, A_ub=A, b_ub=b, A_eq=A_eq, b_eq=b_eq, bounds=(x1_bounds, x2_bounds), method='simplex')
    print("Ex6 a = 3")
    print(res)

ex1()
ex4()
ex5()
ex6()


import timeit
from typing import Dict, Tuple
import numpy as np

# Generic functions

def trapezoidal(func, n: int, left: float, right: float) -> float:
    res = (func(left) + func(right)) / 2
    delta = (right - left) / n
    for x in np.arange(left + delta, right, delta):
        res += func(x)
    return delta * res

def simpson_1_3(func, n: int, left: float, right: float) -> float:
    res = func(left) + func(right)
    delta = (right - left) / n
    for i, x in enumerate(np.arange(left + delta, right, delta)):
        res += func(x) * (4 if i % 2 == 0 else 2)
    return delta * res / 3

def simpson_3_8(func, n: int, left: float, right: float) -> float:
    res = func(left) + func(right)
    delta = (right - left) / n
    for i, x in enumerate(np.arange(left + delta, right, delta)):
        res += func(x) * (2 if (i + 1) % 3 == 0 else 3)
    return delta * res * 3 / 8

class Romberg:
    def __init__(self, func, left: float, right: float) -> None:
        self._func = func
        self._left = left
        self._right = right
        self._cache: Dict[Tuple[int, int], float] = dict()

    def calculate(self, j: int, k: int) -> float:
        if k <= 0:
            if (0, 0) in self._cache:
                return self._cache[(0, 0)]
            self._cache[(0, 0)] = trapezoidal(self._func, 2 ** j, self._left, self._right)
            return self._cache[(0, 0)]
        res = self._cache.get((j, k))
        if res is not None:
            return res
        res = (4 ** k * self.calculate(j, k - 1) - self.calculate(j - 1, k - 1)) / (4 ** k - 1)
        self._cache[(j, k)] = res
        return res

def numeric_function(x: float) -> float:
    return x * x * x * 5 - 8

def run_test(test_name: str, func_name: str, num_section: int) -> None:
    setup_str = 'from integration import {0}, numeric_function'.format(func_name)
    code_str = '{0}(numeric_function, {1}, 0.0, 5.0)'.format(func_name, num_section)
    num_iter = 1000
    duration_ns = timeit.timeit(code_str, setup=setup_str, number=num_iter) / num_iter * 1e+9
    print('{0} {1}: {2:.2f}ns'.format(test_name, num_section, duration_ns))

if __name__ == '__main__':
    num_sections = (1, 2, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
    test_dict = {
        'Trapezoidal': 'trapezoidal',
        'Simpson 1/3': 'simpson_1_3',
        'Simpson 3/8': 'simpson_3_8'
    }
    for name, f_name in test_dict.items():
        for n in num_sections:
            run_test(name, f_name, n)


import numpy as np
import matplotlib.pyplot as plt

a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a)  # exponential function
print(exp_a)

sum_exp_a = np.sum(exp_a)  # sum of the exponential functions
print(sum_exp_a)

y = exp_a / sum_exp_a
print(y)
print("")

a = np.array([1010, 1000, 990])
# np.exp(a) / np.sum(np.exp(a)) #calculation of softmax func

c = np.max(a)  # maximum of the components of a : 1010
d = a - c
print(d)

ans = np.exp(a - c) / np.sum(np.exp(a - c))
print(ans)

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c)  # subtract c in case of overflow when components of a is large
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y


"""
Basic mathematical functions operate element-wise on arrays. They are available both as operator overloads and as functions in the NumPy module.

import numpy

a = numpy.array([1,2,3,4], float)
b = numpy.array([5,6,7,8], float)

print a + b                     #[  6.   8.  10.  12.]
print numpy.add(a, b)           #[  6.   8.  10.  12.]

print a - b                     #[-4. -4. -4. -4.]
print numpy.subtract(a, b)      #[-4. -4. -4. -4.]

print a * b                     #[  5.  12.  21.  32.]
print numpy.multiply(a, b)      #[  5.  12.  21.  32.]

print a / b                     #[ 0.2         0.33333333  0.42857143  0.5       ]
print numpy.divide(a, b)        #[ 0.2         0.33333333  0.42857143  0.5       ]

print a % b                     #[ 1.  2.  3.  4.]
print numpy.mod(a, b)           #[ 1.  2.  3.  4.]

print a**b                      #[  1.00000000e+00   6.40000000e+01   2.18700000e+03   6.55360000e+04]
print numpy.power(a, b)         #[  1.00000000e+00   6.40000000e+01   2.18700000e+03   6.55360000e+04]

Task

You are given two integer arrays,
and of dimensions X

.
Your task is to perform the following operations:

    Add (

+
)
Subtract (
-
)
Multiply (
*
)
Integer Division (
/
)
Mod (
%
)
Power (
**

    )

Input Format

The first line contains two space separated integers,
and .
The next lines contains space separated integers of array .
The following lines contains space separated integers of array

.

Output Format

Print the result of each operation in the given order under Task.

Sample Input

1 4
1 2 3 4
5 6 7 8

Sample Output

[[ 6  8 10 12]]
[[-4 -4 -4 -4]]
[[ 5 12 21 32]]
[[0 0 0 0]]
[[1 2 3 4]]
[[    1    64  2187 65536]]

Use // for division in Python 3.

"""

import numpy as np
n, m = map(int, input().split())
a, b = (np.array([input().split() for _ in range(n)], dtype=int) for _ in range(2))
print(a+b, a-b, a*b, a//b, a%b, a**b, sep='\n')


import numpy as np

# Function
def f(x):
    y = 1 / np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2)
    return y

# General Simpson Method
def Simpson(a, b, tol):
    # Integral Cimputations With Simpson Method
    def S(n):
        h = (b-a)/(2*n)
        I = h/3*(f(a) + f(b))

        for i in range(1, 2*n):
            x_i = a + h*i
            if i % 2 == 0:
                I += 2*h/3 * f(x_i)
            else:
                I += 4*h/3 * f(x_i)

        return I

    # Integral Error
    def dS(n):

        dI = (np.abs(S(2*n) - S(n))) / 15

        return dI

    # min{n} = 3 in General Simpson Method
    n = 3
    d = dS(n)/S(n)

    while d > tol:
        n += 1
        d = dS(n)/S(n)

    return S(n), dS(n), d

if __name__ == "__main__":
    print('\nComputated with General Simpson Method:')
    print(f"\tI ± δI = {Simpson(-5, 5, 0.001)[0]:.8f} ± {Simpson(-5, 5, 0.001)[1]:.8f}")
    print(f'\tThe Relative error is η = {Simpson(-5, 5, 0.001)[2]:.4f} < 0.1 %')


# Import important libraries
import numpy as np
import pylab as plt
import pandas as pd
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

load_iris = datasets.load_iris()
iris_df = pd.DataFrame(load_iris.data, columns=[load_iris.feature_names])

print(iris_df.head())

print(load_iris.data.shape)

standardized_x = StandardScaler().fit_transform(load_iris.data)
print(standardized_x[:2])

print(standardized_x.T)

covariance_matrix_x = np.cov(standardized_x.T)
print(covariance_matrix_x)

eigenvalues, eigenvectors = np.linalg.eig(covariance_matrix_x)

print(eigenvalues)

print(eigenvectors)

total_of_eigenvalues = sum(eigenvalues)
varariance = [(i / total_of_eigenvalues)*100 for i in sorted(eigenvalues, reverse=True)]

print(varariance)

eigenpairs = [(np.abs(eigenvalues[i]), eigenvectors[:,i]) for i in range(len(eigenvalues))]

# Sorting from Higher values to lower value
eigenpairs.sort(key=lambda x: x[0], reverse=True)
print(eigenpairs)

matrix_weighing = np.hstack((eigenpairs[0][1].reshape(4,1),
                      eigenpairs[1][1].reshape(4,1)))
print(matrix_weighing)

Y = standardized_x.dot(matrix_weighing)
print(Y)

plt.figure()
target_names = load_iris.target_names
y = load_iris.target
for c, i, target_name in zip("rgb", [0, 1, 2], target_names):
    plt.scatter(Y[y==i,0], Y[y==i,1], c=c, label=target_name)

plt.xlabel('PCA 1')
plt.ylabel('PCA 2')
plt.legend()
plt.title('PCA')
plt.show()


import pandas as _pd
import numpy as _np
import math as _math

def nth_prime_number(n):
    if n==1:
        return 2
    count = 1
    num = 1
    while(count < n):
        num +=2 #optimization
        if is_prime(num):
            count +=1
    return num

def is_prime(num):
    # TODO: might be faster to use _math.gcd?
    factor = 2
    while (factor * factor <= num):
        if num % factor == 0:
             return False
        factor +=1
    return True

def chebyshev_theta_upto(n):
    '''
    Returns a pandas series with all the values of chebyshev's theta function
    up to n.

    TODO: change range steps to vary with size of n to stop it from loading
    forever for big n.
    '''
    df=_pd.DataFrame(index=range(0, n, 1))
    df['is_prime']=df.index.map(lambda n: n if is_prime(n) else 0)
    df['log']=df.is_prime.map(lambda n: _np.log(n) if n!=0 else 0)
    df['chebyshev_theta']=df.log.cumsum()
    return df['chebyshev_theta']

def modulo_mult_table(n, returns_neg=False):
    df=_pd.DataFrame(index=range(0, n, 1))
    if returns_neg:
        R=range(0,-n, -1)
    else:
        R=range(0, n, 1)
    for i in R:
        df[str(i)]=df.index.map(lambda x: (x*i)%n)
    return df

def modulo_sum_table(n, returns_neg=False):
    df=_pd.DataFrame(index=range(0, n, 1))
    if returns_neg:
        R=range(0,-n, -1)
    else:
        R=range(0, n, 1)
    for i in R:
        df[str(i)]=df.index.map(lambda x: (x+i)%n)
    return df

def primes_less_than(n):
    # TODO: there's probably a better way to do this lol
    primes=[]
    for i in range(1, n):
        if is_prime(i):
            primes.append(i)
    return primes

def coprimes_less_than(n):
    coprimes=[]
    for i in range(1, n):
        if _math.gcd(i, n)==1:
            coprimes.append(i)
    return coprimes


"""
Created on May 12 22:47:36 2022
"""

import numpy as np

def spherical2cartesian(spherical_coordinates, deg_rad: str = 'rad'):
    """
    Converts spherical coordinates to Cartesian/rectangular coordinates.

    Parameters
    ----------
    spherical_coordinates:
        A tuple containing spherical coordinates.
    deg_rad: str, optional
        Indication whether the input coordinates are in degrees or radians. The default
        is 'rad'.

    Returns
    -------
    list:
        Cartesian coordinates of the input spherical coordinates.

    """

    # convert from degrees to radians (only theta and phi parameters)

    if deg_rad == 'deg':
        cs__ = np.deg2rad(spherical_coordinates[1:])
        cs_ = np.append(spherical_coordinates[0], cs__)
    else:
        cs_ = np.array(spherical_coordinates)

    # reinitialize the coordinates
    rho, theta, phi = cs_.flatten()

    # calculate the Cartesian coordinates
    x = rho * np.sin(phi) * np.cos(theta)
    y = rho * np.sin(phi) * np.sin(theta)
    z = rho * np.cos(phi)

    return x, y, z


'''
Was 2015 anomalous?
100xp

1990 and 2015 featured the most no-hitters of any season of baseball (there were seven).
Given that there are on average 251/115 no-hitters per season, what is the probability of
having seven or more in a season?

Instructions
-Draw 10000 samples from a Poisson distribution with a mean of 251/115 and assign to n_nohitters.
-Determine how many of your samples had a result greater than or equal to 7 and assign to n_large.
-Compute the probability, p_large, of having 7 or more no-hitters by dividing n_large by the total
number of samples (10000).
-Hit 'Submit Answer' to print the probability that you calculated.
'''
import numpy as np

# Seed random number generator
np.random.seed(42)

# Draw 10,000 samples out of Poisson distribution: n_nohitters
n_nohitters = np.random.poisson((251 / 115), size=10000)

# Compute number of samples that are seven or greater: n_large
n_large = np.sum(n_nohitters >= 7)

# Compute probability of getting seven or more: p_large
p_large = n_large / 10000

# Print the result
print('Probability of seven or more no-hitters:', p_large)


# coding: utf-8

# In[ ]:

import numpy as np
import matplotlib.pyplot as plt

# In[ ]:

# Logistic function implementation
def logistic_eq(r,x):
    return r*x*(1-x)

# In[ ]:

# Show the logistic function
x = np.linspace(0, 1)
plt.plot(x, logistic_eq(2, x), 'k')
plt.show()

# logistic_eq(4.0009, 0.75)

# In[ ]:

# Iterate the function for a given r
def logistic_equation_orbit(seed, r, n_iter, n_skip=0):
    print('Orbit for seed {0}, growth rate of {1}, plotting {2} iterations after skipping {3}'.format(seed, r, n_iter, n_skip))
    X_t=[]
    T=[]
    t=0
    x = seed;
    # Iterate the logistic equation, printing only if n_skip steps have been skipped
    for i in range(n_iter + n_skip):
        if i >= n_skip:
            X_t.append(x)
            T.append(t)
            t+=1
        x = logistic_eq(r,x);
    # Configure and decorate the plot
    plt.plot(T, X_t)
    plt.ylim(0, 1)
    plt.xlim(0, T[-1])
    plt.xlabel('Time t')
    plt.ylabel('X_t')
    plt.show()

# In[ ]:

logistic_equation_orbit(0.1, 3.05, 100)
logistic_equation_orbit(0.1, 3.9, 100)
logistic_equation_orbit(0.1, 3.9, 100, 1000)

# In[ ]:

# Create the bifurcation diagram
def bifurcation_diagram(seed, n_skip, n_iter, step=0.0001, r_min=0):
    print("Starting with x0 seed {0}, skip plotting first {1} iterations, then plot next {2} iterations.".format(seed, n_skip, n_iter));
    # Array of r values, the x axis of the bifurcation plot
    R = []
    # Array of x_t values, the y axis of the bifurcation plot
    X = []

    # Create the r values to loop. For each r value we will plot n_iter points
    r_range = np.linspace(r_min, 4, int(1/step))

    for r in r_range:
        x = seed;
        # For each r, iterate the logistic function and collect datapoint if n_skip iterations have occurred
        for i in range(n_iter+n_skip+1):
            if i >= n_skip:
                R.append(r)
                X.append(x)

            x = logistic_eq(r,x);
    # Plot the data
    plt.plot(R, X, ls='', marker=',')
    plt.ylim(0, 1)
    plt.xlim(r_min, 4)
    plt.xlabel('r')
    plt.ylabel('X')
    plt.show()

# In[ ]:

bifurcation_diagram(0.2, 100, 5)
bifurcation_diagram(0.2, 100, 10)
bifurcation_diagram(0.2, 100, 10, r_min=2.8)


import numpy as np

# ndarray with shape 3x4 with zeros

x = np.zeros((3,4))
print(x)

x = np.zeros((3,4), dtype = int)
print(x)

# ndarray full of ones

x = np.ones((3,4))
print(x)

x = np.ones((3,4),dtype = int)
print(x)

# ndarray full of any numbers

x = np.full((3,4), 5)
print(x)

# Identity matrix ,  An Identity matrix is a square matrix that has only 1s in its main diagonal and zeros everywhere else

x = np.eye(5, dtype=int)
print(x)

# Diagonal Matrix, square matrix that only has values in its main diagonal

x = np.diag([10,20,30,50])
print(x)

# ndarrays that have evenly spaced values within a given interval

x = np.arange(10)
print(x)

x = np.arange(1, 11, 3)
print(x)

# arange when used for non integer result can be mallicious

# In the cases where non-integer steps are required, it is usually better to use the function np.linspace()

x = np.linspace(0,25, 10)
print(x)

x = np.linspace(0,25, 10, endpoint = False)
print(x)

# Using arange and linespace for > 1 rank array

# Use reshape() to comine to create rank 2 ndarrays

x = np.arange(20)
print(x)

x = np.reshape(x, (4,5))

print(x)

# Can call as a method
x = np.arange(20).reshape((4,5))

print(x)

x = np.linspace(0,50,10, endpoint=False).reshape(5,2)
print(x)

# Random ndarray float type

x = np.random.random((3,3))
print(x)

# Random ndarray int type
x = np.random.randint(4,15, size=(3,2))
print(x)

# For example, you may want the random numbers in the ndarray to have an average of 0.
# NumPy allows you create random ndarrays with numbers drawn from various probability
# distributions. The function np.random.normal(mean, standard deviation, size=shape),
# for example, creates an ndarray with the given shape that contains random numbers
# picked from a normal (Gaussian) distribution with the given mean and standard deviation.

x = np.random.normal(0, 0.1, size=(1000, 1000))
print(x)

x = np.arange(2, 34, 2, dtype = int).reshape((4,4))
print(x)


'''
shape

The shape tool gives a tuple of array dimensions and can be used to change the dimensions of an array.

(a). Using shape to get array dimensions

import numpy

my__1D_array = numpy.array([1, 2, 3, 4, 5])
print my_1D_array.shape     #(5,) -> 5 rows and 0 columns

my__2D_array = numpy.array([[1, 2],[3, 4],[6,5]])
print my_2D_array.shape     #(3, 2) -> 3 rows and 2 columns
(b). Using shape to change array dimensions

import numpy

change_array = numpy.array([1,2,3,4,5,6])
change_array.shape = (3, 2)
print change_array

#Output
[[1 2]
[3 4]
[5 6]]
reshape

The reshape tool gives a new shape to an array without changing its data. It creates a new array and does not modify the original array itself.

import numpy

my_array = numpy.array([1,2,3,4,5,6])
print numpy.reshape(my_array,(3,2))

#Output
[[1 2]
[3 4]
[5 6]]
Task

You are given a space separated list of nine integers. Your task is to convert this list into a X NumPy array.

Input Format

A single line of input containing  space separated integers.

Output Format

Print the X NumPy array.

Sample Input

1 2 3 4 5 6 7 8 9
Sample Output

[[1 2 3]
 [4 5 6]
 [7 8 9]]
'''

import numpy as np

ip = input().strip().split(' ')
x = np.array(ip)
x = x.astype(np.int32)
x = x.reshape(3,3)
print(x)

#print(ip)

Hackerrank Practice/Shape and Reshape.py


import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from scipy import stats
import pandas as pd

###############################################################################################################
###############################################################################################################
def reg_corr_plot():
    class LinearRegression:
        def __init__(self, beta1, beta2, error_scale, data_size):
            self.beta1 = beta1
            self.beta2 = beta2
            self.error_scale = error_scale
            self.x = np.random.randint(1, data_size, data_size)
            self.y = self.beta1 + self.beta2*self.x + self.error_scale*np.random.randn(data_size)

        def x_y_cor(self):
            return np.corrcoef(self.x, self.y)[0, 1]

    fig, ax = plt.subplots(nrows = 2, ncols = 4,figsize=(24, 12))

    beta1, beta2, error_scale,data_size =  2, .05, 1, 100
    lrg1 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[0, 0].scatter(lrg1.x, lrg1.y)
    ax[0, 0].plot(lrg1.x, beta1 + beta2*lrg1.x, color = '#FA954D', alpha = .7)
    ax[0, 0].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[0, 0].annotate(r'$\rho={:.4}$'.format(lrg1.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, -.6, 1, 100
    lrg2 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[0, 1].scatter(lrg2.x, lrg2.y)
    ax[0, 1].plot(lrg2.x, 2 - .6*lrg2.x, color = '#FA954D', alpha = .7)
    ax[0, 1].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[0, 1].annotate(r'$\rho={:.4}$'.format(lrg2.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 1, 1, 100
    lrg3 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[0, 2].scatter(lrg3.x, lrg3.y)
    ax[0, 2].plot(lrg3.x, beta1 + beta2 * lrg3.x, color = '#FA954D', alpha = .7)
    ax[0, 2].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[0, 2].annotate(r'$\rho={:.4}$'.format(lrg3.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 3, 1, 100
    lrg4 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[0, 3].scatter(lrg4.x, lrg4.y)
    ax[0, 3].plot(lrg4.x, beta1 + beta2 * lrg4.x, color = '#FA954D', alpha = .7)
    ax[0, 3].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[0, 3].annotate(r'$\rho={:.4}$'.format(lrg4.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 3, 3, 100
    lrg5 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[1, 0].scatter(lrg5.x, lrg5.y)
    ax[1, 0].plot(lrg5.x, beta1 + beta2 * lrg5.x, color = '#FA954D', alpha = .7)
    ax[1, 0].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[1, 0].annotate(r'$\rho={:.4}$'.format(lrg5.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 3, 10, 100
    lrg6 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[1, 1].scatter(lrg6.x, lrg6.y)
    ax[1, 1].plot(lrg6.x, beta1 + beta2 * lrg6.x, color = '#FA954D', alpha = .7)
    ax[1, 1].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[1, 1].annotate(r'$\rho={:.4}$'.format(lrg6.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 3, 20, 100
    lrg7 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[1, 2].scatter(lrg7.x, lrg7.y)
    ax[1, 2].plot(lrg7.x, beta1 + beta2 * lrg7.x, color = '#FA954D', alpha = .7)
    ax[1, 2].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[1, 2].annotate(r'$\rho={:.4}$'.format(lrg7.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

    beta1, beta2, error_scale,data_size =  2, 3, 50, 100
    lrg8 = LinearRegression(beta1, beta2, error_scale, data_size)
    ax[1, 3].scatter(lrg8.x, lrg8.y)
    ax[1, 3].plot(lrg3.x, beta1 + beta2 * lrg3.x, color = '#FA954D', alpha = .7)
    ax[1, 3].set_title(r'$Y={}+{}X+{}u$'.format(beta1, beta2, error_scale))
    ax[1, 3].annotate(r'$\rho={:.4}$'.format(lrg8.x_y_cor()), xy=(0.1, 0.9), xycoords='axes fraction')

###############################################################################################################
###############################################################################################################

def central_limit_theorem_plot():
    fig, ax = plt.subplots(4, 3, figsize = (20, 20))

    ########################################################################################
    x = np.linspace(2, 8, 100)
    a = 2 # range of uniform distribution
    b = 8
    unif_pdf = np.ones(len(x)) * 1/(b-a)

    ax[0, 0].plot(x, unif_pdf, lw = 3, color = 'r')
    ax[0, 0].plot([x[0],x[0]],[0, 1/(b-a)], lw = 3, color = 'r', alpha = .9) # vertical line
    ax[0, 0].plot([x[-1],x[-1]],[0, 1/(b-a)], lw = 3, color = 'r', alpha = .9)
    ax[0, 0].fill_between(x, 1/(b-a), 0, alpha = .5, color = 'r')

    ax[0, 0].set_xlim([1, 9])
    ax[0, 0].set_ylim([0, .4])
    ax[0, 0].set_title('Uniform Distribution', size = 18)
    ax[0, 0].set_ylabel('Population Distribution', size = 12)

    ########################################################################################
    ss = 2 #sample size
    unif_sample_mean = np.zeros(1000)
    for i in range(1000):
        unif_sample = np.random.rand(ss)
        unif_sample_mean[i] = np.mean(unif_sample)
    ax[1, 0].hist(unif_sample_mean, bins = 20, color = 'r', alpha = .5)
    ax[1, 0].set_ylabel('Sample Distribution， $n = 2$', size = 12)
    ########################################################################################
    ss = 10 #sample size
    unif_sample_mean = np.zeros(1000)
    for i in range(1000):
        unif_sample = np.random.rand(ss)
        unif_sample_mean[i] = np.mean(unif_sample)
    ax[2, 0].hist(unif_sample_mean, bins = 30, color = 'r', alpha = .5)
    ax[2, 0].set_ylabel('Sample Distribution, $n = 10$', size = 12)

    ########################################################################################
    ss = 1000 #sample size
    unif_sample_mean = np.zeros(1000)
    for i in range(1000):
        unif_sample = np.random.rand(ss)
        unif_sample_mean[i] = np.mean(unif_sample)
    ax[3, 0].hist(unif_sample_mean, bins = 40, color = 'r', alpha = .5)
    ax[3, 0].set_ylabel('Sample Distribution, $n = 1000$', size = 12)

    ########################################################################################
    a = 6
    b = 2
    x = np.linspace(0, 1, 100)
    beta_pdf = sp.stats.beta.pdf(x, a, b)
    ax[0, 1].plot(x, beta_pdf, lw = 3, color = 'g')
    ax[0, 1].set_ylim([0, 6])
    ax[0, 1].fill_between(x, beta_pdf, 0, alpha = .5, color = 'g')
    ax[0, 1].set_title('Beta Distribution', size = 18)
    ########################################################################################

    ss = 2 #sample size
    beta_sample_mean = np.zeros(1000)
    for i in range(1000):
        beta_sample = sp.stats.beta.rvs(a, b, size = ss)
        beta_sample_mean[i] = np.mean(beta_sample)
    ax[1, 1].hist(beta_sample_mean, color = 'g', alpha = .5)

    ########################################################################################

    ss = 10 #sample size
    beta_sample_mean = np.zeros(1000)
    for i in range(1000):
        beta_sample = sp.stats.beta.rvs(a, b, size = ss)
        beta_sample_mean[i] = np.mean(beta_sample)
    ax[2, 1].hist(beta_sample_mean, color = 'g', bins = 20, alpha = .5)

    ########################################################################################

    ss = 100000 #sample size
    beta_sample_mean = np.zeros(1000)
    for i in range(1000):
        beta_sample = sp.stats.beta.rvs(a, b, size = ss)
        beta_sample_mean[i] = np.mean(beta_sample)
    ax[3, 1].hist(beta_sample_mean, color = 'g', bins = 30, alpha = .5)

    ########################################################################################
    a = 6
    x = np.linspace(0, 25, 100)

    gamma_pdf = sp.stats.gamma.pdf(x, a)
    ax[0, 2].plot(x, gamma_pdf, lw = 3, color = 'b')
    ax[0, 2].set_ylim([0, 0.34])
    ax[0, 2].fill_between(x, gamma_pdf, 0, alpha = .5, color = 'b')
    ax[0, 2].set_title('Gamma Distribution', size = 18)

    ########################################################################################
    ss = 2 #sample size
    gamma_sample_mean = np.zeros(1000)
    for i in range(1000):
        gamma_sample = sp.stats.gamma.rvs(a, size = ss)
        gamma_sample_mean[i] = np.mean(gamma_sample)
    ax[1, 2].hist(gamma_sample_mean, color = 'b', alpha = .5)

    ########################################################################################
    ss = 10 #sample size
    gamma_sample_mean = np.zeros(1000)
    for i in range(1000):
        gamma_sample = sp.stats.gamma.rvs(a, size = ss)
        gamma_sample_mean[i] = np.mean(gamma_sample)
    ax[2, 2].hist(gamma_sample_mean, bins = 20, color = 'b', alpha = .5)
    ########################################################################################
    ss = 1000 #sample size
    gamma_sample_mean = np.zeros(1000)
    for i in range(1000):
        gamma_sample = sp.stats.gamma.rvs(a, size = ss)
        gamma_sample_mean[i] = np.mean(gamma_sample)
    ax[3, 2].hist(gamma_sample_mean, bins = 30, color = 'b', alpha = .5)
    ########################################################################################
    plt.show()

##########################################################################################################
##########################################################################################################
def type12_error():
    x = np.linspace(-6, 9, 200)
    null_loc, alter_loc = 0, 3
    y_null = sp.stats.norm.pdf(x, loc = null_loc)
    y_alter =  sp.stats.norm.pdf(x, loc = alter_loc)
    fig, ax = plt.subplots(figsize = (18, 6))
    ax.plot(x, y_null, x, y_alter)
    ax.annotate('Null', (null_loc-.2, max(y_null)/2), size = 15)
    ax.annotate('Alternative', (alter_loc-.6, max(y_alter)/2), size = 15)
    ax.annotate('Type I Error', (2, max(y_alter)/30), size = 15)
    ax.annotate('Type II Error', (0, max(y_alter)/30), size = 15)
    ax.fill_between(x[-98:], y_null[-98:])
    ax.fill_between(x[:103], y_alter[:103])
    ax.set_ylim([0, .5])
    plt.show()

##########################################################################################################
##########################################################################################################
# def reject_region():
#     data = pd.read_csv('500_Person_Gender_Height_Weight_Index.csv')

#     male_mean = data[data['Gender']=='Male']['Height'].mean()
#     male_std = data[data['Gender']=='Male']['Height'].std(ddof=1)
#     male_std_error = male_std/np.sqrt(len(data[data['Gender']=='Male']))
#     male_null = 172

#     df = len(data[data['Gender']=='Male'])-1
#     t_975 = sp.stats.t.ppf(.975, df=df)
#     t_025 = sp.stats.t.ppf(.025, df=df)

#     x = np.linspace(male_null-5, male_null+5, 200)
#     df = len(data[data['Gender']=='Male'])-1
#     y_t = sp.stats.t.pdf(x, df = df, loc = male_null)

#     fig, ax = plt.subplots(2, 1, figsize = (18,8))

#     ax[0].plot(x, y_t, color = 'tomato', lw = 3)

#     rejection_lower = male_null - t_975*male_std_error
#     x_rej_lower = np.linspace(rejection_lower-3, rejection_lower, 30)
#     y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df, loc = male_null)
#     ax[0].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)

#     rejection_upper = male_null + t_975*male_std_error
#     x_rej_upper = np.linspace(rejection_upper, rejection_upper+3, 30)
#     y_rej_upper = sp.stats.t.pdf(x_rej_upper, df = df, loc = male_null)
#     ax[0].fill_between(x_rej_upper, y_rej_upper, color = 'tomato', alpha = .7)

#     ax[0].set_ylim([0, .45])

#     x = np.linspace(-5, 5, 200)
#     y_t = sp.stats.t.pdf(x, df = df, loc = 0)

#     ax[1].plot(x, y_t, color = 'tomato', lw = 3)

#     x_rej_lower = np.linspace(t_025-3, t_025, 30)
#     y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df)
#     ax[1].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)

#     x_rej_lower = np.linspace(t_975+3, t_975, 30)
#     y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df)
#     ax[1].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)

#     ax[1].set_ylim([0, .45])

#     plt.show()

##########################################################################################################
# ##########################################################################################################
# def draw_something():
#     x = np.linspace(0, 10, 100)
#     y = np.sin(x)
#     plt.plot(x, y)

##########################################################################################################
##########################################################################################################
def anova_plot():
    def gen_3samples(loc1, loc2, loc3, scale1, scale2, scale3, size1, size2, size3):
        F_statistic, p_value = [], []
        for i in range(1000):
            a = sp.stats.norm.rvs(loc1, scale1, size1)
            b = sp.stats.norm.rvs(loc2, scale2, size3)
            c = sp.stats.norm.rvs(loc3, scale3, size3)
            F, p = sp.stats.f_oneway(a,b,c)
            F_statistic.append(F)
            p_value.append(p)
        return F_statistic, p_value

    fig, ax = plt.subplots(nrows = 6, ncols = 3, figsize = (17, 34))

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 6, 9, 6, 6, 6, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[0,0].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[0,0].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[0,0].set_title('Simulation 1')
    ax[0,0].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[1,0].hist(p_value,bins = 50)

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 3.1, 2.9, 6, 6, 6, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[0,1].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[0,1].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[0,1].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[1,1].hist(p_value,bins = 50)
    ax[0,1].set_title('Simulation 2')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 3.1, 2.9, 6, 12, 18, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[0,2].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[0,2].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[0,2].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[1,2].hist(p_value,bins = 50)
    ax[0,2].set_title('Simulation 3')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 =  [3, 6, 9, 10, 10, 10, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[2,0].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[2,0].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[2,0].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[3,0].hist(p_value,bins = 50)
    ax[2,0].set_title('Simulation 4')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 =  [3, 5, 6, 10, 10, 10, 10, 10, 10]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[2,1].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[2,1].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[2,1].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[3,1].hist(p_value,bins = 50)
    ax[2,1].set_title('Simulation 5')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 5, 6, 10, 10, 10, 5000, 5000, 5000]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[2,2].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[2,2].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[2,2].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[3,2].hist(p_value,bins = 50)
    ax[2,2].set_title('Simulation 6')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 3, 3, 100, 100, 100, 10, 10, 10]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[4,0].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[4,0].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[4,0].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[5,0].hist(p_value,bins = 50)
    ax[4,0].set_title('Simulation 7')

    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 3, 3, 1, 1, 2, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[4,1].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[4,1].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[4,1].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[5,1].hist(p_value,bins = 50)
    ax[4,1].set_title('Simulation 8')

    params = [3, 3.1, 2.9, .01, .01, .01, 10, 20, 30]
    mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3 = [3, 3, 3, 1, 1, 2, 10, 20, 30]
    params = [mu1, mu2, mu3, sig1, sig2, sig3, size1, size2, size3]
    F_statistic, p_value = gen_3samples(*params)
    n, bins, patches = ax[4,2].hist(F_statistic, bins = 50)
    F_critical = sp.stats.f.ppf(.95, 2, size1+size2+size3-3)
    textstr = '\n'.join((
        '$\mu_1, \mu_2, \mu_3 = {}, {}, {}$'.format(mu1, mu2, mu3),
        '$\sigma_1, \sigma_2, \sigma_3 = {}, {}, {}$'.format(sig1, sig2, sig3),
        '$n_1, n_2, n_3 = {}, {}, {}$'.format(size1, size2, size3),
        r'$F_c = {:.4f}$'.format(F_critical)))
    props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
    ax[4,2].text(max(bins)/2, max(n)/2, textstr, fontsize=10,
            verticalalignment='top', bbox=props)
    ax[4,2].vlines(F_critical, 0, max(n)*1.1, color = 'r')
    ax[5,2].hist(p_value,bins = 50)
    ax[4,2].set_title('Simulation 9')

    #######################||Rectangle||##########################
    ##############################################################

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.10, 0.633), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.3755, 0.633), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.650, 0.633), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.10, 0.37), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.3755, 0.37), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.650, 0.37), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.650, 0.108), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.3755, 0.108), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])

    rect = plt.Rectangle(
        # (lower-left corner), width, height
        (0.1, 0.108), 0.2645, 0.258, fill=False, color="k", lw=2,
        zorder=1000, transform=fig.transFigure, figure=fig
    )
    fig.patches.extend([rect])
    ####################################################################

    plt.show()
###############################################################################################
###############################################################################################
def two_tail_rej_region_demo():
    data = pd.read_csv('500_Person_Gender_Height_Weight_Index.csv')

    df = len(data[data['Gender']=='Male'])-1
    t_975 = sp.stats.t.ppf(.975, df=df)
    t_025 = sp.stats.t.ppf(.025, df=df)

    male_mean = data[data['Gender']=='Male']['Height'].mean()
    male_std = data[data['Gender']=='Male']['Height'].std(ddof=1)
    male_std_error = male_std/np.sqrt(len(data[data['Gender']=='Male']))
    male_null = 172

    x = np.linspace(male_null-5, male_null+5, 200)

    y_t = sp.stats.t.pdf(x, df = df, loc = male_null)

    fig, ax = plt.subplots(2, 1, figsize = (18,8))

    ax[0].plot(x, y_t, color = 'tomato', lw = 3)

    rejection_lower = male_null - t_975*male_std_error
    x_rej_lower = np.linspace(rejection_lower-3, rejection_lower, 30)
    y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df, loc = male_null)
    ax[0].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)

    rejection_upper = male_null + t_975*male_std_error
    x_rej_upper = np.linspace(rejection_upper, rejection_upper+3, 30)
    y_rej_upper = sp.stats.t.pdf(x_rej_upper, df = df, loc = male_null)
    ax[0].fill_between(x_rej_upper, y_rej_upper, color = 'tomato', alpha = .7)
    ax[0].set_ylim([0, .45])
    ax[0].set_title('Rejection Region of Original Unit (cm)')

    x = np.linspace(-5, 5, 200)
    y_t = sp.stats.t.pdf(x, df = df, loc = 0)

    ax[1].plot(x, y_t, color = 'tomato', lw = 3)

    x_rej_lower = np.linspace(t_025-3, t_025, 30)
    y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df)
    ax[1].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)

    x_rej_lower = np.linspace(t_975+3, t_975, 30)
    y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = df)
    ax[1].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)
    ax[1].set_ylim([0, .45])
    ax[1].set_title('Rejection Region of t-statistic')

    plt.show()
###############################################################################################
###############################################################################################
def one_tail_rej_region_demo():
    fig, ax = plt.subplots(2, 1, figsize = (18,8))
    x = np.linspace(-5, 5, 200)
    y_t = sp.stats.t.pdf(x, df = len(x), loc = 0)
    ax[0].plot(x, y_t, color = 'tomato')

    ax[0].annotate('$H_0: \mu = \mu_0$\n$H_1: \mu < \mu_0$', (-5, .35), size = 16)
    t_05 = sp.stats.t.ppf(.05, df = len(x))
    x_rej_lower = np.linspace(t_05, t_05-3, 30)
    y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = len(x))
    ax[0].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)
    ax[0].set_ylim([0, .45])

    x = np.linspace(-5, 5, 200)
    y_t = sp.stats.t.pdf(x, df = len(x), loc = 0)
    ax[1].plot(x, y_t, color = 'tomato')

    ax[1].annotate('$H_0: \mu = \mu_0$\n$H_1: \mu > \mu_0$', (4, .35), size = 16)
    t_95 = sp.stats.t.ppf(.95, df = len(x))
    x_rej_lower = np.linspace(t_95, t_95+3, 30)
    y_rej_lower = sp.stats.t.pdf(x_rej_lower, df = len(x))
    ax[1].fill_between(x_rej_lower, y_rej_lower, color = 'tomato', alpha = .7)
    ax[1].set_ylim([0, .45])

    plt.show()


import numpy as np

'''
Exercise 1: NumPy: Create an array with values ranging from 12 to 38
'''

array_range = np.arange(12, 38)
print(array_range)

'''
Exercise 2: Add a border around an existing array
'''

array_ones = np.ones((3, 3))
print(array_ones)
array_ones_border = np.pad(array_ones, pad_width=1)
print(array_ones_border)

'''
Exercise 3: Convert a list and tuple into arrays
'''
my_list = [1, 2, 3, 4, 5, 6, 7, 8]
print(my_list)
print("List to array: ")
print(np.asarray(my_list))

my_tuple = ([8, 4, 6], [1, 2, 3])
print("Tuple to array: ")
print(np.asarray(my_tuple))

'''
Exercise 4: Convert the values of Centigrade degrees into Fahrenheit degrees
'''
val_in_fahrenheit = np.array([0., 12., 45.21, 34., 99.91])
print(val_in_fahrenheit)

val_in_celcius = (val_in_fahrenheit - 32) / 1.8
print(val_in_celcius)

'''
Exercise 5: Write a NumPy program
to find the number of elements of an array,
length of one array element in bytes and total bytes consumed by the elements.
'''

array_range = np.arange(12, 38)
print(f"Number of elements of an array {array_range.size}")
print(f"length of one array element in bytes {array_range.itemsize}")
print(f"total bytes consumed by the elements {array_range.nbytes}")
print(f"total bytes consumed by the elements {array_range.dtype}")

'''
Exercise 6: Get the unique elements of an array
'''

x = np.array([10, 10, 20, 20, 30, 30])
print(np.unique(x))

x = np.array([[1, 1], [2, 3]])
print(np.unique(x))

'''
Exercise 7: Change the dimension of an array
'''

original_array = np.array([1,2,3,4,5,6,7,8,9])
print(original_array)

new_array_shape = original_array.reshape(3,3)
print(new_array_shape)

'''
Exercise 8: Create a 1-D array of 30 evenly spaced elements between 2.5. and 6.5, inclusive
'''

evenly_array_1d_30 = np.linspace(2.5, 6.5, 30)
print(evenly_array_1d_30)

'''
Exercise 9: Convert 1-D arrays as columns into a 2-D array
'''

a = np.array((10,20,30))
b = np.array((40,50,60))

c = np.column_stack((a, b))
print(c)

d = np.row_stack((a,b))
print(d)

'''
Exercise 10: Create a 5x5 matrix with row values ranging from 0 to 4
'''
x = np.zeros((5,5))

#x += np.arange(5)

x[::] = [0,1,2,3,4]

print(x)

'''
Exercise 11: Sum of all the multiples of 3 or 5 below 100
'''

x = np.arange(1,100)
print(x)

n = x [(x%3==0) | (x%5 == 0)]
print(n.sum())

'''
Exercise 12: Combine a one and a two dimensional array together and display their elements
'''

x = np.arange(4)
print("One dimensional array:")
print(x)
y = np.arange(8).reshape(2,4)
print("Two dimensional array:")
print(y)
for a, b in np.nditer([x,y]):
    print(f"{a}:{b}")

'''
Exercise 13: Write a NumPy program to replace all elements of NumPy array that are greater than specified array.
'''
x = np.array([[ 0.42436315, 0.48558583, 0.32924763], [ 0.7439979,0.58220701,0.38213418], [ 0.5097581,0.34528799,0.1563123 ]])
print("Original array:")
print(x)
print("Replace all elements of the said array with .5 which are greater than .5")
x[x > .5] = .5
print(x)

'''
Exercise 14: Add a new row to an empty numpy array
'''

arr = np.empty((0,3), int)
print("Empty array:")
print(arr)
arr = np.append(arr, np.array([[10,20,30]]), axis=0)
arr = np.append(arr, np.array([[40,50,60]]), axis=0)
print("After adding two new arrays:")
print(arr)

'''
Exercise 15: Write a NumPy program to join a sequence of arrays along a new axis.
'''

x = np.array([1, 2, 3])
y = np.array([2, 3, 4])
print("Original arrays:")
print(x)
print(y)
print("Sequence of arrays along a new axis:")
print(np.vstack((x, y)))
x = np.array([[1], [2], [3]])
y = np.array([[2], [3], [4]])
print("\nOriginal arrays:")
print(x)
print()
print(y)
print("Sequence of arrays along a new axis:")
print(np.vstack((x, y)))


import numpy as np

matrix_basic_1 = np.array([10,20,30])
matrix_basic_2 = np.array([15,40,90])

# Concept of vectoriztion
print(matrix_basic_1/2,"\n")
print(matrix_basic_1 - 2 , "\n")
print(matrix_basic_1 + 2 , "\n")

# Comapring two matrices
print(matrix_basic_1 < matrix_basic_2)

# Elementary operations on matrices
print(matrix_basic_1 - matrix_basic_2)
print(matrix_basic_1 + matrix_basic_2)
print(matrix_basic_1 * matrix_basic_2)
print(matrix_basic_1 / matrix_basic_2)

# Accessing and slicing array
# Similar to the python list slicing
arr = np.arange(1,11)
print(arr[-5:])
print(arr[::-1])

# Creating shallow and deep copy
# Shallow copy
shallow_arr = arr

# Deep copy
deep_arr = arr.copy()

# Changing shape of array
# Giving meaning shape
reshaped_array = arr.reshape(2,5)
print(reshaped_array)


# Check README.md for instruction to run

import numpy as np
import argparse

'''
Function to calculate exp(x) for the given x passed as an argument
Taylor Series expansion of exp(x) is such that:
T_i = x^i/i! where i = 0, 1, 2, 3 ...
So, T_i/T_{i-1} = x/i

So, the algorithm for evaluation exp(x) starts with the first term 1 and subsequently finds each term by multiplying by x/i.
All theses terms are summed and the loop continues until the subsequent term becomes NaN, inf or zero.

I also find the number of terms that were summed to get the function value.
'''
def exp(x = 10):

	num_terms = 0
	sum_terms = 0
	i = 1
	term = 1
	while (term != float("inf") and term != float("nan") and term != 0):
		sum_terms += term
		term *= x/i
		num_terms += 1
		i += 1

	return sum_terms, num_terms

'''
A parser is created to accept command line argument
This code can accept one command line arguments -
1. x: Value whose exp(x) is required to be calculated

If no command line argumennt is passed, default value of x is -5.
'''
if __name__ == '__main__':

	parser = argparse.ArgumentParser(description='x for which exp(x) is to be computed')
	parser.add_argument("-x", type = float, default = -5, help='x for which exp(x) is to be computed')
	args = parser.parse_args()

	answer, num_terms = exp(args.x)
	exact_value = np.exp(args.x) # Exact Answer is found using the in-built function in numpy
	error = 100*abs(answer - exact_value)/exact_value # Error is evaluated in percentage by comparing with the exact_value

	print("x = ", args.x)
	print("Obtained Value: ", answer)
	print("Exact Value:", exact_value)
	print("Error (%) : ", error )
	print("Number of terms: ", num_terms, "\n")


import numpy as np

def euler_convert_to_rotation_matrix(yaw, pitch, roll):
    # ZXY rotation, called rpy. problem: Gimbal Lock.
    Rx = np.array([
        [1, 0, 0],
        [0, np.cos(roll), -np.sin(roll)],
        [0, np.sin(roll), np.cos(roll)]
    ])

    Ry = np.array([
        [np.cos(pitch), 0, np.sin(pitch)],
        [0, 1, 0],
        [-np.sin(pitch), 0, np.cos(pitch)]
    ])

    Rz = np.array([
        [np.cos(yaw), -np.sin(yaw), 0],
        [np.sin(yaw), np.cos(yaw), 0],
        [0, 0, 1]
    ])
    # ZYX
    return Rx @ Ry @ Rz

def rotation_matrix_to_euler(R):
    roll = np.arctan(- R[1, 2] / R[2, 2])
    pitch = np.arcsin(R[0, 2])
    yaw = np.arctan(-R[0, 1] / R[0, 0])
    return roll, pitch, yaw

R = np.array([
    [np.sqrt(2) / 2, -np.sqrt(2) / 2, 0.],
    [np.sqrt(2) / 2, np.sqrt(2) / 2, 0.],
    [0., 0., 1.]]
)

print('get euler angle from rotation matrix:')
roll, pitch, yaw = rotation_matrix_to_euler(R)
print(f'roll={np.rad2deg(roll)}, pitch={np.rad2deg(pitch)}, yaw={np.rad2deg(yaw)}')
print('convert euler angle to rotation matrix:')
R_ = euler_convert_to_rotation_matrix(yaw, pitch, roll)
print(R_)


import numpy
from matplotlib import pyplot

def simpsons(f, a, b, N):
    x, dx = numpy.linspace(a, b, N+1, retstep=True)
    fx = f(x)
    return dx/3 * ( (fx[0] + fx[-1]) + \
        2*numpy.sum(fx[2:-1:2]) + 4*numpy.sum(fx[1:-1:2]) )

def simpson_one_interval(f, a, b, h, points, fpoints, tol):
    # Simpson on one interval with three points
    I_2h = 2 * h / 3 * (fpoints[0] + fpoints[2] + 4*fpoints[1])
    # Add new points (and integrand evaluations) for error check
    new_points = [points[0], points[0]+h, points[1], points[1]+h, points[2]]
    f_new_points = [fpoints[0], f(new_points[1]), fpoints[1], f(new_points[3]), fpoints[2]]
    # Simpson over two subintervals
    I_h = h / 3 * (fpoints[0] + fpoints[2] + 2 * fpoints[1] + \
                    4 * (f_new_points[1] + f_new_points[3]))
    # Computable error estimate
    error = abs(I_2h - I_h) / (2**4 - 1)
    # If error on subinterval too big, and interval width not too small,
    # subdivide this subinterval
    if error > tol * (points[2] - points[0]) / (b - a) and (points[2] - points[0])/(b-a) > 1e-3:
        left_I, left_points = simpson_one_interval(f, a, b, h/2, new_points[:3], f_new_points[:3], tol)
        right_I, right_points = simpson_one_interval(f, a, b, h/2, new_points[2:], f_new_points[2:], tol)
        return left_I + right_I, left_points + right_points[1:]
    else:
        return I_h, new_points

def adaptive_quad(f, a, b, tol=1e-6):
    h = (b - a) / 2
    points = [a, a+h, b]
    fpoints = f(numpy.array(points))
    return simpson_one_interval(f, a, b, h/2, points, fpoints, tol)

def gauss_legendre(f, a, b, degree):
    nodes, weights = numpy.polynomial.legendre.leggauss(degree)
    x = (b - a) * (nodes + 1) / 2 + a
    fx = f(x)
    return numpy.sum(fx * weights) * (b - a) / 2

if __name__=="__main__":
    print("Simpson's rule")
    print(simpsons(numpy.sin, 0, numpy.pi/2, 2))
    print(simpsons(numpy.sin, 0, numpy.pi/2, 4))
    Npoints = 2**numpy.arange(1,20)
    dx_all = numpy.pi/2/Npoints
    errors = numpy.zeros_like(dx_all)
    for i, N in enumerate(Npoints):
        I = simpsons(numpy.sin, 0, numpy.pi/2, N)
        errors[i] = abs(I - 1)
    pyplot.loglog(dx_all, errors, 'kx')
    pyplot.loglog(dx_all, errors[0]*(dx_all/dx_all[0])**4, 'b-',
                  label=r"$\propto \Delta x^4$")
    pyplot.legend(loc='upper left')
    pyplot.xlabel(r"$\Delta x$")
    pyplot.ylabel("Error")
    pyplot.show()

    print("Adaptive quadrature")
    I, points = adaptive_quad(numpy.sin, 0, numpy.pi/2)
    print(I, len(points))
    x = numpy.linspace(0, numpy.pi/2, 1000)
    pyplot.plot(x, numpy.sin(x), 'b-')
    pyplot.plot(points, numpy.sin(points), 'kx')
    pyplot.show()

    def g(x):
        return numpy.where(numpy.pi > x, numpy.ones_like(x), numpy.zeros_like(x))
    I, points = adaptive_quad(g, 0, 5)
    x = numpy.linspace(0, 5, 1000)
    pyplot.plot(x, g(x), 'b-')
    pyplot.plot(points, g(numpy.array(points)), 'kx')
    pyplot.ylim(-0.1, 1.1)
    pyplot.show()

    print("Gauss-Legendre")
    print(gauss_legendre(numpy.sin, 0, numpy.pi/2, 2))
    print(gauss_legendre(numpy.sin, 0, numpy.pi/2, 4))
    Npoints = numpy.arange(2,10)
    dx_all = numpy.pi/2/Npoints
    errors = numpy.zeros_like(dx_all)
    for i, N in enumerate(Npoints):
        I = gauss_legendre(numpy.sin, 0, numpy.pi/2, N)
        errors[i] = abs(I - 1)
    pyplot.semilogy(Npoints, errors, 'kx')
    pyplot.xlabel(r"$N$")
    pyplot.ylabel("Error")
    pyplot.show()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 11 21:33:39 2019
This is the test of the numpy
@author: yujijun
"""
import numpy as np
# a simple example of numpy
a = np.arange(15).reshape(3,5)
a.shape
a.ndim
a.dtype.name
a.itemsize
a.size
type(a)

#array create
a = np.array([1,2,3])
b =np.array((1,2,3))
np.zeros((3,4))
np.ones((3,4,5))
np.empty((2,3))
np.arange(10,20,5)
np.arange(1,3,0.3)
np.linspace(2,4,20)
np.random.random((2,3))
x =np.linspace(0,2*np.pi,100)
y = np.sin(x)

#basic operations
a = np.array([10,20,30,40])
b = np.arange(4)
c = a -b
b**2
b*np.sin(a)
a < 35

A = np.arange(4).reshape(2,2)
B = np.arange(2,6).reshape(2,2)
A*B
A@B
A.dot(B)

a = np.random.random((2,3))
a.sum()
a.min()
a.max()

b = np.arange(12).reshape(3,4)
b.sum(axis=0)
b.min(axis=1)
b.cumsum(axis=1)

#universal function
B = np.arange(3)
np.exp(B)
np.sqrt(B)

#reshape
a.ravel()
a.reshape(3,4)
a.resize(3,4)

#stacking together different arrays
a = np.floor(10*np.random.random((2,2)))
b = np.floor(10*np.random.random((2,2)))
c = np.floor(10*np.random.random((2,2)))
np.vstack((a,b,c))
np.hstack((a,b,c))
np.r_[1:4,3,4]
np.c_[1:4,3:6]

#hsplit
a = np.floor(10*np.random.random((2,12)))
np.hsplit(a,3)
np.hsplit(a,(3,5))

#copy and view
# no copy at all (always the same)
a = np.arange(12)
b = a  #a and b are two names for the same ndarray object
b is a
b.shape = 3,4
a.shape

#view and shallow copy (just data is the same)
#shape could change but two dataset still the same
c = a.view()
c is a
c.base is a #True
c.shape = 2,6
a.shape
c[0,4] = 1234
a
#slicing an array returns a view of it :
s = a[:,1:3]
s[:] = 10 #Note the difference between s=10 and s[:]=10
a

#Deep copy (all isn't same)
d = a.copy() #a new array object with new data is created
d is a
d.base is a  #d doesn't share anything with a

#Sometimes copy should be called after slicing if the original array is not required anymore. For example, suppose a is a huge intermediate result and the final result b only contains a small fraction of a, a deep copy should be made when constructing b with slicing:
a = np.arange(int(1e8))
b = a[:100].copy()
del a  # the memory of ``a`` can be released.
#If b = a[:100] is used instead, a is referenced by b and will persist in memory even if del a is executed.

a = np.arange(12).reshape(3,4)
b1 = np.array([0,1,1])
b2 = np.array([False,True,True])
a[b1,:]
a[b2,:]

#fancy indexing and index tricks

#(1)
#arrays can be indexed by arrays of integers and arrays of booleans
#indexing, slicing  and interating
a = np.arange(10)**3
a[2]
a[2:5]
a[::-1]
a[:6:2] = -1000
a = np.arange(12)**2
i = np.array([1,1,2,8,5])
a[i]
j = np.array([[3,4],[9,7]]) # a bidimensional array of indices
a[j] # the same shape as j

#for example
palette = np.array( [ [0,0,0],                # black
                      [255,0,0],              # red
                      [0,255,0],              # green
                      [0,0,255],              # blue
                      [255,255,255] ] )

image = np.array([[0,1,2,0],[0,3,4,0]])
palette[image]

#(2)
a = np.arange(12).reshape(3,4)
a
i = np.array([[0,1],
              [1,2]])
j = np.array([[2,1],
              [3,3]])
a[i,j]
l = [i,j]
a[l]
a[i,2] #broad
a[:,j] #: same as 1,2,3

#(3)
a = np.arange(5)
a[[1,3,4]] = 0
a

a = np.arange(5)
a[[0,0,2]] = [1,2,3] #when the list of indices contains repetitions, the assignment is done several times, leaving
#behind the last value
a

#(4)
#indexing with boolean arrays
a = np.arange(12).reshape(3,4)
b = a > 4
a[b] #1d array with the selected elements
a[b] = 0 #all elements of "a" higher than 4 become 0
a

#(5)
a = np.arange(12).reshape(3,4)
b1 = np.array([False, True,True])
b2 = np.array([True,False,True,False])
a[b1,:]
a[b1]
a[:,b2]
a[b1,b2]

import numpy as np
import matplotlib.pyplot as plt
# Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
mu, sigma = 2, 0.5
v = np.random.normal(mu,sigma,10000)
# Plot a normalized histogram with 50 bins
plt.hist(v, bins=50, density=1)       # matplotlib version (plot)
plt.show()

# Compute the histogram with numpy and then plot it
(n, bins) = np.histogram(v, bins=50, density=True)  # NumPy version (no plot)
plt.plot(.5*(bins[1:]+bins[:-1]), n)
plt.show()