import numpy as np

#TODO add doc + add multiple root finding
def newton(function, derivative, x0, max_depth=10,  eps=1e-10):
    x=x0
    i=0
    while i < max_depth:
        if (derivative(x)==0):
            print("Singularity at x={}".format(x))
        if np.abs(function(x))<=eps:
            return x
        x=x-function(x)/derivative(x)
        i=i+1
    return x

# Find the third root of 27
f = lambda x : 1/3*np.log(27)-np.log(x)
Df = lambda x : -1/x
x = newton(f, Df, 2, 5)
print("The third root of 27 is : {}".format(x))

# Find the the golden ratio
f = lambda x : x**2-x-1
Df = lambda x : 2*x -1
x = newton(f, Df, 4, 6)
print("The golden ratio is : {}".format(x))

# Find the the super golden ratio
f = lambda x : x**3-x**2-1
Df = lambda x : 3*x**2-2*x
x = newton(f, Df, 4, 6)
print("The super golden ratio is : {}".format(x))

# Unprecise function choice
# Find the the value of pi
f = lambda x : np.cos(x)
Df = lambda x : -np.sin(x)
x = newton(f, Df, 1.55, 1000, 1e-10)
print("The value of pi is : {}".format(2*x))


import pandas as pd
import numpy as np
import scipy.stats as st
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns

#Calculating covariance and correlation coeffecient using hand
ad = pd.DataFrame({'watched':[5,4,4,6,8], 'bought':[8,9,10,13,15]})
print(ad)

print(ad.describe())

print(ad.corr())

_ = sns.lmplot('watched', 'bought', data=ad)
plt.show()

# cross-product deviations: multiply the deviations of one variable by the corresponding deviations of a second variable
# for above examples Cross Product Deviations
watched_diff, bought_diff = [], []
for index, row in ad.iterrows():
    watched_diff.append(5.4-row['watched'])
    bought_diff.append(11-row['bought'])

print(watched_diff, bought_diff)

Cross_Product_Deviations = sum(np.multiply(watched_diff, bought_diff))
print(Cross_Product_Deviations)

# Covariance (we got it from cross product deviations)
# covariance = Cross_Product_Deviations/(N-1)
Covariance = Cross_Product_Deviations/(5-1)
print(Covariance)

# now
watched_diff_standarized = np.array(watched_diff)/(ad['watched'].std())
bought_diff_standarized = np.array(bought_diff)/(ad['bought'].std())

Cross_Product_Deviatiions_standarized = sum(np.multiply(watched_diff_standarized, bought_diff_standarized))
print(Cross_Product_Deviatiions_standarized)

# R, correlation coefficient is standarized covariance

Covariance_standarized = Cross_Product_Deviatiions_standarized/(5-1)
print(Covariance_standarized)

# also , we can get it by R = Covariance/(s1*s2)
print(Covariance/(ad['watched'].std() * ad['bought'].std()))

# Correlations

data = pd.read_csv('/home/atrides/Desktop/R/statistics_with_Python/06_Correlation/Data_Files/Exam Anxiety.dat', sep='\t')

print(data.head())

data.set_index('Code', inplace=True, drop=True)

# pearson
print(data.corr())

# kendall
print(data.corr(method='kendall'))

# using scipy.stats, only does one pair of variables at a time

print(st.pearsonr(data['Revise'], data['Exam']))

print(st.spearmanr(data['Revise'], data['Exam']))

print(st.kendalltau(data['Revise'], data['Exam']))

print(st.pearsonr(data['Anxiety'], data['Exam']))

# Note:
#
# use pearson r for parametric
#
# use spearman rho for non-parametric
#
# use tendall tau for non-parametric and small datasets

# seeing the relation with lmplot
_ = sns.lmplot('Revise', 'Exam', data=data)
plt.show()

# R^2 : measure of the amount of variability in one variable that is shared by the other

print((data.corr())**2)

import numpy,import scipy,import statsmodels


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 16 10:03:34 2019

