# --------------
#Importing header files
import pandas as pd
import scipy.stats as stats
import math
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from statsmodels.stats.weightstats import ztest
from statsmodels.stats.weightstats import ztest
from scipy.stats import chi2_contingency
import warnings
warnings.filterwarnings('ignore')
#Sample_Size
sample_size=2000
#Z_Critical Score
z_critical = stats.norm.ppf(q = 0.95)
# Critical Value
critical_value = stats.chi2.ppf(q = 0.95, # Find the critical value for 95% confidence*
                      df = 6)   # Df = number of variable categories(in purpose) - 1
#Reading file
data=pd.read_csv(path)
#Code starts here
#data = pd.read_csv(path)
#print(data.head())
data_sample = data.sample(n = sample_size,random_state = 0)
#n = 30
#print(data_sample.head())

#Confidence Interval
true_mean = data['installment'].mean()
print('True Mean is {}'.format(true_mean))
true_std = data['installment'].std()
true_std1 = data_sample['installment'].std()
print('Population Standard Deviation of the Installment Column is {}'.format(true_std))
sample_mean = data_sample['installment'].mean()
print('Sample mean of the Installment Column is {}'.format(sample_mean))
lower_limit = sample_mean - (z_critical*(true_std1/math.sqrt(sample_size)))
upper_limit = sample_mean + (z_critical*(true_std1/math.sqrt(sample_size)))
confidence_interval = [lower_limit, upper_limit]
print('Confidence Internal for sample mean is {}'.format(confidence_interval))

# CLT test by plotting
sample_sizes = np.array([20,50,100])
plt.figure(figsize = [12,7])
for sample_size in sample_sizes:
    lst = []
    for i in range(1000):
        df_new = data.sample(n = sample_size)
        lst.append(df_new['installment'].mean())
    sns.distplot(lst,hist = True,label = 'sample size {}'.format(sample_size))
    plt.legend()

#Small Business Interests
mean_intrate = data['int.rate'].apply(lambda x: float(x[:-1])).mean()
z_statistic_1, p_value_1 = ztest(data[data['purpose'] == 'small_business']['int.rate'].apply(lambda x: float(x[:-1])), value = mean_intrate, alternative = 'larger')
print('z_statistic_1 is {}'.format(z_statistic_1))
print('p_value_1 is {}'.format(p_value_1))

#Installment vs Loan Defaulting
mean_installment = data[data['paid.back.loan'] == 'Yes'].installment.mean()
x1 = data[data['paid.back.loan'] == 'No']['installment']
x2 = data[data['paid.back.loan'] == 'Yes']['installment']
value = np.mean(x1) - np.mean(x2)
print(value)
z_statistic_2, p_value_2 = ztest(x1,x2, value = 0)
print('z_statistic_2 is {}'.format(z_statistic_2))
print('p_value_2 is {}'.format(p_value_2))

#Purpose vs Loan Defaulting
new_df = data.groupby('paid.back.loan')['purpose'].value_counts().unstack()
chi2, p, dof, expected = chi2_contingency(new_df)
print('Chi-Square value is {}'.format(chi2))
print('Given Critical Value is {}'.format(critical_value))

import numpy,import scipy,from scipy,from statsmodels


#---------------------------------------------------------------------------
#   QR Factorization Using Gram-Schmidt Orthogonalization v.0.0.1
#
#        Q,R = qr_gram_schmidt(A)
#
#        The worst-case complexity of full matrix Q factorization:
#
#              In real case:   p = O(5MNlog2(N))
#              In complex case p = O(5MNlog2(2N) + 6MN)
#
#              An Example: M = 10^3, N = 10^2, p ~= 3.33 x 1e+10 (real)
#                          M = 10^3, N = 10^2, p ~= 3.82 x 1e+10 (complex)
#
#   GNU Public License (C) 2021 Arthur V. Ratz
#---------------------------------------------------------------------------

import numpy as np
import numpy.linalg as lin

# Function: conj_transpose(a) returns the conjugate transpose of vector b

def conj_transpose(a): # Takes the conjugate of complex vector a's-transpose
    return np.conj(np.transpose(a))

# Function: proj_ab(a,b) returns the projection of vector a onto vector b

def proj_ab(a,b): # Divides the product of <a,b> by the norm |b| and multiply the scalar by vector b:
                  # In real case:     <a,b> - the inner product of two real vectors
                  # In complex case:  <a,b> - the inner product of vector a and vector b's conjugate transpose
    return a.dot(conj_transpose(b)) / lin.norm(b) * b

def qr_gs(A, type=complex):

    A = np.array(A, dtype=type)

    (m, n) = np.shape(A) # Get matrix A's shape m - # of rows, m - # of columns

    (m, n) = (m, n) if m > n else (m, m)    # Determine the A's leading dimension:

                                            # The leading dimension must satisfy the following condition:
                                            #
                                            # if m > n, then the shape remains unchanged,
                                            #           and Q,R - are rectangular matrices (m x n)
                                            #
                                            # if m <= n, then the the shape of Q,R is changed to (m x m),
                                            #            and Q,R - are square matrices

    # Q - an orthogonal matrix of m-column vectors
    # R - an upper triangular matrix (the Gaussian elimination of A to the row-echelon form)

    # Initialization: [ Q - a multivector of shape (m x n) ]
    #                 [ R - a multivector of shape (m x n) ]

    Q = np.zeros((m, n), dtype=type) # Q - matrix of 0's
    R = np.zeros((m, n), dtype=type) # R - matrix of 0's

    # **** Objective: ****

    # For each column vector a[k] in A:
       # Compute the sum of a[k]'s projections onto a span of column vectors a[0..k]
       # Compute all column vectors q[k] of the orthogonal matrix Q

    # Finally: Compute the upper triangular matrix R of matrix A

    # Take n as the leading matrix A's dimension

    for k in range(n):

        pr_sum = np.zeros(m, dtype=type)       # Define the sum of projections vector pr_sum

        # For a span of column vectors q[0..k] in Q,
        # compute the sum f a[k]'s projections onto each vector q[0..k] in Q

        for j in range(k):
            pr_sum += proj_ab(A[:,k], Q[:,j])  # Get the projection of a[k] onto q[j],
                                               # and accumulate the result to the vector pr_sum

        # **** Compute the orthogonal matrix Q ****

        Q[:,k] = A[:,k] - pr_sum               # Compute the new k-th column vector q[k] in Q by
                                               # subtracting the sum of projections from the column vector a[k]

        Q[:,k] = Q[:,k] / lin.norm(Q[:,k])     # Normalize the new k-th column vector q[k] dividing it by its norm

    if type == complex:

        # **** Compute the complex upper triangular matrix R ****

        R = conj_transpose(Q).dot(A)           # Compute the matrix R as the product of Q's
                                               # transpose conjugate and matrix A (real case)

        # **** Compute the real upper triangular matrix R ****

    else: R = np.transpose(Q).dot(A)           # Compute the matrix R as the product of Q's-transpose
                                               # and matrix A (complex case)

    return -Q, -R                              # Return the resultant negative matrices Q and R


import numpy as np
import pandas as pd
import random

# samples = [
#     [-1,0.1,0.4,0.7],
#     [-1,0.3,0.7,0.2],
#     [-1,0.6,0.9,0.8],
#     [-1,0.5,0.7,0.1]
# ]

# d = np.array([1,-1,-1,1])

def signal(u):
    return int(abs(u)/u)

def train_perceptron_helper(n, weights, data, result):
    epoch = 0
    err = True
    w = np.array(weights)
    while err and epoch <= 1000:
        err = False
        for s in range(len(data)):
            xk = np.array(data[s])
            u = np.matmul(w, xk.T)
            y = signal(u)
            if y != result[s]:
                val = np.multiply(n * (result[s] - y), xk)
                w = np.add(w, val)
                err = True
        epoch = epoch + 1

    return w, epoch

def test_perceptron(w,X):
    A, B = [], []
    for i in range(len(X)):
        u = np.matmul(w,X[i].T)
        y = signal(u)
        if y == 1:
            A.append(i+1)
        else:
            B.append(i+1)
    return A,B

def get_set(filepath):
    return np.array(pd.read_csv(filepath,header=1))

def prepare_set(filepath):
    data = get_set(filepath)
    set = []
    d = []
    for i in data:
        set.append(i[:len(i)-1])
        d.append(i[-1])
    return np.array(set), np.array(d)

def training():
    n = round(random.uniform(0,0.3),2)
    print("N:",n)
    data_set, d = prepare_set('./apendice1.csv')
    for i in range(5):
        w = []
        for j in range(4):
            w.append(round(random.random(), 2))

        new_w, epoch = train_perceptron_helper(n,w,data_set,d)
        print('Epoch:',epoch)
        print('Initial weights',w)
        print('New weights: ',new_w)
        print('\n')

def test_optimal():
    ww = np.array([
    [-50.71,25.63,40.71,-12.05],
    [-50.28,25.01,40.23,-11.91],
    [-50.77,25.63,40.60,-12.03],
    [-50.74,25.53,40.47,-12.02],
    [-50.27,25.50,40.60,-11.97]
    ])

    amostras = np.array(get_set('./amostra1.csv'))

    for i in range(len(ww)):
        a,b = test_perceptron(ww[i], amostras)
        print("Teste #",i)
        print(a,'\n\n',b)

# running()


from numpy import linalg
import numpy as np

def cos_distance(vector1: np.ndarray, vector2: np.ndarray):
    """
    cos距离，会归一化，输出未0~1，越大越相似
    :param vector1:
    :param vector2:
    :return:
    """
    value = np.dot(vector1.T, vector2)/ (linalg.norm(vector1) * linalg.norm(vector2))
    score = 0.5 + 0.5 * float(value)
    return score


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, ln, pprint, oo )

# Over time, the number of original basic words in a language tends to decrease as words become obsolete or are replaced with new words.
# For a certain language, the proportion of words that remain after t millennia is a random variable that is exponentially distributed with

t = symbols( 't' )
a = 0.298
mu = 1/a
pdf = a * exp( -a*t )

# What is the life expectancy?

round( mu, 2 )

a, b = 3, oo
prob = integrate( pdf, ( t, a, b ) ).doit()
round( prob, 4 )


"""
script to matrix normalization
"""

from functools import reduce
import math as m
import numpy as np

def minmax_normalization(x, type):
    """

    :param x: column of matrix data
    :param type: type of normalization
    :return: min max normalized column of matrix data
    """
    if min(x) == max(x):
        return np.ones(x.shape)

    if type == 'cost':
        return (max(x) - x) / (max(x) - min(x))
    return (x - min(x)) / (max(x) - min(x))

def max_normalization(x, type):
    """

    :param x: column of matrix data
    :param type: type of normalization
    :return: max normalized column of matrix data
    """

    if type == 'cost':
        return 1 - x/max(x)
    return x / max(x)

def sum_normalization(x, type):
    """

    :param x: column of matrix data
    :param type: type of normalization
    :return: sum normalized column of matrix data
    """
    if type == 'cost':
        return (1/x) / sum(1/x)
    return x / sum(x)

def vector_normalization(x, type):
    """

    :param x: column of matrix data
    :param type: type of normalization
    :return: vector normalized column of matrix data
    """
    if type == 'cost':
        return 1 - (x / np.sqrt(sum(x ** 2)))
    return x / np.sqrt(sum(x ** 2))

def logaritmic_normalization(x, type):
    """

    :param x: column of matrix data
    :param type: type of normalization
    :return: logarithmic normalized column of matrix data
    """
    prod = reduce(lambda a, b: a*b, x)
    if type == 'cost':
        return (1 - (np.log(x) / m.log(prod))) / (len(x) - 1)
    return np.log(x) / m.log(prod)

def normalize(matrix, types, method, precision = 2):
    """

    :param matrix: decision matrix
    :param types: types of normalization for columns
    :param method: method of normalization
    :param precision: precision
    :return: normalized matrix
    """

    if matrix.shape[1] != len(types):
        print('Sizes does not match')

    normalized_matrix = matrix.astype('float')
    for i in range(len(types)):
        if type == 1:
            normalized_matrix[:, i] = np.round(method(matrix[:, i], types[i]), precision)
        else:
            normalized_matrix[:, i] = np.round(method(matrix[:, i], types[i]), precision)
    return normalized_matrix


# QR factorization algorithm

import numpy as np

def qr_factor_in(A):
    '''
    QR factorization algorithm working inplace.

    Returns a matrix with the R factor in the upper right corner
    and the Householder's vectors in the lower right part.
    The final Q construction shall be performed by the user.

    Input:
      - A : coefficient matrix.
    Output:
      - A : result matrix.
    '''
    (m, n) = A.shape
    for i in range(0, n):
        alpha = np.linalg.norm(A[i:,i])
        if alpha is 0:
            raise Exception('The matrix does not have maximum rank')
        if A[i,i] > 0:
            alpha = -alpha
        print(alpha)
        v1 = A[i,i] - alpha
        A[i,i] = alpha
        A[i+1:m,i] = A[i+1:m,i]/v1
        # beta = (alpha-z1)/alpha = 2/(u'u)
        #   with u the scaled Householder's vector u=(z-alpha*ei)/(z1-alpha*e1)
        beta = -v1/alpha;
        v = np.insert(A[i+1:m,i], 0, 1, axis=0)
        A[i:m,i+1:n] = A[i:m,i+1:n] - (beta * v) * (v.T * A[i:m,i+1:n])
    return A

