# SETUP begin...

# install necessary libraries first

# if requirements not already satisfied

    # pip install pyprimes

    # pip install pycryptodome

# import necessary modules

import random

import pyprimes

import warnings

from Crypto.Hash import SHA3_256 # for hash

import os.path # for GenerateorRead

import string # for generating random string in fixed range

import sympy # check if the number is prime

import sys # for path

# will be utilized in the implementation, taken from homeworks provided before

def egcd(a, b):

    x,y, u,v = 0,1, 1,0

    while a != 0:

        q, r = b//a, b%a

        m, n = x-u*q, y-v*q

        b,a, x,y, u,v = a,r, u,v, m,n

    gcd = b

    return gcd, x, y

def modinv(a, m):

    if a < 0:

        a = a+m

    gcd, x, y = egcd(a, m)

    if gcd != 1:

        return None  # modular inverse does not exist

    else:

      return x % m

def random_prime(bitsize):

    warnings.simplefilter('ignore')

    chck = False

    while chck == False:

        p = random.randrange(2**(bitsize-1), 2**bitsize-1)

        chck = pyprimes.isprime(p)

    warnings.simplefilter('default')

    return p

def large_DL_Prime(q, bitsize):

    warnings.simplefilter('ignore')

    chck = False

    while chck == False:

        k = random.randrange(2**(bitsize-1), 2**bitsize-1)

        p = k*q+1

        chck = pyprimes.isprime(p) and (p.bit_length() == 2048)

    warnings.simplefilter('default')

    return p

# will be utilized in the implementation, taken from homeworks provided before

# generates public parameters q,p,g

# generate random primes such that q|p-1

# algorithm for choosing the generator:

  # choose a random alpha

  # try a random alpha raised to the power ((p-1)) % q mod(p)

  # if anything other than 1, it's the generator of Z*p with q elements

def Param_Generator(sizeq,sizep): # qsize = 224-bit, psize = 2048-bit

  q = random_prime(sizeq)

  diff = sizep - sizeq # determine the range for generation of p

  p = large_DL_Prime(q, diff) # discrete log prime

  hold = (p)//q

  g = 1

  while g == 1:

      alpha1 = random.randrange(1, p)

      g = pow(alpha1, hold, p)

  return q, p, g

# generate public and private keys for the users

# generates private key -> secret k integer such that 0 < k < q-1

# computes public key by taking modulo p of g^k which is beta

def KeyGen(q,p,g):

  alpha = random.randint(1,q-2) # private

  beta = pow(g,alpha,p) # public

  return alpha, beta

# signature generation

# signature = (s,r)

  # hash function is 256-bit SHA

    # take modulo q after hashing -> get h value

  # select j (random integer) such that range = [1,q-2]

  # compute r such that (g^j(mod(p))) mod(q) is r  -> signature part1

  # compute s such that k*r - j*h (mod(q)) is s -> signature part2 (remember that k is the secret key)

  # plaintext = m

def SignGen(m,q,p,g,alpha):

  hashval = SHA3_256.new() # create new SHA object here

  hashval.update(m) # hash here

  hold = hashval.digest()

  h = int.from_bytes(hold, byteorder='big') # conversion from bytes to integer

  k = random.randint(1,q-2)

  r = pow(g,k,p) % q

  s = ((alpha*r)-(k*h)) % q

  return s,r # returning as a tuple or by comma is essentially the same thing post-function call

# signature verification

  # now (s,r) is the signature for m

  # carry out the same procedure as above with SHA encryption

  # take modulo inverse of h in modulo q and equate this to v

  # compute z1 such that s*v mod(q) is z1

  # compute z2 such that r*v mod(q) is z2

  # finally, compute (((g^-z1)*(beta^z2)) mod(p)) mod(q) and equate this to u

  # only verify the signature if u = r

# Signature verification

def SignVer(m,s,r,q,p,g,beta):

  hashval = SHA3_256.new() # create new SHA object here

  hashval.update(m) # hash here

  hold = hashval.digest()

  h = int.from_bytes(hold, byteorder='big') % q # conversion from bytes to integer

  v = modinv(h,q)

  z1 = (s*v) % q

  z2 = (r*v) % q

  u = ((modinv(pow(g,z1,p),p)*pow(beta,z2,p)) % p) % q

  if(u == r):

    return 0

  else:

    return random.randint(1,42) # return an arbitrary value for failure

# read the file

# if exists, read public parameters from the file

# else, create public parameters and write them to pubparams.txt

def GenerateOrRead(filename):

    if os.path.isfile(filename) == True:   #if we can reach the file open it

            file = open(filename, 'r+')

            if os.stat(filename).st_size == 0:      #if file is empty generate new parameters

                q, p, g = Param_Generator(224, 2048)

                file.write(str(q) + '\n' + str(p) + '\n' + str(g))  #write the new  parameters to file

                file.close()

                return int(q), int(p), int(g)

            else:     #if file is not empty

                file = open(filename, 'r+')   #open the file

                txt_info = file.readlines()  #read each line

                j = 0

                while(j != len(txt_info)):    #set each line to the parameters with respect to their order

                    q = int(txt_info[j])

                    p = int(txt_info[j+1])

                    g = int(txt_info[j+2])

                    return q, p, g

    else:       #if we can't reach the file open new files for reading and writing

        file = open(filename,'w+')

        q, p, g = Param_Generator(224, 2048)

        file.write(str(q) + '\n' + str(p) + '\n' + str(g))  #write the new  parameters to file

        file.close()

        return int(q), int(p), int(g)

# return a random combination of lowercase letters, uppercase letters and digits

def random_string(str_size):

    randomized = "".join(random.choices(string.ascii_lowercase + string.digits + string.ascii_uppercase, k = str_size))

    return randomized

# SETUP end...


import numpy as np
import load_data as ld
from evaluators import *

class LogisticRegression():
    """
    Class for performing logistic regression.
    """
    def __init__(self, learning_rate = 0.7, max_iter = 1000):
        self.learning_rate = learning_rate
        self.max_iter = max_iter
        self.theta = []
        self.no_examples = 0
        self.no_features = 0
        self.X = None
        self.Y = None

    def add_bias_col(self, X):
        bias_col = np.ones((X.shape[0], 1))
        return np.concatenate([bias_col, X], axis=1)

    def hypothesis(self, X):
        return 1 / (1 + np.exp(-1.0 * np.dot(X, self.theta)))

    def cost_function(self):
        """
        We will use the binary cross entropy as the cost function. https://en.wikipedia.org/wiki/Cross_entropy
        """
        predicted_Y_values = self.hypothesis(self.X)
        cost = (-1.0/self.no_examples) * np.sum(self.Y * np.log(predicted_Y_values) + (1 - self.Y) * (np.log(1-predicted_Y_values)))
        return cost

    def gradient(self):
        predicted_Y_values = self.hypothesis(self.X)
        grad = (-1.0/self.no_examples) * np.dot((self.Y-predicted_Y_values), self.X)
        return grad

    def gradient_descent(self):
        for iter in range(1,self.max_iter):
            cost = self.cost_function()
            delta = self.gradient()
            self.theta = self.theta - self.learning_rate * delta
            print("iteration %s : cost %s " % (iter, cost))

    def train(self, X, Y):
        self.X = self.add_bias_col(X)
        self.Y = Y
        self.no_examples, self.no_features = np.shape(X)
        self.theta = np.ones(self.no_features + 1)
        self.gradient_descent()

    def classify(self, X):
        X = self.add_bias_col(X)
        predicted_Y = self.hypothesis(X)
        predicted_Y_binary = np.round(predicted_Y)
        return predicted_Y_binary