@author: BSWOOD9321
"""

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-2*np.pi,2*np.pi,np.pi/20)
sin = np.sin(x)
derivative = np.cos(x)
fdiff = ((np.sin(x+np.pi/20))-np.sin(x))/((x+np.pi/20)-x)
plt.plot(x,fdiff,label='Forward Difference Function')
plt.plot(x,derivative,'r--',label='Derivative[Cos(x)]')
plt.title("Forward Difference Equation and Cosine function")
plt.xlabel("x")
plt.legend(loc=(1.04,0))
plt.show()

grad = np.gradient(sin,np.pi/20)
plt.plot(x,grad,label='Gradient command')
plt.plot(x,fdiff,'r--',label='Forward Difference Function')
plt.plot(x,derivative,'b-',label='Derivative[Cos(x)]')
plt.xlabel("x")
plt.title("ForDiff and Cos(x) and Gradient command")
plt.legend(loc=(1.04,0))
plt.show()

Errorf = np.absolute(fdiff-derivative)/derivative

Errorg = np.absolute(grad-derivative)/derivative

plt.plot(x,Errorf,label='Forward Differential Error')
plt.legend(loc=(1.04,0))
plt.show()
plt.plot(x,Errorg,label='Gradient Error')
plt.legend(loc=(1.04,0))
plt.show()

print("So there is a small difference between the two different ways to find the derivative.")
print("The errors of each, while at similar locations (it looks to be at the points where the derivative=0), have different values.")
print("This is probably due to how the functions calculate the values really close to zero.")

x1 = np.arange(-2*np.pi,2*np.pi,np.pi/20)
sin1 = np.sin(x1+.25)
derivative1 = np.cos(x1+.25)
fdiff1 = ((np.sin(x1+np.pi/20+.25))-np.sin(x1+.25))/((x1+np.pi/20)-x1)
plt.plot(x1,fdiff1,label='Forward Difference Function')
plt.plot(x1,derivative1,'r--',label='Derivative[Cos(x)]')
plt.title("Challenge Plot 1")
plt.xlabel("x")
plt.legend(loc=(1.04,0))
plt.show()

grad1 = np.gradient(sin1,np.pi/20)
plt.plot(x1,grad1,label='Gradient command')
plt.plot(x1,fdiff1,'r--',label='Forward Difference Function')
plt.plot(x1,derivative1,'b-',label='Derivative[Cos(x)]')
plt.xlabel("x")
plt.title("Challenge Plot 2")
plt.legend(loc=(1.04,0))
plt.show()

Errorf1 = np.absolute(fdiff1-derivative1)/derivative1

Errorg1 = np.absolute(grad1-derivative1)/derivative1

plt.plot(x1,Errorf1,label='Forward Differential Error')
plt.legend(loc=(1.04,0))
plt.title("Challenge Error 1")
plt.show()
plt.plot(x1,Errorg1,label='Gradient Error')
plt.legend(loc=(1.04,0))
plt.title("Challenge Error 2")
plt.show()

x2 = np.arange(1,10,.20)
func = (x2**3)/3
derivative2 = x2**2
fdiff2 = (((x2+.20)**3)/3-((x2**3)/3)/.20)
plt.plot(x2,fdiff2,label='Forward Difference Function')
plt.plot(x2,derivative2,'r--',label='Derivative[x^2]')
plt.title("Bonus Plot 1")
plt.xlabel("x")
plt.legend(loc=(1.04,0))
plt.show()

grad2 = np.gradient(func,np.pi/20)
plt.plot(x2,grad2,label='Gradient command')
plt.plot(x2,fdiff2,'r--',label='Forward Difference Function')
plt.plot(x2,derivative2,'b-',label='Derivative[Cos(x)]')
plt.xlabel("x")
plt.title("Bonus Plot 2")
plt.legend(loc=(1.04,0))
plt.show()

Errorf2 = np.absolute(fdiff2-derivative2)/derivative2

Errorg2 = np.absolute(grad2-derivative2)/derivative2

plt.plot(x2,Errorf2,label='Forward Differential Error')
plt.legend(loc=(1.04,0))
plt.title("Bonus Error 1")
plt.show()
plt.plot(x2,Errorg2,label='Gradient Error')
plt.legend(loc=(1.04,0))
plt.title("Bonus Error 2")
plt.show()


import math
import numpy as np

def funct(x):
#    y = math.exp(x)
#    y = y - x

#    y = np.sin(x)

    y = np.pi*(x**2)*3 - ((np.pi)*(x**3))/3 - 30

    return y


# This example is taken from:
# https://machinelearningmastery.com/how-to-use-correlation-to-understand-the-relationship-between-variables

from numpy import mean
from numpy import std
from numpy.random import randn
from numpy.random import seed
from matplotlib import pyplot
from numpy import cov
from scipy.stats import pearsonr
from scipy.stats import spearmanr

# seed random number generator
seed(1)

# prepare data
data1 = 20 * randn(1000) + 100
data2 = data1 + (10 * randn(1000) + 50)
# data2 = 10 * randn(1000) + 50

# summarize
print('data1: mean=%.3f stdv=%.3f' % (mean(data1), std(data1)))
print('data2: mean=%.3f stdv=%.3f' % (mean(data2), std(data2)))

# plot
pyplot.scatter(data1, data2)
pyplot.show()

# calculate covariance matrix
# cov(X, Y) = (sum (x - mean(X)) * (y - mean(Y))) * 1/(n-1)
covariance = cov(data1, data2)
print(covariance)

# calculate Pearson's correlation
# cov(X, Y) = (sum (x - mean(X)) * (y - mean(Y))) * 1/(n-1)
corr, _ = pearsonr(data1, data2)
print('Pearsons correlation: %.3f' % corr)

# calculate spearman's correlation
# Spearman's correlation coefficient = covariance(rank(X), rank(Y)) / (stdv(rank(X)) * stdv(rank(Y)))
corr, _ = spearmanr(data1, data2)
print('Spearmans correlation: %.3f' % corr)


import numpy as np
from scipy.special import gamma
import matplotlib.pyplot as plt

def plot_beta_distribution(a,b,name,points=100):
    '''
    This function will plot the beta distribution give the parameters a & b
    '''
    #Getting the normalixation constant
    constant=gamma(a+b)/(gamma(a)*gamma(b))

    #Sampling the points uniformly
    delta_x=1.0/points
    x=np.arange(0,1+delta_x,delta_x)

    #Now calculating the pdf on these points
    beta=constant*(x**(a-1))*((1-x)**(b-1))

    #Plotting the pdf
    plt.plot(x,beta)
    plt.xlabel('mu (u)')
    plt.ylabel('probability_density')
    plt.title('Probability Density Function : P(u|a,b)')
    #plt.show()
    plt.savefig(name)
    plt.clf()

def iterative_learning(prior_a,prior_b,toss_sample,plot_control=10):
    #Getting the metadata
    N=toss_sample.shape[0]

    #Plotting the initial prior distribution
    plot_beta_distribution(prior_a,prior_b,name='prior.png')
    #Now going iteratively and plotting the beta distribution
    for i in range(N):
        #Calculating the new posteror based on the current evidence of the data
        posterior_a = prior_a+toss_sample[i]
        posterior_b = prior_b+(1-toss_sample[i])

        #Plotting the new distribution
        if(i%plot_control==0):
            plot_beta_distribution(posterior_a,posterior_b,name=str(i)+'.png')

        #Updating the current posterir as prior for next iteration
        prior_a=posterior_a
        prior_b=posterior_b

def one_shot_learning(prior_a,prior_b,toss_sample):
    '''
    This function will give the final posterior distribution by seeing the
    whole data at once.
    '''
    #Getting the metadata
    N=toss_sample.shape[0]
    correction_m=np.sum(toss_sample)
    correction_l=N-correction_m

    #Getting the posteror parameters of the distribution
    posterior_a = prior_a+correction_m
    posterior_b = prior_b+correction_l

    #Plotting the final distribution
    plot_beta_distribution(posterior_a,posterior_b,name='one_shot.png')

if __name__=='__main__':
    ############ CONTROL VARIABLES ##############
    dataset_size=150
    #Parameters for the prior
    a=2
    b=int((0.6/0.4)*a)

    ########### MAIN CODE ######################
    #Sampling the dastaset randomly
    toss_sample=np.random.randint(0,2,size=dataset_size)

    #Plotting the distibution iteratively
    print "Plotting the Iterative solution"
    iterative_learning(a,b,toss_sample,plot_control=1)

    #Plotting the distribution for the one-shot learning
    print "Printing the One-Shot Solution"
    one_shot_learning(a,b,toss_sample)


"""
@file:          least_square.py
@description:   answer to problems in exercises 6.5
@author:        Hiu-kong Dan
@date:          June 1, 2021
"""

from sympy import Matrix

def problem26():
    A = Matrix([[0, .7, 1],
                [-.7,0,.7],
                [-1,-.7,0],
                [-.7,-1,-.7],
                [0,-.7,-1],
                [.7,0,-.7],
                [1,.7,0],
                [.7,1,.7],
                [0,-.7,1],
                [.7,0,-.7],
                [-1,.7,0],
                [.7,-1,.7],
                [0,.7,-1],
                [-.7,0,.7],
                [1,-.7,0],
                [-.7,1,-.7]])
    b = Matrix([.7,
                0,
                -.7,
                -1,
                -.7,
                0,
                .7,
                1,
                0,
                0,
                0,
                0,
                0,
                0,
                0,
                0])

    assert A.shape == (16,3)
    assert b.shape == (16,1)

    M = (A.T * A).col_insert(4, A.T * b)

    assert M.shape == (3, 4)

    x = Matrix.rref(M)[0].col(3)

    print("Using row reduction:")
    print(x)
    print("-"*20)

    # using another mathod
    print("Using inverse matrix:")
    x = (A.T * A).inv() * A.T * b
    print(x)

if __name__ == "__main__":
    problem26()


import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

steps=250

distance=0
x=0
distance_list=[]
steps_list=[]
while x<steps:
    distance+=np.random.randint(-1,2)
    distance_list.append(distance)
    x+=1
    steps_list.append(x)
plt.plot(steps_list,distance_list, color='green', label="Random Walk Data")

steps_list=np.asarray(steps_list)
distance_list=np.asarray(distance_list)

X=steps_list[:,np.newaxis]

#Polynomial fits

#Degree 2
poly_features=PolynomialFeatures(degree=2, include_bias=False)
X_poly=poly_features.fit_transform(X)

lin_reg=LinearRegression()
poly_fit=lin_reg.fit(X_poly,distance_list)
b=lin_reg.coef_
c=lin_reg.intercept_
print ("2nd degree coefficients:")
print ("zero power: ",c)
print ("first power: ", b[0])
print ("second power: ",b[1])

z = np.arange(0, steps, .01)
z_mod=b[1]*z**2+b[0]*z+c

fit_mod=b[1]*X**2+b[0]*X+c
plt.plot(z, z_mod, color='r', label="2nd Degree Fit")
plt.title("Polynomial Regression")

plt.xlabel("Steps")
plt.ylabel("Distance")

#Degree 10
poly_features10=PolynomialFeatures(degree=10, include_bias=False)
X_poly10=poly_features10.fit_transform(X)

poly_fit10=lin_reg.fit(X_poly10,distance_list)

y_plot=poly_fit10.predict(X_poly10)
plt.plot(X, y_plot, color='black', label="10th Degree Fit")

plt.legend()
plt.show()

#Decision Tree Regression
from sklearn.tree import DecisionTreeRegressor
regr_1=DecisionTreeRegressor(max_depth=2)
regr_2=DecisionTreeRegressor(max_depth=5)
regr_3=DecisionTreeRegressor(max_depth=7)
regr_1.fit(X, distance_list)
regr_2.fit(X, distance_list)
regr_3.fit(X, distance_list)

X_test = np.arange(0.0, steps, 0.01)[:, np.newaxis]
y_1 = regr_1.predict(X_test)
y_2 = regr_2.predict(X_test)
y_3=regr_3.predict(X_test)

# Plot the results
plt.figure()
plt.scatter(X, distance_list, s=2.5, c="black", label="data")
plt.plot(X_test, y_1, color="red",
         label="max_depth=2", linewidth=2)
plt.plot(X_test, y_2, color="green", label="max_depth=5", linewidth=2)
plt.plot(X_test, y_3, color="m", label="max_depth=7", linewidth=2)

plt.xlabel("Data")
plt.ylabel("Darget")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()

"""new_dist=distance_list[-1]
step_max=2500
new_x=steps
new_dist_list=[]
new_steps_list=np.arange(steps,step_max)
while new_x>=steps and new_x<step_max:
    dist_prediction=clf.predict(new_x)
    new_dist_list.append(dist_prediction)
    new_x+=1