def qr_factor(A):
    '''
    QR factorization algorithm

    Input:
      - A : coefficient matrix
    Output:
      - Q : first factor
      - R : second factor
    '''
    R = qr_factor_in(A)
    (m, n) = A.shape
    Q = np.matrix(np.identity(m))
    for i in range(0, n):
        v = np.insert(R[i+1:m,i], 0, 1, axis=0)
        H = np.matrix(np.identity(m))
        H[i:m,i:m] = np.matrix(np.identity(m-i)) - 2*(v*v.T)/(v.T*v)
        Q = Q*H
        R[i+1:m,i] = np.zeros((m-i-1,1))
    return (Q, R)


import numpy as np
def funcy(a1,a2,a1m,a2m):
    m=(sum(a1*a2)-len(a1)*a1m*a2m)/(sum(a1**2)-len(a1)*(a1m**2))
    c=a2m-(m*a1m)
    return(m,c)
def errorid(a2,slop,inter):
    ycap=(slop*a1)+inter
    s=np.sqrt((sum((a2-ycap)**2))/len(a2))
    return(s,ycap)
a1=np.array([4,9,10,14,4,7,12,22,1,17])
a2=np.array([31,58,65,73,37,44,60,91,21,84])
a1m=np.mean(a1)
a2m=np.mean(a2)
print("---------Eqaution for linear regression---------")
slop,inter=funcy(a1,a2,a1m,a2m)
print(f' y = {slop} x + {inter}')
print("---------Root of Mean Square Error---------")
l,ycap=errorid(a2,slop,inter) #I am using the metric RMSE(Root Mean Square Error) for understanding the model
print(l)


"""
Special Pythagorean triplet
Problem 9
A Pythagorean triplet is a set of three natural numbers, a < b < c, for which,

a2 + b2 = c2
For example, 32 + 42 = 9 + 16 = 25 = 52.

There exists exactly one Pythagorean triplet for which a + b + c = 1000.
Find the product abc.
"""

import numpy as np

def find_triplet():
    for a in range(1, 500):
        for b in range(1, 500):

            c = np.sqrt(a**2 + b**2)

            if a + b + c == 1000:
                print(f"Triplet found!")
                print(f"a: {a}, b: {b}, c: {c}")
                print(f"a + b + c = {a + b + c}")
                print(f"Product abc = {a*b*c}")
                return

find_triplet()

"""
31875000
"""


import numpy as np

def lin_sol(augmented_matrix):

    matrix = augmented_matrix[:, 0:-1]

    rank = np.linalg.matrix_rank(matrix)
    rank_augmented = np.linalg.matrix_rank(augmented_matrix)

    if (rank < rank_augmented):
        return 'no solution'

    unique_solution = rank == rank_augmented == matrix.shape[1]

    if unique_solution:
        return 'unique solution'

    else:
        return 'infinite solutions'

def least_squares(slope, intercept):
    A = np.zeros((slope.shape[0], 2))
    A[:, 0] = 1
    A[:, 1] = -slope[:, 0]
    return np.linalg.inv(A.T@A)@A.T@intercept

def diagonalize(A):
    eigenvalues_of_A, eigenvectors_of_A = np.linalg.eig(A)
    diagonal_matrix = np.diagflat(eigenvalues_of_A)
    return diagonal_matrix, eigenvectors_of_A


import numpy as np
import matplotlib.pyplot as plt

#parameter:
initial=0
final=50
N=1001
t=np.linspace(initial,final,N)
h=(final-initial)/(N-1)
g=9.8 #(m/s^2)
D=0.1 #(kg*m)
m=70  #(kg)

#formula:
#mg-Dv^2=ma ->a=g-(D/m)*v^2 -> dv/dt= g-(D/m)*v^2 =U(v,t)

def U_function(vi):
    return g-(D/m)*(vi**2)

def Euler(vi,Ui):
    return vi+h*Ui

def RK2(vi): #with (w1,w2,alpha,beta) = (0.5,0.5,1,1)
    k1=U_function(vi)
    k2=U_function(vi+1*h*k1)
    return vi+h*(0.5*k1+0.5*k2)

def initial_condition(): #V(0)=0
    v_euler=[]
    U_euler=[]

    v_euler.append(0)
    U_euler.append(U_function(v_euler[0]))

    v_RK2=[]
    U_RK2=[]

    v_RK2.append(0)
    U_RK2.append(U_function(v_RK2[0]))
    return  v_euler,U_euler,v_RK2,U_RK2

def Euler_method(v_euler,U_euler):
    for i in range(0,N-1):
        U_euler.append(U_function(v_euler[i]))
        v_euler.append(Euler(v_euler[i],U_euler[i]))
    return v_euler

def RK2_method(v_RK2,U_RK2):
    for i in range(0,N-1):
        U_RK2.append(U_function(v_RK2[i]))
        v_RK2.append(RK2(v_RK2[i]))
    return v_RK2

def terminal_speed():
    V_terminal=[]
    for i in range(0,N):
        V_terminal.append(np.sqrt(m*g/D))
    return V_terminal

def visualize(v_euler,v_RK2,V_terminal):
    fig=plt.figure(figsize=(18,6))
    ax1=fig.add_subplot(121)
    ax1.plot(t,v_euler,label='euler method')
    ax1.plot(t,v_RK2,label='RK2 method')
    ax1.plot(t,V_terminal,label='terminal speed (analytic)')
    ax1.set_xlabel('t(sec)')
    ax1.set_ylabel('V(m/s)')
    ax1.set_title('falling object subject to air drag')
    ax1.grid(True)
    ax1.legend(loc='lower right')

    ax2=fig.add_subplot(122)
    ax2.plot(t,v_euler/V_terminal[0],label='euler method')
    ax2.plot(t,v_RK2/V_terminal[0],label='RK2 method')
    ax2.set_xlabel('t(sec)')
    ax2.set_ylabel('$V/V_{terminal}$')
    ax2.set_title('falling object subject to air drag')
    ax2.grid(True)
    ax2.legend(loc='lower right')
    plt.show()

v_euler,U_euler,v_RK2,U_RK2=initial_condition()
v_euler=Euler_method(v_euler,U_euler)
v_RK2=RK2_method(v_RK2,U_RK2)
V_terminal=terminal_speed()
visualize(v_euler,v_RK2,V_terminal)


import matplotlib.pyplot as plt
import numpy as np

d = 2
h = 1
a = 0.1

I = 1
M = I*np.pi*a**2
m_0 = 4*np.pi/10000000

def canvas():
    fig, ax = plt.subplots()
    ax.set_title('Διανυσματικό Δυναμικό Α(x,z)')
    ax.set_xlabel('x(m)')
    ax.set_ylabel('z(m)')
    ax.grid()
    #fig.set_size_inches(2000.5, 2000.5)
    return ax

def Ax(x, y, z):
    f1 = 1/(np.sqrt((x-d)**2 + (y-h)**2 + z**2))
    f2 = 1/(np.sqrt((x-d)**2 + (y+h)**2 + z**2))
    f3 = 1/(np.sqrt((x+d)**2 + (y-h)**2 + z**2))
    f4 = 1/(np.sqrt((x+d)**2 + (y+h)**2 + z**2))
    return (m_0*M/(4*np.pi))*z*(f1**3 - f2**3 + f3**3 - f4**3)

def Az(x, y, z):
    f1 = 1/(np.sqrt((x-d)**2 + (y-h)**2 + z**2))
    f2 = 1/(np.sqrt((x-d)**2 + (y+h)**2 + z**2))
    f3 = 1/(np.sqrt((x+d)**2 + (y-h)**2 + z**2))
    f4 = 1/(np.sqrt((x+d)**2 + (y+h)**2 + z**2))
    return (m_0*M/(4*np.pi))*(-(x-d)*f1**3 + (x-d)*f2**3 - (x+d)*f3**3 - (x+d)*f4**3)

def Ax_aux(x, z):
    return Ax(x, 1, z)

def Az_aux(x, z):
    return Az(x, 1, z)

a_x = np.vectorize(Ax_aux)
a_z = np.vectorize(Az_aux)

x = np.linspace(0, 4, 50)
z = np.linspace(-2, 2, 50)
X, Z = np.meshgrid(x, z)

Ax = a_x(X, Z)
Az = a_z(X, Z)
ax = canvas()
ax.streamplot(X, Z, Ax/(2*((Ax**2 + Az**2)**0.5)),
              Az/(2*((Ax**2 + Az**2)**0.5)))
plt.show()
ax = canvas()
ax.quiver(X, Z, Ax/(2*((Ax**2 + Az**2)**0.5)), Az/(2*((Ax**2 + Az**2)**0.5)),
          (Ax**2 + Az**2)**0.5, cmap='viridis', units='xy', width=0.0035, headwidth=3., headlength=4.)
plt.show()


# Задача 16

import numpy as np

# Проверка достаточных условий
def test_dd(X):
    D = np.diag(np.abs(X)) # Поиск диагональных коэффициентов
    print(D)
    S = np.sum(np.abs(X), axis=1) - D # Find row sum without diagonal
    if np.all(D > S):
        print ('Диагонально доминирующая матрица')
        return True
    else:
        print ('НЕ диагонально доминирующая матрица')
        return False

# Решение СЛАУ методом прогонки
def RTM(a,b):
    if(not test_dd(a)):
        print('Ошибка исходных данных')
        return -1

    n=len(a)
    x=[0 for k in range(0,n)] #обнуление вектора решений
    print('Размерность матрицы:',n,'x',n)

    #прямой ход
    v=[0 for k in range(0,n)]
    u=[0 for k in range(0,n)]

    # для перевой строки
    v[0]=a[0][1] / (-a[0][0])
    u[0]=(-b[0])/(-a[0][0])

    for i in range(1,n-1):
        v[i]=a[i][i+1]/(-a[i][i]-a[i][i-1]*v[i-1])
        u[i]= (a[i][i-1]*u[i-1]-b[i])/(-a[i][i]-a[i][i-1]*v[i-1])

    v[n-1]=0
    u[n-1]=(a[n-1][n-2]*u[n-2]-b[n-1])/(-a[n-1][n-1]-a[n-1][n-2]*v[n-2])

    print(f'Прогоночные коэффициенты: alfa={v} , beta={u}')

    #обратный ход

    x[n-1]=u[n-1]
    for i in range(n-1,0,-1):
        x[i-1]=v[i-1]*x[i]+u[i-1]
    return x

A=np.array([[-11, -7, 0, 0, 0], [7, 10, -1, 0, 0], [0,-10,20,6,0], [0,0,10,18,-7],[0,0,0,6, -13]])
B = np.array([-1,4,5,6,-9])

test_dd(A)

#вызов функции и вывод на экран
print(A)
print(B)
print(RTM(A,B))

print(f'Проверка решения с помощью встроенной функции: {np.linalg.inv(A).dot(B)}')


import numpy as np

x = np.array([0, 1, 3, 4, 6, 8, 9])
y = np.array([2, 5, 7, 6, 8, 14, 12])

x_mean = x.mean()
y_mean = y.mean()
up = 0
bo_l = 0
bo_r = 0
m = len(x)

for i in range(m):
    up += y[i] * (x[i] - x_mean)
    bo_l += x[i] ** 2
    bo_r += x[i]

w = up / (bo_l - bo_r ** 2 / m)

va = 0
for i in range(m):
    va += (y[i] - w * x[i])

b = va / m

# cov_xy = np.cov(x, y)
# print(cov_xy)
# v_x = np.var(x)
# print(v_x)

# print(w, b)
# print(cov_xy / v_x)

cov, v_x = 0, 0
for i in range(m):
    cov += (x[i] - x_mean) * (y[i] - y_mean)
    v_x += (x[i] - x_mean) ** 2

print(cov / v_x)

# Be careful that using numpy to compute the variance and cov_variance is little different than using the formula

Machine Learning Basic Principles/linear_regression.py


import matplotlib.pyplot as plt
import numpy as np

def f(x):
    return x**2 + x + 1

def df(x):
    return 2*x + 1

x = np.linspace(start = -3, stop = 3, num = 500)

x_novo = 3
x_anterior = 0
taxa_de_aprendizado = 0.1
precisao = 0.00001

x_novos = []

for i in range(50000):
    x_anterior = x_novo
    gradiente = df(x_anterior)
    x_novo = x_anterior - gradiente * taxa_de_aprendizado
    if(abs(x_anterior - x_novo) < precisao):
        print("Numero de tentativas:", i)
        break

    x_novos.append(x_novo)

print("Valor minimo:", x_novo)
print("Inclinaçao, ou o valor de df(x) neste ponto:", df(x_novo))
print("Custo neste ponto:", f(x_novo))

x_novos = np.array(x_novos)

plt.figure(figsize=[20,5])

plt.subplot(1, 3, 1)
plt.title("Função de custo")
plt.xlim(-3, 3)
plt.ylim(0, 8)

plt.xlabel("x")
plt.ylabel("f(x)")

plt.plot(x, f(x), color = "blue", linewidth = 5, alpha = 0.8)
plt.scatter(x_novos, f(x_novos), color = "red", s = 100, alpha = 0.6)

plt.subplot(1, 3, 2)
plt.title("Derivada da funçao de custo")
plt.xlim(-2, 3)
plt.ylim(-3, 6)
plt.grid("True")

plt.xlabel("x")
plt.ylabel("df(x)")

plt.plot(x, df(x), color = "skyblue", linewidth = 5, alpha = 0.8)
plt.scatter(x_novos, df(x_novos), color = "red", s = 100, alpha = 0.6)

plt.subplot(1, 3, 3)
plt.title("Gradiente (Zoom)")
plt.xlim(-0.55, 0.2)
plt.ylim(-0.3, 0.8)
plt.grid("True")