to_bin_y = { 1: { 'Iris-setosa': 1, 'Iris-versicolor': 0, 'Iris-virginica': 0 },
             2: { 'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 0 },
             3: { 'Iris-setosa': 0, 'Iris-versicolor': 0, 'Iris-virginica': 1 }
             }

X_train, y_train, X_test, y_test = ld.iris()

Y_train = np.array([to_bin_y[3][x] for x in y_train])
Y_test = np.array([to_bin_y[3][x] for x in y_test])

print("training Logistic Regression Classifier")
lr = LogisticRegression()
lr.train(X_train, Y_train)
print("trained")
predicted_Y_test = lr.classify(X_test)
f1 = f1_score(predicted_Y_test, Y_test, 1)
print("F1-score on the test-set for class %s is: %s" % (1, f1))

# from sklearn.linear_model import LogisticRegression
# logistic = LogisticRegression()
# logistic.fit(X_train,Y_train)
# predicted_Y_test = logistic.predict(X_test)
# f1 = f1_score(predicted_Y_test, Y_test, 1)
# print("F1-score on the test-set for class %s is: %s" % (1, f1))


"""
Description: Numpy implementation of loss functions and their derivatives
author: syedmohsinbukhari@googlemail.com
"""

import numpy as np

class Losses:
    def __init__(self):
        self.target = None
        self.achieved = None

    def forward(self, target, achieved):
        pass

    def backward(self):
        pass

class SSELoss(Losses):
    def forward(self, target, achieved):
        assert np.shape(target) == np.shape(achieved), 'Shape of target and achieved is not same'

        self.target = target
        self.achieved = achieved

        sse_loss = np.multiply(np.power(np.subtract(self.target, self.achieved), 2), 0.5)
        return sse_loss

    def backward(self):
        assert np.shape(self.target) == np.shape(self.achieved), "Shape of target and achieved is not same"
        assert self.achieved is not None, "Need to compute loss before computing gradient"
        assert self.target is not None, "Need to compute loss before computing gradient"

        sse_grad = np.subtract(self.achieved, self.target)

        return sse_grad


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

# Function to estimate point of intersection of normal curves of two classes, at the point of intersection diff
# is zero or a point where the difference changes it sign

def intersection(f, g, x):
    d = f - g
    for i in range(len(d) - 1):
        if d[i] == 0. or d[i] * d[i + 1] < 0.:
            x_ = x[i]
            return x_

def FisherLDA(ds, s):
    row, col = ds.shape
    ds_0 = ds[ds[col-1] == 0].iloc[:, 0:-1].values
    ds_1 = ds[ds[col-1] == 1].iloc[:, 0:-1].values
    m0 = np.mean(ds_0, axis=0)
    m1 = np.mean(ds_1, axis=0)
    SW_0 = np.cov(np.transpose(ds_0))
    SW_1 = np.cov(np.transpose(ds_1))
    SW = SW_0 + SW_1
    SW_inv = np.linalg.inv(SW)
    w = np.dot(SW_inv, (m1-m0))
    wT = np.transpose(w)
    y_0 = []
    y_1 = []
    for i in range(len(ds_0)):
        y_0.append(np.dot(wT, ds_0[i, :]))
    for i in range(len(ds_1)):
        y_1.append(np.dot(wT, ds_1[i, :]))

    fig1, ax1 = plt.subplots()
    ax1.scatter(y_0, np.zeros(np.shape(y_0)), s=1,
                color='r', label="Points in Class 0")
    ax1.scatter(y_1, np.zeros(np.shape(y_1)), s=1,
                color='g', label="Points in Class 1")
    ax1.legend(loc='upper right')
    plt.title("One Dimension transformation for dataset "+str(s))

    mu_0 = np.mean(y_0)
    std_0 = np.std(y_0)
    xmin, xmax = plt.xlim()
    x = np.linspace(xmin, xmax, 10000)
    p_0 = norm.pdf(x, mu_0, std_0)
    fig2, ax2 = plt.subplots()
    ax2.set_xlim([-15, 20])
    ax2.set_ylim([0, 0.7])
    ax2.plot(x, p_0, 'k', linewidth=2, color='red', label="Class 0")
    mu_1 = np.mean(y_1)
    std_1 = np.std(y_1)
    x = np.linspace(xmin, xmax, 10000)
    p_1 = norm.pdf(x, mu_1, std_1)
    ax2.plot(x, p_1, 'k', linewidth=2, color='green', label="Class 1")
    ax2.legend(loc='upper right')
    intr = intersection(p_0, p_1, x)
    misclass = 0
    if mu_0 < intr:
        for y in y_0:
            if y > intr:
                misclass = misclass+1
        for y in y_1:
            if y < intr:
                misclass = misclass+1
    else:
        for y in y_0:
            if y < intr:
                misclass = misclass+1
        for y in y_1:
            if y > intr:
                misclass = misclass+1
    plt.title("Normal curve for dataset "+str(s)+"\nThreshold is "+str(intr))
    print("Threshold values is", intr)
    print("Accuracy :", 100-(misclass*100/row))
    plt.show()

def main():
    ds1 = pd.read_csv("./../../datasets/a1_d1.csv", header=None)
    ds2 = pd.read_csv("./../../datasets/a1_d2.csv", header=None)
    FisherLDA(ds1, 1)
    FisherLDA(ds2, 2)

main()


import sys
if 'autograd.numpy' not in sys.modules:
    import numpy as np
from qubiter.BitVector import *

class HadamardTransform:
    """
    This class contains only static methods and no constructor. Its
    functions are related to the Hadamard Transform.

    """

    @staticmethod
    def ht(num_qbits, in_arr):
        """
        This function calculates the Hadamard transform of in_arr. Let H be
        the 2 dimensional Hadamard matrix and let ht be the num_bit-fold
        tensor product of H. Then this function returns the matrix product
        ht*in_arr = out_arr. in_arr and out_arr both have the same shape (
        2^num_qbits,).

        Parameters
        ----------
        num_qbits : int
            The number of bits. The dimension of the Hadamard tranform is
            2^num_qbits

        in_arr : np.ndarray
            Input array.

        Returns
        -------
        np.ndarray

        """
        length = (1 << num_qbits)
        half_len = (length >> 1)
        assert len(in_arr) == length, \
            "in_arr for Hadamard Transform has wrong length"
        prev_arr = np.zeros(length, dtype=float)
        root2 = np.sqrt(2)
        out_arr = np.copy(in_arr)
        for beta in range(num_qbits):
            prev_arr[:] = out_arr[:]
            for k in range(half_len):
                x = prev_arr[2*k]
                y = prev_arr[2*k+1]
                out_arr[k] = (x + y)/root2
                out_arr[half_len+k] = (x - y)/root2
        return out_arr

    @staticmethod
    def hadamard_mat(num_qbits, is_quantum=True):
        """
        This function return a numpy array with the num_qbits-fold tensor
        product of the 2 dim Hadamard matrix H. If is_quantum=True (False,
        resp.), it returns a complex (real, resp.) array.

        Parameters
        ----------
        num_qbits : int
        is_quantum : bool

        Returns
        -------

        """
        num_rows = (1 << num_qbits)
        norma = np.sqrt(num_rows)
        if is_quantum:
            ty = complex
        else:
            ty = float
        mat = np.full((num_rows, num_rows),
                      fill_value=1/norma, dtype=ty)
        bvec = BitVector(num_qbits, 0)
        for j in range(num_rows):
            for k in range(num_rows):
                bvec.dec_rep = j & k
                if bvec.get_num_T_bits() % 2 == 1:
                    mat[j, k] = - mat[j, k]
        return mat

if __name__ == "__main__":
    def main():
        num_qbits = 3
        length = 1 << num_qbits
        in_arr = np.random.rand(length) - 0.5
        out_arr = HadamardTransform.ht(num_qbits, in_arr)
        in_arr1 = HadamardTransform.ht(num_qbits, out_arr)
        # print("in_arr=", in_arr)
        # print("out_arr=", out_arr)
        err = np.linalg.norm(in_arr-in_arr1)
        print("error=", err)

        print(HadamardTransform.hadamard_mat(2))
    main()


import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm

alpha, beta = 0.4, 0.5
def f(x1, x2):
    return alpha * np.log(x1) + beta * np.log(x2)

p1, p2, m = 1.0, 1.2, 4
def budget_line(x1):
    return (m - p1 * x1) / p2

x1star = (alpha / (alpha + beta)) * (m / p1)
x2star = budget_line(x1star)
maxval = f(x1star, x2star)

xgrid = np.linspace(1e-2, 4, 50)
ygrid = xgrid

# === plot value function === #
fig, ax = plt.subplots(figsize=(8,6))
x, y = np.meshgrid(xgrid, ygrid)

if 1:
    ax.plot(xgrid, budget_line(xgrid), 'k-', lw=2, alpha=0.8)
    #ax.fill_between(xgrid, xgrid * 0.0, budget_line(xgrid), facecolor='blue', alpha=0.3)

if 0:  # Annotate with text
    ax.text(1, 1, r'$p_1 x_1 + p_2 x_2 < m$', fontsize=16)

    ax.annotate(r'$p_1 x_1 + p_2 x_2 = m$',
             xy=(2, budget_line(2)),
             xycoords='data',
             xytext=(40, 40),
             textcoords='offset points',
             fontsize=16,
             arrowprops=dict(arrowstyle="->"))

if 1:  # Add maximizer
    ax.annotate(r'$(x_1^*, x_2^*)$',
             xy=(x1star, x2star),
             xycoords='data',
             xytext=(30, 30),
             textcoords='offset points',
             fontsize=16,
             arrowprops=dict(arrowstyle="->"))
    ax.plot([x1star], [x2star],  'ro', alpha=0.6)

if 1:  # Plot with contours
    #points = [-10, -2, -1, 0, 0.4, 0.6, 0.8, 1.0, 1.2, 4]
    points = [-10, -2, -1, 0, 0.6, 1.0, 1.2, 4]
    ax.contourf(x, y, f(x, y), points, cmap=cm.jet, alpha=0.5)
    cs = ax.contour(x, y, f(x, y), points, colors='k', linewidth=2, alpha=0.7,
            antialias=True)
    plt.clabel(cs, inline=1, fontsize=12)

ax.set_xlim(0, 4)
ax.set_ylim(0, 4)
ax.set_xticks((0.0, 1.0, 2.0, 3.0))
ax.set_yticks((1.0, 2.0, 3.0))
ax.set_xlabel(r'$x_1$', fontsize=16)
ax.set_ylabel(r'$x_2$', fontsize=16)
plt.show()


# -*- coding: utf-8 -*-
"""
Making array of zeros without numpy: https://stackoverflow.com/questions/4056768/how-to-declare-array-of-zeros-in-python-or-an-array-of-a-certain-size
"""
from pylab import *
from numpy import *
import scipy as sp
import time

C = array([[4,12,-16],[12,37,-43],[-16,-43,98]]) # Test matrix from wikipedia page
random.seed(20)

n = 300
E = random.rand(n,n)*random.random()
#D = 0.5*(D+transpose(D))
E = matmul(E,transpose(E)) # Generating random positive definite matrix

print('Randomly generated matrix E is',E)

test = linalg.cholesky(E)

def choleskymanual(A):
#    L=[[0]]*len(A) # Numpy independent method
#    for i in range(len(A)):
#        L[i]=L[i]*len(A)
    L = zeros((len(A),len(A)))
    for i in range(len(A)):
        for j in range(i+1):
            lsum=0
            for k in range(j):
                lsum+=sum(L[i,k]*conj(L[j,k]))
            if i>j:
                L[i,j]=(1/L[j,j])*(A[i,j]-lsum)
            else:
                L[j,j]=sqrt(A[j,j]-lsum)
    return L

def cmLDL(A):
    L = zeros((len(A),len(A)))
    D = zeros((len(A),len(A)))
    for i in range(len(A)):
        for j in range(i+1):
            lsum=0
            lsum2=0 # Require seperate sums as the calculations for elements of D and L use different elements from matrix L
            for k in range(j):
                lsum+=L[j,k]*conj(L[j,k])*D[k,k]
                lsum2+=L[i,k]*conj(L[j,k])*D[k,k]
            D[j,j]=A[j,j]-lsum
#            if i>j:
            L[i,j]=(1/D[j,j])*(A[i,j]-lsum2)
    return L,D

L=choleskymanual(E)
LD,D=cmLDL(E)
#print(L)
Ls=conj(transpose(L))
LDs=conj(transpose(LD))
Anew=matmul(L,Ls)
ADnew=matmul(LD,matmul(D,LDs))

print('L matrix from LL decomposition:',L)
print('L matrix from LDL decomposition:',LD)
print('Diagonal elements from D matrix from LDL decomposition:',diag(D))

#%%
"""
1st Application: Matrix Inversion
"""
start = time.time()
# How to get inverse: [A]][Ainv]=[LL*][Ainv]=I
# Print statements for troubleshooting
def cholinverse(A):
    L=choleskymanual(A)
    Ls=conj(transpose(L))
    I = identity((len(L)))
    U = zeros([len(A),len(A)])
    Ainv = zeros([len(A),len(A)])
    for i in range(len(U)):
        for j in range(i+1):
            if i==j:
                U[i,i]=(I[i,i]/L[i,i])
                Ainv[i,i]=(U[i,i]/Ls[i,i])
#    print(U)
#    print(Ainv)
    for i in range(len(U)):
#        print('i=',i)
#        lsum=0
        for j in range(i+1):
#            print('j=',j)
            lsum=0
            for k in range(i):
                lsum+=L[i,k]*U[k,j]
#                print('k=',k)
#                print(lsum)
#            print(lsum)
#            print(U)
            if i>j:
                U[i,j]=(-lsum)/L[i,i]
    for i in range(len(U)-1,-1,-1):
#        print('i=',i)
#        lsum=0
        for j in range(i,-len(U),-1):
#            print('j=',j)
            lsum=0
            for k in range(i-1,-len(U),-1):
                lsum+=Ls[i,k]*Ainv[k,j]
#                print('k=',k)
#                print(lsum)
#            print(lsum)
#            print(Ainv)
#            if i>j:
            Ainv[i,j]=(U[i,j]-lsum)/Ls[i,i]
            Ainv[j,i]=(U[i,j]-lsum)/Ls[i,i]
    return U,Ainv

AI = linalg.inv(E)
SA  = cholinverse(E)
print('The matrix inversion from numpy.linalg is')
print(AI)
print('The matrix inversion from manual method is')
print(SA[1])

ratioinv=AI/SA[1]
ratioinvf=array([])
for i in range(len(ratioinv)):
    o=mean(ratioinv[i])
    ratioinvf=append(ratioinvf,o)

ratioinvf=mean(ratioinvf)

print('The average ratio between the numpy and manual method at n =',n,' for matrix inversion is',ratioinvf)

#%%
"""
2nd Application: Monte-Carlo Simulation - Corellating unrelated random variables
"""

ranvar = random.normal(0,1,(n,100000)) # Generating n amount of variables for 2 variables from a gaussian distribution
#ranvar[1]=ranvar[1]*10000
#random.seed(40)
#ranvar1 = random.normal(0,1,100000)
#random.seed(60)
#ranvar2 = random.normal(0,1,100000)*10000
#ranvart=zeros(2,dtype=object)
#ranvart[0],ranvart[1]=muarray,ranvar2
#Imat = [[0.6,1],[1,0.4]]

#covmat = matmul(transpose(C),C)
covmat = matmul(transpose(E),E)

L = choleskymanual(covmat)

xcorr = dot(L,ranvar)
#xstd = sqrt(diag(covmat))

plot(abs(xcorr[0]),abs(xcorr[n-2]),'.')
grid()
xlabel('Correlated data set for variable x0')
ylabel('Correlated data set for variable x%s'%(n-2))
title('Correlation between correlated variables x0 and x%s'%(n-2))
figure()
plot(ranvar[0],ranvar[n-1],'.')
grid()
xlabel('Uncorrelated data set for variable x0')
ylabel('Uncorrelated data set for variable x%s'%(n-2))
title('Correlation between uncorrelated variables x0 and x%s'%(n-2))
#%%
"""
3rd Application: Least-Squares

Because I've already computed the inverse of the matrix via Cholesky, I can simply take Ax=b -> x=Ainvb to get the system of unknown variables x
"""
#B=[0,1,2] # Use for C matrix or other random 3x3 matrix
B = random.rand(n,1)

X = matmul(cholinverse(E)[1],B)
print('The linear equation for a solution')
print(B)
print('via matrix inversion from cholesky gives vector x =')
print(X)

"""
Or I  can use linalg.solve
"""
X = linalg.solve(E,B)
print('The linear equation for a solution')
print(B)
print('via linalg.solve from cholesky gives vector x =')
print(X)

"""
Or I can calculate it more explicitly.

This is a modification of the cholinverse function since they both use back/forward substitution
Ax=b -> A*Ax=A*b -> (LLs)x=A*b -> (L)u=A*b
"""

def cholLS(A,b):
    posdef = matmul(transpose(A),A)
    newb=matmul(transpose(A),b)
    L=choleskymanual(posdef)
    Ls=conj(transpose(L))
    U = zeros([len(b)])
    X = zeros([len(b)])
    for i in range(len(A)):
        U[i]=(newb[i]-dot((L[i]),U))/L[i,i]
    for i in range(len(A)-1,-1,-1):
        X[i]=(U[i]-dot(Ls[i],X))/Ls[i,i]
    return X

print('The linear equation for a solution')
print(B)

Y = cholLS(E,B)

print('via least squares linear regression from cholesky gives vector x =')
print(Y)

ratiols=transpose(X)/Y
ratiolsf=array([])
for i in range(len(ratiols)):
    o=mean(ratiols[i])
    ratiolsf=append(ratiolsf,o)

ratiolsf=mean(ratiolsf)

print('The average ratio between the numpy and manual method at n =',n,' for matrix inversion is',ratioinvf)
print('The average ratio between the numpy and manual method at n =',n,' for least squares is',ratiolsf)

end = time.time()
print('Total time taken to run code = ',end - start)


import numpy as np

if __name__ == "__main__":
    # initializing matrices
    x = np.array([[1, 2], [4, 5]])
    y = np.array([[7, 8], [9, 10]])

    # element wise addition
    print("The element wise addition of matrix is : ")
    print(np.add(x, y))

    # element wise subtraction
    print("The element wise subtraction of matrix is : ")
    print(np.subtract(x, y))

    # element wise division
    print("The element wise division of matrix is : ")
    print(np.divide(x, y))

    # element wise multiplication
    print("The element wise multiplication of matrix is : ")
    print(np.multiply(x, y))

    # matrix multiplication
    print("Matrix multiplication is : ")
    print(np.dot(x, y))

    # matrix transpose
    print("Matrix transpose is : ")
    print(np.transpose(x))

    # matrix inverse
    print("Matrix inverse is : ")
    print(np.linalg.inv(x))


import numpy as np
import matplotlib.pyplot as plt

x_data = [ 338., 333., 328., 207., 226., 25., 179., 60., 208.,  606. ]
y_data = [ 640., 633., 619., 393., 428., 27., 193., 66., 226., 1591. ]
# y_data = w * x_data + bias

x = np.arange(-200, -100, 1) # bias
y = np.arange(-5, 5, 0.1) # weight
Z = np.zeros((len(x), len(y)))
X, Y = np.meshgrid(x, y)

for i in range(len(x)):
    for j in range((len(y))):
        b = x[i]
        w = y[j]
        Z[j][i] = 0
        for n in range(len(x_data)):
           Z[j][i] = Z[j][i] +(y_data[n] - b - w*x_data[n])**2
        Z[j][i] = Z[j][i]/len(x_data)

b = -129 # intialize b
w = -4 # intialize w
lr = 0.0000001 # learning rate
iteration = 100000

# Store intial values for plotting
b_history = [b]
w_history = [w]

# Iteration
for i in range(iteration):
    b_grad = 0.0
    w_grad = 0.0
    for n in range(len(x_data)):
        b_grad = b_grad - 2.0*(y_data[n] - b - w*x_data[n])*1.0
        w_grad = w_grad - 2.0*(y_data[n] - b - w*x_data[n])*x_data[n]

    # Update parameters
    b = b - lr * b_grad
    w = w - lr * w_grad

    # Store the parameters for plotting
    b_history.append(b)
    w_history.append(w)

# plot the figure
plt.contour(x, y, Z, 50, alpha=0.5, cmap=plt.get_cmap('jet'))
plt.plot([-188.4], [2.67], 'x', ms=12, markeredgewidth=3, color='orange')
plt.plot(b_history, w_history, 'o-', ms=3, lw=1.5, color='black')
plt.xlim(-200, -100)
plt.ylim(-5,5)
plt.xlabel(r'$b$', fontsize=16)
plt.ylabel(r'$w$', fontsize=16)
plt.show()

## Change Learning_Rate to Learning_Rate*10
b = -129 # intialize b
w = -4 # intialize w
lr = 0.000001 # learning rate
iteration = 100000

# Store intial values for plotting
b_history = [b]
w_history = [w]

# Iteration
for i in range(iteration):
    b_grad = 0.0
    w_grad = 0.0
    for n in range(len(x_data)):
        b_grad = b_grad - 2.0*(y_data[n] - b - w*x_data[n])*1.0
        w_grad = w_grad - 2.0*(y_data[n] - b - w*x_data[n])*x_data[n]

    # Update parameters
    b = b - lr * b_grad
    w = w - lr * w_grad

    # Store the parameters for plotting
    b_history.append(b)
    w_history.append(w)

# plot the figure
plt.contour(x, y, Z, 50, alpha=0.5, cmap=plt.get_cmap('jet'))
plt.plot([-188.4], [2.67], 'x', ms=12, markeredgewidth=3, color='orange')
plt.plot(b_history, w_history, 'o-', ms=3, lw=1.5, color='black')
plt.xlim(-200, -100)
plt.ylim(-5,5)
plt.xlabel(r'$b$', fontsize=16)
plt.ylabel(r'$w$', fontsize=16)
plt.show()

## Change Learning_Rate to Learning_Rate*10
b = -129 # intialize b
w = -4 # intialize w
lr = 0.00001 # learning rate
iteration = 100000

# Store intial values for plotting
b_history = [b]
w_history = [w]

# Iteration
for i in range(iteration):
    b_grad = 0.0
    w_grad = 0.0
    for n in range(len(x_data)):
        b_grad = b_grad - 2.0*(y_data[n] - b - w*x_data[n])*1.0
        w_grad = w_grad - 2.0*(y_data[n] - b - w*x_data[n])*x_data[n]

    # Update parameters
    b = b - lr * b_grad
    w = w - lr * w_grad

    # Store the parameters for plotting
    b_history.append(b)
    w_history.append(w)

# plot the figure
plt.contour(x, y, Z, 50, alpha=0.5, cmap=plt.get_cmap('jet'))
plt.plot([-188.4], [2.67], 'x', ms=12, markeredgewidth=3, color='orange')
plt.plot(b_history, w_history, 'o-', ms=3, lw=1.5, color='black')
plt.xlim(-200, -100)
plt.ylim(-5,5)
plt.xlabel(r'$b$', fontsize=16)
plt.ylabel(r'$w$', fontsize=16)
plt.show()

    ## Using adagrad to solve the problem
'''
If increasing the iteration to speed up the rate of convergence, that would spped down actually.
Cause it have to calculate gradient at each iteration, but that would cost lots of time.
Therefore, we modify the learning rate, and change improve rate of convergence at meantime.
'''
b = -129
w = -4
lr = 1
iteration = 100000

b_lr = 0.0
w_lr = 0.0

# Store initial values for plotting
w_history = [w]
b_history = [b]

# Iteration
for i in range(iteration):
    b_grad = 0.0
    w_grad = 0.0
    for n in range(len(x_data)):
        b_grad = b_grad - 2.0*(y_data[n] - b - w*x_data[n])*1.0
        w_grad = w_grad - 2.0*(y_data[n] - b - w*x_data[n])*x_data[n]

    # Add
    b_lr = b_lr + b_grad ** 2
    w_lr = w_lr + w_grad ** 2

    # Update parameters
    b = b - lr /np.sqrt(b_lr) * b_grad
    w = w - lr /np.sqrt(w_lr) * w_grad

    # Store the parameters for plotting
    b_history.append(b)
    w_history.append(w)

# plot the figure
plt.contour(x, y, Z, 50, alpha=0.5, cmap=plt.get_cmap('jet'))
plt.plot([-188.4], [2.67], 'x', ms=12, markeredgewidth=3, color='orange')
plt.plot(b_history, w_history, 'o-', ms=3, lw=1.5, color='black')
plt.xlim(-200, -100)
plt.ylim(-5,5)
plt.xlabel(r'$b$', fontsize=16)
plt.ylabel(r'$w$', fontsize=16)
plt.show()


import numpy as np
import scipy as sp
from matplotlib import pyplot as plt

import shapes

#Prob 1

def polarZ(z):
	if(z == 0):
		return (0,0)
	else :
		a = z.real
		b = z.imag
		return( sp.hypot(a,b), sp.arctan(b/a))

#Prob 2

def fFunc(z):
	return (3*z - 4)/(z-2)

def gFunc(z):
	return z**2 - 2
def confMap(shape,mapfunc):
	shapemapped = [None]*len(shape)
	for i in range(0,len(shape)):
		shapemapped[i] = mapfunc(shape[i])

	plt.scatter(sp.real(shape),sp.imag(shape),color='r')
	plt.scatter(sp.real(shapemapped),sp.imag(shapemapped),color='b')
	plt . show ()

'''
Examples:

triangle = shapes.genRTriangle(1.,1.,0.,0.,100)
confMap(triangle,fFunc)

box = shapes.genBox(1.,1.,0.,0.,400)
confMap(box,gFunc)

circle = shapes.genCircle(1.,1.,0.,0.,400)
confMap(circle,gFunc)
'''

#Prob 3

def testHolo(func,a,b,r,tol):
	circle = shapes.genCircle(r,a,b,400)
	vals = func(circle)
	diff = max(vals) - min(vals)
	if(diff < tol ):
		return "differentiable"
	else:
		return "not differentiable {0}".format(diff)
#my solution apparently didn't work, it says that all the functions are not differentiable

print testHolo(lambda z: sp.real(z),1,1,0.01,1)
print testHolo(lambda z: sp.absolute(z),1,1,0.01,1)
print testHolo(lambda z: sp.conj(z),1,0,0.01,1)
print testHolo(lambda z: z*sp.real(z),0,0,0.01,1)
print testHolo(lambda z: z*sp.real(z),1,1,0.01,1)
print testHolo(lambda z: sp.exp(z),0,np.pi,0.01,1)

print testHolo(lambda z: sp.sin(sp.real(z))*sp.cosh(sp.imag(z))+
	sp.cos(sp.real(z))*sp.sinh(sp.imag(z))*1j ,0,np.pi,0.01,1)
print testHolo(lambda z: sp.sin(sp.real(z))*sp.cosh(sp.imag(z))
	-sp.cos(sp.real(z))*sp.sinh(sp.imag(z))*1j,0,np.pi,0.01,1)


# -*- coding: utf-8 -*-
"""
Interpolaciones de diferente orden para la funcion

  2*exp(x) + sin(3*x)

"""
from __future__ import division, print_function
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import lagrange
import sympy as sym

plt.rcParams["axes.spines.right"] = False
plt.rcParams["axes.spines.top"] = False
plt.rcParams["mathtext.fontset"] = "cm"

def base_lagrange(x_datos, var, cont):
    """Crea un polinomio base de Lagrange para los datos x"""
    prod = sym.prod((var - x_datos[i])/(x_datos[cont] - x_datos[i])
                    for i in range(len(x_datos)) if i != cont)

    return sym.lambdify(var, sym.simplify(prod), "numpy")

def deriv_base_lagrange(x_datos, var, cont):
    """Crea un polinomio base de Lagrange para los datos x"""
    prod = sym.prod((var - x_datos[i])/(x_datos[cont] - x_datos[i])
                    for i in range(len(x_datos)) if i != cont)
    return sym.lambdify(var, sym.simplify(prod.diff(var)), "numpy")

fun = lambda x: 2*np.exp(x) + np.sin(3*x)
grad = lambda x: 2*np.exp(x) + 3*np.cos(3*x)
x = np.linspace(-1, 1, 101)
y = fun(x)
dy = grad(x)

plt.close("all")
plt.figure(figsize=(5, 6))

npts = [2, 3, 5]
for cont in range(3):
    ax = plt.subplot(3, 2, 2*cont + 1)
    if cont==0:
        plt.title("Interpolación")
    x_inter = np.linspace(-1, 1, npts[cont])
    y_inter = fun(x_inter)
    f_inter = lagrange(x_inter, y_inter)
    plt.plot(x, y)
    plt.plot(x, f_inter(x), linestyle="dashed",
             label="Orden {}".format(npts[cont] - 1))
    plt.plot(x_inter, y_inter, "ok")
    plt.ylabel("$y$", fontsize=14)
    plt.legend(frameon=False)
    if cont != 2:
        ax.xaxis.set_ticks([])
        ax.spines["bottom"].set_color("none")
    if cont == 2:
        plt.xlabel("$x$", fontsize=14)

for cont in range(3):
    ax = plt.subplot(3, 2, 2*cont + 2)
    if cont==0:
        plt.title("Funciones base")
    for cont_base in range(npts[cont]):
        x_inter = np.linspace(-1, 1, npts[cont])
        base = base_lagrange(x_inter, sym.symbols("x"), cont_base)
        y_base = base(x)
        plt.plot(x, y_base)
        plt.ylim(-0.6, 1.2)
        plt.yticks(np.linspace(-0.5, 1, 4))
        plt.ylabel("$y$", fontsize=14)
    if cont != 2:
        ax.xaxis.set_ticks([])
        ax.spines["bottom"].set_color("none")
    if cont == 2:
        plt.xlabel("$x$", fontsize=14)

plt.tight_layout()
plt.savefig("interp_multiple.pdf", bbox_inches="tight", transparent=True)

#%% Derivadas
plt.figure(figsize=(5, 2.5))
plt.subplot(122)
plt.title("Funciones base")
x_inter = np.linspace(-1, 1, 5)
y_inter = fun(x_inter)
dy_inter = np.zeros_like(x)
for cont_base in range(5):
    deriv_base = deriv_base_lagrange(x_inter, sym.symbols("x"), cont_base)
    y_base = deriv_base(x)
    dy_inter += y_base * y_inter[cont_base]
    plt.plot(x, y_base)
    plt.xlabel("$x$", fontsize=14)
    plt.ylabel("$y$", fontsize=14)

plt.subplot(121)
plt.title("Derivadas")
plt.plot(x, dy)
plt.plot(x, dy_inter, linestyle="dashed")
plt.plot(x_inter, grad(x_inter), "ok")
plt.xlabel("$x$", fontsize=14)
plt.ylabel("$y$", fontsize=14)
plt.tight_layout()
plt.savefig("interp_multiple_deriv.pdf", bbox_inches="tight", transparent=True)
#plt.show()


import numpy as np
import sys

matrix = None
def main():
    global matrix
    matrix = np.array([[1,1,-1],[6,2,2],[-3,4,1]],dtype=float)
    aug_matrix = np.concatenate((matrix,identity_matrix(3)),axis=1)
    #gaussian2(aug_matrix) #gaussian elimination with maximum pivoting
    LUdecomp(matrix) #LU decompose a square matrix

def identity_matrix(n):
    m = np.empty([n,n],dtype=float)
    for i in range (0,n):
        for j in range (0,n):
            if(i==j): m[i][j] = 1
            else: m[i][j] = 0

    return m

def gaussian2(m):
    print()
    print("Gaussian Elimination 2.0:")
    print()

    for j in range (0,m.shape[0]):
        max_pivot_row = j
        max_pivot = m[j][j]
        for a in range (1, m.shape[0]-j):
            if(m[j+a][j]>max_pivot):
                max_pivot_row = j+a
                print("maximum pivoting: R",(j+1),"<->","R",(max_pivot_row+1))
                print()

        m[[j,max_pivot_row]] = m[[max_pivot_row,j]]

        if(m[j][j]==0):
            print("no unique solution")
            break

        for i in range (j+1, m.shape[0]):
            c = (m[i][j])/(m[j][j])
            if(c==0):
                print("c==0!!")
                continue
            for a in range (0, m[j].shape[0]):
                m[i][a]=m[i][a]-c*(m[j][a])

            print("row operation: R",(i+1),"-",c,"*R",(j+1))
            print(m)
            print()

def LUdecomp(m):
    L = identity_matrix(m.shape[0])

    for j in range (0, m.shape[0]):
        for i in range (j+1, m.shape[0]):
            c = (m[i][j])/(m[j][j])
            if(c==0):
                continue
            for a in range (0, m[j].shape[0]):
                m[i][a]=m[i][a]-c*(m[j][a])
            L[i][j] = c

            print("row operation: R",(i+1),"-",c,"*R",(j+1))
            print(m)
            print()

    print("matrix L is:")
    print(L)
    print("matrix U is:")
    print(m)

main()


from numpy import *
from newton import *
from quasi_newton import *
from steepest_descent import *
from conjugate_gradient import *

if __name__ == '__main__':
  def f(x): return 100 * math.pow(x[1] - math.pow(x[0], 2), 2) + math.pow(1 - x[0], 2)
  def df_dx1(x): return 400*math.pow(x[0], 3) - 400*x[0]*x[1] + 2*x[0] - 2
  def df_dx2(x): return 200*x[1] - 200*math.pow(x[0], 2)
  def fd(x): return array([ df_dx1(x), df_dx2(x) ])

  def df_dx1_dx1(x): return 1200*math.pow(x[0], 2) - 400*x[1] + 2
  def df_dx1_dx2(x): return-400*x[0]

  def fdd(x):
    return array([
        [df_dx1_dx1(x), df_dx1_dx2(x)],
        [df_dx1_dx2(x), 200]])

  def print_error(i, direction, alpha, x):
    opt = f(array([1,1]))
    print("%d, %.20f" % (i, f(x)-opt))

  def print_gradient(i, direction, alpha, x):
    print("%d, %.20f" % (i, linalg.norm(fd(x))))

  def print_all(i, direction, alpha, x):
    print("iteration %d: \t direction: %s \t alpha: %.7f \t x: %s"
        % (i, ["%.7f" % _ for _ in direction], alpha, ["%.7f" % _ for _ in x]))

  x = array([0, 0])
  precision = 10e-6
  max_iterations = 100
  callback = print_all

  print("steepest descent:")
  steepest_descent(f, fd, x, max_iterations, precision, callback)

  print("\nnewton:")
  newton(f, fd, fdd, x, max_iterations, precision, callback)

  print("\nquasi newton:")
  quasi_newton(f, fd, x, max_iterations, precision, callback)

  print("\nconjugate gradient:")
  conjugate_gradient(f, fd, x, max_iterations, precision, callback)

Computational Mathematics | Python/Rosenbrock.py


#!/usr/bin/python3
# -*- coding: utf-8 -*-

from scipy.stats import geom
import matplotlib.pyplot as plt
import numpy as np

def testGeom():# {{{
    """
    Geometric Distribution (discrete)

    Notes
    -----
        伯努利事件进行k次, 第一次成功的概率

    为什么是几何分布呢, 为什么不叫几毛分布?
    与几何数列有关 (乘积倍数)

    p: 成功的概率
    q: 失败的概率(1-p)
    k: 第一次成功时的经历的次数 (前k-1次是失败的)
        geom.pmf(k, p), (1-p)**(k-1)*p)
    """

    # 准备数据: 已知 p.
    # X轴: 第k次才成功
    # Y轴: 概率
    p = 0.4
    xs = np.arange(geom.ppf(0.01, p), geom.ppf(0.99, p), step = 1)

    # E(X) = 1/p, D(X) = (1-p)/p**2
    mean, var, skew, kurt = geom.stats(p, moments='mvsk')
    print("mean: %.2f, var: %.2f, skew: %.2f, kurt: %.2f" % (mean, var, skew, kurt))

    fig, axs = plt.subplots(2, 2)

    # 显示pmf1
    ys = geom.pmf(xs, p)
    axs[0][0].plot(xs, ys, 'bo', markersize=5, label='geom pmf')
    axs[0][0].vlines(xs, 0, ys, colors='b', linewidth=5, alpha=0.5, label='vline pmf')
    axs[0][0].legend(loc='best', frameon=False)

    # 显示pmf2
    ys = (1-p)**(xs-1)*p
    axs[0][1].plot(xs, ys, 'bo', markersize=5, label='geom pmf')
    axs[0][1].vlines(xs, 0, ys, colors='b', linewidth=5, alpha=0.5, label='vline pmf')
    axs[0][1].legend(loc='best', frameon=False)
    axs[0][1].set_title('ys = (1-p)**(xs-1)*p')

    # 显示cdf P(X<=x)
    ys = geom.cdf(xs, p)
    axs[1][0].plot(xs, ys, 'bo', markersize=5, label='geom cdf')
    axs[1][0].legend(loc='best', frameon=False)
    print(np.allclose(xs, geom.ppf(ys, p))) # ppf:y-->x cdf:x-->y

    # 生成随机数据(random variables)
    data = geom.rvs(p, size=1000)
    import sys
    sys.path.append("../../thinkstats")
    import Pmf
    pmf = Pmf.MakePmfFromList(data)
    xs, ys = pmf.Render()
    axs[1][1].plot(xs, ys, 'bo', markersize=5, label='rvs-pmf')

    plt.show()

# }}}

if __name__ == "__main__":
   testGeom()


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

df = pd.read_csv("china_gdp.csv")
df.head(10)

plt.figure(figsize=(8,5))
x_data, y_data = (df["Year"].values, df["Value"].values)
plt.plot(x_data, y_data, 'ro')
plt.ylabel('GDP')
plt.xlabel('Year')
plt.show()

X = np.arange(-5.0, 5.0, 0.1)
Y = 1.0 / (1.0 + np.exp(-X))

plt.plot(X,Y)
plt.ylabel('Dependent Variable')
plt.xlabel('Indepdendent Variable')
plt.show()

def sigmoid(x, Beta_1, Beta_2):
    y = 1 / (1 + np.exp(-Beta_1 * (x - Beta_2)))
    return y

beta_1 = 0.10
beta_2 = 1990.0

#logistic function
Y_pred = sigmoid(x_data, beta_1 , beta_2)

#plot initial prediction against datapoints
plt.plot(x_data, Y_pred*15000000000000.)
plt.plot(x_data, y_data, 'ro')

# Lets normalize our data
xdata =x_data/max(x_data)
ydata =y_data/max(y_data)

from scipy.optimize import curve_fit
popt, pcov = curve_fit(sigmoid, xdata, ydata)
#print the final parameters
print(" beta_1 = %f, beta_2 = %f" % (popt[0], popt[1]))

x = np.linspace(1960, 2015, 55)
x = x/max(x)
plt.figure(figsize=(8,5))
y = sigmoid(x, *popt)
plt.plot(xdata, ydata, 'ro', label='data')
plt.plot(x,y, linewidth=3.0, label='fit')
plt.legend(loc='best')
plt.ylabel('GDP')
plt.xlabel('Year')
plt.show()

# PRACTICE: Accuracy Test

# split data into train/test
msk = np.random.rand(len(df)) < 0.8
train_x = xdata[msk]
test_x = xdata[~msk]
train_y = ydata[msk]
test_y = ydata[~msk]

# build the model using train set
popt, pcov = curve_fit(sigmoid, train_x, train_y)

# predict using test set
y_hat = sigmoid(test_x, *popt)
print(y_hat)

# evaluation
print("Mean absolute error: %.2f" % np.mean(np.absolute(y_hat - test_y)))
print("Residual sum of squares (MSE): %.2f" % np.mean((y_hat - test_y) ** 2))
from sklearn.metrics import r2_score
print("R2-score: %.2f" % r2_score(y_hat , test_y) )


import numpy as np

def nb_path_maze(maze):
	"""
	Given a maze with 0 as paths and 1 as walls, beginning at top left,
	with only possibilities to go down or right. Return the number of possible paths.

	O(n) time and space
	"""
	I,J = maze.shape
	sheet = np.zeros((I,J))

	sheet[0,0] = 1

	for i in range(I):
		for j in range(J):
			if maze[i,j] == 0 and (i,j) != (0,0) :
				sheet[i,j] = sheet[max(0,i-1),j] + sheet[i,max(0,j-1)]

	return sheet[i,j]

maze = np.array(
	[[0,1,0],
	 [0,0,1],
	 [0,0,0]]
	)
print(maze)
print(f"{nb_path_maze(maze)}, expected 2")

maze = np.array(
	[[0,1,0,0],
	 [0,0,0,0],
	 [1,0,1,0],
	 [0,0,0,0]]
	)
print(maze)
print(f"{nb_path_maze(maze)}, expected 2")

maze = np.array(
	[[0,0,1,0],
	 [0,0,0,0],
	 [1,0,1,0],
	 [0,0,0,0]]
	)
print(maze)
print(f"{nb_path_maze(maze)}, expected 4")

maze = np.array(
	[[0,0,0,0],
	 [0,0,1,0],
	 [0,1,0,0],
	 [0,0,0,0]]
	)
print(maze)
print(f"{nb_path_maze(maze)}, expected 2")


import numpy as np

def euclidean(u, v):
    """
    Calculates the euclidean distance between vectors u an v

    precondition:
        u.shape == v.shape

    :param u: vector of numbers
    :param v: vector of numbers
    :return: euclidean distance between u and v
    """

    if u.shape != v.shape:
        raise ValueError("The size of u and v differs")

    return np.sqrt(np.power(u - v, 2).sum())

def manhattan(u, v):
    """
    Calculates the manhattan distance (also known as taxicab geometry) between
    vectors u an v.

    precondition:
        u.shape == v.shape

    :param u: vector of numbers
    :param v: vector of numbers
    :return: manhattan distance between u and v
    """

    if u.shape != v.shape:
        raise ValueError("The size of u and v differs")

    return np.abs(u - v).sum()

def cosine(u, v):
    """
    Calculates the cosine distance (1 - cosine similarity) between two vectors

    precondition:
        u.shape == v.shape

    :param u: vector of numbers
    :param v: vector of numbers
    :return: cosine distance between u and v
    """

    if u.shape != v.shape:
        raise ValueError("The size of u and v differs")

    return 1 - u.dot(v) / np.sqrt(u.dot(u)*v.dot(v))


import numpy as np
import time

def monte_carlo(func, low, high, scale_coefficient=None, random_generator=None, sample_numbers=None, density_func=None):
    start_time = time.time()
    integral_func = func
    if density_func is not None:
        integral_func = lambda x: func(x) / density_func(x)

    if sample_numbers is None:
        sample_numbers = int(np.abs((high - low) * 1000))

    if random_generator is None:
        random_generator = lambda n: (high - low) * np.random.rand(n) + low

    if scale_coefficient is None:
        scale_coefficient = high - low

    random_x = random_generator(sample_numbers)
    func_values = integral_func(random_x)
    integral_value = scale_coefficient * np.mean(func_values)
    value_error = scale_coefficient * np.std(func_values) / np.sqrt(sample_numbers)

    end_time = time.time()

    return integral_value, value_error, (end_time - start_time)

if __name__ == "__main__":
    func = lambda x: np.exp(-(x ** 2))
    low = 0
    high = 2
    sample_numbers = 10 ** np.arange(3, 9)

    for sample in sample_numbers:
        simple_sampling_value, simple_sampling_error, simple_sampling_runtime = monte_carlo(
            func, low, high, sample_numbers=sample)

        random_generator_1 = lambda n: -np.log(np.exp(-low) - (np.exp(-low) - np.exp(-high)) * np.random.rand(n))
        density_func_1 = lambda x: np.exp(-x)
        scale_coefficient_1 = np.exp(-low) - np.exp(-high)
        important_sampling_value_1, important_sampling_error_1, important_sampling_runtime_1 = monte_carlo(
            func, low, high, scale_coefficient_1, random_generator_1, sample, density_func_1)

        random_generator_2 = lambda n: np.tan((np.arctan(high) - np.arctan(low)) * np.random.rand(n))
        density_func_2 = lambda x: 1 / (x ** 2 + 1)
        scale_coefficient_2 = np.arctan(high) - np.arctan(low)
        important_sampling_value_2, important_sampling_error_2, important_sampling_runtime_2 = monte_carlo(
            func, low, high, scale_coefficient_2, random_generator_2, sample, density_func_2)

        print(f'Sample Numbers: {sample}:')

        print(f'Simple Sampling:\n'
              f'\tvalue: {simple_sampling_value}\n'
              f'\terror: {simple_sampling_error}\n'
              f'\ttime: {simple_sampling_runtime}')

        print(f'Important Sampling: g(x) = e^(-x)\n'
              f'\tvalue: {important_sampling_value_1}\n'
              f'\terror: {important_sampling_error_1}\n'
              f'\ttime: {important_sampling_runtime_1}')

        print(f'Important Sampling: g(x) = 1 / (x^2 + 1)\n'
              f'\tvalue: {important_sampling_value_2}\n'
              f'\terror: {important_sampling_error_2}\n'
              f'\ttime: {important_sampling_runtime_2}')

        print(end='\n')


import numpy as np
from math import *
import matplotlib.pyplot as plt

''' The problem is: dy/dt = f(t,y).'''

def f(t, y):
    return 0.5*y

# We will use RK4 to compute some intiall values needed to Adams-Bashforth Method
## a,b: edges of the interval, w0:ititial value, h:step
def RungeKutta4(a, b, w0, h):
    n = ceil((b-a) / h)

    w, t = np.zeros(n+1), np.zeros(n+1)
    w[0], t[0] = w0, a

    for i in range(n):
        k1 = h*f(t[i], w[i])
        k2 = h*f(t[i] + h/2, w[i] + k1/2)
        k3 = h*f(t[i] + h/2, w[i] + k2/2)
        k4 = h*f(t[i] + h, w[i] + k3)
        w[i+1] = w[i] + 1/6*(k1 + 2*k2 + 2*k3 + k4)
        t[i+1] = t[i] + h

    return w, t

# a,b: edges of the interval, w0,w1,w2,w3:ititial value, h:step
def AdamsBashforth(a, b, w0, w1, w2, w3, h):
    n = ceil((b-a) / h)

    w, t = np.zeros(n+1), np.zeros(n+1)
    t[0], t[1], t[2], t[3] = a, a+h, a+2*h, a+3*h
    w[0], w[1], w[2], w[3] = w0, w1, w2, w3

    for i in range(3, n):
        w[i+1] = w[i] + h/24*(55*f(t[i],w[i]) - 59*f(t[i-1],w[i-1]) + 37*f(t[i-2],w[i-2]) - 9*f(t[i-3],w[i-3]))
        t[i+1] = t[i] + h

    return w, t

if __name__ == '__main__':

    a, b, w0, h = 0, 2, 1, 0.2
    w = RungeKutta4(a, b, w0, h)[0]
    print("\n", AdamsBashforth(a, b, w0, w[1], w[2], w[3], h)[0])

    # Visualization
    x = np.linspace(0, 2, 100)

    y = np.zeros(len(x))

    for j in range(len(x)):
        y[j] = exp(0.5*x[j])

    plt.plot(x, y, color="black", label="Solution")
    plt.plot(AdamsBashforth(a, b, w0, w[1], w[2], w[3], h)[1], AdamsBashforth(a, b, w0, w[1], w[2], w[3], h)[0], label="Adams-Bashforth")
    plt.xlabel("t")
    plt.ylabel("y")
    plt.legend()
    plt.show()


import numpy as np

def sigmoid(z):
    """
    Return:
    s -- sigmoid(z)
    """

    s = 1./(1+np.exp(-z))

    return s

def RELU(z):
    """
    Return:
    s -- RELU(z)
    """
    s = np.maximum(z,0)
    return s
def Parametric_RELU(z, a):
    """
    z -- vector
    a -- parameter int
    Return:
    s -- Parametric_RELU(z)
    """
    s = np.maximum(z, z*a )
    return s

def Leaky_RELU(z):
    """
    Return:
    s -- RELU(z)
    """
    # s = np.where( z>0 , z, 0.01*z)
    s = np.maximum(z, z*0.01 )
    return s

def Tanh(z):
    """
    Return:
    s -- tanh(z)
    """
    s = (np.exp(z) - np.exp(-z))/(np.exp(z) + np.exp(-z))

    return s

def Silu(z):
    """
    Return:
    s -- sigmoid(z)*z
    """
    s = z*sigmoid(z)

    return s


# Gradient descent method of univariate linear regression.

import numpy as np
import matplotlib.pyplot as plt

class LinearRegression:
    def __init__(self):
        self._X = None
        self._y = None
        self._beta0 = 0.
        self._beta1 = 0.
        self._correlation = 0.

    def _mean(self, arr):
        return np.mean(arr)

    def _diff(self, arr, num):
        return np.subtract(arr, num)

    def _run_epoch(self, X, y, num_epochs, lr):
        curr_error = 0.
        n = len(y)
        for i in range(num_epochs):
            curr_prediction = self.predict(X)
            curr_error = self._rmse_error(y, curr_prediction)
            beta0_grad = (2. / n) * np.sum(np.subtract(curr_prediction, y))
            beta1_grad = (2. / n) * np.sum(np.dot(np.subtract(curr_prediction, y), X))

            self._beta0 -= lr * beta0_grad
            self._beta1 -= lr * beta1_grad

            print('Loss at {} epoch: {}'.format((i + 1), round(curr_error, 5)))
        return curr_error

    def fit(self, X, y, num_epochs, lr, seed=np.random.randint(0, 1000000)):
        self._X = X
        self._y = y
        self._compute_correlation()
        np.random.seed(seed)
        self._beta0 = np.random.uniform(-0.1, 0.1)
        self._beta1 = np.random.uniform(-0.1, 0.1)
        rmse_error = self._run_epoch(self._X, self._y, num_epochs, lr)
        return rmse_error

    def _rmse_error(self, y, y_pred):
        return np.sqrt(np.mean(np.subtract(y, y_pred) ** 2))

    def predict(self, input):
        return np.add(self._beta0, np.dot(self._beta1, input))

    def weight_coefficients(self, decimal=4):
        return round(self._beta0, decimal), round(self._beta1, decimal)

    def _compute_correlation(self):
        sum_x = np.sum(self._X)
        sum_y = np.sum(self._y)
        sum_xy = np.sum(np.multiply(self._X, self._y))
        sum_xsquare = np.sum(np.multiply(self._X, self._X))
        sum_ysquare = np.sum(np.multiply(self._y, self._y))
        n = len(self._X)
        self._correlation = (n * sum_xy - sum_x * sum_y) / np.sqrt(((n * sum_xsquare - sum_x ** 2) * (n * sum_ysquare - sum_y ** 2)))

    def correlation(self, decimal=4):
        return round(self._correlation, decimal)

    def plot_regression(self, X, y):
        plt.scatter(X, y, color="red", marker="x", s=20)

        y_pred = self._beta0 + np.multiply(self._beta1, X)

        plt.plot(X, y_pred, color="green")

        plt.xlabel('X')
        plt.ylabel('y')

        plt.show()

def main():
    linear_reg = LinearRegression()
    X = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    y = np.array([6, 8, 9, 11, 13, 16, 17, 19, 20, 24])

    rmse_error = linear_reg.fit(X, y, 200, 0.03, seed=12345)
    weights = linear_reg.weight_coefficients()
    print 'Weight coefficients for y = mx + c: m = {}, c = {}\n'.format(weights[1], weights[0])
    print 'Correlation between X and y: {}\n'.format(linear_reg.correlation())
    print 'Root mean-squared error for training: {}\n'.format(rmse_error)

    test_y = [3, 5, 10]
    print 'Prediction of {}: {}'.format(test_y, (linear_reg.predict(test_y)))
    linear_reg.plot_regression(X, y)

if __name__ == '__main__':
    main()


# arrays
# python has inbuilt arrays and well as numpy arrays
# we just use numpy array as the inbuilt ones are less flexible
l=[546,375,254,651,781,569,454,564]

import numpy as np
a=np.array(l)
# if we convert a list with multiple data types (such as int, string, boolean, etc.)
# the numpy.array() function will convery all elements into a string, so it becomes an array of strings
# so data type of all elements in an array are same

print(a)

# when you slice an array you dont create a copy of an array
# so any modification to the slice of an array will be reflected in the actual array
a=[1,2,3,4]
a=np.array(a) # array([1, 2, 3, 4])
b=a[0:2]
b[:]=111 # array([1, 2])
# now, b becomes array([111, 111])
# but c also becomes array([111, 111,   3,   4])

# lets say we have an array a
a=[1,2,3,4]
np.where(a == np.max(a))
# numpy.where() function iterates over a bool array, and for every True, it yields corresponding the element array x, and for every False, it yields corresponding item from array y. So, it returns an array of elements from x where the condition is True and elements from y elsewhere.
# in our example we get (array([x,y,z,..], dtype=int64),) as output
# this means, max value of the array exists at following indices array([x,y,z,..],)
# if max value exists in muliple places then all indices where it exists will be returned

# building your first matrix
import numpy as np
a=np.arange(0,20)
np.reshape(a,(5,4),order='F') # order is optional, if not specified then 'C' is the default order
# we could also do
a.reshape(5,4)
# C (default behavior) - matrix gets filled row by row, similar to C
# F - matrix gets filled column by column, similar to Fortran

# INDEXING
# MULTI DIMENTIONAL LIST  -  CONVERT INTO A NUMPY ARRAY AND THEN INDEXING
L= [ [1,2,3], [4,5,6] , [7,8,9] ] # this list has 3 rows and 3 columns
L=np.array(L)
L[1,1] # returns 5
L[0,:] # returns first row; we could also just say L[0]
L[:,0] # returns first column

# Dictionary is quite useful in matrix indexing, example
m=np.array([[1,2,3],[4,5,6],[7,8,9]])
col_names={'age':0,'weight':1,'height':2}
row_names={'vivek':0,'ale':1,'neety':2}
# now we can get weight of ale using actual indexes or dict indexes
m[1,1] # 5
m[row_names['ale'],col_names['weight']] # 5

# arithmetic operation is element by element
# division by zero shows a warning, and the result of div by zero is nan
# result can be rounded using matrix.round method
A=np.array([[1,2,3],[4,5,6],[7,8,9]])
B=np.array([[1,2,3],[4,5,6],[7,8,9]])
A+B
A-B
A*B
np.matrix.round(A/B)
A**B

# visualization
import numpy as np
import matplotlib.pyplot as plt
# in jupyter notebook we need to specify %matplotlib inline
plt.rcParams['figure.figsize']=8,4 # just need to set it once, can alter the size of plots

plt.plot(m[0], c='Blue',ls='--',marker='s',ms=17,label='vivek') # c is color, ls - line style, marker='s' - square marker, marker size=17
plt.plot(m[1], c='Magenta',ls='--',marker='s',ms=17,label='ale')
plt.plot(m[2], c='Green',ls='--',marker='s',ms=17,label='neety')
plt.legend(loc='upper left', bbox_to_anchor=(1,1))
plt.xticks([0,1,2],['age','weight','height'],rotation='vertical') #xticks replaces 0,1,2 in x-axis with 'age','weight','height'. We could also specify [0,1,2] using range function like list(range(0,3))
plt.show()

# Covariance matrix
data = np.array([[45,37,42,35,39],
                [38,31,26,28,33],
                [10,15,17,21,12]])
covMatrix = np.cov(data,bias=True)
sns.heatmap(covMatrix, annot=True, fmt='g')

# Create a Correlation Matrix using Pandas
df = pd.DataFrame({'A': [45,37,42,35,39],
        'B': [38,31,26,28,33],
        'C': [10,15,17,21,12]})
corrMatrix=df.corr()
sns.heatmap(corrMatrix, annot=True)


#!/bin/env python3
# -*- coding:utf-8 -*-

# def p(date):
#     print(date)

def create_array():
    from numpy import arange, uint16, array

    a = arange(5)
    print("a = ",a," a.dtype = ",a.dtype," a.shape  = ",a.shape)

    m = array([arange(5),arange(10)])
    print("m = ",m," m.dtype = ",m.dtype," m.shape  = ",m.shape)

    t = arange(7, dtype = uint16)
    print("t = ",t," t.dtype = ",t.dtype)

def array_property():
    from numpy import arange

    a = arange(27).reshape(3,3,3)
    print("a = ", a)
    print("a.ndim = ",a.ndim)
    print("a.size = ",a.size)
    a.resize(3,9)
    # print("a.resize = ", a.resize(3,9))
    print("a = ", a)
    print("a.T = ", a.T)
    # p("a.T = ", a.T)

def lissajout():
    import sys
    from matplotlib.pyplot import plot,show
    from numpy import linspace,pi,sin

    a = float(sys.argv[1] )
    b = float(sys.argv[2] )

    t = linspace(-pi, pi, 201)
    x = sin(a + t + pi / 2)
    y = sin(b * t)
    plot(x,y)
    show()

def fourier():
    from numpy.fft import fft, ifft
    from matplotlib.pyplot import plot, show
    from numpy import linspace, pi, cos, abs, all

    x = linspace(0, 2 * pi, 30)
    wave = cos(x)
    transformed = fft(wave)
    print(all(abs(ifft(transformed) -wave) < 10 ** -9))
    plot(transformed)
    show()

def fourier2():
    from numpy.fft import fft, ifft
    from matplotlib.pyplot import plot, show
    import numpy as np

    x = np.linspace(0, 2 * np.pi, 30)
    wave = np.cos(x)
    transformed = fft(wave)
    print(np.all(np.abs(ifft(transformed) -wave) < 10 ** -9))
    plot(transformed)
    show()

def plot_sinc():
    from numpy import sinc, linspace
    from matplotlib.pyplot import plot, show

    x = linspace(0, 4, 100)
    vals = sinc(x)

    plot(x, vals)
    show()

def plot_sinc2d():
    from numpy import linspace, outer, sinc
    from matplotlib.pyplot import imshow, show

    x = linspace(0, 4, 100)
    xx = outer(x,x)
    vals = sinc(xx)

    print("x = ", x, " xx = ", xx, "vals = ", vals)
    imshow(vals)
    show()

def kaiser():
    from numpy import kaiser
    from matplotlib.pyplot import plot, show

    window = kaiser(42,14)
    plot(window)
    show()

if __name__ == '__main__':
    # create_array()
    # array_property()
    # lissajout()
    # print(sin(4))  # undefined
    # fourier()
    # fourier2()
    # plot_sinc()
    # plot_sinc2d()
    kaiser()


import numpy as np

#x should be [.....,1] for the bias term
def logit(x,theta):
    return 1/(1+1/np.exp(x@theta))

def get_single_loss(x,y,theta,epsilon):
    return y*np.log(max(logit(x,theta),epsilon))+(1-y)*np.log(max(1-logit(x,theta),epsilon))

def get_gradient(x,y,theta):
    return y*x-x*logit(x,theta)

def get_loss(X,Y,theta,epsilon):
    n=len(X[0])
    loss=[]
    for i in range(n):
        loss.append(get_single_loss(X[i],Y[i],theta,epsilon))
    return loss

class logistic_model:
    def __init__(self):
        self.theta=[]

    def fit(self,X,Y,lr=0.01,limit=10000,epsilon=1e-4,verbose=True):

        #initialize weights

        n=len(X[0])
        self.theta=np.random.rand(n)
        # e is scalar
        k=0
        while k<limit:
            for i in range(n):
#                 print(X[i]@self.theta)
#                 print(logit(X[i],self.theta))
                self.theta=self.theta+lr*get_gradient(X[i],Y[i],self.theta)
                k+=1
                if k>limit:
                    break
#             print(X[0]@self.theta)
#             print(logit(X[0],self.theta))
#             print(get_gradient(X[0],Y[0],self.theta))
#             print(logit(X[8],self.theta))
            if verbose:
                print(sum(get_loss(X,Y,self.theta,epsilon)))
#             print(self.theta[:5])
        return

    def predict(self,X):
        n=len(X)
        res=[]
        for i in range(n):
            ind=logit(X[i],self.theta)
            res.append(ind)
        return res


from sympy import *
from quaternion_R_utils import *

fx,fy,cx,cy = symbols('fx fy cx cy');
px, py, pz = symbols('px py pz')
q0, q1, q2, q3 = symbols('q0 q1 q2 q3')
tie_px, tie_py, tie_pz = symbols('tie_px tie_py tie_pz');
u_kp, v_kp = symbols('u_kp v_kp')

position_symbols = [px, py, pz]
quaternion_symbols = [q0, q1, q2, q3]
tie_point_symbols = [tie_px, tie_py, tie_pz]
all_symbols = position_symbols + quaternion_symbols + tie_point_symbols

RT_wc = matrix44FromQuaternion(px, py, pz, q0, q1, q2, q3)
R_cw=RT_wc[:-1,:-1].transpose()
T_wc=Matrix([px, py, pz]).vec()
T_cw=-R_cw*T_wc

point_global=Matrix([tie_px, tie_py, tie_pz]).vec()
point_local = R_cw * point_global + T_cw;
s = 1.0 / point_local[2];
u = s * fx * point_local[0] + cx;
v = s * fy * point_local[1] + cy;
u_delta = u_kp - u;
v_delta = v_kp - v;

obs_eq = Matrix([u_delta, v_delta]).vec()
obs_eq_jacobian = obs_eq.jacobian(all_symbols)

print(obs_eq)
print(obs_eq_jacobian)

with open("perspective_camera_quaternion_wc_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void observation_equation_perspective_camera_quaternion_wc(Eigen::Matrix<double, 2, 1> &delta, double fx, double fy, double cx, double cy, double px, double py, double pz, double q0, double q1, double q2, double q3, double tie_px, double tie_py, double tie_pz, double u_kp, double v_kp)\n")
    f_cpp.write("{")
    f_cpp.write("delta.coeffRef(0,0) = %s;\n"%(ccode(obs_eq[0,0])))
    f_cpp.write("delta.coeffRef(1,0) = %s;\n"%(ccode(obs_eq[1,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_perspective_camera_quaternion_wc_jacobian(Eigen::Matrix<double, 2, 10, Eigen::RowMajor> &j, double fx, double fy, double cx, double cy, double px, double py, double pz, double q0, double q1, double q2, double q3, double tie_px, double tie_py, double tie_pz)\n")
    f_cpp.write("{")
    for i in range (2):
        for j in range (10):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(obs_eq_jacobian[i,j])))
    f_cpp.write("}")


import argparse
from math import pi
import numpy as np
import os
import pandas as pd

MIN_RADIUS = 0.5
MAX_RADIUS = 2.0
MIN_HEIGHT = 0.5
MAX_HEIGHT = 2.0
ADDED_ERROR = 0.1
NUM_DECIMALS = 1

def generate_cylinder_df(size):
    """Generate a dataframe of cylinders where the radius and height of
    each cylinder are both in the range 0.5 to 2.0 and the volume equals
    (pi * r^2) * h. Then add up to a 10% error (uniformly distributed) to
    the volume, followed by rounding off radius, height and volume to the
    nearest 0.1.
    """

    # Generate radiuses and heights
    radius = np.random.uniform(MIN_RADIUS, MAX_RADIUS, size=size)
    height = np.random.uniform(MIN_HEIGHT, MAX_HEIGHT, size=size)

    # Calculate the correct volumes with those radiuses and heights
    volume = (pi * radius ** 2) * height

    # Add the error to the volumes
    volume = volume * np.random.uniform(
        1 - ADDED_ERROR,
        1 + ADDED_ERROR,
        size=size)

    # Then round off radius, height and volume
    radius = np.round(radius, decimals=NUM_DECIMALS)
    height = np.round(height, decimals=NUM_DECIMALS)
    volume = np.round(volume, decimals=NUM_DECIMALS)

    df = pd.DataFrame({
        'volume': volume,
        'radius': radius,
        'height': height, })

    return df

if __name__ == '__main__':
    if ('get_ipython' not in dir()) & ('PYCHARM_HOSTED' not in os.environ):
        # i.e. if run from the command line

        # Handle command line arguments
        parser = argparse.ArgumentParser()
        parser.add_argument(
            '--filename',
            type=str,
            help='the filename to save the cylinders to',
            required=True)
        parser.add_argument(
            '--size',
            type=int,
            help='the number of cylinders to generate',
            required=True)
        # parser.add_argument(
        #     '--job-dir',
        #     help='this model ignores this field, but it is required by gcloud',
        #     default='junk')
        args = parser.parse_args()
        arguments = args.__dict__
        # arguments.pop('job_dir', None)

        # Generate cylinders and write them to file
        generate_cylinder_df(arguments['size']).to_csv(
            arguments['filename'],
            index=False)

        print('saved {} cylinders to {}'.format(
            arguments['size'],
            arguments['filename']))

    else:  # if run from a notebook or IDE
        files_to_generate = {
            'input/cylinders_train.csv': 8000,
            'input/cylinders_eval.csv': 1000,
            'input/cylinders_test.csv': 1000,}

        for filename, size in files_to_generate.items():
            generate_cylinder_df(size=size).to_csv(
                filename,
                index=False)

            print('saved {} cylinders to {}'.format(
                size,
                filename))


import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import simps
from numpy import trapz
from scipy.integrate import quad
x1= np.array([0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0])
y1=np.array([0.0,0.5,2.0,4.05,8.0,12.5,18.0,24.5,32.0,40.5,50.0])
h=0.1 ; a=x1[0] ; b=x1[9]
n=int((b-a)/h) ; S=0.5*(y1[0]+y1[10]) ; S1 = y1[0]+y1[10]
for i in range(1,10):
       if i%2 == 0:
           S1 = S1 + 2 * y1[i]
       else:
           S1 = S1 + 4 * y1[i]
       S+= y1[i]
Integral = S1 * h/3 ; Power = S * h
print("Power using trapezoidal and simpson",("%f"%Power,"%f"%Integral))
def trap(f,a,b,n):
    h=(b-a)/n
    S=0.5*(f(a)+f(b))
    for i in range(1,n):
        S+= f(a+i*h)
    Integral = S * h
    return Integral
def simpson(f,a,b,n):
    h=(b-a)/n
    S = f(a) + f(b)
    for i in range(1,n):
        if i%2 == 0:
            S = S + 2 * f(a + i*h)
        else:
            S = S + 4 * f(a + i*h)
    Integral = S * h/3
    return Integral

def integration(f,a,b,n):
    print("a =",a,"; b =" ,b, " ; Number of intervals =",n)
    h=(b-a)/n
    x = np.linspace(a,b,n+1)
    y = f(x)
    inte , err = quad(f, a, b)   #absolute value taken for comparing my results
    Integral_trap= trap(f,a,b,n)
    Integral_simp=simpson(f,a,b,n)
    print("Integral using scipy function (Quad) = ",inte)
    print("Integral (using composite trapezoidal formula) = %f" %Integral_trap)
    print("Error in composite trapezoidal (by subtracting from the one i got from inbuilt quad)= ",abs(Integral_trap-inte))
    print("Integral (using composite simpson formula) = %f" %Integral_simp)
    print("Error in composite simpson (by subtracting from the one i got from inbuilt quad) = ",abs(Integral_simp-inte))
    y_data_2,y_data,y_data_3=[],[],[]
    geo= np.array([10**i for i in range(4) ])
    n_array=np.arange(30,400,2)
    h_array=(b-a)/n_array
    for n in n_array:
        x_data=np.linspace(a,b,n+1)
        y_data.append(trap(f,a,b,n))
        y_data_2.append(simpson(f,a,b,n))
    d=[inte]*len(h_array)
    y_data_3=np.array(d)
    plt.plot(h_array,y_data,label="Trapezoidal rule")
    plt.scatter(h_array,y_data,label="Trapezoidal rule")
    plt.plot(h_array,y_data_2,label="Simpson's rule")
    plt.scatter(h_array,y_data_2,label="Trapezoidal rule")
    plt.plot(h_array,y_data_3,linestyle='--',label="scipy's simpson implementation")
    plt.xlabel("h")
    plt.ylabel("I(h)")
    plt.title("I(h) vs h plot [Convergence Test]")
    plt.legend()
    plt.grid()
    plt.xscale('log')
    plt.show()
f = lambda x : x*x
integration(f,0,10,100)


import numpy as np

from network.my_types import Data

# Activation Functions

def null(_: float) -> float:
    """
    Function that always returns 0
    :param _: Input value to function
    :type _: int
    :return: Always 0
    :rtype: int
    """
    return 0

def sigmoid(x: float) -> float:
    """
    Implementation of the Sigmoid activation function
    :param x: Input value to function
    :type x: float
    :return: sigmoid(x)
    :rtype: float
    """
    return 1 / (1 + np.exp(-x))

def gaussian(x: float) -> float:
    """
    Implementation of the Gaussian activation function
    :param x: Input value to function
    :type x: float
    :return: gaussian(x)
    :rtype: float
    """
    return np.exp(-(x**2/2))

def identity(x: float) -> float:
    """
    Identity function to be used in place of no activation function
    :param x: Input value to function
    :type x: float
    :return: x
    :rtype: float
    """
    return x

# Training Functions
def cubic(x: float) -> float:
    """
    Cubic training function
    :param x: Input value to function
    :type x: float
    :return: x^3
    :rtype: float
    """
    return x ** 3

def xor(x1: int, x2: int) -> int:
    """
    xor training function
    :param x1: Input value to function
    :param x2: Input value to function
    :type x1: int
    :type x2: int
    :return: x1 XOR x2
    :rtype: int
    """
    return x1 ^ x2

def complex_train(x1: float, x2: float) -> float:
    """
    complex training function
    :param x1: Input value to function
    :param x2: Input value to function
    :type x1: float
    :type x2: float
    :return: 1.9{ 1.35 + e^x1-x2 sin[13(x1 -0.6)^2]sin[7x2] }
    :rtype: int
    """
    return 1.9 * (1.35 + np.exp(x1 - x2) * np.sin(13*((x1-0.6)**2)) * np.sin(7*x2))

# Error Functions
def mean_squared_error(predictions: Data, ground_truth: Data) -> float:
    """
    Function to take the MSE (Mean Squared Error) of two equal length lists of output vectors.
    Commonly used to take get the MSE of predictions from a network and the ground truth. All vectors in the list must
    have equal length. While the order of the supplied lists doesn't matter, they are named for clarity and convention.
    :param predictions: The predictions from a network
    :type predictions: Data
    :param ground_truth: The ground truths to compare against
    :type ground_truth: Data
    :return: The MSE (Mean Squared Error) of all supplied samples
    :rtype: float
    """
    # Ensure arguments are of the same length
    if len(ground_truth) != len(predictions):
        raise ValueError(f"Number of predictions ({len(predictions)}) does not match ground "
                         f"truth ({len(ground_truth)})")

    # As the function accepts single vectors or lists of vectors, make the arguments conform to expectations
    if type(predictions[0]) == int:
        predictions = [predictions]
    if type(ground_truth[0]) == int:
        ground_truth = [ground_truth]

    # Create a list of the absolute summed error between corresponding vectors
    per_sample_error = [sum(np.absolute(np.array(x) - np.array(y))) for x, y in zip(ground_truth, predictions)]

    # Square error between samples and take the mean
    return sum(np.array(per_sample_error) ** 2) / len(ground_truth)


#Una caja contiene n_total transistores de los cuales n_funciona
#funcionan correctamente y n_no_funciona no funcionan
#correctamente. Los transistores se van probando uno a uno para ver si
#funcionan. Una vez se prueba un transistor se retira de la caja y no
#se devuelve. Sea N_1 el número de pruebas que se hicieron hasta
#encontrar el primer transistor dañado y N_2 el número de pruebas
#realizadas hasta encontrar el segundo transistor dañado. Escriba una
#función de Python que encuentre la probabilidad conjunta de N_1 y N_2
#usando un método Monte Carlo usando un número total de diez mil
#iteraciones.

#La función debe llamarse probabilidad_conjunta.  La función toma como
#entrada dos variables enteras n_funciona y n_no_funciona (en ese
#orden). La función debe devolver un array bidimensional de numpy
#(i.e. con filas y columnas). En cada dirección el array tiene
#dimensión n_total+1. Al calificar la entrega vamos a llamar a la
#función de la siguiente manera:

# p = probabilidad_conjunta(n_funciona, n_no_funciona)

# print(p[N_1, N_2])

#donde n_funciona, n_no_funciona, N_1, y N_2 son variables enteras que
#se inicializan al momento de calificar.

# La función debe estar en un archivo llamado
# "ApellidoNombre_Ejercicio09.py" donde Apellido y Nombre
# debe reemplazarlos con su apellido y nombre.  Suba ese archivo como
# respuesta a esta actividad.

#Al ejecutar "python ApellidoNombre_Ejercicio09.py" o al
#llamar la funci ón no se debe producir ningún error.

# Solamente puede utilizar las funciones y métodos vistas en clase
# (videos o clases sincrónicas, o que ya se encuentren en el
# repositorio) .

import numpy as np

def probabilidad_conjunta(n_funciona, n_no_funciona):
    funciona = 0
    no_funciona = 1

    transistores = np.array(n_funciona*[funciona] + n_no_funciona*[no_funciona])

    n_total = n_funciona + n_no_funciona
    probabilidad = np.zeros([n_total+1, n_total+1])
    n_realizaciones = 100000
    for i in range(n_realizaciones):
        np.random.shuffle(transistores)

        intentos = np.arange(len(transistores))+1

        intentos_sin_funcionar = intentos[transistores==no_funciona]

        N_1 = intentos_sin_funcionar[0]
        N_2 = intentos_sin_funcionar[1]

        probabilidad[N_1, N_2] += 1

    probabilidad /= n_realizaciones
    return probabilidad


# Inverse Kinematics
# Analytical Solution
# Given in "Intro to Robotics by craig" p.109

import numpy as np

def R3robot_IK(T_0N, output_unit='deg', solution1or2=1):
    """
    TODO
    """

    # constants
    L1 = 4 # m
    L2 = 3 # m
    L3 = 2 # m

    # 3 parameters for Planar Robots
    # x, y, phi
    cos_phi = T_03[0,0]
    sin_phi = T_03[0,1]
    phi = np.arccos(cos_phi)
    x = T_03[0,3]
    y = T_03[1,3]
    if x==0 and y==0:
        return "theta 1 is arbitrary for (x,y)=(0,0)"
    cos_theta2 = (x**2 + y**2 - L1**2 - L2**2) / (2*L1*L2)
    if cos_theta2<-1 or cos_theta2>1:
        return "solution does not exist"
    if solution1or2==1: # first solution
        sin_theta2 = (1 - (cos_theta2**2))**0.5
        theta2 = np.arctan2(sin_theta2, cos_theta2)
        k2 = L2*sin_theta2
    elif solution1or2==2: # seconds solution
        sin_theta2 = -1*(1 - (cos_theta2**2))**0.5
        theta2 = np.arctan2(sin_theta2, cos_theta2) # second solution
        k2 = L2*sin_theta2
    k1 = L1 + L2*cos_theta2
    theta1 = np.arctan2(y,x) - np.arctan2(k2,k1)
    theta3 = phi - theta1 - theta2

    if output_unit.lower()=="deg":
        return np.rad2deg(theta1), np.rad2deg(theta2), np.rad2deg(theta3)
    return theta1, theta2, theta3

if __name__ == "__main__":
    from R3robot_FK import R3robot_FK
    # T_03 = R3robot_FK(10,20,30,unit='deg')
    # print(T_03)
    T_03 = np.array([[1,0,0,9],[0,1,0,0],[0,0,1,0],[0,0,0,1]])
    print(R3robot_IK(T_03,solution1or2=2))


import numpy as np
import scipy

def question_1_1(l=84):
    A = np.zeros((l, l))
    b = np.zeros(l)

    for i in range(l):
        A[i, i] = 6
        if i != 0: A[i, i-1] = 8
        if i != l-1: A[i, i+1] = 1

        if i == 0: b[i] = 7
        elif i == l-1: b[i] = 14
        else: b[i] = 15

    A = A.astype(np.float64)
    b = b.astype(np.float64)
    x = np.ones(l)
    return A, b, x

def question_1_2_1(l=100):
    A = np.zeros((l, l))
    for i in range(l):
        A[i, i] = 10
        if i != 0: A[i, i-1] = 1
        if i != l-1: A[i, i+1] = 1
    b = np.random.rand(100)
    x = scipy.linalg.solve(A, b) # scipy as the ground truth
    return A, b, x

def question_1_2_2(l=40):
    A = np.zeros((l, l))
    b = np.zeros(l)
    for i in range(l):
        for j in range(l):
            A[i, j] = 1 / (i + j + 1);
            b[i] += A[i, j]
    x = np.ones(l)
    return A, b, x

def question_4_1(n=100, a=1/2, epsilon=1):
    def get_res(x):
        return (1-a)/(1-np.e**(-1/epsilon))*(1-np.e**(-x/epsilon))+a*x
    h = 1 / n
    A = np.zeros((n-1, n-1))
    b = np.zeros(n-1)
    x = np.zeros(n-1)
    for i in range(n-1):
        b[i] = a * h**2
        if i == n-2: b[i] -= epsilon + h # x[-1] = 0, x[n] = 1
        x[i] = get_res((i+1)*h)
        for j in range(n):
            if i>0: A[i, i-1] = epsilon
            A[i, i] = - (2 * epsilon + h)
            if i<n-2: A[i, i+1] = epsilon + h
    return A, b, x

def question_6_1(a=[3, -5, 1]):
    n = len(a)
    A = np.zeros((n,n))
    A[n-1, 0] = 1
    A[n-1, n-1] = -a[n-1]
    for i in range(1, n):
        A[0, i] = -a[i-1]
        if i > 1: A[i-1, i] = 1
    return A


import numpy as np
import math
import scipy.stats as stats

'''
Module for misc. statistical methods
for time series data
'''

# Detrends the target values of a time series
# by subtracting the residuals resulting
# from linear regression.
# Returns a 3-tuple
# containing the vector of the detrended time series,
# and the slope and intercept of the regression line
def detrend(x,y):
	result = stats.linregress(x,y)
	slope = result[0]
	intercept = result[1]
	detrended = []
	for val in range(len(y)):
		pred = slope*val+intercept
		residual = y[val]-pred
		detrended.append(residual)
	return (detrended,slope,intercept)

# Applies a linear trend to a series of data points
# i.e. a[z] = y[z] + trend
#           = y[z] + slope*x[z] + intercept
def reapplyTrend(x,y,slope,intercept):
	rev = np.zeros(len(y))
	for dex in range(len(y)):
		rev[dex] = y[dex] + (slope*x[dex]+intercept)
	return rev

# Performs linear regression on univariate time
# series repr. by xTrain,yTrain, and then makes predictions
# for xTest
# Returns a 1-d vector representing predictions
def predictLinearRegression(xTrain,yTrain,xTest):
	result = stats.linregress(xTrain,yTrain)
	slope = result[0]
	intercept = result[1]
	predict = np.zeros(len(xTest))
	for x in range(len(xTest)):
		predict[x] = slope*xTest[x]+intercept
	return predict

# Performs linear regression on univariate time
# series repr. by xTrain,yTrain, and then makes predictions
# for xTest
# Returns a 1-d vector representing predictions
def predictPolyRegression(xTrain,yTrain,xTest,deg):
	coeff = np.polyfit(xTrain,yTrain,deg)
	predict = np.zeros(len(xTest))
	for x in range(len(xTest)):
		for c in range(len(coeff)):
			predict[x] += coeff[c]*math.pow(xTest[x],len(coeff)-c-1)
	return predict

# Computes the Mean Squared Error for predicted values against
# actual values
def meanSquareError(actual,pred):
	if (not len(actual) == len(pred) or len(actual) == 0):
		return -1.0
	total = 0.0
	for x in range(len(actual)):
		total += math.pow(actual[x]-pred[x],2)
	return total/len(actual)

# Computes Normalized Root Mean Square Error (NRMSE) for
# predicted values against actual values
def normRmse(actual,pred):
	if (not len(actual) == len(pred) or len(actual) == 0):
		return -1.0
	sumSquares = 0.0
	maxY = actual[0]
	minY = actual[0]
	for x in range(len(actual)):
		sumSquares += math.pow(pred[x]-actual[x],2.0)
		maxY = max(maxY,actual[x])
		minY = min(minY,actual[x])
	return math.sqrt(sumSquares/len(actual))/(maxY-minY)

# Computes Mean Absolute Percent Error (MAPE) for predicted
# values against actual values
def mape(actual,pred):
	if (not len(actual) == len(pred) or len(actual) == 0):
		return -1.0
	total = 0.0
	for x in range(len(actual)):
		total += abs((actual[x]-pred[x])/actual[x])
	return total/len(actual)

# Estimates missing data denoted by <targ> in
# a list of lists <data> by averaging the neighboring
# values if they exist, otherwise the mean of the entire
# list (i.e. if a missing value is at the start or end of
# a list)
def estimateMissing(data,targ):
    for x in range(len(data)):
        for y in range(len(data[x])):
            if (data[x][y] == targ):
                if (y > 0 and y < len(data[x])-1):
                    data[x][y] = (data[x][y-1]+data[x][y+1])/2.0
                else:
                    data[x][y] = np.mean(data[x])


import matplotlib.pyplot as plt
import matplotlib.ticker as mtick

import numpy as np
from scipy.stats import norm

# Create an array of points to use as the x-coordinates for plotting the normal distribution
x_min = norm.ppf(0.00005) # we will plot 99.99 % of the normal curve.
x_max = norm.ppf(0.99995)
x = np.linspace(x_min, x_max, 201)

plt.style.use('seaborn-whitegrid')

# paramaters for the normal distribution
mu = 2
sigma = 3

# how to translate between different scales on the x-axis'
def toZscore(x):
    return ((x - mu) / sigma)

def fromZscore(x):
    return ((sigma * x) + mu)

# get a reference to a frozen version of the normal function with the loc and scale paramaters set.
n = norm(loc=mu, scale=sigma)

# Rebuild the array of points to use as the x-coordinates for plotting the normal distribution
x_min = n.ppf(0.000005) # we will plot 99.999 % of the normal curve.
x_max = n.ppf(0.999995)
x = np.linspace(x_min, x_max, 201)

# create a figure and an axes
fig, ax = plt.subplots()
fig.set_figheight(6)
fig.set_figwidth(8)

# Create an Axes object that shares an x axis with the ax Axes that we just created above.
ax2 = ax.twinx()

# format that axis as a percentage.
ax2.yaxis.set_major_formatter(mtick.PercentFormatter(1.0))

# the second curve we want to plot, the CDF.
ax2.plot(x,
         n.cdf(x),
         label='Cumulative Distribution\n Function norm.cdf(x, loc={}, scale={})'.format(mu, sigma),
         lw=2,
         color='green'
        )

# plot our normal function.
ax.plot(x,
        n.pdf(x),
        label='Normal Distribution\n norm.pdf(x, loc={}, scale={})'.format(mu, sigma)
       )

# add our points of interest
ax.plot(fromZscore(-1.96),
        n.pdf(fromZscore(-1.96)),
        color='red',
        marker='o',
        alpha=0.5
       )
ax.plot(fromZscore(1.96),
        n.pdf(fromZscore(1.96)),
        color='red',
        marker='o',
        alpha=0.5
       )
# add vertical lines
ax.vlines(x=[fromZscore(-1.96), fromZscore(1.96)],
          ymin=[0, 0],
          ymax=[n.pdf(fromZscore(-1.96)), n.pdf(fromZscore(1.96))],
          color='red',
          alpha=0.5
         )

# add axis labels, title and legend
ax.set_xlabel('X')
ax.set_ylabel('Probability')
ax.set_title('The Normal (Gaussian) Distribution')

# customize our legend
fig.legend(
    fontsize='small',
    loc='upper left',
    framealpha=0.5,
    bbox_to_anchor=(0.15, 0.85) # note that here the coordinates are relative the the whole figure
)

# add some text.  By default the xy are in the axes coordinates
# but that can be changed with transformations
ax.text(-3.3*sigma,
        n.pdf(fromZscore(-1.96)),
        '$(-1.96\sigma, {0:.2})$'.format(n.pdf(fromZscore(-1.96)))
       )
ax.text(2.1 * sigma + mu,
        n.pdf(1.96*sigma + mu),
        '$(1.96\sigma, {0:.2})$'.format(n.pdf(fromZscore(1.96)))
       )

# add fill below the normal curve
# create an array of x-values to plot against
x2=np.linspace(x_min, n.ppf(0.025))
ax.fill_between(x2, n.pdf(x2), alpha=0.75, facecolor='lightblue')

# add annotation.  By default the xy are in the axes coordinates
# but that can be changed with transformations
text_string ='This area is equal to\nthe value of the CDF at\n'
text_string += 'the right boundary.\nIn this case the right\nboundary is at $-1.96\sigma$\n'
text_string += 'where $\sigma$ = {} and the\n area is $2.50\%$ of the\n area under the curve.'.format(sigma)
ax.annotate(
    text_string,
    xy=(-2.1*sigma + mu, n.pdf(fromZscore(-1.96))/3),
    xytext=(2*sigma + mu,0.05),
    arrowprops=dict(arrowstyle='->'))

# add a horizontal line from the CDF to the right axis and a point at the intersection
ax2.hlines(y=n.cdf(fromZscore(-1.96)),
           xmin=-1.96*sigma + mu,
           xmax=x_max,
           color='green',
           alpha=0.5
          )
# add our dot
ax2.plot(fromZscore(-1.96), n.cdf(fromZscore(-1.96)), marker='o', color='green', alpha=0.4)

# add text to highlight the value of the CDF at -0.96
ax2.text(x_max,0.04, '$2.5\%$'.format(n.cdf(fromZscore(-1.96))))

# adjust the transparency and color of the grids for each axex object
ax.grid(visible=True, color='blue', alpha=.2)
ax2.grid(visible=True, color='green', alpha=0.2)

# add a vertical dashed line at the mean, -sigma and sigma.
ax.vlines(x=mu, ymin=0, ymax=n.pdf(mu), linestyle='dotted')
ax.vlines(x=[mu-sigma, mu+sigma], ymin=0, ymax=n.pdf(mu+sigma), linestyle='dotted')
ax.text(mu+0.02,
        n.pdf(mu)/12,
        '$\mu$ = {}'.format(mu)
       )
ax.text(mu+sigma+0.02,
        n.pdf(mu)/12,
        '$\mu + \sigma$'
       )
ax.text(mu-sigma+0.02,
        n.pdf(mu)/12,
        '$\mu - \sigma$'
       )

# add an additional x-axis
# shift the original axes object up
fig.subplots_adjust(bottom=0.2)
ax3 = ax.secondary_xaxis(-0.08, functions=(toZscore, fromZscore))
ax3.set_xlabel('Standard Deviations')
ax3.xaxis.set_label_coords(-.11,-0.1)
ax3.set_xticks([x for x in range(-4,6)], minor=True)
ax3.tick_params(length = 10, which='major', direction='in')
ax3.tick_params(length= 10, which='minor', direction='inout')
# add tics to our primary x axis
ax.tick_params(length = 10, which='major', direction='in')
ax.tick_params(length= 5, which='minor', direction='inout')
ax.set_xticks([x for x in range(-10,16)], minor=True)

# move our initial x-axis label off the the left.
ax.xaxis.set_label_coords(-0.02, -0.025)

#plt.show()
plt.savefig('figure_3.png', bbox_inches='tight')


# -*- coding: utf-8 -*-
"""
Created on Mon May 11 17:30:16 2020

@author: Dilay Ercelik
"""

import numpy as np
import matplotlib.pyplot as plt
import pickle

# Loads python pickle file
with open('c10p1.pickle', 'rb') as f:
    data = pickle.load(f)

c10p1 = data['c10p1']

print('Code for Question 1:', '\n')

# Given the input correlation matrix Q:
Q = np.array([[0.2, 0.1],[0.1, 0.3]])

# Calculates the eigenvalues ev and the eigenvectors e of the matrix Q:
ev, e = np.linalg.eig(Q)

print(ev)
print(e)

# If we allow learning to go on for a long period of time,
# w becomes the principal eigenvector of matrix Q
# i.e. the eigenvector e of Q with the greater corresponding eigenvalue ev

# Finds the index of the greatest eigenvalue of the 2 eigenvectors of Q
max_ev_index = ev.argmax()

# Prints the expected w for a learning for a long period of time
w_long_learning = 2 * e[:, max_ev_index]

print('If we allow learning to go on for a long period of time, w = {}'.format(w_long_learning))

# Question 1 is not related to Questions 7-8-9

print('Code for Question 7:', '\n')

def normal(data):
    """
    Performs a "zero-mean centering step", or normalisation, on the input data

    Parameters
    ----------
    data : float
        Input data (x, y).

    Returns
    -------
    normal_data : float
        Normalised input data.

    """
    mean = np.mean(data, axis = 0)

    normal_data = data - mean

    return normal_data

# Creates our_normal_data from the raw data c10p1
our_normal_data = normal(c10p1)

# Visualisation of our_normal_data
# To make sure our transformed data is zero-mean centered
plt.figure(1)
plt.scatter(our_normal_data[:, 0], our_normal_data[:, 1], c = 'r', marker = 'o')
plt.title('Oja\'s rule: data cloud centered around (0,0)')
plt.xlabel('u1 (input 1)')
plt.ylabel('u2 (input 2)')

# Implements the update rule for Oja's rule
eta = 1
alpha = 1
delta_t = 0.01

# Creates our random first w value: w_zero
w_zero = w = np.random.rand(2)

# Implementing the updating rule to update w for a number of iterations (e.g. 500).
# Oja's rule for updating w
for step in np.arange(0, 500, 1):

    plt.figure(1)
    u = our_normal_data[np.remainder(step, our_normal_data.shape[0]), :]
    v = u @ w     # @ is used for matrix multiplication
    w = w + delta_t * eta * (v * u - alpha * v * v * w)
    plt.plot(v, marker = '^',  c = 'g')
    plt.plot(w[0], w[1], marker = '*', c = 'b')

# Calculates the correlation matrix C (formula given in the quiz)
# Because our_normal_data is mean_centered, C is also its covariance matrix.
# our_normal_data.shape[0] gives the number M of lines (the number of samples in our_normal_data)
C = np.dot(our_normal_data.T, our_normal_data) / our_normal_data.shape[0]

# Prints the correlation/covariance matrix C of our_normal_data
print('This is the correlation/covariance matrix C of our mean-centered data (our_normal_data): {}'.format(C), '\n')

# Calculates the eigenvalues ev and the eigenvectors e of the matrix C:
ev, e = np.linalg.eig(C)

# Prints eigenvalues (ev) and eigenvectors (e: 2D vectors) of
# the correlation matrix C of the mean-centered data (our_normal_data)
print('The eigenvalues of the correlation/covariance matrix C of our_normal_data are {} and {}'.format(ev[0], ev[1]), '\n')

print('The eigenvectors of the correlation/covariance matrix C of our_normal_data are {} and {}'.format(e[0], e[1]), '\n')

# Answer to Question 7:
## "The correlation matrix C has only one principal eigenvector,
## but there two vectors of length 1/alpha that are parallel to this eigenvector.
## w can converge to either of these two vectors."

### Note to myself:
# The eigenvector corresponding to the eigenvalue of largest magnitude is called the principal eigenvector.

print('Code for Question 8:', '\n')

# Creates our_normal_data with adjusted mean: data2, not zero-mean centered
constants = [2, 2]
data2 = our_normal_data + np.tile(constants, (our_normal_data.shape[0], 1))

# Visualisation of data2
plt.figure(2)
plt.scatter(data2[:, 0], data2[:, 1], marker = 'o',  c = 'r')
plt.title('Oja\'s rule: data2 cloud')
plt.xlabel('u1 (input 1)')
plt.ylabel('u2 (input 2)')

# Creates our random first w value for the second data (not zero-mean centered data)
w2 = w_zero

# Implementing the updating rule to update w for a number of iterations (e.g. 500).
# Oja's rule for updating w
for step in np.arange(0, 500, 1):

    plt.figure(2)
    u = data2[np.remainder(step, data2.shape[0]), :]
    v = u @ w2
    w2 = w2 + delta_t * eta * (v * u - alpha * v * v * w2)
    plt.plot(v, marker = '^',  c = 'g')
    plt.plot(w2[0], w2[1], marker = '*',  c = 'b')

# Answer to question 8:
## "The two vectors that w (w2) converges to in different runs of the algorithm
## are parallel to the vector that points roughly towards the mean of the data (data2)."

print('Code for Question 9:', '\n')

# Visualisation
plt.figure(3)
plt.scatter(our_normal_data[:, 0], our_normal_data[:, 1], marker = 'o',  c = 'r')
plt.title('Data cloud for Hebb Rule')
plt.xlabel('u1 (input 1)')
plt.ylabel('u2 (input 2)')

# Creates our random first w value for the third data (our_normal_data with Hebb Rule)
w3 = w_zero

# Implementing the updating rule to update w for a number of iterations (e.g. 500).
# Hebb rule for updating w

for step in np.arange(0, 500, 1):

    plt.figure(3)
    u = our_normal_data[np.remainder(step, our_normal_data.shape[0]), :]
    v = u @ w3
    plt.plot(v, marker='^',  c='b')
    w3 = w3 + delta_t * eta * (v * u)  # Hebb Rule
    plt.plot(v, marker = '^',  c = 'g')
    plt.plot(w3[0], w3[1], marker = '*',  c = 'b')

plt.show()

# Answer to Question 9:
## "The vectors found by the Hebb learning rule have the same direction
## as those found by Oja's rule (on our_normal_data, in Question 7),
## but the length (of w3) grows without bound as a function of the number of iterations.


import numpy as np
from scipy.linalg import hilbert

def cholesky(A):
	# return np.linalg.cholesky(A)
	n = A.shape[0]
	L = np.zeros_like(A)
	for k in range(n):
		L[k, k] = (A[k, k] - ((L[k, :k] ** 2).sum() if k > 0 else 0)) ** .5
		for i in range(k + 1, n):
			L[i, k] = (A[i, k] - L[i, :k].dot(L[k, :k])) / L[k, k]
	return L

def solve(n, delta=0):
	print('n =', n, ', delta =', delta)
	H = hilbert(n)
	x = np.zeros(n) + 1 + delta
	b = H.dot(x)
	L = cholesky(H)
	# Hx = b, H = LL'
	# LL'x = b
	# x = L'^-1 L^-1 b
	xp = np.linalg.solve(L.T, np.linalg.solve(L, b))
	r = b - H.dot(xp)
	dx = xp - x
	print('||r||_inf:', abs(r).max())
	print('||dx||_inf:', abs(dx).max())

def gen36():
	print('t36 result'.center(30, '-'))
	solve(10)
	solve(10, 1e-7)
	solve(8)
	solve(12)
	print('end t36'.center(30, '-')+'\n')

if __name__ == '__main__':
	gen36()


"""
Optimizer Module
"""

import numpy as np

def derivative(f, x, epsilon = 1e-10):
    """
    Calculate derivative of f at x

    ...

    Parameters
    ---
    f : Function to calculate derivative for
    x : Value at which to calculate derivative
    epsilon(optional): Adjustable precision

    Returns
    ---
    value: Derivative of f at x
    """

    x_ = x + epsilon
    value = (f(x_) - f(x)) / epsilon

    return value

def newton_method(f, x_init = 0, epsilon = 1e-10):
    """
    Newton Raphson Optimizer

    ...

    Parameters
    ---
    f: Function to calculate root for
    x_init(optional) : initial value of x
    epsilon(optional): Adjustable precision

    Returns
    ---
    x: Value of root
    """
    prev_value = x_init + 2 * epsilon
    value = x_init

    iterations = 0
    while abs(prev_value - value) > epsilon:
        prev_value = value

        f_dash = derivative(f, value)
        value = value - f(value) / f_dash

        iterations += 1

    print(f"Newton Method converged in {iterations} iterations")

    return value


##### BT: y = 4 + 3x

import numpy as np
import matplotlib
import matplotlib.pyplot as plt

np.random.seed(2)

X = np.random.rand(1000, 1)
y = 4 + 3*X + 0.2 * np.random.rand(1000, 1)     # noise added

# Building Xbar
matrixOne = np.ones((X.shape[0], 1))
Xbar = np.concatenate((matrixOne, X), axis = 1)
print Xbar

def grad(w):
    """ Dao ham """
    N = Xbar.shape[0]
    return 1/N * Xbar.T.dot(Xbar.dot(w) - y)

def cost(w):
    """ Cost function """
    N = Xbar.shape[0]
    return .5/N*np.linalg.norm(y - Xbar.dot(w), 2)**2

A = np.dot(Xbar.T, Xbar)
b = np.dot(Xbar.T, y)
w_lr = np.dot(np.linalg.pinv(A), b)

print('Solution found by formula: w = ',w_lr.T)

def numerical_grad(w, cost):
    eps = 1e-4
    g = np.zeros_like(w)
    for i in range(len(w)):
        w_p = w.copy()
        w_n = w.copy()
        w_p[i] += eps
        w_n[i] -= eps
        g[i] = (cost(w_p) - cost(w_n))/(2*eps)
    return g

def check_grad(w, cost, grad):
    w = np.random.rand(w.shape[0], w.shape[1])
    grad1 = grad(w)
    grad2 = numerical_grad(w, cost)
    return True if np.linalg.norm(grad1 - grad2) < 1e-6 else False

print( 'Checking gradient...', check_grad(np.random.rand(2, 1), cost, grad))

def myGD(w_init, grad, eta):
    w = [w_init]
    temp = 0
    for it in range(100):
        w_new = w[-1] - eta*grad(w[-1])
        temp += 1
        if np.linalg.norm(grad(w_new))/len(w_new) < 1e-3:
            break
        w.append(w_new)
    return (w, temp)

w_init = np.array([[2], [1]])
(w1, it1) = myGD(w_init, grad, 1)
print('Solution found by GD: w = ', w1[-1].T, ',\nafter %d iterations.' %(it1+1))

# Display result
w = w_lr
w_0 = w[0][0]
w_1 = w[1][0]
x0 = np.linspace(0, 1, 2, endpoint=True)
y0 = w_0 + w_1*x0

# Draw the fitting line
plt.plot(X.T, y.T, 'b.')     # data
plt.plot(x0, y0, 'y', linewidth = 2)   # the fitting line
plt.axis([0, 1, 0, 10])
plt.show()


# estimate delta, gamma, theta, veta
"""
  Name     : c10_39_greeks.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

from scipy import log,exp,sqrt,stats
#
tiny=1e-9
S=40
X=40
T=0.5
r=0.01
sigma=0.2

def bsCall(S,X,T,r,sigma):
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)

def delta1(S,X,T,r,sigma):
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    return stats.norm.cdf(d1)

def delta2(S,X,T,r,sigma):
    s1=S
    s2=S+tiny
    c1=bsCall(s1,X,T,r,sigma)
    c2=bsCall(s2,X,T,r,sigma)
    delta=(c2-c1)/(s2-s1)
    return delta

print("delta (close form)=", delta1(S,X,T,r,sigma))
print("delta (tiny number)=", delta2(S,X,T,r,sigma))


import numpy as np

### Function def
def sigmoid(x):
    """Sigmoid function"""
    return 1 / (1 + np.exp(-x))

def stable_log(x):
    """A more stable version of the log"""
    eps = 1e-15
    return np.log(x + eps)

### Loss functions
def compute_loss_ls(y, tx, w):
    """Computes the loss of the least squares"""
    N = len(y)
    e = y - tx @ w
    loss = 1/(2*N) * e.T @ e

    return loss

def compute_loss_ce(y, tx, w):
    """Computes the cross entropy loss for the sigmoid function"""
    eps = 1e-15
    N = len(y)
    Z = tx @ w
    y_ = sigmoid(Z)
    pos_mask = y > 0

    loss = -(1/N) * (stable_log(y_[pos_mask]).sum() + stable_log(1 - y_[~pos_mask]).sum())

    return loss

def compute_loss_reg_ce(y, tx, w, lambda_):
    """Loss of the regularised cross entropy"""
    return compute_loss_ce(y, tx, w)  + lambda_/2 * np.linalg.norm(w)**2

### Gradient of loss functions
def compute_gradient_ls(y, tx, w):
    '''Gradient of least squares'''
    N = len(y)
    e = y - tx @ w
    gradient = -(1/N) * tx.T @ e

    return gradient

def compute_gradient_logreg(y, tx, w):
    '''Gradient of logistic regression with cross entropy loss'''

    N = len(y)
    e = sigmoid(tx @ w) - y
    gradient = tx.T @ e

    return gradient

def compute_gradient_reg_logreg(y, tx, w, lambda_):
    '''Gradient of regularised logistic regression with cross entropy loss'''
    usual_grad = compute_gradient_logreg(y, tx, w)

    return usual_grad + lambda_*w