plt.plot(new_steps_list,new_dist_list, color='red')
plt.show()"""


import sympy
from sympy import *
import random

def choose_modulus(k,l,m):
	p1 = find_prime(k,l)
	p2 = find_prime(k,l)
	#while not (log(p1,2) + m < log(p2, 2)):
	#	p1 = find_prime(k,l)
	#	p2 = find_prime(k,l)
	#	print(p1, p2)
	#print(log(p1,2)+m < log(p2,2))
	print("prime 1: ", p1)
	print("prime 2: ", p2)
	return p1,p2

def choose_encryption_key_old(m):
	e = random.randrange(2,m)
	while not gcd(e, totient(m)) == 1:
		e = random.randrange(2,m)
	return e

def choose_encryption_key(p1,p2):
	m = p1*p2
	e = random.randrange(2, p1*p2)
	while not gcd(e, (p1-1)*(p2-1)) == 1:
		e = random.randrange(2, p1*p2)
	return e

def compute_decryption_key(e, p1, p2):
	# d = e inverse mod phi(m) m = p1*p2
	#phi(m) = (p1-1)*(p2-1)
	d = inv_mod(e, (p1-1)*(p2-1))
	return d

def RSA_encrypt(P, e, m):
	return power_mod(P,e,m)

def RSA_decrypt(C, d, m):
	return power_mod(C,d,m)

def RSA_crack(C, e, m):
	return power_mod(C, inv_mod(e,totient(m)), m)

def power_mod(a, b, m):
	#replace with better version for large numbers
	#return ((a%m)**(b%m))%m
	return pow(a,b,m)

def string_to_int(s):
	return int.from_bytes(s.encode(),'big')

def int_to_string(n):
	return n.to_bytes((n.bit_length()+7)//8,'big').decode()

def find_prime(k, l):
	x = random.randrange(2**k+2, 2**l-1)
	while (not isprime(x)):
		x = random.randrange(2**k+2, 2**l-1)
	return x

def mod_inv_old(a,m):
	#replace with euclid's algorithm - will be using large numbers
	for i in range(0,m):
		if (i*a)%m == 1:
			return i
	return -1

def xgcd(b, n):
    """ Return g, x0, y0
        such that x0*b + y0*n = g
        and g is the gcd of (b,n)"""
    x0, x1, y0, y1 = 1, 0, 0, 1
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return b, x0, y0

def inv_mod(b, n):
    """ Return the modular inverse of b mod n
     or None if gcd(b,n) > 1 """
    g, x, _ = xgcd(b, n)
    if g == 1:
        return x % n

def unit_test1(kv, lv, dv, textv):
	(p1, p2) = choose_modulus(kv, lv, dv)
	m = p1 * p2
	e = choose_encryption_key(p1, p2)
	d = compute_decryption_key(e, p1, p2)
	plaintext = textv
	P = string_to_int(plaintext)
	print("Plaintext in bits = ", P)
	C = RSA_encrypt(P, e, m)
	print("Plaintext: ", plaintext)
	print("Text Len: ", 8*len(plaintext), "bits")
	print("Modulus len: ",  int(log(m)/log(2)), "bits")
	print("Ciphertext in bit format = ", C)
	D = RSA_decrypt(C, d, m)
	print("D (Decrypted text in bit format) = ", D)
	decoded_text = int_to_string(D)
	print("Decoded Text: ", decoded_text, "\n\n--------------\n")

def unit_test2(kv, lv, dv, textv):
	(p1, p2) = choose_modulus(kv, lv, dv)
	m = p1 * p2
	e = choose_encryption_key(p1, p2)
	d = compute_decryption_key(e, p1, p2)
	plaintext = textv
	P = string_to_int(plaintext)
	print("Plaintext in bits = ", P)
	C = RSA_encrypt(P, e, m)
	print("Plaintext: ", plaintext)
	print("Text Len: ", 8*len(plaintext), "bits")
	print("Modulus len: ",  int(log(m)/log(2)), "bits")
	print("C (Ciphertext in bit format) = ", C)
	D = RSA_crack(C, e, m)
	print("D (decrypted text in bit format) = ", D)
	decoded_text = int_to_string(D)
	print("Decoded Text: ", decoded_text, "\n\n--------------\n")

unit_test1(10, 12, 1, "Hi")
unit_test1(200,201,50,"I've got the best RSA. Tremendous!")
unit_test2(10, 12, 5, "Hi")


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time     : 2018/12/06 12:41
# @Author   : Iydon
# @File     : 9.3.py

import numpy as np
import scipy as sp

def gersgorin_circle(A, numpy):
    r"""
    |z-a_{ii}|<=\sum_{j!=i}|a_{ij}|
    """
    shape = A.shape
    if shape[0] != shape[-1]:
        raise Exception("Dimensions do not match.")
    result = [[] for i in range(shape[0])]
    for i in range(shape[0]):
        summation = numpy.sum(numpy.abs(A[i,])) - numpy.abs(A[i,i])
        result[i] = [A[i,i], summation]
    return result

def power_method(A, numpy, x0, judge=None, eps=1e-6, max_loop=64, disply=False, p=0):
    r"""
    Numpy
    B = A - pI.
    """
    if judge==None: judge = lambda x,y: numpy.linalg.norm(x-y, float("inf"))
    A_    = A.copy() - p*numpy.eye(A.__len__())
    count = 0
    mu    = numpy.max(numpy.abs(x0))
    x0    = x0 / mu
    last  = x0
    while count<max_loop:
        count += 1
        uv = numpy.matmul(A_, x0)
        mu = numpy.max(numpy.abs(uv))
        x0 = uv / mu
        if disply:
            print("%%%dd: %%s"%(len(str(max_loop)))%(count, mu))
        if judge(last,x0)<eps:
            break
        last = x0
    return mu + p

def inverse_power_method(A, scipy, x0, judge=None, eps=1e-6, max_loop=64, disply=False, p=0):
    r"""
    Scipy
    pass
    """
    from scipy import linalg
    if judge==None:
        norm  = lambda x: scipy.linalg.norm(x, float("inf"))
        judge = lambda x,y: abs(norm(x-y))
    A_    = A.copy() - p*scipy.eye(len(A))
    LU    = linalg.lu_factor(A_)
    count = 0
    mu    = max(abs(x0))
    x0    = x0 / mu
    last  = x0
    while count<max_loop:
        count += 1
        uv = linalg.lu_solve(LU, x0)
        mu = max(abs(x0))[0]
        x0 = uv / mu
        if disply:
            print("%%%dd: %%s"%(len(str(max_loop)))%(count, p+1/mu))
        if judge(last,x0)<eps:
            break
        last = x0
    return p + 1/mu

A  = np.matrix([[1,1,1],[1,1,0],[1,0,1]])
x0 = np.matrix([-1,0,1]).T
result = power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=64, disply=True, p=0)
print(result)

result = inverse_power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=64, disply=True, p=0)
print(result)

print(np.max(np.linalg.eigvals(A)))
print(np.min(np.linalg.eigvals(A)))

A  = np.matrix([[2,1,1],[1,2,1],[1,1,2]])
x0 = np.matrix([1,-1,2]).T
result = power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=64, disply=True, p=0)
print(result)
result = inverse_power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=64, disply=True, p=0)
print(result)

print(np.max(np.linalg.eigvals(A)))
print(np.min(np.linalg.eigvals(A)))

print("\n"*10)

A = np.matrix([[0,0,2,4],[1/2,0,0,0],[0,1/4,0,0],[0,0,1/8,0]])
x0 = np.matrix([[1],[1],[1],[1]])
result = power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=128, disply=True, p=0)
print(result)
print(np.max(np.linalg.eigvals(A)))

print("\n"*10)

M = 11
a = 3/4
A = np.diag([(1+2*a) for i in range(M-1)]) - np.diag([a for i in range(M-2)],1) - np.diag([a for i in range(M-2)],-1)
x0 = np.ones((M-1,1))
result = inverse_power_method(A, np, x0=x0, judge=None, eps=1e-6, max_loop=128, disply=True, p=0)

print(result)
print(np.min(np.linalg.eigvals(A)))

print("\n"*10)

M = 11
a = 3/4
A = np.diag([(1+a) for i in range(M-1)]) - np.diag([-a/2 for i in range(M-2)],1) - np.diag([-a/2 for i in range(M-2)],-1)
B = np.diag([(1+a) for i in range(M-1)]) - np.diag([a/2 for i in range(M-2)],1) - np.diag([a/2 for i in range(M-2)],-1)
C = np.matmul( np.linalg.inv(A), B )
x0 = np.ones((M-1,1))
result = power_method(C, np, x0=x0, judge=None, eps=1e-6, max_loop=1024, disply=True, p=0)

print(result)
print(np.max(np.linalg.eigvals(C)))


import numpy as np
def minkowski_distances(vector_x, vector_z, p=2):
    """ Calculates the Minkowski distance for a choosen value p between two vectors.

    -----------
    Parameters:

    vector_x / vector_z : numpy array
            Both are expected to have the same size and conntain float values

    p : int or +/- np.inf
            The value to  raise and then take the root in the minkowski distance formula

    Using +/- np.inf for representing a infinity, they are preprocessed to their max/min representation.
    Be careful since very large values of p may take a some time to process, even though we use a vectorized approach
    """
    if p == np.inf:
        return max(np.abs(vector_x - vector_z))
    elif p == - np.inf:
        return min(np.abs(vector_x - vector_z))

    if not isinstance(p, int):
        raise TypeError("p is {type(p)}, not a integer!")
    elif p <= 0:
        raise ValueError("p must be a non negative, non zero integer!")

    return np.sum(np.abs(vector_x-vector_z)**p) ** (1/p)

def hamming_distance(vector_x, vector_z):
    """ Returns the Hamming distance (amount of different elements) between two vectors
    We divide by the length of the vector to follow sklean's convention

    Usefull as a distance metric for categorical data
    ----------
    Parameters
    vector_x / vector_z : numpy array or pd.Series
    """

    # If you want, you can check scipy's hamming distance, but it still gives
    #   different results to sklearn
    #from scipy.spatial.distance import hamming
    #return hamming(vector_x, vector_z)
    return np.sum(~(vector_x == vector_z)) / len(vector_x)


# IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.style as style
from IPython.core.display import HTML

# PLOTTING CONFIG
%matplotlib inline
style.use('fivethirtyeight')
plt.rcParams["figure.figsize"] = (14, 7)
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: center;
}
</style>
""")
plt.figure(dpi=100)