plt.xlabel("x")
plt.ylabel("df(x)")

plt.plot(x, df(x), color = "skyblue", linewidth = 5, alpha = 0.8)
plt.scatter(x_novos, df(x_novos), color = "red", s = 100, alpha = 0.6)

#plt.show()
#plt.savefig("Aula 05.jpg")


from __future__ import print_function  # For printing no newline
import sympy
from sympy import Rational
from sympy import factorial
import numpy as np

def Taylor( n, dx ):
    """Compyute n terms in the Taylor expansion for a function centered 'dx'
    away from where the terms will be evaluated."""

    return [ (dx)**j/factorial(j) for j in range(n) ]

def compute_derivs( u_stencil, index, dx ):
    """Compute all of the derivatives of u_stencil.  Index refers to the
    location where these values will be computed.  For example,

        u_stencil = [u0, u1, u0] and index = 0

    will compute all of the derivatives at u0, u0_x, u0_xx.

    However, index = 0.5 will compute the derivatives u1/2, u1/2_x and
    u1/2_xx.
"""

    # Set up matrix that needs to be inverted
    n = len( u_stencil )
    A = sympy.Matrix( n, n, lambda i,j : 0 )
    for i in range(n):
        t = Taylor( n, (i-index)*dx )
        for j in range(n):
            A[i,j] = t[j]

    return A.inv()

# (uniform) grid spacing
dx = sympy.symbols("dx")

uim2 = sympy.symbols("uim2")
uim1 = sympy.symbols("uim1")
ui   = sympy.symbols("ui")
uip1 = sympy.symbols("uip1")
uip2 = sympy.symbols("uip2")

u_stencil = [uim2, uim1, ui, uip1, uip2]

# Compute derivative using the whole stencil:
gamma = compute_derivs( u_stencil, 2, dx )

# Three sub-stencils
u0 = [uim2, uim1,   ui]
u1 = [uim1, ui,   uip1]
u2 = [ui,   uip1, uip2]
u  = [u0, u1, u2]

# Three Lagrange polynomials and their derivatives:
p0 = compute_derivs( u[0], 2, dx )
p1 = compute_derivs( u[1], 1, dx )
p2 = compute_derivs( u[2], 0, dx )
p  = [p0, p1, p2]

interps = [0,0,0]
for j in range(3):
    interps[j] = u[j][0]*(p[j][1,0]*dx) +  u[j][1]*(p[j][1,1]*dx) + u[j][2]*(p[j][1,2]*dx)

# Goal: find unknowns (g0, g1, g2) such that
#       g0 * u0 + g1 * u1 + g2 * u2 = gamma[1,:]*u_stencil
g0 = Rational(1,6)
g1 = Rational(2,3)
g2 = Rational(1,6)

sub_sten_val = g0*interps[0] + g1*interps[1] + g2*interps[2]
linear_val   = sum( dx*u*g for (u,g) in zip( u_stencil, gamma[1,:] ) )

error = sympy.simplify( sympy.expand( sub_sten_val - linear_val ) )
print( error )

interps2 = [0,0,0]
for j in range(3):
    interps2[j] = u[j][0]*(p[j][2,0]*dx**2) +  u[j][1]*(p[j][2,1]*dx**2) + u[j][2]*(p[j][2,2]*dx**2)

# Goal: find unknowns (g0, g1, g2) such that
#       g0 * u0 + g1 * u1 + g2 * u2 = gamma[1,:]*u_stencil
g0 = Rational(-1,12)
g1 = Rational( 7, 6)
g2 = Rational(-1,12)

sub_sten_val = g0*interps2[0] + g1*interps2[1] + g2*interps2[2]
linear_val   = sum( (dx**2)*u*g for (u,g) in zip( u_stencil, gamma[2,:] ) )

error = sympy.simplify( sympy.expand( sub_sten_val - linear_val ) )
print( error )

# Now, work out 2nd-derivative using larger stencil

# Smoothness indicators
#   eps  = sympy.symbols("epsilon")
#   beta = [None]*3
#   beta[0] = Rational(13,12)*(uim2-2*uim1+ui)**2 + Rational(1,4)*(uim2-4*uim1+3*ui)**2
#   beta[1] = Rational(13,12)*(uim1-2*ui+uip1)**2 + Rational(1,4)*(uim1-uip1)**2
#   beta[2] = Rational(13,12)*(ui-2*uip1+uip2)**2 + Rational(1,4)*(3*ui-4*uip1+uip2)**2


# To add a new cell, type '#%%'
# To add a new markdown cell, type '#%% [markdown]'

#%%
# Numpy continued
import numpy as np

#%%
# Stacking numpy arrays
a = np.floor(10*np.random.random((2,3)));print('a:',a)
b = np.floor(10*np.random.random((2,3)));print('b:',b)
# a=[1,2,3]
# b=[4,5,6]
z = np.vstack((a,b)); print('vstack z:',z)
z1 = np.hstack((a,b)); print('hstack z1:',z1)
z2 = np.column_stack((a,b))
print('column_stack z2:\n',z2)
# column_stack same as hstack between two numpy arrays here
# the two has different behavior acting on lists
# try comment in the python list defs for a and b above.
print('#',50*"-")

#%%
# newaxis
from numpy import newaxis
a = np.array([4.,2.,6.])
b = np.array([3.,7.,5.])
print('a[:,newaxis] :\n',a[:,newaxis])
z3= np.column_stack((a[:,newaxis],b[:,newaxis]))
print('z3:\n',z3)
z3b= np.hstack((a[:,newaxis],b[:,newaxis]))
print('z3b:\n',z3b)
z4= np.vstack((a[:,newaxis],b[:,newaxis]));
print('z4:\n',z4)
print('#',50*"-")

#%%
# Splitting Numpy arrays
a = np.floor(10*np.random.random((2,12)))
print('a:',a)
z1 = np.hsplit(a,3) # Split a into 3. If dimension not divisible -> ValueError
print('type(z1):',type(z1))
print('len(z1):',len(z1))
for elt in z1:
  print('elt:\n',elt)

#%%
# Split a after the third and the fourth column
print('a:',a)
z2 = np.hsplit(a,(3,4))
# print('type(z1):',type(z1))
print('len(z2):',len(z2))
for elt in z2:
  print('elt:\n',elt)
print('#',50*"-")

#%%
# The .ix_() function ( I interpret it as Index-eXtraction )
# https://numpy.org/doc/stable/reference/generated/numpy.ix_.html
#
e = np.arange(25).reshape(5,5)
print('e:\n',e)
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [10 11 12 13 14]
#  [15 16 17 18 19]
#  [20 21 22 23 24]]
print()

sub_indices = np.ix_([1,3],[0,4]) # If your ultimate ndarray is rank 3 (m x n x k), then you will need three arguments
print('type(sub_indices) :', type(sub_indices))
print('len(sub_indices) :', len(sub_indices)) # In other words, for rank 3 ndarray, you'll need tuple here of length 3.
print('sub_indices :\n', sub_indices)
print()

print('type(sub_indices[0]) :', type(sub_indices[0]))
print('sub_indices[0].shape :', sub_indices[0].shape)
print('type(sub_indices[1]) :', type(sub_indices[1]))
print('sub_indices[1].shape :', sub_indices[1].shape)

print('\ne[sub_indices]:\n', e[sub_indices])

#%%
# rank 3 case
a = np.array([4,2,3])
b = np.array([5,4])
c = np.array([5,4,0,3])
ax,bx,cx = np.ix_(a,b,c) # This separates out the [0], [1], [2] elements of the tuple in one step
print('ax:',ax); print('bx:',bx); print('cx:',cx)
print('shapes:', ax.shape, bx.shape, cx.shape)
result = ax+bx*cx
print('result:',result)
print('result[3,2,4]:',result[2,1,3])
print('individual:',a[2]+b[1]*c[3])

# More commonly, we use it for filtering like our previous rank 2 example.
e = np.arange(6*6*6).reshape(6,6,6)  #216 elements
print('\ne[(ax,bx,cx)]:\n', e[(ax,bx,cx)])

#%%
# Automatic Reshaping
a = np.arange(30)
print('Original- a.shape:',a.shape); print('Original- a:',a)
a.shape = 2,-1,3
print('After- a.shape:',a.shape); print('After- a:',a)
# Unlike the reshape() method, this way changes a directly!!
print('#',50*"-")
#%%
# Other functions worth learning:
# https://numpy.org/doc/stable/reference/generated/numpy.indices.html
idlabels =  np.indices( (3,4) )
# This creates a set of i, j values
# indicating the row/column indices, with dimensions 3 by 4
i, j = idlabels
print('i:',i)
print('j:',j)
# We can try, for example, create a matrix of M, whose values are 3*i - 2*j
m = 2*i - 2*j
print('m:',m)

#%%
#

#%%
# Examples from https://www.machinelearningplus.com/python/101-numpy-exercises-python/
# and https://www.w3resource.com/python-exercises/numpy/index-array.php

#%%


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/11/07 20:33
# @Author   : Iydon
# @File     : course6.1.py

import numpy as np

def test():
    def matrix_is(A):
        # 0:no, 1:vector, 2:matrix
        c = len(A[0])
        for a in A:
            if len(a) is not c: return False
        return True

    def matrix_shape(A):
        return len(A), len(A[0])

    def matrix_init(size):
        return [[0 for j in range(size[-1])] for i in range(size[0])]

    def matrix_add(A, B):
        tA,tB = matrix_shape(A),matrix_shape(B)
        if tA != tB: return False
        C = matrix_init(tA)
        for i in range(tA[0]):
            for j in range(tA[-1]):
                C[i][j] = A[i][j] + B[i][j]
        return C

    def matrix_prod(A, B):
        tA,tB = matrix_shape(A),matrix_shape(B)
        if tA[-1] != tB[0]: return False
        C = matrix_init([tA[0],tB[-1]])
        for i in range(tA[0]):
            for j in range(tB[-1]):
                for k in range(tA[-1]):
                    C[i][j] += A[i][k] * B[k][j]
        return C

    A = matrix_init([3,2])
    B = [[1,2], [3,4], [5,6]]
    print(matrix_add(A,B))
    A = [[1,1,1], [1,1,1]]
    print(matrix_prod(A, B))

# ----------------------------

def gaussian_elimination(A, b):
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n-1):
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            for j in range(k,n):
                A[i,j] = A[i,j] - m*A[k,j]
            b[i,0] = b[i,0] - m*b[k,0]
    x[-1,0] = b[-1,0] / A[-1,-1]
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += A[i,j] * x[j,0]
        x[i,0] = (b[i,0] - tmp) / A[i,i]
        tmp = 0
    return x

def gaussian_elimination_partial_pivoting(A, b):
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n-1):
        M = k
        for m in range(k+1,n):
            if A[m,k] > A[M,k]: M = m
        A[[k,M]] = A[[M,k]]
        b[[k,M]] = b[[M,k]]
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            for j in range(k,n):
                A[i,j] = A[i,j] - m*A[k,j]
            b[i,0] = b[i,0] - m*b[k,0]
    x[-1,0] = b[-1,0] / A[-1,-1]
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += A[i,j] * x[j,0]
        x[i,0] = (b[i,0] - tmp) / A[i,i]
        tmp = 0
    return x

def gaussian_elimination_scaled_partial_pivoting(A, b):
    np_result = A**-1 * b
    print(np_result.T)
    n = A.shape[0]
    x = np.zeros((n,1))
    tmp = 0
    for k in range(n):
        M = np.max(A[k,:])
        A[k,:] /= M
        b[k,0] /= M
    for k in range(n-1):
        for i in range(k+1,n):
            m = A[i,k] / A[k,k]
            for j in range(k,n):
                A[i,j] = A[i,j] - m*A[k,j]
            b[i,0] = b[i,0] - m*b[k,0]
    x[-1,0] = b[-1,0] / A[-1,-1]
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            tmp += A[i,j] * x[j,0]
        x[i,0] = (b[i,0] - tmp) / A[i,i]
        tmp = 0
    return x

A = np.matrix([[1,2,3], [2,5,7],[3,7,11]])
b = np.matrix([[1.],[2],[3]])
result = gaussian_elimination(A, b)
print(result.T)
print("*")
A = np.matrix([[1.,2,3], [2,5,7],[3,7,11]])
b = np.matrix([[1.],[2],[3]])
result = gaussian_elimination_partial_pivoting(A, b)
print(result.T)
print("*")
A = np.matrix([[1.,2,3], [2,5,7],[3,7,11]])
b = np.matrix([[1.],[2],[3]])
result = gaussian_elimination_scaled_partial_pivoting(A, b)
print(result.T)


#!/usr/bin/env python
# coding: utf-8

# In[1]:

get_ipython().run_line_magic('pylab', 'inline')

# ## Reproducing PyMC3 ODE_API_introduction
#
# https://github.com/pymc-devs/pymc3/blob/master/docs/source/notebooks/ODE_API_introduction.ipynb
#
# text for the equations taken from this original.  All simulations done with emcee and pyndamics.

# In[2]:

from scipy.integrate import odeint

# In[3]:

from pyndamics import Simulation
from pyndamics.emcee import *

# # A Differential Equation For Freefall
#
# An object of mass $m$ is brought to some height and allowed to fall freely until it reaches the ground. A differential equation describing the object's speed over time is
#
# $$ y' = mg - \gamma y $$
#
# The force the object experiences in the downwards direction is $mg$, while the force the object experiences in the opposite direction (due to air resistance) is proportional to how fast the object is presently moving. Let's assume the object starts from rest (that is, that the object's inital velocity is 0).  This may or may not be the case.  To showcase how to do inference on intial conditions, I will first assume the object starts from rest, and then relax that assumption later.
#
# Data on this object's speed as a function of time is shown below.  The data may be noisy because of our measurement tools, or because the object is an irregular shape, thus leading to times during freefall when the object is more/less aerodynamic.  Let's use this data to estimate the proportionality constant for air resistance.
#
#