# PDF P = .2
plt.scatter(np.arange(21),
            (stats.binom.pmf(np.arange(21), p=.2, n=20)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(21),
         (stats.binom.pmf(np.arange(21), p=.2, n=20)),
         alpha=0.75,
        )

# PDF P = .5
plt.scatter(np.arange(21),
            (stats.binom.pmf(np.arange(21), p=.5, n=20)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(21),
         (stats.binom.pmf(np.arange(21), p=.5, n=20)),
         alpha=0.75,
        )

# PDF P = .9
plt.scatter(np.arange(21),
            (stats.binom.pmf(np.arange(21), p=.9, n=20)),
            alpha=0.75,
            s=100
       )
plt.plot(np.arange(21),
         (stats.binom.pmf(np.arange(21), p=.9, n=20)),
         alpha=0.75,
        )

# LEGEND
plt.text(x=3.5, y=.075, s="$p = 0.2$", alpha=.75, weight="bold", color="#008fd5")
plt.text(x=9.5, y=.075, s="$p = 0.5$", alpha=.75, weight="bold", color="#fc4f30")
plt.text(x=17.5, y=.075, s="$p = 0.9$", alpha=.75, weight="bold", color="#e5ae38")

# TICKS
plt.xticks(range(21)[::2])
plt.tick_params(axis = 'both', which = 'major', labelsize = 18)
plt.axhline(y = 0, color = 'black', linewidth = 1.3, alpha = .7)

# TITLE, SUBTITLE & FOOTER
plt.text(x = -2.5, y = .37, s = "Binomial Distribution - $p$",
               fontsize = 26, weight = 'bold', alpha = .75)
plt.text(x = -2.5, y = .32,
         s = 'Depicted below are three Binomial distributed random variables with varying $p $. As one can see\nthe parameter $p$ shifts and skews the distribution.',
         fontsize = 19, alpha = .85)
plt.text(x = -2.5,y = -0.065,
         s = '   ©Joshua Görner                                                                                github.com/jgoerner   ',
         fontsize = 14, color = '#f0f0f0', backgroundcolor = 'grey');


"""
Functions that return a NumPy array containing a wave such as
a sine wave or square wave.

Parameters
----------
  frequency: int or float
     wave frequency in Hertz (1/second)
  max_amplitude: float
     The maximum amplitude of the wave
  phase_shift: float
     Initial phase of the wave
  sample_rate: int or float
     Number of samples per second of the waveform
  time_duration: int or float
     The time, in seconds, of the output wave.

Variables
---------
    num_samples is the size of the output array
    time_sequence is an array [0, v, 2*v, .... num_samples*v] where
        (num_samples + 1)*v == time_duration.
"""
import numpy as np
import matplotlib.pyplot as plt

def generate_trignometric_wave(
        func, frequency, max_amplitude,
        phase_shift, sample_rate, time_duration):
    num_samples = int(time_duration * sample_rate)
    time_sequence = np.linspace(0, time_duration, num_samples, endpoint=False)
    return max_amplitude*func(2*np.pi*frequency*time_sequence + phase_shift)

def generate_sine_wave(
        frequency, max_amplitude, phase_shift,
        sample_rate, time_duration):
    return generate_trignometric_wave(
        np.sin, frequency, max_amplitude, phase_shift,
        sample_rate, time_duration)

def generate_cosine_wave(
        frequency, max_amplitude, phase_shift,
        sample_rate, time_duration):
    return generate_trignometric_wave(
        np.cos, frequency, max_amplitude, phase_shift,
        sample_rate, time_duration)

def generate_square_wave(
        frequency, max_amplitude,
        phase_shift, sample_rate, time_duration):
    num_samples = time_duration * sample_rate
    time_sequence = np.linspace(0, time_duration, num_samples, endpoint=False)
    def func(v):
        return - np.sign((v - v.astype(int)) - 0.45)
    return max_amplitude*func(frequency*time_sequence + phase_shift)

def plot_signal(signal, time_duration, sample_rate):
    plt.figure(1)
    plt.clf()
    num_samples = time_duration * sample_rate
    time_sequence = np.linspace(0, time_duration, num_samples, endpoint=False)
    plt.plot(time_sequence, signal)
    plt.show()
    return

# TESTS

def test():
    # Plot
    frequency = 1.0
    max_amplitude = 1.0
    phase_shift = 0.0
    sample_rate = 100
    time_duration = 2

    signal = generate_square_wave(frequency, max_amplitude, phase_shift, sample_rate, time_duration)
    plot_signal(signal, time_duration, sample_rate)

    phase_shift = np.pi/2.0 # 90 degrees
    signal = generate_sine_wave(frequency, max_amplitude, phase_shift, sample_rate, time_duration)
    plot_signal(signal, time_duration, sample_rate)

    signal = generate_cosine_wave(frequency, max_amplitude, phase_shift, sample_rate, time_duration)
    plot_signal(signal, time_duration, sample_rate)

if __name__ == '__main__':
    test()


import pandas as pd
import numpy as np
import scipy.stats as stat
import matplotlib as mat
"""
Problem 1:  Generate a random 20x20 grid of two digit natural numbers. Find the largest product of four
            diagonally adjacent numbers.
"""
a = np.random.randint(10, 99, (20, 20))

def largest_product(a):
    prd = 0
    d = dict()
    l = []
    for row in np.arange(0,a.shape[0]-3):
        for col in np.arange(0,a.shape[0]-3):
            temp_down = a[row, col]*a[row+1, col+1]*a[row+2, col+2]*a[row+3, col+3]
            temp_up = a[row+3, col]*a[row+2, col+1]*a[row+1, col+2]*a[row, col+3]
            if temp_up > temp_down:
                temp = temp_up
                address = f"Up Diag {(row+3, col),(row+2,col+1),(row+1,col+2),(row,col+3)}"
            else:
                temp = temp_down
                address = f"Down Diag {(row, col),(row+1,col+1),(row+2,col+2),(row+3,col+3)}"
            l.append([temp,temp_up,temp_down])
            if temp > prd:
                prd = temp
                d = {"product": prd,
                  "address": address}
    return d

print(f"Maximum Product: {largest_product(a)['product']} \nAddress: {largest_product(a)['address']}")

"""
Probelm 2:  The Social Security administration has this neat data by year of what names are most popular
            by gender for born that year in the USA. The popular name list for the year 1990 is given in the
            website <url>. Write a python program to identify the names that appear in the list of popular
            names of both genders.
"""

data = pd.read_csv("popular_names.csv")

def popular_name(data):

    males = set(data['Male name'])
    females = set(data['Female name'])
    common = males.intersection(females)
    d = dict()
    for name in common:
        combined_rank = int(data['Rank'][data['Male name']== name] ) + int(data['Rank'][data['Female name']== name])
        d.update({combined_rank:name})
        #print(f"Name: {name},\t Rank: {combined_rank}")

    name = d[min(d.keys())]

    return name

print(f"Most Popular name {popular_name(data)}")

"""
Problem 3: Invert matrix using first principle
"""

#b = np.random.randint(10, 99, (3, 3))

def matrix_invert_first_principles(b):

    if b.shape[0] != b.shape[1]:
        return print("Please input a square matrix")

    b = b.astype("float")
    c = b.copy()
    I = np.eye(b.shape[0], b.shape[1])

    for i in np.arange(0, b.shape[0]):
        I[i] = I[i, :] / b[i, i]
        b[i] = b[i, :] / b[i, i]
        for j in np.arange(0, b.shape[0]):
            if j == i:
                pass
            else:
                I[j] = I[j] - b[j, i] * I[i]
                b[j] = b[j] - b[j, i] * b[i]

    def check_result(result, actual):

        res = np.matmul(result, actual).astype("int")
        #print(res)
        if (np.eye(result.shape[0], result.shape[1]) == res).all():
            ok = True
            d_res = None
        else:
            ok = False
            d_res = {"input": actual}

        return ok, d_res

    if not check_result(I, c)[0]:
        return (f"Something not ok. Input {check_result(I, c)[1]['input']}")

    return I

# b = np.array(  [[35, 40, 82],
#                 [68, 86, 29],
#                 [38, 53, 13]])

print(f"Result Problem 3: {matrix_invert_first_principles(b)}")

"""
Problem 5:  Numerical Integration using Monte Carlo simulation.

"""

def integrate_func_monte_carlo(func,a,b,N=1000):
    x = np.random.uniform(a, b, N)
    res = func(x)
    return ((b-a)/N)*sum(res)

def func0(x):
    return x

def func_a(x):
    return np.exp(-x**2)

def func_b(x):
    return 1/(1 + x**2)

def func_c(x):
    return np.sqrt(x**4 + 1)

def confidence_interval(arr, p = 0.95):

    u = arr.mean()
    sd = arr.std()
    z = stat.norm.ppf(1-(1-p)/2)

    ci = z*(sd/np.sqrt(len(arr)))
    d = {"mean": u,
         "interval":ci}
    return d

def compute_value(func,a,b,theo_vaule,N=1000):
    print(N)
    r = []
    for _ in np.arange(0,100):
        r.append(integrate_func_monte_carlo(func, a, b, N))

    result = np.array(r)
    diff_arr = result - theo_vaule
    result_dict = {"result":result}
    result_dict.update(confidence_interval(diff_arr))

    return result_dict

l_N = [10, 50, 100, 500, 1000, 5000, 10000, 50000, 1000000]
#solutions :

solution_a = [compute_value(func_a, 0, 1, 0.5, N) for N in l_N]
solution_b = [compute_value(func_b, 1, 2, 0.5, N) for N in l_N]
solution_c = [compute_value(func_c, 0, 1, 0.5, N) for N in l_N]


import scipy as sp
import scipy.linalg as la

def Problem2(Q,b,x0,tol=1e-10):
    """Use conjugate gradient method to minimize Ax-b"""

    x = x0.copy()
    g = Q.dot(x)-b
    d = -g

    ndim = x.size

    for iter in range(ndim):
        if la.norm(g)<tol:
            break
        a = -1.0*(g.T.dot(d))/(d.T.dot(Q).dot(d))
        x += a*d
        g = Q.dot(x)-b
        beta = (g.T.dot(Q).dot(d))/(d.T.dot(Q).dot(d))
        d = -g+beta*d

    return x

def Problem1(A,b,x0,tol=1e-7,maxiter=5000):
    """Find the minimum of a function"""

    x = x0.copy()
    Q = A.T.dot(A)
    niter = maxiter
    while la.norm(A.dot(x)-b)>tol and maxiter>0:
        grad_f = 2.0*A.T.dot(A.dot(x)-b)
        alpha_f = (grad_f.T.dot(grad_f))/(4.0*grad_f.T.dot(Q.dot(grad_f)))
        x = x + -1.0*alpha_f*grad_f
        niter -= 1

    return x, maxiter-niter

"""
Explanations:
    We found that 4.0*denominator of alpha_f converged faster than just having the denominator.

    I wasn't able to get the eigenvector of A.T.dot(A) to converge in 1 iteration.  The lowest convergence happened in 236 iterations

"""


import numpy as np
a = np.array([1, 2, 3])
print(a)
print(a.shape) # tuple of ndim elements
print(a.dtype) # data types
print(a.ndim) # number of dimensions
print(a.size) # total number of elements
print(a.itemsize) # return size of each elements in bytes

b = a * np.array([2, 0, 2]) # element wise multiplication
c = a + np.array([4, 2, 3]) # element wise summation
print(b)

print(type(b))

# dot product
l1 = np.array([1, 2, 3])
l2 = np.array([4, 5, 6])
dot = np.dot(l1, l2) # dot = l1 @ l2
print(dot)

# boolean indexing
bool_arr = a > 1
print(bool_arr, a[bool_arr]) # a[bool_arr] will have rank 1
b = np.where(a > 1, a, -1)
print(b)

# fancy indexing
a = np.array([10, 19, 30, 41, 50, 61])
idx = [1, 3, 5]
print(a[idx])

even_idx = np.argwhere(a % 2 == 0).flatten()
print(a[even_idx])

a = np.arange(1, 7)
print(a, a.shape)
b = a[:, np.newaxis]
print(b)
b = a[np.newaxis, :]
print(b)

# concatenation
print('Concatenation')
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6]])
c = np.concatenate((a, b))
print(c)
c = np.concatenate((a, b), axis = 0)
print(c)
c = np.concatenate((a, b), axis = None)
print(c)