# In[4]:

# For reproducibility
np.random.seed(20394)

def freefall(y, t, p):
    return 2.0*p[1] - p[0]*y[0]

# Times for observation
times = np.arange(0,10,0.5)
gamma,g, y0, sigma = 0.4, 9.8, -2, 2
y = odeint(freefall, t=times, y0=y0, args=tuple([[gamma,g]]))
yobs = np.random.normal(y,2)

fig, ax = plt.subplots(dpi=120)
plt.plot(times,yobs, label='observed speed', linestyle='dashed', marker='o', color='red')
plt.plot(times,y, label='True speed', color='k', alpha=0.5)
plt.legend()
plt.xlabel('Time (Seconds)')
plt.ylabel(r'$y(t)$');
plt.show()

# ```python
# ode_model = DifferentialEquation(
#     func=freefall,
#     times=times,
#     n_states=1, n_theta=2,
#     t0=0
# )
#
# with pm.Model() as model:
#     # Specify prior distributions for soem of our model parameters
#     sigma = pm.HalfCauchy('sigma',1)
#     gamma = pm.Lognormal('gamma',0,1)
#
#     # If we know one of the parameter values, we can simply pass the value.
#     ode_solution = ode_model(y0=[0], theta=[gamma, 9.8])
#     # The ode_solution has a shape of (n_times, n_states)
#
#     Y = pm.Normal('Y', mu=ode_solution, sd=sigma, observed=yobs)
#
#     prior = pm.sample_prior_predictive()
#     trace = pm.sample(2000, tune=1000, cores=1)
#     posterior_predictive = pm.sample_posterior_predictive(trace)
#
#     data = az.from_pymc3(trace=trace, prior=prior, posterior_predictive=posterior_predictive)
# ```

# In[5]:

sim=Simulation()
sim.add("y' = m*g - γ*y",0)
sim.params(m=2,g=9.8,γ=0.4)
sim.add_data(t=times,y=yobs)
sim.run(0,10)

# In[6]:

t,y=sim.t,sim.y
plot(t,y)

t,y=sim.components[0].data['t'],sim.components[0].data['value']
plot(t,y,'o')

# In[7]:

model=MCMCModel(sim,
                _sigma_y=Uniform(0.1,5),
                γ=Uniform(0,1),
               )

# In[8]:

model.run_mcmc(300,repeat=2)
model.plot_chains()

# In[9]:

model.plot_distributions()

# In[10]:

model.plot_many(0,10,'y')

# In[11]:

model=MCMCModel(sim,
                _sigma_y=HalfCauchy(1),
                γ=LogNormal(0,1),
               )

# In[12]:

model.run_mcmc(300,repeat=2)
model.plot_chains()

# In[13]:

model.plot_distributions()

# In[14]:

model.plot_many(0,10,'y')

# ```python
# with pm.Model() as model3:
#     sigma = pm.HalfCauchy('sigma',1)
#     gamma = pm.Lognormal('gamma',0,1)
#     g = pm.Lognormal('g',pm.math.log(10),2)
#     # Initial condition prior.  We think it is at rest, but will allow for perturbations in initial velocity.
#     y0 = pm.Normal('y0', 0, 2)
#
#     ode_solution = ode_model(y0=[y0], theta=[gamma, g])
#
#     Y = pm.Normal('Y', mu=ode_solution, sd=sigma, observed=yobs)
#
#     prior = pm.sample_prior_predictive()
#     trace = pm.sample(2000, tune=1000, target_accept=0.9, cores=1)
#     posterior_predictive = pm.sample_posterior_predictive(trace)
#
#     data = az.from_pymc3(trace=trace, prior=prior, posterior_predictive=posterior_predictive)
# ```

# In[15]:

model=MCMCModel(sim,
                γ=LogNormal(0,1),
                g=LogNormal(log(10),2),
                initial_y=Normal(0,2),
               )

# In[16]:

model.run_mcmc(300,repeat=2)
model.plot_chains()

# In[17]:

model.plot_distributions()

# In[18]:

model.plot_many(0,10,'y')

# ## SIR Model

# In[19]:

def SIR(y, t, p):
    ds = -p[0]*y[0]*y[1]
    di = p[0]*y[0]*y[1] - p[1]*y[1]
    return [ds, di]

times = np.arange(0,5,0.25)

beta,gamma = 4,1.0
# Create true curves
y = odeint(SIR, t=times, y0=[0.99, 0.01], args=((beta,gamma),), rtol=1e-8)
# Observational model.  Lognormal likelihood isn't appropriate, but we'll do it anyway
yobs = np.random.lognormal(mean=np.log(y[1::]), sigma=[0.2, 0.3])

plt.plot(times[1::],yobs, marker='o', linestyle='none')
plt.plot(times, y[:,0], color='C0', alpha=0.5, label=f'$S(t)$')
plt.plot(times, y[:,1], color ='C1', alpha=0.5, label=f'$I(t)$')
plt.legend()
plt.show()

# ```python
# sir_model = DifferentialEquation(
#     func=SIR,
#     times=np.arange(0.25, 5, 0.25),
#     n_states=2,
#     n_theta=2,
#     t0=0,
# )
#
# with pm.Model() as model4:
#     sigma = pm.HalfCauchy('sigma', 1, shape=2)
#
#     # R0 is bounded below by 1 because we see an epidemic has occured
#     R0 = pm.Bound(pm.Normal, lower=1)('R0', 2,3)
#     lam = pm.Lognormal('lambda',pm.math.log(2),2)
#     beta = pm.Deterministic('beta', lam*R0)
#
#     sir_curves = sir_model(y0=[0.99, 0.01], theta=[beta, lam])
#
#     Y = pm.Lognormal('Y', mu=pm.math.log(sir_curves), sd=sigma, observed=yobs)
#
#     prior = pm.sample_prior_predictive()
#     trace = pm.sample(2000,tune=1000, target_accept=0.9, cores=1)
#     posterior_predictive = pm.sample_posterior_predictive(trace)
#
#     data = az.from_pymc3(trace=trace, prior = prior, posterior_predictive = posterior_predictive)
# ```

# In[20]:

sim=Simulation()
sim.add("S'= -β*S*I",0.99,plot=1)
sim.add("I'= +β*S*I - γ*I",0.01,plot=1)
sim.params(β=4,γ=1)
sim.add_data(t=times[1:],S=yobs[:,0],plot=1)
sim.add_data(t=times[1:],I=yobs[:,1],plot=1)
sim.run(0,5)

# In[21]:

model=MCMCModel(sim,  # _sigma_S and _sigma_I default to Jeffreys
                β=Normal(0,20,all_positive=True),
                γ=Normal(0,20,all_positive=True),
               )

# In[22]:

model.run_mcmc(300,repeat=2)
model.plot_chains()

# In[23]:

model.plot_distributions()

# In[24]:

model.plot_many(0,5,['S','I'])

# In[25]:

R0=model.eval('β/γ')

# In[26]:

model.plot_distributions(R0=R0)

# In[ ]:


# coding: utf-8

# In[38]:

##Optimum Function

# In[39]:

import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns
get_ipython().magic(u'matplotlib inline')

# Required libraries and packages are imported.

# In[40]:

x = np.arange(-120,80,0.1)

# Random x values were created.

# In[41]:

def f(x):
    return x**4 + 200*(x+2000)**2 + 10000

# Function is defined with x as input variable.

# In[42]:

def df(x):
    return 4*x**3 + 400*(x+2000)

# Derivative of the f(x) is defined.

# In[43]:

plt.plot(x,f(x))

# The plot was generated between input x variable and f(x).

# In[44]:

def optimum_function(x_old,x_new,gamma,precision):
    x_search = [x_new]
    x_iterations = [0] #number of iterations list is assigned zero before the loop
    n = 0 #variable initially set to zero
    while abs(x_new - x_old) > precision:
        x_old = x_new
        x_new = x_old - gamma*df(x_old)
        n = n+1 #Increase the count of the iteration steps
        x_search.append(x_new)
        z = len(x_search) #To get total number of iterations involved in the process
        x_iterations.append(n)

    print(z,"iterations")
    print("the local minimum occurs at %f" %x_new)
    print(gamma)
    x_iterations_graph = np.array(x_iterations)
    x_search_graph = np.array(x_search)
    y_search = f(x_search_graph) #decreasing trend of function
    plt.plot(x_iterations_graph,y_search)

# In the above cell, the minimum point at which function has least value is calculated based on precision.
# Next, a plot is generated between the number of iterations and function with decreasing values [x_search--x_new]

# In[45]:

x_old = 70
x_new = 50
gamma = 0.000001
precision = 1e-12
optimum_value = optimum_function(x_old,x_new,gamma,precision)

# This fulfills the optimum_function assignment

# In[46]:

##Adaptive Optimum Function

# In[47]:

def adaptive_optimum(x_old, x_new, gamma, t, precision):
    nextIter = True #boolean condition
    while nextIter:
            gamma *=t #gamma value will be decremented with the value of 't' - decreasing rate of gamma
            x_old_try = x_old
            x_new_try = x_new
            count = 0 #variable is set to zero before loop

            try:
                while abs(x_new_try - x_old_try) > precision:
                    count = count + 1 #increment the variable
                    x_old_try = x_new_try
                    x_new_try = x_old_try - gamma * df(x_old_try)

                    if (count > 10000): #As per loop runs for 10000 iterations and stops if it is greater than 10000
                        break

                if (count <= 10000) and (abs(x_new_try - x_old_try) < precision):
#If this condition is achieved stop the loop and come out of the loop to print optimum gamma and corresponding minimum x_new_try
                    nextIter = False
                    break
            except:
                nextIter = True

    print("Found gamma:", gamma)
    print("The minimum is at:", x_new_try)
    print("The minimum value of f(x) is:", f(x_new_try))

# In[48]:

x_old = 70
x_new = 50

precision = 1e-12

t=0.9

gamma = 1

adaptive_optimum(x_old, x_new, gamma, t, precision)

# Thus, optimum gamma value is calculated by the program as well as corresponding minimum point and function value at this point was calculated. This fulfills the adaptive_optimum assignment.


try:
    import numpy as _np
    from typing import Union, Tuple, Dict, List
    from math import sqrt as _sqrt, pi as _pi, asin as _asin, log as _log
    from univariate._base import BoundedInterval
except Exception as e:
    print(f"some modules are missing {e}")