# hstack, vstack to concatenate horizontaly or verticaly.

# generate arrays
a = np.zeros((2, 3)) # deefault data types float64
a = np.ones((2, 3))
a = np.eye(3)
a = np.full((2, 3), 5.0)
print(a)
a = np.linspace(0, 10, 5)
print(a)
a = np.random.random((3, 2)) # unifor distribution
print(a)
a = np.random.randn(2, 3) # gaussian or normal distribution, mean = 0, var = 1

a = np.random.randint(2, 10, size = (2, 2))
a = np.random.choice([-2, -1, 5], size = 5)
print(a)

# linalg
a = np.array([[1, 2], [3, 4]])
eigenvalues, eigenvectors = np.linalg.eig(a)
print(eigenvalues)
print(eigenvectors) #column vector
# e_vec * e_val = A * e_vec
b = eigenvectors[:, 0] * eigenvalues[0]
c = a @ eigenvectors[:, 0]
print(b == c)
print(np.allclose(b, c))


# Program the function distance(p1, p2) which returns the distance between the points p1 and p2 in
# n-dimensional space. p1 and p2 will be given as arrays.
#
# Your program should work for all lengths of arrays, and should return -1 if the arrays aren't of
# the same length or if both arrays are empty sets.
#
# If you don't know how to measure the distance between two points,
# go here: http://mathworld.wolfram.com/Distance.html
import numpy as np

def distance(p1, p2):
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)

    # compare if both arrays are of same shape, and neither one of them of size zero
    if p1.shape == p2.shape and (np.size(p1) != 0 or np.size(p2) != 0):
        return np.linalg.norm(p1 - p2)
    else:
        return -1

print(distance([2, 2], [1, 1]))
print(distance([1], [1, 1, 1, 1, 1, 1, 1, 1, 1]))
# test.describe("Normal cases")
# test.assertEqual(distance([2,2],[1,1]), 2**0.5)
# test.assertEqual(distance([4],[1]), 3)
# test.assertEqual(distance([1,1,1],[0,0,0]), 3**0.5)
# test.assertEqual(distance([2,1,3,1],[2,0,2,-1]), 6**0.5)
# test.assertEqual(distance([3,2,3],[0,1,1]), 14**0.5)

# test.describe("Bad input/edge cases")
# test.assertEqual(distance([],[]), -1)
# test.assertEqual(distance([1],[1,1,1,1,1,1,1,1,1]), -1)


import numpy as np
import math
from sklearn import metrics

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

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

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

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

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

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

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

            b_equals_y = np.where(B == y)

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

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

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

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


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

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

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

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

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

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

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

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

import numpy,from scipy,import jax,from jax


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

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

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

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

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

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

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

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

modregulaFalsi(a,b)

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

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

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

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

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

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

fixedPointIteration(x0, e, N)

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

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

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

"""Secant Technique"""

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

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

"""Modified Secant Technique"""

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

"""Inverse Quadratic Interpolation"""

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

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

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

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

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

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

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

def zero_test ( ):

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

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

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

def timestamp ( ):

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

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

  return None

def timestamp_test ( ):

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

  import platform

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

if __name__ == '__main__':
    main()

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

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

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


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

from py_variance_std import t_percentile

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

import sys, getopt

opts, args = getopt.getopt(sys.argv[1:], "hm:")
method_reg = "linear"
for op, value in opts:
    if op == "-m":
        if value == "non-linear":
            method_reg = value
        elif value == "polynomial":
            method_reg = value
    elif op == "-h":
        print("-m linear/non-linear/polynomial")
        sys.exit()

sample_cnt= 32

data_x = np.linspace(start = 0, stop = sample_cnt/4, num = sample_cnt).reshape(-1, 1)
rand_n = np.random.randn(sample_cnt).reshape(-1, 1)

# curve using linear
curve_linear = np.sin(data_x)
θ0, θ1 = 0.3, -0.05
curve_linear = yr = θ0 + θ1*data_x
curve_linear += rand_n * 0.03

# curve using polynomial
θ0, θ1, θ2, θ3 = 0.1, -0.02, 0.03, -0.04
curve_polynomial = θ0 + θ1*data_x + θ2*(data_x**2) + θ3*(data_x**3)
curve_polynomial += rand_n

plt.subplot(2, 2, 1)
plt.plot(data_x, curve_linear, 'b.')
plt.xlabel("np.linspace(0, 8, 32)")
plt.ylabel("curve_linear(data_x)")