class Arcsine(BoundedInterval):
    """
    This class contains methods concerning Arcsine Distirbution [#]_.

    .. math::
        \\text{Arcsine}(x)={\\frac{1}{\\pi \\sqrt{x(1-x)}}}

    Args:

        x(float): random variable between 0 and 1

    Reference:
        .. [#] Wikipedia contributors. (2020, October 30). Arcsine distribution. https://en.wikipedia.org/w/index.php?title=Arcsine_distribution&oldid=986131091
    """

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): random variables

        Raises:
            ValueError: when there exist a value less than 0 or greater than 1
            TypeError: when parameter is not of type float | List[float] | numpy.ndarray

        Returns:
            Union[float, numpy.ndarray]: evaluation of pdf at x
        """
        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            if _np.any((x <= 0) | (x >= 1)):
                raise ValueError(
                    f'random variable should have values between [0,1].')
            return 1/(_pi * _np.sqrt(x*(1-x)))

        if type(x) is float:
            if x < 0 or x > 1:
                raise ValueError(
                    f'random variable should have values between [0,1].')
            return 1/_pi*_sqrt(x * (1-x))

        raise TypeError(
            'parameter x is expected to be of type float | List[float] | numpy.ndarray')

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): data point

        Raises:
            ValueError: when there exist a value less than 0 or greater than 1
            TypeError: when parameter is not of type float | List[float] | numpy.ndarray

        Returns:
            Union[float, numpy.ndarray]: evaluation of cdf at x
        """
        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            if _np.any((x <= 0) | (x >= 1)):
                raise ValueError(
                    f'values can only be evaluated in the domain [0,1]')
            return 1/(_pi)*_np.arcsin(_np.sqrt(x))

        if type(x) is float:
            if x <= 0 or x >= 1:
                raise ValueError(
                    f'values can only be evaluated in the domain [0,1]')
            return 1/_pi * _asin(_sqrt(x))

        raise TypeError(
            'parameter x is expected to be of type float | List[float] | numpy.ndarray')

    def mean(self) -> float:
        """
        Returns:
            mean of Arcsine distribution.
        """
        return 0.5

    def median(self) -> float:
        """
        Returns:
             median of Arcsine distribution
        """
        return 0.5

    def mode(self) -> Tuple[float, float]:
        """
        Returns:
            mode of Arcsine distribution
        """
        return (0, 1)

    def var(self) -> float:
        """
        Returns:
            variance of Arcsine distribution
        """
        return 0.125

    def std(self) -> float:
        """
        Returns:
            standard deviation of Arcsine distribution
        """
        return _sqrt(0.125)

    def skewness(self) -> float:
        """
        Returns:
            skewness of Arcsine distribution
        """
        return 0.0

    def kurtosis(self) -> float:
        """
        Returns:
            kurtosis of Arcsine distribution
        """
        return 1.5

    def entropy(self) -> float:
        """
        Returns:
            entropy of Arcsine distribution
        """
        return _log(_pi/4)

    def summary(self) -> Dict[str, Union[float, Tuple[float, float]]]:
        """
        Returns:
            Dictionary of Arcsine distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }


import numpy as np

# random number from 0 to MAX_NUMBER for guessing
MAX_NUMBER = 100
# how many times to test algorithm
ITERATION_COUNT = 10000

def score_game(game_core):
    """Запускаем игру 1000 раз, чтобы узнать, как быстро игра угадывает число"""
    count_ls = []
    # фиксируем RANDOM SEED, чтобы ваш эксперимент был воспроизводим!
    np.random.seed(1)
    random_array = np.random.randint(1, MAX_NUMBER + 1, size=(ITERATION_COUNT))
    for number in random_array:
        count_ls.append(game_core(number))
    score = int(np.mean(count_ls))
    print(f"Ваш алгоритм угадывает число в среднем за {score} попыток")
    return score

def game_core_v3(number):
    """Custom algorithm for guessing random number.

    Args:
        number (int): Number to guess

    Returns:
        int: Number of attempts
    """
    count = 1
    predict = int(MAX_NUMBER / 2)
    step = int(predict / 2) + int(predict % 2 > 0)

    while number != predict:
        count += 1
        if number > predict:
            predict += step
        elif number < predict:
            predict -= step
        step = int(step / 2) + int(step % 2 > 0)

    return count

def game_core_v4(number):
    """Guessing based on Binary Search.

    Args:
        number (int): Number to guess

    Returns:
        int: Number of attempts
    """
    left = 0
    right = MAX_NUMBER + 1
    count = 1
    predict = MAX_NUMBER // 2

    while number != predict:
        count += 1
        if predict < number:
            left = predict
        else:
            right = predict
        predict = (left + right) // 2

    return count

if __name__ == "__main__":
    score_game(game_core_v3)
    score_game(game_core_v4)


"""
Program to measure the time that NumPy takes
to perform common linear algebra routines
supported by BLAS and LAPACK libraries
"""

import time
import numpy as np
from numpy.linalg import inv
from numpy.linalg import qr
from numpy.linalg import svd
from numpy.linalg import cholesky
from numpy.linalg import eig

from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_spd_matrix

np.show_config()

def dot_product_timer(x_shape=(5000, 5000),
                      y_shape=(5000, 5000),
                      mean=0,
                      std=10,
                      seed=8053):
  """
  A timer for the formula array1.dot(array2).

  Inputs:
    x_shape: Tuple of 2 Int
             Shape of array1;
    y_shape: Tuple of 2 Int
             Shape of array2;
    mean: Float
          Mean of the normal distribution used for random
          selection of elements of array1 and array2;
    std: Float
         Standard deviation of the normal distribution used for random
         selection of elements of array1 and array2;
    seed: Int
          Seed used in np.random.seed

  Outputs:
    delta_t: Float
             Number of seconds for completing array1.dot(array2);
  """
  np.random.seed(seed)
  array1 = np.random.normal(mean, std, x_shape)
  array2 = np.random.normal(mean, std, y_shape)
  start = time.time()
  array1.dot(array2)
  delta_t = time.time() - start

  return delta_t

def sym_matrix_eig_timer(row_n=2000,
                         mean=0,
                         std=10,
                         seed=8053):
  """
  A timer for calculating the eigenvalues of a symmetric matrix.
  The symmetric matrix is defined as (matrix + matrix.T)/2,
  where the elements of matrix are randomly selected.

  Inputs:
    row_n: Int
           Number of rows and columns of matrix;
    mean: Float
          Mean of the normal distribution used for random
          selection of elements of matrix;
    std: Float
         Standard deviation of the normal distribution used for random
         selection of elements of matrix;
    seed: Int
          Seed used in np.random.seed

  Outputs:
    delta_t: Float
             Number of seconds for completing eig(sym_matrix);
  """
  np.random.seed(seed)
  matrix = np.random.normal(mean, std, (row_n, row_n))
  sym_matrix = (matrix + matrix.T)/2
  start = time.time()
  eig(sym_matrix)
  delta_t = time.time() - start

  return delta_t

def sym_matrix_cholesky_timer(row_n=2000,
                              seed=8053):
  """
  A timer for calculating the Cholesky decomposition of a symmetric matrix.
  The symmetric matrix is generated using sklearn.datasets.make_spd_matrix.

  Inputs:
    row_n: Int
           Number of rows and columns of matrix;
    seed: Int
          Seed used in sklearn.datasets.make_spd_matrix

  Outputs:
    delta_t: Float
             Number of seconds for completing cholesky(sym_matrix);
  """
  np.random.seed(seed)
  sym_matrix = make_spd_matrix(row_n, random_state=seed)
  start = time.time()
  cholesky(sym_matrix)
  delta_t = time.time() - start

  return delta_t

def svd_timer(shape=(2000, 1000),
              mean=0,
              std=10,
              seed=8053):
  """
  A timer for calculating the singular value decomposition of a matrix whose elements
  are randomly selected.

  Inputs:
    shape: Tuple of 2 Int
           Shape of the matrix;
    mean: Float
          Mean of the normal distribution used for random
          selection of elements of the matrix;
    std: Float
         Standard deviation of the normal distribution used for random
         selection of elements of the matrix;
    seed: Int
          Seed used in np.random.seed

  Outputs:
    delta_t: Float
             Number of seconds for completing svd(matrix);
  """
  np.random.seed(seed)
  matrix = np.random.normal(mean, std, shape)
  start = time.time()
  svd(matrix)
  delta_t = time.time() - start

  return delta_t

def qr_timer(shape=(2000, 1000),
             mean=0,
             std=10,
             seed=8053):
  """
  A timer for calculating the qr factorization of a matrix whose elements
  are randomly selected.

  Inputs:
    shape: Tuple of 2 Int
           Shape of the matrix;
    mean: Float
          Mean of the normal distribution used for random
          selection of elements of the matrix;
    std: Float
         Standard deviation of the normal distribution used for random
         selection of elements of the matrix;
    seed: Int
          Seed used in np.random.seed

  Outputs:
    delta_t: Float
             Number of seconds for completing qr(matrix);
  """
  np.random.seed(seed)
  matrix = np.random.normal(mean, std, shape)
  start = time.time()
  qr(matrix)
  delta_t = time.time() - start

  return delta_t

def linear_regression_timer(obs_n=2000000,
                            var_n=150,
                            coeff_mean=0,
                            coeff_std=10,
                            x_std=20,
                            error_std=1,
                            check_result=False,
                            seed=8053):
  """
  A timer of the formula inv(matrix_x.T.dot(matrix_x)).dot(matrix_x.T).dot(vector_y)
  used to calculate the coefficients of a linear regression model.

  NOTE: We only use this formula for testing numpy speed.
  This formula is not recommended for parameter estimation.
  We use the recommended sklearn.linear_model.LinearRegression
  for testing the result

  Inputs:
    obs_n: Int
           Number of rows of matrix_x;
    var_n: Int
           Number of columns of matrix_x;
    coeff_mean: Float
                Mean of the normal distribution used for random
                selection of var_n coefficients;
    coeff_std: Float
              Standard deviation of the normal distribution used for random
              selection of var_n coefficients;
    x_std: Float
           standard deviation of the normal distribution used for random
           selection of the entries of matrix_x. The mean of this distribution is zero.
    error_std: Float
               standard deviation of the residuals;
    check_result: Boolean
                  If True sklearn.linear_model.LinearRegression
                  is used to check the accuracy of
                  inv(matrix_x.T.dot(matrix_x)).dot(matrix_x.T).dot(vector_y).
    seed: Int
          Seed used in np.random.seed

  Outputs:
    delta_t: Float
             Number of seconds for completing the calculation of
             inv(matrix_x.T.dot(matrix_x)).dot(matrix_x.T).dot(vector_y);
  """

  np.random.seed(seed)
  coefficients = np.random.normal(coeff_mean, coeff_std, var_n)
  errors = np.random.normal(0, error_std, obs_n)
  matrix_x = np.random.normal(0, x_std, (obs_n, var_n))
  vector_y = matrix_x.dot(coefficients)+errors

  start = time.time()
  coef1 = inv(matrix_x.T.dot(matrix_x)).dot(matrix_x.T).dot(vector_y)
  delta_t = time.time() - start

  if check_result:
    reg = LinearRegression(fit_intercept=False).fit(matrix_x, vector_y)
    max_error = np.max(np.abs(coef1-reg.coef_))
    print(max_error)

  return delta_t

def my_test(function_list,
            repeat=10,
            percentiles=(0, 5, 10, 50, 90, 95, 100)):
  """
  A function that calls other test functions and prints time percentiles for
  each test function,

  Inputs:
    function_list: List of functions
                   List of test functions
    n: Number of tests. This is the length of the vector that contains time measurements
      for each test function;
    percentiles: Tuple of floats
                List of percentiles to compute on the array that contains n time measurements
                for each test function

  Outputs:
    time_percentiles: Array of floats with shape (len(function_list), len(percentiles))
                      Each entry i,j is the q-th percentile of time measurements for the
                      i-th function of function_list where q is the j-th value of percentiles.
  """

  func_num = len(function_list)
  time_measurements = np.zeros((func_num, repeat))
  time_percentiles = np.zeros((func_num, len(percentiles)))

  for count in range(repeat):
    for func_count in range(func_num):
      time_measurements[func_count, count] = function_list[func_count]()

  for func_count in range(func_num):
    time_percentiles[func_count] = np.percentile(time_measurements[func_count], percentiles)

  return time_percentiles

if __name__ == "__main__":
  def scalar_product():
    """
    A wrapper for dot_product_timer in order to define a scalar product between arrays.
    """
    x_shape=(1000000,)
    delta_t = dot_product_timer(x_shape=x_shape, y_shape=x_shape)
    return delta_t

  function_lst = [scalar_product, dot_product_timer, linear_regression_timer,
                  svd_timer, qr_timer, sym_matrix_cholesky_timer, sym_matrix_eig_timer]
  t_percentiles = my_test(function_list=function_lst)

  # Print results
  L = len(function_lst)
  for f_count in range(L):
    print(function_lst[f_count].__name__,
          " ".join(["{:.2f}".format(x) for x in t_percentiles[f_count]]))


'''

Overview of major numpy functions

'''

import numpy as np

#create list and convert to array
lst = [1,2,3,4]
arr = np.array(lst)

# Create ARRAY 0-9.5 in step sizes of 0.5
a = np.arange(0, 10, 0.5)

# create 2d array of ZEROS, row x column
b = np.zeros((5,5))

# create 2d array of ONES, row x column
c = np.ones((2,4))

# Create RANDOM  integer between 0 and 100
d = np.random.randint(0, 100)

# Create array of random integers
e = np.random.randint(0,100,(5,5))

# Create LINEARLY spaced array from 0 to 10 with 6 numbers
f = np.linspace(0, 10, 100)

# Set seed in random number generator so we get consistent random numbers
np.random.seed(101)
g = np.random.randint(0,100,10)

# Find max of array
test = np.random.randint(0,100,10)
max_test = test.max()
min_test = test.min()
mean_test = test.mean()

# Return index location of max/min
max_index = test.argmax()
min_index = test.argmin()

# Indexes in array
arr1 = np.arange(0,100).reshape(10,10) # create 10x10 array
print(arr1[5,2]) #print a single value
print(arr1[:,1]) #print all values from a column
print(arr1[5,:]) #print all values from a row

# Masking
print(arr1>40) #return matrix of boolean True/False


import numpy as np
import time

degToRad = np.pi/180
radToDeg = 180/np.pi

def currentTime():
    return time.time()

def intervalMessage(initialTime, message):
    t1 = currentTime()
    print(message, round(t1-initialTime, 3))
    return t1

def latLonRToXYZ(lat, lon, r):
    x = r*np.cos(lat*degToRad)*np.cos(lon*degToRad)
    y = r*np.cos(lat*degToRad)*np.sin(lon*degToRad)
    z = r*np.cos((lat-90)*degToRad)
    return x, y, z

def xyzToLatLonR(x, y, z):
    r = np.sqrt(x**2 + y**2 + z**2)
    lat = np.arcsin(z/r)*radToDeg
    lon = np.arctan2(y,x)*radToDeg
    return lat, lon, r

def randomPointOnSphere(r=1, grid=None):
    lat = np.random.random()*180-90
    lon = np.random.random()*360-180
    if grid != None:
        lat -= lat%grid[0]
        lon -= lon%grid[1]
        lat += grid[0]/2.
        lon += grid[1]/2.
    return (lat, lon, r)

def haversine(a):
    return np.sin(a/2.)**2

def normArray(x: np.array):
    return x/arrayMagnitude(x)

def angleBetweenBearings(b1, b2):
    return min((b1-b2+360)%360, (b2-b1+360)%360)

def bearingBetween(lat1, lon1, lat2, lon2):
    lon1r = lon1 * degToRad
    lat1r = lat1 * degToRad
    lon2r = lon2 * degToRad
    lat2r = lat2 * degToRad
    dlonr = lon2r - lon1r
    theta = np.arctan2(np.sin(dlonr)*np.cos(lat2r), np.cos(lat1r)*np.sin(lat2r)-np.sin(lat1r)*np.cos(lat2r)*np.cos(dlonr))
    return (theta*radToDeg + 360) % 360

def rasterize(lat, lon, grid):
    # convert lat and lon into grid coords
    adjlat = lat+90
    adjlon = lon+180
    i = np.clip(int(adjlat/grid[0]), 0, int(180/grid[0])-1)
    j = np.clip(int(adjlon/grid[1]), 0, int(360/grid[1])-1)
    return i, j

def gridPointToLatLon(i, j, rows, cols):
    # cell centers, not edges
    dlat = (180-2*180/rows)/rows
    dlon = (360-2*180/cols)/cols
    lat = (rows - i)*dlat - 90 + 180./rows
    lon = j*dlon - 180 + 360./rows
    return lat, lon

def lonGridSpacing(i, Lat, Lon, R):
    dlon = (Lon[0,1] - Lon[0,0])*degToRad
    r = R*np.cos(Lat[i,0]*degToRad)
    return r*dlon

def latGridSpacing(Lat, R):
    dlat = (Lat[0,0] - Lat[1,0])*degToRad
    return R*dlat

def areaProportions(Lat, Lon, R):
    shape = Lat.shape
    dlat = latGridSpacing(Lat, R)
    arr = np.array([dlat*lonGridSpacing(i, Lat, Lon, R) for i in range(shape[0])])
    return np.tile(arr.reshape(-1,1), (1,shape[1]))

def arrayMagnitude(x: np.array):
    return np.sqrt(np.dot(x, x))

def bearingToQuadrant(bearing):
    # Convention is bearing 0 = quadrant 0, clockwise
    if bearing <= 45: return 0
    if bearing <= 135: return 1
    if bearing <= 225: return 2
    if bearing <= 315: return 3
    if bearing <= 360: return 0

def bearingToOctant(bearing):
    # Convention is bearing 0 = octant 0, clockwise
    if bearing <= 22.5: return 0
    if bearing <= 67.5: return 1
    if bearing <= 112.5: return 2
    if bearing <= 157.5: return 3
    if bearing <= 202.5: return 4
    if bearing <= 247.5: return 5
    if bearing <= 292.5: return 6
    if bearing <= 337.5: return 7
    if bearing <= 360: return 0

def haversineDist(lat1, lon1, lat2, lon2, r):
    lon1r = lon1 * degToRad
    lat1r = lat1 * degToRad
    lon2r = lon2 * degToRad
    lat2r = lat2 * degToRad
    dlatr = lat1r - lat2r
    dlonr = lon1r - lon2r
    a = haversine(dlatr) + np.cos(lat1r)*np.cos(lat2r)*haversine(dlonr)
    c = 2*np.arctan2(np.sqrt(a), np.sqrt(1-a))
    dist = r*c
    return dist

def gCircleDist(xyz1, xyz2):
    mag1 = arrayMagnitude(xyz1)
    mag2 = arrayMagnitude(xyz2)
    costheta = np.dot(xyz1, xyz2)/(mag1*mag2)
    costheta = np.clip(costheta, -1, 1)
    return mag1*np.arccos(costheta)


"""

https://en.wikipedia.org/wiki/Orthogonal_coordinates
https://en.wikipedia.org/wiki/Covariance_and_contravariance_of_vectors
https://en.wikipedia.org/wiki/Log-polar_coordinates
https://en.wikipedia.org/wiki/Parabolic_coordinates
https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant
https://archive.lib.msu.edu/crcmath/math/math/p/p055.htm
https://en.wikiversity.org/wiki/PlanetPhysics/Transformation_Between_Cartesian_Coordinates_and_Polar_Coordinates
https://en.wikipedia.org/wiki/Elliptic_coordinate_system
https://en.wikipedia.org/wiki/Bipolar_coordinates
https://www.researchgate.net/publication/309803947_A_Note_on_Elliptic_Coordinates

"""

from sympy import *
from sympy.abc import *

"""

The matrix for the coordinate transform are not unique, we can parameterize it in some other way.
Depending on the parameterization, the determinants are different even though the matrix transform gives the same formula at the end.
If we want consistency, we like the transformation to and from to preserve the determinant, though the determinant can be 0 for the most part,
since in a different coordinate system orientation/volumes are usually not preserved.

These coordinate transformation from one coordinate to another are not constant values, so writing it as a matrix doesn't have advantages.
If the values were constant we can think of them as a transformation in that coordinate system rather than going to a different coordinate system.

Cartesian <-> Polar
r = sqrt(x^2 + y^2)
t = atan2(y, x)

x = r*cos(t)
y = r*sin(t)

Cartesian <-> Log Polar
p = log(sqrt(x^2 + y^2))
t = atan2(y, x)

x = e^p*cos(t)
y = e^p*sin(t)

Cartesian <-> Parabolic
x = s*t
y = 0.5*(s*s - t*t)

s = sqrt(sqrt(x^2 + y^2) + y)
t = sqrt(sqrt(x^2 + y^2) - y)

Cartesian <-> Elliptic

Let c be a fixed constant
u >= 0
0 <= v <= 2*pi
x = c*cosh(u)*sinh(v)
y = c*sinh(u)*sin(v)

B = x^2 + y^2 - c^2
d = sqrt(B^2 + 4*c^2*y^2)
e = 2*c^2
p = (-B+d)/e
q = (-B-d)/e
u = 0.5*ln(1 - 2*q + 2*sqrt(q^2 - q))

v0 = asin(sqrt(p))
v = v0        [x >= 0, y >= 0]
v = pi - v0   [x < 0, y >= 0]
v = pi + v0   [x <= 0, y < 0]
v = 2*pi - v0 [x > 0, y < 0]

"""
def test_coordinate_matrix():
    print("Coordinate Transformation Matrix\n")

    l = 0.5*sqrt(x**2 + y**2)
    t = 0.5*atan(y/x)
    M = Matrix([[l/x, l/y], [t/x, t/y]])
    v = Matrix([x, y])
    print("Cartesian to Polar det(M)={}".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

    M = Matrix([[cos(theta), 0], [sin(theta), 0]])
    v = Matrix([r, t])
    print("Polar to Cartesian det(M)={}.".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

    l = log(sqrt(x**2 + y**2))/2
    M = Matrix([[l/x, l/y], [t/x, t/y]])
    v = Matrix([x, y])
    print("Cartesian to Log Polar det(M)={}".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

    M = Matrix([[exp(phi)*cos(theta)/phi, 0], [exp(phi)*sin(theta)/phi, 0]])
    v = Matrix([phi, theta])
    print("Log Polar to Cartesian det(M)={}".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

    a = 0.5*sigma*tau
    b = 0.25*(sigma**2 - tau**2)
    M = Matrix([[a/sigma, a/tau], [b/sigma, b/tau]])
    v = Matrix([sigma, tau])
    print("Parabolic to Cartesian det(M)={}".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

    print("Cartesian to Parabolic det(M)={}".format(M.det()))
    a = sqrt(x**2 + y**2)
    b = 0.5*sqrt(a + y)
    c = 0.5*sqrt(a - y)
    M = Matrix([[b/x, b/y], [c/x, c/y]])
    v = Matrix([x, y])
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("\n")

"""

Given a coordinate system, we want to generate a transformation matrix that transforms a covariant vector
The general matrix can be generated using the following method:

Given a coordinate system of N variables, we want to transform it into another system of N variables, hence
we will have a NxN matrix we shall call A

Aij = partial derivative of the ith variable in the second coordinate system with respect to the jth derivative in the first derivative

For example, for polar to cartesian we have two variables:
(r, theta) -> (x, y)

A11 = (x, r)
A12 = (x, theta)
A21 = (y, r)
A21 = (y, theta)

We calculate the partial derivative of those variables and fill the matrix, in general they are also not constant values but some function

If they were just constant values, the matrix transform will be just be inverse of that matrix.
This matrix is also known as the jacobian matrix and its determinant is known as the jacobian determinant.