plt.subplot(2, 2, 2)
plt.plot(data_x, curve_polynomial, 'b.')
plt.xlabel("np.linspace(0, 8, 32)")
plt.ylabel("curve_polynomial(data_x)")

if method_reg == "linear":
    linear_reg = LinearRegression()
    linear_reg.fit(data_x, curve_linear)
    print(linear_reg.intercept_, linear_reg.coef_)
    fit_x = np.linspace(start = 0, stop = sample_cnt/4, num = 1024).reshape(-1, 1)
    fit_linear = np.dot(fit_x, linear_reg.coef_.T) + linear_reg.intercept_

    plt.subplot(2, 2, 3)
    plt.plot(fit_x, fit_linear, 'r-')
    plt.plot(data_x, curve_linear, 'b.')
    plt.xlabel("np.linspace(0, 8, 32)")
    plt.ylabel("curve fitting using Linear")

    linear_reg_fail = LinearRegression()
    linear_reg_fail.fit(data_x, curve_polynomial)
    print(linear_reg_fail.intercept_, linear_reg_fail.coef_)
    fit_x = np.linspace(start = 0, stop = sample_cnt/4, num = 1024).reshape(-1, 1)
    fit_linear_fail = np.dot(fit_x, linear_reg_fail.coef_.T) + linear_reg_fail.intercept_

    plt.subplot(2, 2, 4)
    plt.plot(fit_x, fit_linear_fail, 'r-')
    plt.plot(data_x, curve_polynomial, 'b.')
    plt.xlabel("np.linspace(0, 8, 32)")
    plt.ylabel("curve fitting using Linear")

    hypo = np.dot(data_x, linear_reg.coef_.T) + linear_reg.intercept_
    hypo_fail = np.dot(data_x, linear_reg_fail.coef_.T) + linear_reg_fail.intercept_
    print(mean_squared_error(hypo, curve_linear))          # MSE: 0.00069
    print(mean_squared_error(hypo_fail, curve_polynomial)) # MSE: 6.40752

elif method_reg == "non-linear":
    method_reg = value
elif method_reg == "polynomial":
    poly_features_1 = PolynomialFeatures(degree = 3)
    linear_reg = LinearRegression()
    linear_reg.fit(poly_features_1.fit_transform(data_x), curve_linear)
    print(poly_features_1.get_params())
    fit_x = np.linspace(start = 0, stop = sample_cnt/4, num = 1024).reshape(-1, 1)
    fit_linear = linear_reg.predict(poly_features_1.fit_transform(fit_x))

    plt.subplot(2, 2, 3)
    plt.plot(fit_x, fit_linear, 'r-')
    plt.plot(data_x, curve_linear, 'b.')
    plt.xlabel("np.linspace(0, 8, 32)")
    plt.ylabel("curve fitting using Polynomial")

    poly_features_3 = PolynomialFeatures(degree = 3)
    linear_reg_best = LinearRegression()
    linear_reg_best.fit(poly_features_3.fit_transform(data_x), curve_polynomial)
    fit_x = np.linspace(start = 0, stop = sample_cnt/4, num = 1024).reshape(-1, 1)
    fit_linear_best = linear_reg_best.predict(poly_features_3.fit_transform(fit_x))

    plt.subplot(2, 2, 4)
    plt.plot(fit_x, fit_linear_best, 'r-')
    plt.plot(data_x, curve_polynomial, 'b.')
    plt.xlabel("np.linspace(0, 8, 32)")
    plt.ylabel("curve fitting using Polynomial")

    hypo = linear_reg.predict(poly_features_1.fit_transform(data_x))
    hypo_best = linear_reg_best.predict(poly_features_3.fit_transform(data_x))
    print(mean_squared_error(hypo, curve_linear))          # MSE: 0.00055
    print(mean_squared_error(hypo_best, curve_polynomial)) # MSE: 0.61483

#Adjust subgraph spacing
plt.subplots_adjust(wspace =0.3, hspace =0.4)
plt.show()


# Question 3 Lab Assignment 2
# AB Satyaprakash - 180123062

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

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

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

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

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

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

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

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

    px = nl0+nl1+nl2
    return px

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

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

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

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

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

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

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


import numpy as np
import matplotlib.pyplot as mp

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

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

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

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

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

		curIntegration = h * sum + lastIntegration / 2

		if track_efficiency:
			interalArray[n] = curIntegration

	if track_efficiency:
		return curIntegration, interalArray
	else:
		return curIntegration

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

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

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

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

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

		curIntegration = h * sum + lastIntegration / 3

		if track_efficiency:
			interalArray[n] = curIntegration

	if track_efficiency:
		return curIntegration, interalArray
	else:
		return curIntegration

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

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

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

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

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

if __name__ == "__main__":
	test_integral()


import numpy as np

def compare_operate():
    a = np.arange(10)
    print('a = ', a)
    print('less than :', a < 5)
    print('greater than :', a > 5)
    print('less and equal :', a <= 5)
    print('greater and equal :', a >= 5)
    print('equal :', a == 5)
    print('not equal :', a != 5)
    print('even :',  a % 2 == 0)
    b = np.random.randint(10, size=(2, 3))
    print('2 dims less than :', b < 5)

def compare_count():
    a = np.random.randint(10, size=(3, 4))
    print('a = ', a)
    print('count:', np.count_nonzero(a < 6))
    print('sum:', np.sum(a < 6))
    print('count in each row:', np.sum(a < 6, axis=1))
    print('have >8: ', np.any(a > 8))
    print('all >8: ', np.all(a > 8))
    print('row all >8', np.all(a > 8, axis=1))

    print('count 2-5:', np.sum((a > 2) & (a < 5)))
    print('count 2-5', np.sum(~((a <= 2) | (a >= 5))))

    print('< 5 mask: ', a[a < 5])
    print('< 5 mask median:', np.median(a[a < 5]))
    more = a > 5
    even = a % 2 == 0
    print('> 5 & even mask median:', np.median(a[more & even]))

def keywords_vs_operators():
    print('bool(42) =', bool(42), 'bool(0) =', bool(0))
    print('bool(42 and 0) =', bool(42 and 0), 'bool(42 or 0) =', bool(42 or 0))
    print('bin(42) =', bin(42), 'bin(59) =', bin(59))
    print('bin(42 & 59) =', bin(42 & 59), 'bin(42 | 0) =', bin(42 | 59))

    a = np.array([1, 0, 1, 0, 1, 0], dtype=bool)
    b = np.array([1, 1, 1, 0, 1, 1], dtype=bool)
    print('A | B = ', a | b)
    # print('A or B = ', a or b) # ValueError: The truth value of an array with more than one element is ambiguous
    x = np.arange(10)
    print('(x > 4) & (x < 8)', (x > 4) & (x < 8))
    # print('(x > 4) and (x < 8)', (x > 4) and (x < 8)) # ValueError:

if __name__ == '__main__':
    print('Numpy Version', np.__version__)
    compare_operate()
    compare_count()
    # keywords_vs_operators()


# import modules
import numpy as np

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

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

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

# Output(s):
polygon area (area)

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

is obtained: area = 84.0

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

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

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

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

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

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


# Graphical Solutions
## Introduction to Linear Programming

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

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

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

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

Product resource requirements and unit profit:

Decision Variables:

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

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