"""

def test_contravariant_matrix():
    print("Contravariant Vector Transformation Matrix\n")

    l = sqrt(x**2 + y**2)
    M = Matrix([[x/l, y/l], [-y/l**2, x/l**2]])
    v = Matrix([x, y])
    print("Cartesian to Polar det(M)={}".format(M.det()))
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("")

    print("Polar to Cartesian det(M)={}".format(M.det()))
    M = Matrix([[cos(theta), -r*sin(theta)], [sin(theta), r*cos(theta)]])
    v = Matrix([r, theta])
    pprint(simplify(M))
    pprint(simplify(M@v))
    print("")

test_coordinate_matrix()
test_contravariant_matrix()


# Load modules
from __future__ import print_function
import os
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt

#Read dataset into a pandas.DataFrame
beer_df = pd.read_csv('datasets/quarterly-beer-production-in-aus-March 1956-June 1994.csv')

#Display shape of the dataset
print('Shape of the dataframe:', beer_df.shape)
beer_df.head()

#Rename the 2nd column
beer_df.rename(columns={'Quarterly beer production in Australia: megalitres. March 1956 ? June 1994':
                        'Beer_Prod'
                       },
               inplace=True
              )

#Remove missing values
missing = (pd.isnull(beer_df['Quarter'])) | (pd.isnull(beer_df['Beer_Prod']))
print('Number of rows with at least one missing values:', missing.sum())
beer_df = beer_df.loc[~missing, :]
print('Shape after removing missing values:', beer_df.shape)

# Function for Sigle exponential smoothing
def double_exp_smoothing(x, alpha, beta):
    yhat = [x[0]] # first value is same as series
    for t in range(1, len(x)):
        if t==1:
            F, T= x[0], x[1] - x[0]
        F_n_1, F = F, alpha*x[t] + (1-alpha)*(F+T)
        T=beta*(F-F_n_1)+(1-beta)*T
        yhat.append(F+T)
    return yhat

beer_df['DEF'] = double_exp_smoothing(beer_df['Beer_Prod'], 0.4, 0.7)

### Plot Single Exponential Smoothing forecasted value
fig = plt.figure(figsize=(5.5, 5.5))
ax = fig.add_subplot(2,1,1)
beer_df['Beer_Prod'].plot(ax=ax)
ax.set_title('Beer Production')
ax = fig.add_subplot(2,1,2)
beer_df['DEF'].plot(ax=ax, color='r')
ax.set_title('Double Smoothing Forecast')
plt.savefig('plots/ch2/B07887_03_14.png', format='png', dpi=300)

# Single vs Double Forecast value
# Function for Sigle exponential smoothing
def single_exp_smoothing(x, alpha):
    F = [x[0]] # first value is same as series
    for t in range(1, len(x)):
        F.append(alpha * x[t] + (1 - alpha) * F[t-1])
    return F
beer_df['Single_Exponential_Forecast'] = single_exp_smoothing(beer_df['Beer_Prod'], 0.4)

### Plot Single Exponential Smoothing forecasted value
f, axarr = plt.subplots(2, sharex=True)
f.set_size_inches(5.5, 5.5)
beer_df['Beer_Prod'].iloc[:153].plot(color='b', linestyle = '-', ax=axarr[0])
beer_df['DEF'].iloc[:153].plot(color='r', linestyle = '--', ax=axarr[0])
axarr[0].set_title('Actual Vs Double Smoothing Forecasting')

beer_df['Beer_Prod'].iloc[:153].plot(color='b', linestyle = '-', ax=axarr[1])
beer_df['Single_Exponential_Forecast'].iloc[:153].plot(color='r', linestyle = '--', ax=axarr[1])
axarr[1].set_title('Actual Vs Single Smoothing Forecasting')

# Plot single and double exponential smoothing
fig = plt.figure(figsize=(5.5, 5.5))
ax = fig.add_subplot(2,1,1)
beer_df['Single_Exponential_Forecast'].plot(ax=ax)
ax.set_title('Single Exponential Smoothing')
ax = fig.add_subplot(2,1,2)
beer_df['DEF'].plot(ax=ax, color='r')
ax.set_title('Double Smoothing Forecast')
plt.savefig('plots/ch2/B07887_03_14.png', format='png', dpi=300)

time series regression/autocorelation, mov avg etc/doubleExponentialSmoothing.py


'''
Example code showing different options for fits to exponential data
'''

import numpy as np
from matplotlib import pyplot as plt
import scipy.stats
from scipy.optimize import curve_fit

plt.ion()
plt.clf()

'''
Our standard curve-fitting tools always require you to write a function
containing your model.  In scipy.optimize.curve_fit, the function
specifically takes the x-value as its first argument, and however many
free parameters as the 2nd-thru-nth arguments.

In this snippet, we will compare the two common approaches (a) "take
the log of the data and fit a line" vs (b) "actually fit an
exponential function to the data".

In this case I am specifying a function of the form 10^x rather than
e^x and will do everything in log-10.   The parameter called "wid"
in the exponential model is the same as the parameter called "a" in
the linear model.
'''

def exp_model(x,norm,slope):
    return norm*np.power(10,x*slope)

def linear_model(x,lognorm,slope):
    return x*slope + lognorm

'''
Let's suppose that the following theory were true, and its truth led
to the existence of some data.  For the sake of writing a little
tutorial here, I will *generate* fake data in this code snippet,
but if you had done a real experiment the data would have been
laboriously obtained from an experimental apparatus.

The (true) theory is given in "exact_y_values".
The (fake) data is "exp_y_values".
'''

true_norm = 1000
true_slope = -0.025
x_values =  np.array([i for i in np.arange(0,40,2)])
exact_y_values = np.array([exp_model(x,true_norm,true_slope) for x in x_values])

'''
Tricky little block:
If this is False, we will look at "fresh" fake data every time
we run the code.  Do this if you want to see the full scope of possible
behaviors of the code.
If this is True, there is an immutable fixed dataset which you
can look at over and over.  Do this if you want to debug the code while
looking a particular (if randomly-chosen) realization of the data.
'''
if False:
    np.random.seed(123456789)

'''
To illustrate the point I'm trying to make, I will choose an error bar
that's constant over the range of the data.  Imagine you have a
voltmeter with 100-mV noise which is present whether you are measuring
10^4 mV or 100 mV.  The fractional error is larger on the smaller
voltages.

(This will fail sometimes because the noise will make the "data"
negative, and you can't take the log of a negative number.  If it
fails, run it again ... or think about what to modify to handle that
situation.)
'''

y_err = np.array([100.0 for x in x_values])    # this is "pythonic"
exp_y_values = np.random.normal(exact_y_values,y_err) # this is "numpy-ic"

if any([x <= 0 for x in exp_y_values]):
    raise ValueError("This run happened to have some negative data.       It's not going to behave well when we take logs.  Instead of writing      code to clean it up (that's your job) I have opted to exit.  Run again     and it'll probably work.")

f, (ax1,ax2,ax3,ax4) = plt.subplots(4,1,figsize=(8,8))
ax1.set_title("Exponential data")

'''
EXAMPLE 1: Take logs of data and fit a straight line, IGNORING THE
ERROR BARS.  This is th "default" approach you will see in many
student writeups involving exponential data.  I want to emphasize
that it's not a great idea.  This fit will work, BUT ...
'''
logdata = np.log10(exp_y_values)
logtruth = np.log10(exact_y_values)

'''
First, how do you even put an error bar on the log10 of a value?  In
the limit of small errror bars, this is how, and it's about as good
as you can do in practice.  When the error bars are a large fraction
of the data value, you properly speaking would need *asymmetric* error
bars, and that's a whole other thing.  (See the README.md for why.)
'''
logerrorbars = (y_err/exp_y_values)/np.log(10)

'''
But we're not going to tell scipy.curve_fit about those error bars.
We'll use the defaults.
'''

popt, pcov = curve_fit(linear_model, x_values, logdata)

uncertainties = [np.sqrt(pcov[i][i]) for i in range(len(pcov))]

for i,pname in enumerate(["lognorm","slope"]):
    print("%d %s = %f +/- %f"%(i,pname,popt[i],uncertainties[i]))
ex1slope = popt[1]
ex1err = uncertainties[1]

best_fit = np.array([linear_model(x,*popt) for x in x_values])

ndof = len(x_values) - 2
chi2 = sum(((best_fit - logdata)/logerrorbars)**2)
pchi2 = scipy.stats.chi2.cdf(chi2,ndof)

'''
What you will probably see is that the fit has converged, and the
chi2 is *absurdly* small, yielding a P(chi2) that's "too good to be
true" and unlikely to have arisen from real fluctuations.
'''

ax1.errorbar(x_values,logdata,yerr=logerrorbars,marker="*",
                 linestyle=" ",label="data")
ax1.plot(x_values,logtruth,label="truth",linestyle=":")
ax1.plot(x_values,best_fit,label="best_fit")
ax1.text(0.3,0.9,"Example 1: straight-line fit ignoring errors",
             transform=ax1.transAxes)
ax1.text(0.3,0.1,"chi2=%f, P=%f"%(chi2,pchi2),transform=ax1.transAxes)
ax1.legend()

'''
EXAMPLE 2: Same as above, but this time we tell the fitter to use the
(logged) error bars.

'''

popt, pcov = curve_fit(linear_model, x_values, logdata,
                           sigma=logerrorbars,absolute_sigma=True)
uncertainties = [np.sqrt(pcov[i][i]) for i in range(len(pcov))]

ex2slope = popt[1]
ex2err = uncertainties[1]

for i,pname in enumerate(["lognorm","slope"]):
    print("%d %s = %f +/- %f"%(i,pname,popt[i],uncertainties[i]))

best_fit = np.array([linear_model(x,*popt) for x in x_values])

chi2 = sum(((best_fit - logdata)/logerrorbars)**2)
pchi2 = scipy.stats.chi2.cdf(chi2,ndof)

ax2.errorbar(x_values,logdata,yerr=logerrorbars,marker="*",
                 linestyle=" ",label="data")
ax2.plot(x_values,logtruth,label="truth",linestyle=":")
ax2.plot(x_values,best_fit,label="best_fit")
ax2.text(0.3,0.9,"Example 2: straight-line fit using errors",
             transform=ax2.transAxes)

ax2.text(0.3,0.1,"chi2=%f, P=%f"%(chi2,pchi2),
             transform=ax2.transAxes)

ax2.legend()

'''
EXAMPLE 3: Fit an exponential curve using the real model

We're not going to take any logs, we're going to fit the real model
to the real data (data and noise in mV or whatever), and use a real
log axis to plot it.
'''

popt, pcov = curve_fit(exp_model, x_values, exp_y_values,p0=[1e4,0.05],sigma=y_err,absolute_sigma=True)

uncertainties = [np.sqrt(pcov[i][i]) for i in range(len(pcov))]
ex3slope = popt[1]
ex3err = uncertainties[1]

for i,pname in enumerate(["norm","slope"]):
    print("%d %s = %f +/- %f"%(i,pname,popt[i],uncertainties[i]))

best_fit = np.array([exp_model(x,*popt) for x in x_values])

chi2 = sum(((best_fit - exp_y_values)/y_err)**2)
pchi2 = scipy.stats.chi2.cdf(chi2,ndof)

ax3.errorbar(x_values,exp_y_values,yerr=y_err,marker="*",linestyle=" ",label="data")
ax3.plot(x_values,exact_y_values,label="truth",linestyle=":")

ax3.plot(x_values,best_fit,label="best_fit")
ax3.set_yscale('log')
ax3.text(0.3,0.9,"Example 3: exponential fit",transform=ax3.transAxes)

ax3.text(0.3,0.1,"chi2=%f, P=%f"%(chi2,pchi2),transform=ax3.transAxes)

ax3.legend()

'''
Here we summarize the three results.   You should see that the no-error
-bars fit is Pretty Bad, the other two are Pretty Good, but the real
non-straight-line  model fit is systematically a bit better.   The
linearized fit works OK in many cases but is notably bad at handling
outliers.
'''

ax4.errorbar([1,],[ex1slope,],yerr=[ex1err,],
                 label="line fit, no err",marker='o')
ax4.errorbar([2,],[ex2slope,],yerr=[ex2err,],
                 label="line fit w/ err",marker='o')
ax4.errorbar([3,],[ex3slope,],yerr=[ex3err,],
                 label="raw data fit",marker='o')
ax4.plot([0,4],[true_slope,true_slope],
             linestyle=":",color='red')
ax4.legend()


import torch as th
import torch.nn as nn
import numpy as np
'''
prediction:
gradient computation:
loss computation:
parameter updates:
'''

'''Use loss and optimizer with torch'''

# 1. Design model(input_size, output_size, forward pass)
# 2. Construct Loss and optimizer
# 3. Training loop
#   - forward pass:
#   - backward pass:
#   - update weights

# f = w*x
# f = 2.x

X = th.tensor([1,2,3,4],dtype=th.float32)
Y = th.tensor([2,4,6,8],dtype=th.float32)

w = th.tensor(0.0,dtype=th.float32,requires_grad=True)

def forward(x):
    return w*x

# def loss(y,yp):
#     return ((y-yp)**2).mean()

# def gradient(x,y,yp):
#     #MSE = 1/N*(wx-y)**2
#     #dJ/dw = 1/N*2x*(w*x - y)
#     return np.dot(2*x, yp-y).mean()

print(f'prediction before training f(5)= {forward(5):.3f}')

# training
lr = 0.01
n_iters = 100

loss = nn.MSELoss()
optimizer = th.optim.SGD([w],lr=lr)

for epoch in range(n_iters):
    # prediction
    y_pred = forward(X)

    # loss
    l = loss(Y,y_pred)

    #gradient
    l.backward() # dl/dw

    # dw = gradient(X,Y,y_pred)
    # before optim step, gradients are already there wrt loss function in w.grad aha!
    optimizer.step()

    # Update weights without gradient tracking
    with th.no_grad():
        w -= lr * w.grad

    # zero gradients
    w.grad.zero_()

    if epoch%10==0:
        print(f'epoch {epoch+1}: w: {w:.8f}, loss = {l:.8f}')

print(f'Prediction after training: f(5) = {forward(5):.3f}')


# coding: utf-8

# In[15]:

'''
Statistical Computing for Scientists and Engineers
Homework 2
Fall 2018
University of Notre Dame
'''
import numpy as np
import matplotlib.pyplot as plt

#exact solution
I = 0.25*(pow(4,4)-pow(3,4))+(5*4*np.sin(4)+5*np.cos(4))-(5*3*np.sin(3)+5*np.cos(3))
print (I)

#-- Part (a)
xa= np.random.uniform(3,4,10000)
fa = pow(xa,3)+5*xa*np.cos(xa)
sol_a = np.mean(fa)
print (sol_a)
plt.scatter(xa,fa)
plt.title("N=10,000 samples")
plt.xlabel("x")
plt.ylabel("f=x^3+5*x*cos(x)")
plt.show()

# In[38]:

#-- Part (b)

Nb = np.array([0]*991)
Ib = np.array([0.0]*991)
Errb = np.array([0.00]*991)

for i in range(0,991):
    Nb[i] = i+10
for i in range(0,991):
    xb= np.random.uniform(3,4,Nb[i])
    Ib[i] = np.mean(pow(xb,3)+5*xb*np.cos(xb))
    Errb[i] = (Ib[i]-I)/I

plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.plot(Nb, Ib , alpha=0.7)
plt.xlabel("N")
plt.ylabel("I")

plt.subplot(1,2,2)
plt.plot(Nb, Errb , alpha=0.7)
plt.xlabel("N")
plt.ylabel("Error")
plt.show()

# In[40]:

#-- Part (c)

# use this to plot histograms. Plot 4 histograms.
MCc1 = np.array([0.0]*10000)
MCc2 = np.array([0.0]*10000)
MCc3 = np.array([0.0]*10000)
MCc4 = np.array([0.0]*10000)

for i in range(0,10000):
    xc1= np.random.uniform(3,4,100)
    MCc1[i] = np.mean(pow(xc1,3)+5*xc1*np.cos(xc1))
    xc2= np.random.uniform(3,4,1000)
    MCc2[i] = np.mean(pow(xc2,3)+5*xc2*np.cos(xc2))
    xc3= np.random.uniform(3,4,10000)
    MCc3[i] = np.mean(pow(xc3,3)+5*xc3*np.cos(xc3))
    xc4= np.random.uniform(3,4,100000)
    MCc4[i] = np.mean(pow(xc4,3)+5*xc4*np.cos(xc4))
    print (i)

# In[43]:

plt.figure(figsize=(12,12))
plt.subplot(2,2,1)
plt.title("N=100")
plt.hist(MCc1, bins=30)
plt.xlabel("MC solution")

plt.subplot(2,2,2)
plt.title("N=1000")
plt.hist(MCc2, bins=30)
plt.xlabel("MC solution")

plt.subplot(2,2,3)
plt.title("N=10000")
plt.hist(MCc3, bins=30)
plt.xlabel("MC solution")

plt.subplot(2,2,4)
plt.title("N=100000")
plt.hist(MCc4, bins=30)
plt.xlabel("MC solution")

plt.show()

##############################################################
# INSERT CODE ABOVE
##############################################################


import numpy as np

from mygrad.operation_base import Operation
from mygrad.tensor_base import Tensor

class Sigmoid(Operation):
    def __call__(self, a):
        self.variables = (a,)
        x = np.asarray(-1.0 * a.data)
        np.exp(x, out=x)
        x += 1
        np.reciprocal(x, out=x)
        self.sigmoid = x
        return self.sigmoid

    def backward_var(self, grad, index, **kwargs):
        return grad * self.sigmoid * (1.0 - self.sigmoid)

def sigmoid(x, constant=False):
    """ Applies the sigmoid activation function::

      f(x) = 1 / (1 + exp(-x))

    Parameters
    ----------
    x : array_like
        sigmoid is applied element-wise on ``x``.

    constant : bool, optional(default=False)
        If ``True``, the returned tensor is a constant (it
        does not back-propagate a gradient)

    Returns
    -------
    mygrad.Tensor

    Examples
    --------
    >>> import mygrad as mg
    >>> from mygrad.nnet import sigmoid
    >>> x = mg.linspace(-5, 5, 10)
    >>> sigmoid(x)
    Tensor([0.00669285, 0.02005754, 0.0585369 , 0.1588691 , 0.36457644,
        0.63542356, 0.8411309 , 0.9414631 , 0.97994246, 0.99330715])"""
    return Tensor._op(Sigmoid, x, constant=constant)


# # Assignment 2: Enhancement and Filtering
# ## SCC0251.2020.1 - Image Processing
# ### Prof. Dr. Moacir Ponti
# ### 10284952 - Vitor Gratiere Torres
# https://github.com/vitorgt/SCC0251

# Imports
import numpy as np
import imageio

# Formulae described on assignment's pdf

def scale(image, c=0, d=255):
    a = np.min(image)
    b = np.max(image)
    return (image-a)*((d-c)/(b-a))+c

def gaussian(x, s):  # Gaussian kernel
    return np.exp((-(x**2))/(2*(s**2)))/(2*np.pi*(s**2))

def bilateralFilter(image):

    def e(x, y):  # Eclidean distance
        return np.sqrt(x**2 + y**2)

    n = int(input())
    ss = float(input())
    sr = float(input())

    # padding lenght
    pad = int(np.floor(n/2))

    # original image dimensions
    ix, iy = image.shape

    # final image
    ans = np.array([[0.0 for y in range(0, iy)] for x in range(0, ix)])

    # range to create spatial gaussian component
    # it creates ranges like [-2, -1, 0, 1, 2] for n == 5
    nrng = range(int(np.ceil(-n/2)), int(np.ceil(n/2)))

    # spatial gaussian component
    sgc = np.array([[gaussian(e(x, y), ss) for y in nrng] for x in nrng])

    # padding
    image = np.pad(image, pad, 'constant').astype(np.float)

    # for each index in image avoiding the pad
    for i in range(pad, ix+1):
        for j in range(pad, iy+1):

            # range gaussian component for each neighborhood
            # it subtracts the middle value from each valeu in the neighborhood
            # and applies it to the gaussian, and creates a new matrix, rgc
            rgc = np.array([[gaussian(image[x, y]-image[i, j], sr)
                             for y in range(j-pad, j+pad+1)]
                            for x in range(i-pad, i+pad+1)])
            w = np.multiply(rgc, sgc)
            # multiplies windowed image and w, sums each point
            # than devides by the sum of w
            ans[i-pad, j-pad] = np.sum(np.multiply(image[i-pad:i+pad+1,
                                                         j-pad:j+pad+1],
                                                   w))/np.sum(w)
    return ans

def unsharpMask(image):
    c = float(input())
    if(c > 1):
        raise ValueError("C must be <= 1")

    k = int(input())
    if(k == 1):
        k = np.array([[0, -1, 0],
                      [-1, 4, -1],
                      [0, -1, 0]])
    elif(k == 2):
        k = np.array([[-1, -1, -1],
                      [-1, 8, -1],
                      [-1, -1, -1]])
    else:
        raise ValueError("K must be 1 or 2")

    # original image dimensions
    ix, iy = image.shape

    # final image
    ans = np.array([[0.0 for y in range(0, iy)] for x in range(0, ix)])

    # padding
    image = np.pad(image, 1, 'constant').astype(np.float)

    # for each index in image avoiding the pad
    for i in range(1, ix+1):
        for j in range(1, iy+1):
            ans[i-1, j-1] = np.sum(np.multiply(image[i-1:i+2, j-1:j+2], k))

    ans = scale(ans)
    ans = (c * ans) + image[1:-1, 1:-1]
    return scale(ans)

def vignette(image):
    row = float(input())
    col = float(input())

    # original image dimensions
    ix, iy = image.shape

    # it creates ranges like [-2, -1, 0, 1, 2] and [-1, 0, 1, 2]
    xrng = range(int(np.ceil((-ix+1)/2)), int(np.ceil((ix+1)/2)))
    yrng = range(int(np.ceil((-iy+1)/2)), int(np.ceil((iy+1)/2)))

    # creates the gaussian arrays
    cgc = np.transpose(np.vstack(np.array([gaussian(y, col) for y in yrng])))
    rgc = np.vstack(np.array([gaussian(x, row) for x in xrng]))

    # matrix product
    vig = np.matmul(rgc, cgc)

    return scale(np.multiply(image, vig))

# Root Squared Error function
def rse(r, m):
    return np.sqrt(np.sum((m.astype(np.float)-r.astype(np.float))**2))

# Inputs, following assignment's pdf requested sequence
r = imageio.imread(str(input()).rstrip()).astype(np.uint8)
m = int(input())
s = (False, True)[int(input())]

# Dictionary for functions
f = {1: bilateralFilter, 2: unsharpMask, 3: vignette}

# Appling 'm' transformation to original image 'r'
# Storing back on 'm'
m = f[m](r)

# Calculating RSE and printing
print("{:.4f}".format(rse(r, m)))

# Saving
if s:
    imageio.imwrite("images/output_img.png", m)

# Plotting
# plt.figure(figsize=(20, 20))

# plt.subplot(121)
# plt.imshow(r, cmap="gray")
# plt.axis('off')

# plt.subplot(122)
# plt.imshow(m.astype(np.uint8), cmap="gray")
# plt.axis('off')


import sympy

x1, x2 = sympy.symbols('x1 x2')
f = 100*(x2 - x1**2)**2 + (1-x1)**2

df_dx1 = sympy.diff(f,x1)
df_dx2 = sympy.diff(f,x2)
H = sympy.hessian(f, (x1, x2))

xs = sympy.solve([df_dx1, df_dx2], [x1, x2])

H_xs = H.subs([(x1,xs[0][0]), (x2,xs[0][1])])
lambda_xs = H_xs.eigenvals()

count = 0
for i in lambda_xs.keys():
    if i.evalf() <= 0:
        count += 1

if count == 0:
    print 'Local minima'
elif count == len(lambda_xs.keys()):
    print 'Lacal maxima'
else:
    print 'Saddle point'


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import math

def sin_2x(x):
    return np.sin(2 * x)

def square_2x(x):
    y = sin_2x(x)
    y = np.where(y >= 0, 1, -1)
    return y

def data_gen(pattern, start=0, end=2 * np.pi, step_size=0.1, noise=False):
    X = np.arange(start, end, step_size)
    if pattern == "sin":
        y = sin_2x(X)
    elif pattern == "square":
        y = square_2x(X)
    if noise:
        X += np.random.normal(0, np.sqrt(0.1))
    return X, y

# Guassian RBFs
# implement on the hidden layers
def phi(x, mu, sigma):
    return np.exp(- (x - mu) ** 2 / (2 * sigma ** 2))

def init_nodes(nnodes):
    # mu_list = np.random.uniform(0, 2*np.pi, nnodes)
    mu_list = [(i + 0.5) * 2 * np.pi / nnodes for i in range(nnodes)]
    # sigma = 2 * np.pi / nnodes
    sigma = 0.7
    return sigma, mu_list

def init_weights(nnodes):
    W = np.random.uniform(-1.0, 1.0, nnodes)
    # W = np.random.rand(nnodes, )
    return W

def phi_matrix(nnodes, x_train):
    # init_phi = np.zeros((nnodes, nfeatures))
    sigma, mu = init_nodes(nnodes)
    # phi_x = np.array([[phi(x, mu[i], sigma) for i in range(nnodes)]
    #                   for x in x_train])
    phi_x = np.array([[phi(x_train[j], mu[i], sigma) for i in range(nnodes)]
                      for j in range(len(x_train))])
    return phi_x

def abs_residual_error(y, pred):
    return np.mean(abs(y - pred))

def rbf_ls(x_train, y_train, x_test, y_test, nnodes=5, pattern="sin"):
    phi_x = phi_matrix(nnodes, x_train)
    w = np.linalg.solve(np.dot(phi_x.T, phi_x), np.dot(phi_x.T, y_train))
    phi_test = phi_matrix(nnodes, x_test)
    y_pred = np.dot(phi_test, w)
    if pattern != "sin":
        y_pred = np.where(y_pred >= 0, 1, -1)

    abs_re = abs_residual_error(y_test, y_pred)
    return y_pred, abs_re

def error_compute(y, y_pred):
    return np.mean(np.square(y - y_pred))

# batch mode delta rule
def rbf_delta1(x_train, y_train, x_test, y_test,
               nnodes=5, learning_rate=0.0001, pattern="sin"):
    w = init_weights(nnodes)
    n = x_train.shape[0]
    for i in range(n):
        phi_x = phi_matrix(nnodes, x_train[i:i + 1])
        e = y_train[i] - np.dot(phi_x, w)
        dw = learning_rate * np.dot(e, phi_x)
        w += dw
    phi_test = phi_matrix(nnodes, x_test)
    y_pred = np.dot(phi_test, w)
    mse = error_compute(y_test, y_pred)
    return y_pred, mse

# batch mode delta rule
def rbf_delta(x_train, y_train, x_test, y_test,
              nnodes=5, epochs=500, learning_rate=0.001, pattern="sin"):
    w = init_weights(nnodes)
    phi_x = phi_matrix(nnodes, x_train)
    # k = 0
    for epoch in range(epochs):

        # error = y_train[k] - np.dot(phi_x[k], w)
        # k = (k+1) % x_train.shape[0]
        # dw = learning_rate * error * phi_x[k]
        error = y_train - np.dot(phi_x, w)
        dw = learning_rate * np.dot(error, phi_x)
        # print(dw.shape, error.shape, phi_x.shape, phi_x.T.shape)
        w += dw
    phi_test = phi_matrix(nnodes, x_test)
    y_pred = np.dot(phi_test, w)
    if pattern != "sin":
        y_pred = np.where(y_pred >= 0, 1, -1)
    mse = error_compute(y_test, y_pred)
    return y_pred, mse

def fitting_curve(y, y_pred, title):
    plt.plot(y, label="Truth")
    plt.plot(y_pred, label="Predictions")
    plt.legend()
    plt.title(f"Fitting curve of {title}")

def plot_error(errors, title):
    plt.plot(errors)
    plt.title(f'Learning error curve for the {title}')
    plt.xlabel = ("nnodes")
    plt.ylabel("Error")
    plt.show()

# change by nnodes
def rbf_nnodes(nnodes_list, x_train, y_train, x_test, y_test, pattern="sin", rbf="lse"):
    plt.plot(y_test, label="Truth", lw=3.5)
    error_lists = []
    if rbf == "lse":
        for nnodes in nnodes_list:
            pred, error = rbf_ls(x_train, y_train, x_test, y_test, nnodes=nnodes, pattern=pattern)
            plt.plot(pred, label=f"nnodes={nnodes}", ls="--")
            error_lists.append(error)
        plt.title("Fitting curve of Batch RBF")
        plt.legend()
        plt.show()
    else:
        for nnodes in nnodes_list:
            pred, error = rbf_delta(x_train, y_train, x_test, y_test, nnodes=nnodes, pattern=pattern)
            plt.plot(pred, label=f"nnodes={nnodes}", ls="--")
            error_lists.append(error)
        plt.title("Fitting curve of Delta on-line RBF")
        plt.legend()
        plt.show()
    return error_lists

# change by variance (sigma)
def rbf_sigma():
    return

if __name__ == '__main__':
    pattern = "square"
    x_train, y_train = data_gen(pattern=pattern, noise=True)
    x_test, y_test = data_gen(start=0.05, pattern=pattern, noise=True)

    # plot the error change by nnodes, delta
    nnodes_list = [i for i in range(2, 11)]
    error_lists = rbf_nnodes(nnodes_list, x_train, y_train, x_test, y_test, pattern=pattern)
    # plot_error(error_lists, "batch RBF")

    error_lists = rbf_nnodes(nnodes_list, x_train, y_train, x_test, y_test, pattern=pattern, rbf="delta")
    # plot_error(error_lists, "delta on-line RBF")


import numpy as np

def generate_fbm_path(N, H, n_paths = 1, end_time = 10):
    """
    Generates n_paths paths of length N of a fractal Brownian motion of parameter H
    """

    time_vec = np.linspace(0,end_time, N)
    N        = N-1
    time_vec = time_vec[1:]

    T, S = np.meshgrid(time_vec,time_vec)

    # Covariance matrix
    R = 0.5*( T**(2*H) + S**(2*H) - np.abs(T-S)**(2*H))

    X = generate_gaussian_vector(N, R, n_samples = n_paths)
    X = np.vstack(  ( np.zeros((1, n_paths)), X )    )

    # Return path
    return X

def generate_gaussian_vector(N, C, mu = None, n_samples = 1):
    """
    Generate n_samples of a gaussian vector of length N.
    Dimension of the output = (N, n_samples)

    Input C must be a (N, N) matrix
    Input mu must have N elements
    """
    if mu == None:
        mu = np.zeros((N, n_samples))
    else:
        mu = mu.reshape( (N,1) )
        mu = mu.dot(np.ones( (1, n_samples) ))

    v = np.random.randn(N, n_samples)

    L = np.linalg.cholesky(C)
    u = L.dot(v) + mu
    return u


# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues, eigenvectors = np.linalg.eig(adj_mat)
# Eigen vector corresponding to 1
eigen_1 = abs(eigenvectors[:,0]/np.linalg.norm(eigenvectors[:,0],1))
# most important page
page = np.argmax(eigen_1)+1
print("Highest Ranked Page is:",page)
# Code ends here

# --------------
# Code starts here

# Initialize stationary vector I
init_I = np.array([1,0,0,0,0,0,0,0])
# Perform iterations for power method
for i in range(10):
    init_I = np.dot(adj_mat, init_I)
    init_I = init_I/np.linalg.norm(init_I,1)
power_page = np.argmax(init_I)+1
# Code ends here

# --------------
# Code starts here

# New Adjancency matrix
# New Adjancency matrix
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Initialize stationary vector I
new_init_I = np.array([1,0,0,0,0,0,0,0])
# Perform iterations for power method
for i in range(10):
    new_init_I = np.dot(new_adj_mat, new_init_I)
    new_init_I = new_init_I/np.linalg.norm(new_init_I,1)
print(new_init_I)
# Code ends here

# --------------
# Alpha value
alpha = 0.85
# Code starts here
# Modified adjancency matrix
n = len(new_adj_mat)
one = np.ones(new_adj_mat.shape)
G = alpha * new_adj_mat + (1-alpha) * 1/n * one
# Initialize stationary vector I
final_init_I = np.array([1,0,0,0,0,0,0,0])
# Perform iterations for power method
for i in range(1000):
    final_init_I = np.dot(G,final_init_I)
    final_init_I = final_init_I/np.linalg.norm(final_init_I,1)
print(final_init_I)
# Code ends here


def r2(y_true,y_pred):
    """
    y_true -> true labels
    y_pred -> predicted labels
    R2 defines how well does the model fit the data.
    """
    import numpy as np
    mean_y_true = np.mean(y_true)
    num = np.sum(np.square(y_true-y_pred))
    den = np.sum(np.square(y_true-mean_y_true))
    return 1 - (num/den)