Profit (Z)  Mazimization

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

Labor Constraint Check

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

Clay (Physicial Resource) Constraint Check

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

Negative Production Constaint Check

$x_{1}$ > 0

$x_{2}$ > 0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Q1 Challenge

What if the profit function is:

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

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

q1_profit_answer
q1_x1_answer
q1_x2_answer


import numpy as np

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

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

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

if __name__ == "__main__":
    main()


import numpy as np

from scipy.optimize import linprog

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

# Generic functions

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

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

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

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

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

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

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

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


import numpy as np
import matplotlib.pyplot as plt

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

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

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

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

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

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

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


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

import numpy

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

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

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

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

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

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

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

Task

You are given two integer arrays,
and of dimensions X

.
Your task is to perform the following operations:

    Add (

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

    )

Input Format

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

.

Output Format

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

Sample Input

1 4
1 2 3 4
5 6 7 8

Sample Output

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

Use // for division in Python 3.

"""

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


from copy import deepcopy
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
plt.rcParams['figure.figsize'] = (16, 9)
plt.style.use('ggplot')

# Importing the dataset
data = pd.read_csv('xclara.csv')
print("Input Data and Shape")
print(data.shape)
data.head()

# Getting the values and plotting it
f1 = data['V1'].values
f2 = data['V2'].values
X = np.array(list(zip(f1, f2)))
plt.scatter(f1, f2, c='black', s=7)

# Euclidean Distance Caculator
def dist(a, b, ax=1):
    return np.linalg.norm(a - b, axis=ax)

# Number of clusters
k = 3
# X coordinates of random centroids
C_x = np.random.randint(0, np.max(X)-20, size=k)
# Y coordinates of random centroids
C_y = np.random.randint(0, np.max(X)-20, size=k)
C = np.array(list(zip(C_x, C_y)), dtype=np.float32)
print("Initial Centroids")
print(C)

# Plotting along with the Centroids
plt.scatter(f1, f2, c='#050505', s=7)
plt.scatter(C_x, C_y, marker='*', s=200, c='g')

# To store the value of centroids when it updates
C_old = np.zeros(C.shape)
# Cluster Lables(0, 1, 2)
clusters = np.zeros(len(X))
# Error func. - Distance between new centroids and old centroids
error = dist(C, C_old, None)
# Loop will run till the error becomes zero
while error != 0:
    # Assigning each value to its closest cluster
    for i in range(len(X)):
        distances = dist(X[i], C)
        cluster = np.argmin(distances)
        clusters[i] = cluster
    # Storing the old centroid values
    C_old = deepcopy(C)
    # Finding the new centroids by taking the average value
    for i in range(k):
        points = [X[j] for j in range(len(X)) if clusters[j] == i]
        C[i] = np.mean(points, axis=0)
    error = dist(C, C_old, None)

colors = ['r', 'g', 'b', 'y', 'c', 'm']
fig, ax = plt.subplots()
for i in range(k):
        points = np.array([X[j] for j in range(len(X)) if clusters[j] == i])
        ax.scatter(points[:, 0], points[:, 1], s=7, c=colors[i])
ax.scatter(C[:, 0], C[:, 1], marker='*', s=200, c='#050505')

'''
==========================================================
scikit-learn
==========================================================
'''

from sklearn.cluster import KMeans

# Number of clusters
kmeans = KMeans(n_clusters=3)
# Fitting the input data
kmeans = kmeans.fit(X)
# Getting the cluster labels
labels = kmeans.predict(X)
# Centroid values
centroids = kmeans.cluster_centers_

# Comparing with scikit-learn centroids
print("Centroid values")
print("Scratch")
print(C) # From Scratch
print("sklearn")
print(centroids) # From sci-kit learn


import numpy as np

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

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

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

        return I

    # Integral Error
    def dS(n):

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

        return dI

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

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

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

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


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

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

print(iris_df.head())

print(load_iris.data.shape)

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

print(standardized_x.T)

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

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

print(eigenvalues)

print(eigenvectors)

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

print(varariance)

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

import numpy as np

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

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

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

    """

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

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

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

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

    return x, y, z


import math

import numpy as np
import pytest

from skspatial.measurement import area_signed
from skspatial.measurement import area_triangle
from skspatial.measurement import volume_tetrahedron
from skspatial.objects import Points

@pytest.mark.parametrize(
    ("array_a", "array_b", "array_c", "area_expected"),
    [
        ([0, 0], [1, 0], [0, 1], 0.5),
        ([0, 0], [1, 1], [2, 0], 1),
        ([0, 0], [1, 10], [2, 0], 10),
        ([0, 0], [1, 0], [2, 0], 0),
        ([0, 0], [-5, -2], [5, 2], 0),
        ([1, 0, 0], [0, 1, 0], [0, 0, 1], math.sin(np.pi / 3)),
        ([2, 0, 0], [0, 2, 0], [0, 0, 2], 4 * math.sin(np.pi / 3)),
    ],
)
def test_area_triangle(array_a, array_b, array_c, area_expected):

    area = area_triangle(array_a, array_b, array_c)
    assert math.isclose(area, area_expected)

@pytest.mark.parametrize(
    ("array_a", "array_b", "array_c", "array_d", "volume_expected"),
    [
        ([0, 0], [2, 0], [1, 1], [10, -7], 0),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 1], 1 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, -1], 1 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 2], 2 / 3),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [0, 0, 3], 1),
        ([0, 0, 0], [2, 0, 0], [1, 1, 0], [-56, 10, 3], 1),
        ([0, 1, 1], [0, 1, 5], [0, -5, 7], [0, 5, 2], 0),
    ],
)
def test_volume_tetrahedron(array_a, array_b, array_c, array_d, volume_expected):

    volume = volume_tetrahedron(array_a, array_b, array_c, array_d)
    assert math.isclose(volume, volume_expected)

@pytest.mark.parametrize(
    ("points", "area_expected"),
    [
        # Counter-clockwise triangle
        ([[2, 2], [6, 2], [4, 5]], 6),
        # Clockwise triangle
        ([[1, 3], [-4, 3], [-3, 4]], -2.5),
        # Counter-clockwise square
        ([[-1, 2], [2, 5], [-1, 8], [-4, 5]], 18),
        # Clockwise irregular convex pentagon
        ([[-2, 2], [-5, 2], [-8, 5], [-4, 8], [-1, 5]], -25.5),
        # Counter-clockwise irregular convex hexagon
        ([[3, -2], [6, -3], [10, -1], [8, 4], [4, 3], [1, 1]], 39.5),
        # Clockwise non-convex polygon
        ([[5, -2], [1, -1], [0, 4], [6, 6], [3, 3]], -22),
        # Self-overlapping polygon
        ([[-4, 4], [-4, 1], [2, 4], [2, 1]], 0),
    ],
)
def test_area_signed(points, area_expected):

    points = Points(points)
    area = area_signed(points)

    assert area == area_expected

@pytest.mark.parametrize(
    ("points", "message_expected"),
    [
        ([[1, 0, 0], [-1, 0, 0], [0, 1, 0]], "The points must be 2D."),
        ([[2, 0], [-2, 0]], "There must be at least 3 points."),
    ],
)
def test_area_signed_failure(points, message_expected):

    with pytest.raises(ValueError, match=message_expected):
        area_signed(points)


'''
Was 2015 anomalous?
100xp

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

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

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

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

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

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

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