import numpy as np
from checker.fault import get_fault

np.set_printoptions(suppress=True)

def solve_jacobi(matrix_a: list, vector_b: list, epsilon=10 ** (-6)) -> list:
    """
    Solve by Jacobi method(simple iteration)
    :param matrix_a: start matrix
    :param vector_b: start vector
    :param epsilon: number for comparing
    :return: solution vector
    """
    solution_vector = [0 for _ in range(len(matrix_a[0]))]

    matrix_c = []
    vector_d = []
    # Create matrix C and vector D
    for i in range(len(matrix_a)):
        element_d = vector_b[i] / matrix_a[i][i]
        vector_d.append(element_d)

        element_c = []
        for j in range(len(matrix_a[0])):
            if i == j:
                element_c.append(0)
            else:
                element_c.append((-1) * matrix_a[i][j] / matrix_a[i][i])
        matrix_c.append(element_c)

    iterations = 0
    tmp = 0
    # Start the main algorithm
    while True:
        divs = []
        left_part = np.dot(matrix_c, solution_vector)
        for i in range(len(solution_vector)):
            x_next = left_part[i] + vector_d[i]
            divs.append(abs(x_next - solution_vector[i]))
            solution_vector[i] = x_next
        # Check if we need to stop
        if max(divs) < epsilon:
            print(f'Last result: {np.matrix(solution_vector).round(6)}')
            # It's time to stop!
            break
        else:
            if tmp < 3:
                print(f'Temporary result: {np.matrix(solution_vector).round(6)}')
                tmp += 1
            print(f'Residual vector: {np.matrix(np.subtract(vector_b, np.dot(matrix_a, solution_vector)), float).round(6)}')
            iterations += 1
    print(f'Iterations: {iterations}')
    return solution_vector

a = [[4.4944, 0.1764, 1.7956, 0.7744],
     [0.1764, 15.6025, 3.4969, 0.1849],
     [1.7956, 3.4969, 8.8804, 0.2116],
     [0.7744, 0.1849, 0.2116, 19.7136]]
b = [31.97212, 9.18339, 19.51289, 51.39451]
print(f'Matrix A:', np.matrix(a).round(6))
print(f'Vector b:', b)
sol = solve_jacobi(a.copy(), b.copy())
sol_np = np.linalg.solve(a, b)
print(f'Our solution: {np.matrix(sol).round(6)}')
print(f'Residual vector: {np.matrix(np.subtract(b, np.dot(a, sol))).round(6)}')
print(f'NumPy solution: {sol_np}')
print(f'Residual vector for NumPy: {np.matrix(np.subtract(b, np.dot(a, sol_np))).round(6)}')
print('Fault:', round(get_fault(sol, sol_np), 6))


# Trigonometric Functions
import numpy as np
a = np.array([0,30,45,60,90])

print ('Sine of different angles:')
# Convert to radians by multiplying with pi/180
print (np.sin(a*np.pi/180))
print ('\n')

print ('Cosine values for angles in array:')
print (np.cos(a*np.pi/180))
print ('\n')

print ('Tangent values for given angles:')
print (np.tan(a*np.pi/180))

import numpy as np
a = np.array([0,30,45,60,90])

print ('Array containing sine values:')
sin = np.sin(a*np.pi/180)
print (sin)
print ('\n')

print ('Compute sine inverse of angles. Returned values are in radians.')
inv = np.arcsin(sin)
print (inv)
print ('\n')

print ('Check result by converting to degrees:')
print (np.degrees(inv))
print ('\n')

print ('arccos and arctan functions behave similarly:')
cos = np.cos(a*np.pi/180)
print (cos)
print ('\n')

print ('Inverse of cos:')
inv = np.arccos(cos)
print (inv)
print ('\n')

print ('In degrees:')
print (np.degrees(inv))
print ('\n')

print ('Tan function:')
tan = np.tan(a*np.pi/180)
print (tan)
print ('\n')

print ('Inverse of tan:')
inv = np.arctan(tan)
print (inv)
print ('\n')

print ('In degrees:')
print (np.degrees(inv))

# Functions for Rounding
import numpy as np
a = np.array([1.0,5.55, 123, 0.567, 25.532])

print ('Original array:')
print (a)
print ('\n')

print ('After rounding:')
print (np.around(a))
print (np.around(a, decimals = 1))
print (np.around(a, decimals = -1))

import numpy as np
a = np.array([-1.7, 1.5, -0.2, 0.6, 10])

print ('The given array:')
print (a)
print ('\n')

print ('The modified array:')
print (np.floor(a))

import numpy as np
a = np.array([-1.7, 1.5, -0.2, 0.6, 10])

print ('The given array:')
print (a)
print ('\n')

print ('The modified array:')
print (np.ceil(a))


import numpy as np
from numpy.linalg import qr
np.set_printoptions(precision=2, suppress=True) # It is set to display & secure the output with 4 decimals

def qr_factorization(A):      #Defining the function
    m, n = A.shape
    Q = np.zeros((m, n))
    R = np.zeros((n, n))

    for j in range(n):
        v = A[:, j]

        for i in range(j):
            q = Q[:, i]
            R[i, j] = q.dot(v)
            v = v - R[i, j] * q

        norm = np.linalg.norm(v)
        Q[:, j] = v / norm
        R[j, j] = norm
    return Q, R

a = np.array([[0,2],
              [2,3]])

n = int(input("How many iterations you want?"))
print("\n Given Matrix : A = ",a)
print("\n QR Factorization of A : ",qr_factorization(a))

i=0  #Initial Iteration
while i<n:
      q,r = qr_factorization(a)
      a = np.dot(r,q)
      i=i+1
      print('\n Iteration Number : ',i)
      print(a)
eigenvalues = a.diagonal()

print("Eigenvalues of the given matrix : ",eigenvalues)

##OUTPUT:
##How many iterations you want?10
##
## Given Matrix : A =  [[0 2]
## [2 3]]
##
## QR Factorization of A :  (array([[0., 1.],
##       [1., 0.]]), array([[2., 3.],
##       [0., 2.]]))
##
## Iteration Number :  1
##[[3. 2.]
## [2. 0.]]
##
## Iteration Number :  2
##[[ 3.92  0.62]
## [ 0.62 -0.92]]
##
## Iteration Number :  3
##[[ 4.    0.16]
## [ 0.16 -1.  ]]
##
## Iteration Number :  4
##[[ 4.    0.04]
## [ 0.04 -1.  ]]
##
## Iteration Number :  5
##[[ 4.    0.01]
## [ 0.01 -1.  ]]
##
## Iteration Number :  6
##[[ 4.  0.]
## [ 0. -1.]]
##
## Iteration Number :  7
##[[ 4.  0.]
## [ 0. -1.]]
##
## Iteration Number :  8
##[[ 4.  0.]
## [ 0. -1.]]
##
## Iteration Number :  9
##[[ 4.  0.]
## [ 0. -1.]]
##
## Iteration Number :  10
##[[ 4.  0.]
## [ 0. -1.]]
##Eigenvalues of the given matrix :  [ 4. -1.]


import sympy as sp
from math import pi, sqrt

def simpson_rule(equation, a, b, n):
    """
    Method to get the integration approximation of a function using simpson rule

    Parameters:
    equation: The equation f(x)
    a: lower bound of the integral
    b: upper bound of the integral
    n: no of segments
    """

    h = (b-a)/n
    x = sp.Symbol('x')
    xVal = a

    result = 0
    #First segment
    result += equation.evalf(subs={x:xVal})
    xVal += h
    #Other segments
    for i in range(1, n+1):
        if (i == n):  #If last segment
            result += equation.evalf(subs={x:xVal})
        else:
            if (i % 2 != 0):
                result += equation.evalf(subs={x:xVal}) * 4
            else:
                result += equation.evalf(subs={x:xVal}) * 2
        xVal += h

    result *= h/3

    return result

def trapezoid_rule(equation, a, b, n):
    """
    Method to get the integration approximation of a function using trapezoid rule

    Parameters:
    equation: The equation f(x)
    a: lower bound of the integral
    b: upper bound of the integral
    n: no of segments
    """

    h = (b-a)/n
    x = sp.Symbol('x')
    xVal = a

    result = 0
    #First segment
    result += equation.evalf(subs={x:xVal})
    xVal += h
    #Other segments
    for i in range(1, n+1):
        if (i == n):  #If last segment
            result += equation.evalf(subs={x:xVal})
        else:
            result += 2 * equation.evalf(subs={x:xVal})
        xVal += h

    result *= (h/2)

    return result

#Example usage

def gaussian_quadrature_2_points(equation, a, b):
    """
    Method to get the integration approximation of a function using Gaussian Quadrature with two points

    Parameters:
    equation: The equation f(x)
    a: lower bound of the integral
    b: upper bound of the integral
    """

    x = sp.Symbol('x')

    c1 = (b-a)/2
    c2 = c1
    x1 = c1 * (-1 * (1/sqrt(3))) + (b+a)/2
    x2 = c1 * (1/sqrt(3)) + (b+a)/2

    answer = c1 * equation.evalf(subs={x: x1}) + c2 * equation.evalf(subs={x: x2})

    return answer

def example():
    x = sp.Symbol('x')
    equa = 8 + 4 * sp.cos(x)
    segments = 4
    a = 0
    b = pi/2
    print(simpson_rule(equa, a, b, segments))
    print(trapezoid_rule(equa, a, b, segments))
    print(gaussian_quadrature_2_points(equa, a, b))


# Question 1, Lab 6
# AB Satyaprakash, 180123062

# imports
from sympy.abc import t,y
import numpy as np
import sympy as sp

# functions
def getEulerApproximation(f,X,Y,h):
    for i in range(1,Y.shape[0]):
        Y[i]=Y[i-1] + (f.subs({t:X[i-1], y:Y[i-1]})*h)

# program body
# Case (A):
# t belongs to [0,1] and y(0)=1, with h = 0.5
a, b, h = 0, 1, 0.5
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Y[0] = 1

f = sp.exp(t-y) # from question
getEulerApproximation(f,X,Y,h)
print('(a)')
print('The approx solution in this case is given as:')
for i in range(X.shape[0]):
    print('y({}) = {}'.format(X[i],Y[i]))

# Case (B):
# t belongs to [1,2] and y(1)=2, with h = 0.5
a, b, h = 1, 2, 0.5
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Y[0] = 2

f = (1+t)/(1+y) # from question
getEulerApproximation(f,X,Y,h)
print('(b)')
print('The approx solution in this case is given as:')
for i in range(X.shape[0]):
    print('y({}) = {}'.format(X[i],Y[i]))

# Case (C):
# t belongs to [2,3] and y(2)=2, with h = 0.25
a, b, h = 2, 3, 0.25
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Y[0] = 2

f = -y+(t*sp.sqrt(y)) # from question
getEulerApproximation(f,X,Y,h)
print('(c)')
print('The approx solution in this case is given as:')
for i in range(X.shape[0]):
    print('y({}) = {}'.format(X[i],Y[i]))

# Case (D):
# t belongs to [1,2] and y(1)=2, with h = 0.25
a, b, h = 1, 2, 0.25
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Y[0] = 2

f = (sp.sin(2*t)-(2*t*y))/(t*t) # from question
getEulerApproximation(f,X,Y,h)
print('(d)')
print('The approx solution in this case is given as:')
for i in range(X.shape[0]):
    print('y({}) = {}'.format(X[i],Y[i]))

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


import numpy as np
from scipy.stats import linregress
import statsmodels.api as sm
import matplotlib.pyplot as plt
from gekko import GEKKO

# Data
x = np.array([4,5,2,3,-1,1,6,7])
y = np.array([0.3,0.8,-0.05,0.1,-0.8,-0.5,0.5,0.65])

# calculate R^2
def rsq(y1,y2):
    yresid= y1 - y2
    SSresid = np.sum(yresid**2)
    SStotal = len(y1) * np.var(y1)
    r2 = 1 - SSresid/SStotal
    return r2

# Method 1: scipy linregress
slope,intercept,r,p_value,std_err = linregress(x,y)
a = [slope,intercept]
print('R^2 linregress = '+str(r**2))

# Method 2: numpy polyfit (1=linear)
a = np.polyfit(x,y,1); print(a)
yfit = np.polyval(a,x)
print('R^2 polyfit    = '+str(rsq(y,yfit)))

# Method 3: numpy linalg solution
#       y =     X a
#   X^T y = X^T X a
X = np.vstack((x,np.ones(len(x)))).T
# matrix operations
XX = np.dot(X.T,X)
XTy = np.dot(X.T,y)
a = np.linalg.solve(XX,XTy)
# same solution with lstsq
a = np.linalg.lstsq(X,y,rcond=None)[0]
yfit = a[0]*x+a[1]; print(a)
print('R^2 matrix     = '+str(rsq(y,yfit)))

# Method 4: statsmodels ordinary least squares
X = sm.add_constant(x,prepend=False)
model = sm.OLS(y,X).fit()
yfit = model.predict(X)
a = model.params
print(model.summary())

# Method 5: Gekko for constrained regression
m = GEKKO(remote=False); m.options.IMODE=2
c  = m.Array(m.FV,2); c[0].STATUS=1; c[1].STATUS=1
c[1].lower=-0.5
xd = m.Param(x); yd = m.Param(y); yp = m.Var()
m.Equation(yp==c[0]*xd+c[1])
m.Minimize((yd-yp)**2)
m.solve(disp=False)
c = [c[0].value[0],c[1].value[1]]
print(c)

# plot data and regressed line
plt.plot(x,y,'ko',label='data')
xp = np.linspace(-2,8,100)
slope     = str(np.round(a[0],2))
intercept = str(np.round(a[1],2))
eqn = 'LstSQ: y='+slope+'x'+intercept
plt.plot(xp,a[0]*xp+a[1],'r-',label=eqn)
slope     = str(np.round(c[0],2))
intercept = str(np.round(c[1],2))
eqn = 'Constraint: y='+slope+'x'+intercept
plt.plot(xp,c[0]*xp+c[1],'b--',label=eqn)
plt.grid()
plt.legend()
plt.show()

import numpy,from scipy,import statsmodels


import numpy as np
import matplotlib.pyplot as plt

# Calculate Predator next value.
def predator(x, y, a, b, e):
	return x + a*x + b*y + e*x*x

# Calculate Prey next value.
def prey(x, y, c, d, f):
	return y + c*x + d*y + f*y*y

# Plot phase plot.
def plot(title, figure, x, y):

	plt.figure(figure)
	plt.title(title)
	plt.xlabel("Time")
	plt.ylabel("Population")
	plt.plot(x)
	plt.plot(y)

# Iterate and use the values provided to produce a stable osscilation.
def iterate(a, iterations):

	x = np.zeros(iterations + 1, dtype=float)
	y = np.zeros(iterations + 1, dtype=float)

	x[0] = 1000.0
	y[0] = 500.0

	# Set values for all constants to produce a stable osscilation.
	b = -3
	e = 0.0000001
	c = 0.3
	d = -2
	f = 0.00001

	# Iterate to calculate next states.
	for i in range(0, iterations):
		x[i + 1] = predator(x[i], y[i], a, b, e)
		y[i + 1] = prey(x[i], y[i], c, d, f)

	return (x, y)

def iterate_extended_terms(a, iterations):

	x = np.zeros(iterations + 1, dtype=float)
	y = np.zeros(iterations + 1, dtype=float)

	x[0] = 1000.0
	y[0] = 500.0

	# Set values for all constants to produce a stable osscilation.
	b = -3
	e = 0.0000001
	g = 0
	c = 0.3
	d = -2
	f = 0.00001
	h = 0

	# extended terms
	g = 0.00001
	h = 0.00001

	for i in range(0, iterations):
		x[i + 1] = predator(x[i], y[i], a, b, e) + g * x[i] * y[i]
		y[i + 1] = prey(x[i], y[i], c, d, f) + h * x[i] * y[i]

	return (x, y)

# Calculate the mean of Predator and Prey.
def mean(x, y):
	return (np.sum(x)/x.size, np.sum(y)/y.size)

def solution():

	# ******** Equations without the extended terms g and h are used for the following solutions **********

	# Number of iterations.
	iterations = 1000

	# Setting the value of a
	a = 0.6
	x, y = iterate(a, iterations)
	print("**** Plotting the stable Oscillation without the extended terms g and h. You would be able to see it on the screen. ****")
	plot("Stable Oscillation without extended terms g and h.", 1, x, y)

	# Calculate mean x and mean y using a = 0.6.
	print("Mean with a = 0.6")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	## Calculate mean x and mean y using a = 0.59. To see impact of decreasing a on mean x and mean y.
	a = 0.59
	x, y = iterate(a, iterations)

	print("Mean with a = 0.59")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	## Calculate mean x and mean y using a = 0.61. To see impact of increasing a on mean x and mean y.
	a = 0.61
	x, y = iterate(a, iterations)

	print("Mean with a = 0.61")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	# Print observations.
	print("**** MEAN Observations Result ****")
	print("As you can see that on increasing a the mean population of both Predator and Prey gets decreased, and on decreasing a the mean population of both gets increased.")

	# *********************************************************************************************

def solution_extended_terms():

	# ******** Equations with the extended terms g and h are used for the following experiments **********

	# Number of iterations.
	iterations = 1000

	# Setting the value of a
	a = 0.6
	x, y = iterate_extended_terms(a, iterations)
	print("**** Plotting the stable Oscillation with the extended terms g and h. You would be able to see it on the screen. ****")
	plot("Stable Oscillation with extended terms g and h.", 2, x, y)

	# Calculate mean x and mean y using a = 0.6.
	print("Mean with a = 0.6")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	## Calculate mean x and mean y using a = 0.59. To see impact of decreasing a on mean x and mean y.
	a = 0.59
	x, y = iterate_extended_terms(a, iterations)

	print("Mean with a = 0.59")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	## Calculate mean x and mean y using a = 0.61. To see impact of increasing a on mean x and mean y.
	a = 0.61
	x, y = iterate_extended_terms(a, iterations)

	print("Mean with a = 0.61")
	meanx, meany = mean(x, y)
	print("Mean Predator x: " + str(meanx))
	print("Mean Prey y: " + str(meany))
	print("")

	## Print observations.
	print("**** MEAN Observations Result ****")
	print("As you can see that on increasing a the mean population of both Predator and Prey gets decreased, and on decreasing a the mean population of both gets increased.")

	# *********************************************************************************************

if __name__== "__main__":

	# Solution with equations without using extended terms g and h.
	solution()

	print("")

	# Solution with equations using extended terms g and h.
	solution_extended_terms()

	# Plot both the graphs
	plt.show()

	print("")

	# Print result of final observations of both the systems.
	print("**** Result from both the systems ****")
	print("As you can see in the results above from both the systems that with the extended terms g and h the population size is decreased as compared to when they are no extended terms.")


# Project Euler Problem 1 Solution
#
# Problem statement:
# If we list all the natural numbers below 10 that are multiples
# of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
# Find the sum of all the multiples of 3 or 5 below 1000.
#
# Solution description:
# This script compares a brute force algorithm with O(n) with a
# formula-based algorithm with O(1). Performance differences are
# best observed with input numbers > 1000000.
#
# Authors: Philipp Schuette, Daniel Schuette
# Date: 2018/09/24
# License: MIT (see ../LICENSE.md)
import time

import numpy as np

def slow_divisible(a=3, b=5, target=1000):
    """
    this function calculates the sum of all integers from 0 to
    `target' that are divisible by `a' or `b' with a remander
    of 0
    this is a slow implementation of the algorithm
    -------------------------
    params:
        a, b    - the integers that are used for the modulo operation
        target  - the interval over which to calculate the sum
    """
    sum = 0
    for i in range(target):
        if (i % 3) == 0:
            sum += i
        elif (i % 5) == 0:
            sum += i
    return sum

def fast_summation(x, target):
    """
    this function calculates the sum of all integers from 0 to
    `target' that are divisible by `x' with a remander of 0
    this is a fast implementation of the algorithm
    -------------------------
    params:
        x       - the integer that is used for the modulo operation
        target  - the interval over which to calculate the sum
    """
    n = (target - 1) // x
    sum = x * n * (n + 1) / 2
    return sum

def fast_divisible(a=3, b=5, target=1000):
    """
    this function calculates the project euler problem 1
    solution using a fast solution
    `fast_summation()' is used to do so
    """
    sum = (fast_summation(a, target) + fast_summation(b, target) -
           fast_summation(a * b, target))
    return sum

if __name__ == "__main__":
    # get a target from the user
    target = int(input("please provide a positive integer: "))

    # time the slow function
    slow_start = time.time()
    slow_solution = slow_divisible(target=target)
    slow_end = time.time()
    slow_time = slow_end - slow_start

    # time the fast function
    fast_start = time.time()
    fast_solution = fast_divisible(target=target)
    fast_end = time.time()
    fast_time = fast_end - fast_start

    # print the results
    print("slow solution: {}\nfast solution: {}".format(
        slow_solution,
        fast_solution,
    ))
    print("slow time: {}s\nfast time: {}s".format(
        np.round(slow_time, 6), np.round(fast_time, 6)))


from numpy import array, zeros, sqrt

def decomposition(A):
    N = len(A)
    L = zeros((N,N))
    U = zeros((N,N))

    for j in range(N):                                  # j = 0 to N-1

        # Upper Triangular
        for i in range(j+1):                            # i = 0 to j

            U[i,j] = A[i,j] - sum(U[:i,j] * L[i,:i])    # k = 0 to i-1

        # Lower Triangular
        L[j,j] = 1                                          # i = j, Diagonal 1

        for i in range(j+1, N):                             # i = j+1 to N-1

            L[i,j] = (A[i,j] - sum(U[:j,j]*L[i,:j]))/U[j,j] # k = 0 to j-1

    print('LU Decomposition:')
    print('[L] =\n', L, sep='')
    print('[U] =\n', U, sep='', end='\n\n')
    return L, U

def solveLU(A, B):
    L, U = decomposition(A)
    N = len(L)

    X = zeros(N)
    Y = zeros(N)

    # Forward Substitution
    for i in range(N):                                      # i = 0 to N-1
        Y[i] = (B[i] - sum(L[i,:i] * Y[:i]))                # j = 0 to i-1

        # sumj = 0
        # for j in range(i):
        #     sumj += L[i,j] * Y[j]
        # Y[i] = (B[i] - sumj)

    # Backward Substitution
    for i in range(N-1, -1, -1):                            # i = N-1 to 0
        X[i] = (Y[i] - sum(U[i,i+1:] * X[i+1:])) / U[i,i]   # j = i+1 to N-1

        # sumj = 0
        # for j in range(i+1, N):
        #     sumj += U[i,j] * X[j]
        # X[i] = (Y[i] - sumj) / U[i,i]

    return X

# System of Equations

A = array([[3,  -.1, -.2],
           [.1,  7,  -.3],
           [.3, -.2, 10]], float)
B = array([10.3, 33.6, 60.2], float)

N = len(A)

X = solveLU(A, B)

print("The Solution of the System:")
for i in range(N):
    print('X[', i+1, '] = ', round(X[i], 6), sep='')

'''
Decomposition: [A] = [L][U]   # [L][U] ≠ [U][L]    Not commutative

│a11 a12 a13 ... a1n│   │L11  0   0  ...  0 ││U11 U12 U13 ... U1n│
│a21 a22 a23 ... a2n│   │L21 L22  0  ...  0 ││ 0  U22 U23 ... U2n│
│a31 a32 a33 ... a3n│ = │L31 L32 L33 ...  0 ││ 0   0  U33 ... U3n│
│... ... ... ... ...│   │... ... ... ... ...││... ... ... ... ...│
│an1 an2 an3 ... ann│   │Ln1 Ln2 Ln3 ... Lnn││ 0   0   0  ... Unn│

For Doolittle: Lii = 1  All elements on main diagonal of [L] are 1

│a11 a12 a13 ... a1n│   │ 1   0   0  ...  0 ││U11 U12 U13 ... U1n│
│a21 a22 a23 ... a2n│   │L21  1   0  ...  0 ││ 0  U22 U23 ... U2n│
│a31 a32 a33 ... a3n│ = │L31 L32  1  ...  0 ││ 0   0  U33 ... U3n│
│... ... ... ... ...│   │... ... ...  1  ...││... ... ... ... ...│
│an1 an2 an3 ... ann│   │Ln1 Ln2 Ln3 ...  1 ││ 0   0   0  ... Unn│

│a11 a12 a13 ... a1n│
│a21 a22 a23 ... a2n│
│a31 a32 a33 ... a3n│ =
│... ... ... ... ...│
│an1 an2 an3 ... ann│

│U11    U12           U13                  ... U1n                         │
│U11L21 U12L21+U22    U13L21+U23           ... U1nL21+U2n                  │
│U11L31 U12L31+U22L32 U13L31+U23L32+U33    ... U1nL31+U2nL32+U3n           │
│ ...        ...             ...           ...             ...             │
│U11Ln1 U12Ln1+U22Ln2 U13Ln1+U23Ln2+U33Ln3 ... U1nLn1+U2nLn2+U3nLn3+...+Unn│

 U1jLi1 U1jLi1+U2jLi2 U1jLi1+U2jLi2+U3jLi3 ... U1jLi1+U2jLi2+U3jLi3+...+Unj

 where i = 1 to n, j = 1 to n, k = 1 to j

U11 = a11       U12 =  a12              U13 =  a13                     ...
L21 = a21/U11   U22 =  a22-U12L21       U23 =  a23-U13L21              ...
L31 = a31/U11   L32 = (a32-U12L31)/U22  U33 =  a33-U13L31-U23L32       ...
Ln1 = an1/U11   Ln2 = (an3-U12Ln1)/U22  Ln3 = (an3-U13L31-U23L32)/U33  ...

Uij = aij       Uij =  aij-U1jLi1       Uij =  aij-U1jLi1-U2jLi2
Lij = aij/Ujj   Lij = (aij-U1jLi1)/Ujj  Lij = (aij-U1jLi1-U2jLi2)/Ujj

Ljj = 1                       j = 1   to n
Uij =  aij - ∑ Ukj*Lik        i = 1   to j, k = 1 to i-1
Lij = (aij - ∑ Ukj*Lik)/Ujj   i = j+1 to n, k = 1 to j-1

For U1j there is no ∑ and is implimented by k = 1 to 1-1, not entering k loop
For Li1 there is no ∑ and is implimented by k = 1 to 1-1, not entering k loop

Substitution: [A]{X}={B}    => [L][U]{X}={B}    => [U]{X}={y} and [L]{y}={B}

Forward Substitution: [L]{y}={B}

│ 1   0   0  ...  0 ││y1│   │b1│
│L21  1   0  ...  0 ││y2│   │b2│
│L31 L32  1  ...  0 ││y3│ = │b3│
│... ... ... ... ...││……│   │……│
│Ln1 Ln2 Ln3 ...  1 ││yn│   │bn│

y1 =  b1
y2 = (b2 - L21y1)
y3 = (b3 - L31y1 - L32y2)
yn = (bn - Ln1y1 - Ln2y2 - ... - L[n,n-1]y[n-1])

yi = (bi - ∑ Lij*yj)       , i = 1 to n, j = 1 to i-1

For y1 there is no ∑ and is implimented by j = 1 to 1-1, not entering j loop

Backward Substitution: [U]{X}={y}

│U11 U12 U13 ... U1n││x1│   │y1│
│ 0  U22 U23 ... U2n││x2│   │y2│
│ 0   0  U33 ... U3n││x3│ = │y3│
│... ... ... ... ...││……│   │……│
│ 0   0   0  ... Unn││xn│   │yn│

xn =  yn / Unn
x3 = (y3                   - U34*x4) / U33
x2 = (y2          - U23*x3 - U24*x4) / U22
x1 = (y1 - U12*x2 - U13*x3 - U14*x4) / U11
xi = (yi - Uij*xj - Uij*xj - Uij*xj) / Uii

xi = (yi - ∑ Uij*xj) / Uii , i = n to 1, j = i+1 to n

For xn there is no ∑ and is implimented by j = n+1 to n, not entering j loop

'''

5. Systems of Linear Equations/4. Doolittle's (Lii=1) Triangularization (Factorization or LU Decomposition) Method.py


import numpy as np
import matplotlib.pyplot as plt

# calculates the y value of a function given its lagrange expansion
def lagrange_i(x_i, x_list, y_list):
    y_i = 0
    for j in range(len(x_list)):
        l = 1
        for m in range(len(x_list)):
            if m != j:
              l *= (x_i - x_list[m])/(x_list[j]-x_list[m])
        y_i = y_i + y_list[j]*l
    return y_i

#  calculates the Lagrange Extrapolation of a discrete function given a list of x coordinates,
#  corresponding y coordinates, and the number of point wanted to make the extrapolation
def lagr_interpol(x_list, y_list, num_points):
    x_pts = np.linspace(x_list[0], x_list[len(x_list)-1], num_points)
    y_pts = np.zeros(shape=(num_points, 1))
    for i in range(num_points):
        y_i = lagrange_i(x_pts[i], x_list, y_list)
        y_pts[i] = y_i
    return x_pts, y_pts

if __name__ == '__main__':
    # list of H and B values provided in the assignment
    B = [0, 0.2, 0.4, 0.6, 0.8, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
    H = [0, 14.7, 36.5, 71.7, 121.4, 197.4, 256.2, 348.7, 540.6, 1062.8, 2318.0, 4781.9, 8687.4, 13924.3, 22650.2]

    B_1 = B[0:6]  # subdomain of B for Q1a)
    H_1 = H[0:6]  # subdomain of H for Q1a)
    ans = lagr_interpol(B_1, H_1, 100)
    x1, y1 = ans

    B_2 = [0, 1.3, 1.4, 1.7, 1.8, 1.9]  # subdomain of B for Q1b)
    H_2 = [0, 540.6, 1062.8, 8687.4, 13924.3, 22650.2]  # subdomain of H for Q1b)

    ans2 = lagr_interpol(B_2, H_2, 100)
    x2, y2 = ans2

# comment these out to test for the question 1 a), or 1b)
    plt.title("Question 1a")
    plt.xlabel("B (T)")
    plt.ylabel("H (A/m)")
    plt.plot(x1, y1, color="black", label="Lagrange Interpolation")
    plt.plot(B_1, H_1, label='Original Values')
    plt.legend(loc="upper left")
    plt.show()

# uncomment to see 1b) answer
#     plt.title("Question 1b")
#     plt.xlabel("B (T)")
#     plt.ylabel("H (A/m)")
#     plt.plot(x2, y2, color="black", label="Lagrange Interpolation")
#     plt.plot(B_2, H_2, label='Original Values')
#     plt.legend(loc="upper left")
#     plt.show()


"""
Title: Numerically Integrating f(x) = xe^(-ax) using the One-dimensional Trapezoidal Method

Solution to Problem Set 2, Problem 1
~ Arsh R. Nadkarni

To run:
python trapezoidal_2_1.py

"""
# import libraries
import numpy as np
from math import exp
import matplotlib.pyplot as plt

# function to be integrated (integrand)
def f(x):
    return x*np.exp(-1*2*x)

# construct the trapezoidRule function based on the pseudo-code in ProblemSet2.pdf
def trapezoidalRule(func,a,b,n):
    h = (b-a)/n
    I1 = func(a) + func(b)
    I2 = 0
    i = 1
    for i in range(1,n):
        x = a+i*h
        I2 += f(x)
        i += 1
    I = (h/2)*(I1+(2*I2))
    return I

# function to return the value for the definite integral at n=100 (Problem 1 - Part 1)
def A():
    answer = trapezoidalRule(f, 0, 1, 100)
    print("The value of the integral computed using the Trapeizodal Rule is", answer)
A()

# function to evaluate the error and plot the relative error as a function of n on a log-log plot, comparing it with the expected order of convergence (Problem 1 - Part 2)
def B():
    t = 4
    N = np.zeros(t)
    err = np.zeros(t)
    I = np.zeros(t)
    aval = 0.1484985375725405 # analytical value of the integral
    for i in range(t):
        n = 50 * (2**i) # n= 50,100,200,400
        N[i] = n
        I[i] = trapezoidalRule(f, 0, 1, n)
        err[i] = np.abs((aval-I[i])/I[i])
    fig,a = plt.subplots(figsize=(10,8))
    a.loglog(N,err,'r--',label='Relative Error',lw=3)
    a.loglog(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    a.set_xlabel('log(n)',fontweight='bold')
    a.set_ylabel('log(Relative Error)',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n: log-log Plot (Case: i=1,..,n-1)', fontweight='bold')
    plt.show()
B()

# function to find an approximate n such that the numerical answer is accurate to at least five significant figures (Problem 1 - Part 3)
def C():
    aval = 0.1484985375725405 # analytical value of the integral (Calculated by Hand)
    eps = 1e-5 # tolerance of 5 significant figures
    n = 1
    while n < 1000:
        I = trapezoidalRule(f, 0, 1, n)
        err = abs((I-aval)/I)
        if err < eps:
            print("Approx. n such that numerical answer is accurate to at least 5 significant figures is", n)
            n = 1001
        else:
            n += 1
C()

# construct the trapezoidRule function to iterate i to n instead of n-1
def trapezoidalRule_n(func,a,b,n):
    h = (b-a)/n
    I1 = func(a) + func(b)
    I2 = 0
    i = 1
    for i in range(0,n+1):
        x = a+i*h
        I2 += f(x)
        i += 1
    I = (h/2)*(I1+(2*I2))
    return I

# function to evaluate the error and plot the relative error as a function of n on a log-log plot, comparing it with the expected order of convergence (Problem 1 - Part 2)
def D():
    t = 4
    N = np.zeros(t)
    err = np.zeros(t)
    I = np.zeros(t)
    aval = 0.1484985375725405 # analytical value of the integral (Calculated by Hand)
    for i in range(t):
        n = 50 * (2**i) # n= 50,100,200,400
        N[i] = n
        I[i] = trapezoidalRule_n(f, 0, 1, n)
        err[i] = np.abs((aval-I[i])/I[i])
    fig,a = plt.subplots(figsize=(10,8))
    a.loglog(N,err,'r--',label='Relative Error',lw=3)
    a.loglog(N,1/N**2,'b-',label='Expected Convergence (1/$n^2$)',lw=3)
    a.set_xlabel('log(n)',fontweight='bold')
    a.set_ylabel('log(Relative Error)',fontweight='bold')
    plt.legend()
    plt.title('Relative Error as a function of n: log-log Plot (Case: i=0,..,n) ', fontweight='bold')
    plt.show()
D()

"""
The order of convergence does not match the expected order (O(1/n^2)) and we obtain a slope in the plot which is equal to −1.
This is likely because in part D) we are iterating i from 0,..,n which means the function is integrated at both its endpoints,
thus increasing the value of the approximation and the errors corresponding to each n.

"""

Problem Sets/Problem Set 2/trapezoidal_2_1.py


# Cubic spline interpolation

import numpy as np

def spline3(x, y, X):
    '''
    Cubic Spline interpolation method.

    Input:
      - x  : precomputed points x axis values
      - y  : precomputed points y axis values
      - xq : query points x axis
    Output:
      - yq : interpolation result for query points
    '''
    (a, b, c, d) = spline3_coef(x, y)
    Y = np.zeros(len(X))
    for i in range(0, len(X)):
        Y[i] = spline3_eval(a, b, c, d, x, X[i])
    return Y

def spline3_eval(a, b, c, d, x, Xi):
    '''
    Evaluation function.

    Input:
      - [ a, b, c, d ] : spline coefficients list
      - x  : precomputed points x axis values
      - Xi : query point
    Output:
      - Yi : spline value at query point
    '''
    i = 0
    while i < len(x)-1 and Xi > x[i+1]:
        i = i+1
    # Xi is between x[i] and x[i+1]
    Yi = a[i]*(Xi-x[i])**3 + b[i]*(Xi-x[i])**2 + c[i]*(Xi-x[i]) + d[i]
    return Yi

def build_tridiag(h, n):
    '''
    Internal helper function to build the trigiagonal matrix used to find
    the unknown values m(i)
    '''
    T = np.zeros((n,n))
    for i in range(0, n):
        T[i,i] = 2*(h[i] + h[i+1])
    for i in range(0, n-1):
        T[i+1,i] = h[i+1]
        T[i,i+1] = h[i+1]
    return T

def spline3_coef(x, y):
    '''
    Helper function returning a list of coefficients vectors.
    Assumption: x and y shall be of the same length

    Input:
      - x : interpolation nodes x axis values
      - y : interpolation nodes y axis values
    Output:
      - [a,b,c,d] : List of coefficients vectors
        The polynomial used in the interval [x(i), x(i+1)) is constructed as
        Ci(x) = a[i](x-x[i])^3 + b[i](x-x[i])^2 + c[i](x-x[i]) + d[i]
    '''
    n = len(x)
    # Intervals lengths
    h = x[1:n] - x[0:n-1]
    # Divided differences
    dd = (y[1:n] - y[0:n-1]) / h
    # Build the tridiagonal matrix
    T = build_tridiag(h, n-2)
    # System right hand side and solve the system
    rhs = 6*(dd[1:len(dd)] - dd[0:len(dd)-1])
    m = np.linalg.solve(T, rhs)
    # Natural boundary conditions: second derivative is zero at endpoints
    m = np.array([0] + list(m) + [0])
    # Get the coefficients
    d = y
    c = dd - h * (2*m[0:n-1] + m[1:n]) / 6
    b = m/2
    a = (m[1:n] - m[0:n-1]) / (6*h)
    return (a, b, c, d)


# %%
"""In this code we wanna solve a linear equation."""
import numpy as np
# %%
"""We wanna solve this system of equations:
3x + y = 9
x + 2y = 8
"""
a = np.array([[3, 1], [1, 2]])
a
b = np.array([9, 8])
b
# %%
np.linalg.solve(a, b)
# %%
b = np.array([9, 8]).reshape((-1, 1))
b
# %%
"""The shape of output array will be the same as b."""
np.linalg.solve(a, b)


import tensorflow as tf
import numpy as np

"""

Exercise 1.1: Diagonal Gaussian Likelihood

Write a function which takes in Tensorflow symbols for the means and
log stds of a batch of diagonal Gaussian distributions, along with a
Tensorflow placeholder for (previously-generated) samples from those
distributions, and returns a Tensorflow symbol for computing the log
likelihoods of those samples.

"""

def gaussian_likelihood(x, mu, log_std):
    """
    Args:
        x: Tensor with shape [batch, dim]
        mu: Tensor with shape [batch, dim]
        log_std: Tensor with shape [batch, dim] or [dim]

    Returns:
        Tensor with shape [batch]
    """
    '''
    computation follows Docs Section

    https://spinningup.openai.com/en/latest/spinningup/rl_intro.html#part-1-key-concepts-in-rl

    Log-Likelihood.

    The log-likelihood of a k -dimensional action a, for a diagonal Gaussian with mean \mu = \mu_{\theta}(s) and standard deviation \sigma = \sigma_{\theta}(s),

     is given by

    \log \pi_{\theta}(a|s) = -\frac{1}{2}\left(\sum_{i=1}^k \left(\frac{(a_i - \mu_i)^2}{\sigma_i^2} + 2 \log \sigma_i \right) + k \log 2\pi \right).
    '''
    # action space dimensionality
    k = x.shape.as_list()[-1]

    sqrd_diff_from_mean =   (x - mu)**2
    weighted_sqrd_diff_from_mean = sqrd_diff_from_mean / (tf.exp(log_std)**2)
    biased_weighted_sqrd_diff_from_mean = weighted_sqrd_diff_from_mean + 2 * log_std
    log_likelihood = - 0.5 * (tf.reduce_sum(biased_weighted_sqrd_diff_from_mean, axis=1) + k * np.log(2 * np.pi))
    return log_likelihood

if __name__ == '__main__':
    """
    Run this file to verify your solution.
    """
    from spinup.exercises.problem_set_1_solutions import exercise1_1_soln
    from spinup.exercises.common import print_result

    sess = tf.Session()

    dim = 10
    x = tf.placeholder(tf.float32, shape=(None, dim))
    mu = tf.placeholder(tf.float32, shape=(None, dim))
    log_std = tf.placeholder(tf.float32, shape=(dim,))

    your_gaussian_likelihood = gaussian_likelihood(x, mu, log_std)
    true_gaussian_likelihood = exercise1_1_soln.gaussian_likelihood(x, mu, log_std)

    batch_size = 32
    feed_dict = {x: np.random.rand(batch_size, dim),
                 mu: np.random.rand(batch_size, dim),
                 log_std: np.random.rand(dim)}

    your_result, true_result = sess.run([your_gaussian_likelihood, true_gaussian_likelihood],
                                        feed_dict=feed_dict)

    correct = np.allclose(your_result, true_result)
    print_result(correct)


# 1. Write a NumPy program to generate five random numbers from the normal distribution.
import numpy as np

x = np.random.normal(size=5)
print(x)
# --------------------------------------------------------------------------------------------------#
# 2. Write a NumPy program to generate six random integers between 10 and 30.
import numpy as np

x = np.random.randint(low=10, high=30, size=6)
print(x)
# --------------------------------------------------------------------------------------------------#
# 3. Write a NumPy program to create a 3x3x3 array with random values
import numpy as np

x = np.random.random((3, 3, 3))
print(x)
# --------------------------------------------------------------------------------------------------#
# 4. Write a NumPy program to create a 5x5 array with random values and find the minimum and maximum values.
import numpy as np

x = np.random.random((5, 5))
print("Original Array:")
print(x)
xmin, xmax = x.min(), x.max()
print("Minimum and Maximum Values:")
print(xmin, xmax)
# --------------------------------------------------------------------------------------------------#
# 5. Write a NumPy program to create a random 10x4 array and extract the first five rows of the array and store them into a variable.
import numpy as np

x = np.random.rand(10, 4)
print("Original array: ")
print(x)
y = x[:5, :]
print("First 5 rows of the above array:")
print(y)
# --------------------------------------------------------------------------------------------------#
# 6. Write a NumPy program to shuffle numbers between 0 and 10 (inclusive)
import numpy as np

x = np.arange(10)
np.random.shuffle(x)
print(x)
print("Same result using permutation():")
print(np.random.permutation(10))
# --------------------------------------------------------------------------------------------------#
# 7. Write a NumPy program to normalize a 3x3 random matrix.
import numpy as np

x = np.random.random((3, 3))
print("Original Array:")
print(x)
xmax, xmin = x.max(), x.min()
x = (x - xmin) / (xmax - xmin)
print("After normalization:")
print(x)
# --------------------------------------------------------------------------------------------------#
# 8. Write a NumPy program to create a random vector of size 10 and sort it.
import numpy as np

x = np.random.random(10)
print("Original array:")
print(x)
x.sort()
print("Sorted array:")
print(x)
# --------------------------------------------------------------------------------------------------#
# 9. Write a NumPy program to find the nearest value from a given value in an array.
import numpy as np

x = np.random.uniform(1, 12, 5)
v = 4
n = x.flat[np.abs(x - v).argmin()]
print(n)
# --------------------------------------------------------------------------------------------------#
# 10. Write a NumPy program to check two random arrays are equal or not.
import numpy as np

x = np.random.randint(0, 2, 6)
print("First array:")
print(x)
y = np.random.randint(0, 2, 6)
print("Second array:")
print(y)
print("Test above two arrays are equal or not!")
array_equal = np.allclose(x, y)
print(array_equal)
# --------------------------------------------------------------------------------------------------#


from scipy.stats import norm
import numpy as np
from scipy import special
from scipy import integrate

def integrate_normal(x1, x2, mu, sigma):
    sup = 0.5*((special.erf((x2-mu)/(sigma*np.sqrt(2))))-(special.erf((x1-mu)/(sigma*np.sqrt(2)))))
    return sup

my_mu = 0
my_sigma = 1

my_x1 = 0
my_x2 =  my_sigma

# The expected value is equal to 0.3413...
my_sup = integrate_normal(x1= my_x1, x2= my_x2, mu = my_mu, sigma = my_sigma)

x = np.arange(my_x1, my_x2, 0.0001)
y =  norm.pdf(x, loc=my_mu, scale= my_sigma) # normal_pdf(x, mean = my_mu, std = my_sigma)

sup_trapz = integrate.trapz(y,x)
sup_simps = integrate.simps(y,x)

print("Solution Using erf: {:.9f}".format(my_sup))
print("Using the trapezoidal rule, trapz: {:.10f}".format(sup_trapz))
print("Using the composite Simpson rule, simps: {:.10f}".format(sup_simps))

'''
Output:
Solution Using erf: 0.341344746
Using the trapezoidal rule, trapz: 0.3413205476
Using the composite Simpson rule, simps: 0.3413205478
'''


import math
import numpy as np
# =========================================================================== #
# This class of distance function is for computing distance between two       #
# vectors with equal length                                                   #
# =========================================================================== #
def euclidDistance(vector_1, vector_2):
    result = 0.0
    nn = len(vector_1)
    for i in range(nn):
        result += (vector_1[i] - vector_2[i])**2
    return math.sqrt(result)

def manhattanDistance(vector_1, vector_2):
    result = 0.0
    nn = len(vector_1)
    for i in range(nn):
        result += (vector_1[i] - vector_2[i])
    return math.sqrt(result)

def cosineSimilarity(vector_1, vector_2):
    dotproduct = norm_1 = norm_2 = 0.0
    nn =  len(vector_1)
    for i in range(nn):
        dotproduct += vector_1[i]*vector_2[i]
        norm_1 += vector_1[i]
        norm_2 += vector_2[i]
    dotproduct = math.sqrt(dotproduct)
    norm_1 = math.sqrt(norm_1)
    norm_2 = math.sqrt(norm_2)
    return dotproduct/(norm_1*norm_2)

def vectorDistanceLambda(func_name, vector_1, vector_2):
    if func_name == "euclidDistance":
        return euclidDistance(vector_1,vector_2)
    elif func_name == "manhattanDistance":
        return manhattanDistance(vector_1,vector_2)
    elif func_name == "cosineSimilarity":
        return cosineSimilarity(vector_1,vector_2)
    else:
        return 0.0


from sympy.abc import x
import sympy as sp
import math

def newton_iteration(x0, func, tol=1e-9, Max_iter=100):
    """ Solve non-linear equation by Newton iteratin method.

    Args:
        x0: double, the initial value of the iteration
        func: symbol object, the non-linear equation to be solved
        tol: double, the iteration accuracy
        Max_iter: int, maximum iteration number

    Returns:
        k: int, iteration number
        v: double, root of the non-linear equation
    """
    # derivative
    y_diff = sp.diff(func(x))

    # first iteration
    k = 1
    u = x0
    v = u - func(u)/y_diff.subs(x, u)

    # iteration
    while math.fabs(v - u) >= tol and y_diff.subs(x, v) != 0 and k < Max_iter:
        k += 1
        u = v
        v = u - func(u)/y_diff.subs(x, u)

    return k, v

def f(x):
    return sp.Pow(x, 3) + sp.Pow(x, 2) - 3*x - 3

if __name__ == '__main__':
    """ The initial values
    When x0 = -2, it converges to another root -sqrt{3}
    When x0 = -1, it is the root of the function
    When x0 = 0, it converges to -1
    When x0 = 2, it converges to sqrt{3} quickly
    When x0 = 3000000, it converges to sqrt{3} slowly
    """
    x0_lst = [-2.0, -1.0, 0.0, 2.0, 9999999999999999.0]
    for x0 in x0_lst:
        n, root = newton_iteration(x0, f, 1e-6)
        print(f"The root of the non-linear equation with initial value {x0} is {root:.7f}.")
        print(f"Iteration number is {n}.")


from sympy import Matrix
from pprint import pprint

"""
    Vedant Raghuwanshi(118EE0705)
    EE3302 Advanced Control System Project: Algorithm to find if the i/p matrix is nilpotent or not.
    Principle:
        A matrix is nilpotent iff:
            1. The matrix is square and all the eigenvalues are 0.
                                or
            2. A square matrix A such that A^n is the zero matrix 0 for some positive
               integer matrix power n, known as the index.
"""

def check_nilpotency(matrix):
    sp_matrix = Matrix(matrix)
    eigenvalues = sp_matrix.eigenvals()  # finding eigenvalues of the input matrix

    # eigenvalues is dictionary with eigen values of the matrix as keys and their multiplicity as dictionary values.

    for x in eigenvalues.keys():
        if (
            x != 0
        ):  # if any value other than 0 is present in keys then matrix is NOT nilpotent
            return eigenvalues, False

    return eigenvalues, True  # matrix is nilpotent

if __name__ == "__main__":

    size = int(input("Enter Matrix Size: "))
    matrix = []
    for i in range(size):
        row = list(map(complex, input().split()))
        matrix.append(row)

    print("Your input matrix is:")
    print(matrix)
    print()
    eigvalues, isnilp = check_nilpotency(matrix)
    print(f"The matrix is nilpotent: {isnilp}")
    print()
    print("The eigen values of the matrix and their corresponding multiplicity are: \n")
    pprint(eigvalues)


import numpy as np

# MATRIX ADDITION
# dim of two matrices must match

np.random.seed(0)

matrix1 = np.random.randint(1,100,(5,4))

matrix2 = np.random.randint(1,100,(5,4))

matrix1 + matrix2

# what happens when matrix dim doesn't match !!

matrix3 = np.arange(1,3)

matrix2 + matrix3

# ValueError: operands could not be broadcast together with shapes (5,4) (2,)

# MATRIX SUBTRACTION

matrix1 - matrix2

# MATRIX DIVISION

matrix1/matrix2

# MATRIX MULTIPLICATION

# 1. hadamard product
# 2. dot or matrix matrix product

# 1. hadamard product --> element or position wise multiplication

hadamard_matrix1 = np.random.randint(1,10,(2,2))
hadamard_matrix2 = np.random.randint(1,10,(2,2))

hadamard_matrix1 * hadamard_matrix2

# 2. dot or matrix matrix product --> multiply 1st row with 1st column, 1st row with 2nd column and so on

dot_matrix1 = np.random.randint(1,10,(2,2))
dot_matrix2 = np.random.randint(1,10,(2,2))

np.matmul(dot_matrix1,dot_matrix2)

# alternate to matmul is "@" and "dot" function

dot_matrix1 @ dot_matrix2

dot_matrix1.dot(dot_matrix2)

# MATRIX VECTOR MULTIPLICATION --> important for machine learning

'''
how ?

dataset is divided into independent and dependent features

suppose independent feature set is 4 X 3 --> X
suppose dependent feature set is 4 X 1 --> y

multiplication is not possible between two matrices as number of columns in
independent feature does not match the rows in dependent feature

in order to get the dependent feature, have to multiple with unknown vector (W, weight vector) to get
dependent feature

NOTE: is the core logic of ML algorithms

'''

matrix1 = np.random.randint(10,100,(4,4)) # X

matrix2 = np.arange(2,6) # y

output_vector = matrix1.dot(matrix2) # W

# MATRIX SCALAR MULTIPLICATION

2 * matrix1


'''
    Newton's Method
    Purpose:
        Computes approximate solution of f(x) = 0 by Newton's method
    Input:
        f: function handle
        f_der: the derivative of function f
        x0: initial guess
        tol: tolerance error
        iters: iteration number
        max_iters: maximum iteration number
    Output:
        Approximate solution

Last edited by Xiaozhou Li, September 23, 2020
'''

import numpy as np

def newton_iters(f, f_der, x0, iters):
    x = np.zeros(iters+1)
    x[0] = x0
    for i in range(iters):
        x[i+1] = x[i] - f(x[i])/f_der(x[i])
    return x

def newton_tol(f, f_der, x0, tol, max_iters=100):
    x_old = x0
    x_new = x_old - f(x_old)/f_der(x_old)
    iters = 1
    while (np.abs(x_new - x_old) > tol):
        x_old = x_new
        x_new = x_old - f(x_old)/f_der(x_old)
        iters += 1
        if (iters > 100):
            print ("Maximum iteration number achieved!")
            return
    return x_new

#########################################################
# Example
def f(x):
    return x**3 + 10*x - 20

def f_der(x):
    return 3*x**2 + 10

print (newton_tol(f, f_der, 1.5, 1.e-12))
print (newton_iters(f, f_der, 1.5, 20))


import scipy.stats as st
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-5,10,200) #200 points distrubuted between -5 and 10
ks = np.arange(50) #from 0-49

#DISCRETE PMF
plt.title("DISCRETE PMF")
pmf_binomial = st.binom.pmf(ks,50,0.25) #0.25 = successful chance, 50 = event
plt.bar(ks,pmf_binomial,label="Binomial Example (Dice)",alpha=0.8)

#Poisson Dist. - change the parameters according to formulae
pmf_poisson = st.poisson.pmf(ks,30) #same as bino because they're discrete
#30 = characteristic rate
plt.bar(ks,pmf_poisson,label="Poisson Example (car crash)",alpha=0.8)
plt.legend()

print("Binomial Dist for chance of rolling 1 10 times")
print(st.binom.pmf(10,50,0.25)) #chances of rolling 10 "1's" on a default
print("Poisson Dist for chance of getting 50 crashes")
print(st.poisson.pmf(50,30)) #what is the chance that we get 50 crashes

plt.show()

#CONTINUOUS PDF/PMF

#Uniform, normal, exponential, student-t, log-normal, skew-normal
plt.title("CONTINUOUS PDF")

pdf_uniform = st.uniform.pdf(x,-4,10)   #parameterized by low and up bound
plt.plot(x,pdf_uniform,label="Uniform (-4,6)")

pdf_normal = st.norm.pdf(x,5,2) #parameterized by location and scalerespectively
plt.plot(x,pdf_normal,label="Normal (5,2)")

pdf_exponential = st.expon.pdf(x,loc=-2,scale=2) #characteristic rate = scale(1)
plt.plot(x,pdf_exponential,label="Exponential(0.5)")

pdf_studentt = st.t.pdf(x,1)# parameterized by degree of freedom
# dof = no of datapoints - 1
plt.plot(x,pdf_studentt,label="Student-t")

pdf_lognorm = st.lognorm.pdf(x,1)#parameterized by scale parameter
plt.plot(x,pdf_lognorm,label="Lognorm(1)")

pdf_skewnorm = st.skewnorm.pdf(x,-6) #default instead of -6 is 0 (alpha param)
plt.plot(x,pdf_skewnorm,label="Skewnorm(-6)")

plt.legend()
plt.xlabel("x")
plt.ylabel("Probability")
plt.show()

# loc and scale(alpha) can be used anywhere as it's mu and sigma. It doesn't
# change anything as it is linear transformation of input vector, eg below
# loc and scale are convinient linear transformation as you can change data
plt.plot(x,st.t.pdf(x,1,loc=4,scale=2),label="In built")
plt.plot(x,st.t.pdf((x-4)/2,1,loc=0,scale=1),label="Manually")

plt.legend()
plt.show()


from numpy import zeros

X = [0.0, 1.5, 2.8, 4.4, 6.1, 8.0]
Y = [0.0, 0.9, 2.5, 6.6, 7.7, 8.0]

n = len(X)-1    # Degree of polynomial = number of points - 1

print("X =", X)
print("Y =", Y, end='\n\n')

xp = float(input("Find Y for X = "))

## y(x) = a0 + (x-x1)a1 + (x-x1)(x-x2)a2 + ... + (x-x1)(x-x2)...(x-xn)an

## Divided differences procedure to get the a's for n+1 = 4 data points:

## Col:     1        2       3       4
## x1   y11 = Y1                            a0 = y11
## x2   y21 = Y2    y22                     a1 = y22
## x3   y31 = Y3    y32     y33             a2 = y33
## x4   y41 = Y4    y42     y43     y44     a3 = y44    an = y[n+1][n+1]

## yi2     = (yi1 - y11) / (xi - x1) where i = 2,3,4
## yi3     = (yi2 - y22) / (xi - x2) where i =   3,4
## yi4     = (yi3 - y33) / (xi - x3) where i =     4
## yi(j+1) = (yij - yjj) / (xi - xj) where j = 1 to n; i = j+1 to n+1

Dy = zeros((n+1, n+1))  # Dy[6,6] # Dy[0-5, 0-5]       # Index stars from 0
Dy[:,0] = Y     # y11 = Y1, y21 = Y2, y(n+1)1 = Y(n+1) # Dy[i][0] = Y[i]

for j in range(n):
    for i in range(j+1, n+1):
        Dy[i, j+1] = (Dy[i,j] - Dy[j,j]) / (X[i] - X[j])

## Substitution procedure:
## y(x) = y11 + (x-x1)y22 + (x-x1)(x-x2)y33+...+(x-x1)(x-x2)...(x-xn)y[n+1][n+1]

## Index starts from 0
##Dy(x) = y00 + (x-x0)y11 + (x-x0)(x-x1)y22+...+(x-x0)(x-x1)...(x-x[n-1])ynn
##Dy(x) = y00 + sum( prod(x-xj) * y[i+1][i+1] ) where i= 0 to n-1; j= 0 to i

yp = Dy[0,0]                    # Initial summation value, a0, y00
for i in range(n):

    xprod = 1                   # Initial product value
    for j in range(i+1):
        xprod *= xp - X[j]      # x[0] to x[i] where i = 0 to n-1

    yp += xprod * Dy[i+1][i+1]  # Dy[1,1] to D[n,n]

##  yp += prod(xp - X[:i+1]) * Dy[i+1][i+1]

print("The Y = %.1f"% yp)

2. Interpolation and Curve Fitting/3. Newton's Interpolation Method.py


# Solve the roots of the quadratic equation 1: x^2 - 2x - 8 = 0
# -----------------------------------------------------------------
# a = 1
# b = -2
# c = -8

# r1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
# r2 = (-b - (b**2-4*a*c)**0.5)/(2*a)

# print("r1 = %6.4f, r2 = %6.4f" %(r1, r2))
# -----------------------------------------------------------------

# Solve the roots of the quadratic equation 2: x^2 - 2x - 8 = 0
# -----------------------------------------------------------------
# from sympy import *

# x = Symbol('x');
# f = Symbol('f');
# f = x**2 - 2*x -8
# root = solve(f)
# print(root)
# -----------------------------------------------------------------

# Draw a quadratic equation in one variable 1: y = 3x^2 -12x +10
# -----------------------------------------------------------------
# import matplotlib.pyplot as plt
# import numpy as np

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

# a = 3
# b = -12
# c = 10

# r1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
# r2 = (-b - (b**2-4*a*c)**0.5)/(2*a)
# r1_y = f(r1)
# r2_y = f(r2)
# print('root1 = ', r1)
# print('root2 = ', r2)

# x = np.linspace(0, 4, 100)
# y = 3*x**2 - 12*x + 10
# plt.plot(x, y)

# plt.plot(r1, r1_y, '-o')
# plt.text(r1-0.3, r1_y+0.3, '('+str(round(r1,2))+','+str(0)+')')
# plt.plot(r2, r2_y, '-o')
# plt.text(r2-0.3, r2_y+0.3, '('+str(round(r2,2))+','+str(0)+')')

# plt.show()
# -----------------------------------------------------------------

# Draw a quadratic equation in one variable 2: y = -3x^2 +12x -9
# -----------------------------------------------------------------
# import matplotlib.pyplot as plt
# import numpy as np

# def f(x):
#     return (-3*x**2 + 12*x - 9)

# a = -3
# b = 12
# c = -9

# r1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
# r2 = (-b - (b**2-4*a*c)**0.5)/(2*a)
# r1_y = f(r1)
# r2_y = f(r2)
# print('root1 = ', r1)
# print('root2 = ', r2)

# x = np.linspace(0, 4, 100)
# y = -3*x**2 + 12*x - 9
# plt.plot(x, y)

# plt.plot(r1, r1_y, '-o')
# plt.text(r1-0.3, r1_y+0.3, '('+str(round(r1,2))+','+str(0)+')')
# plt.plot(r2, r2_y, '-o')
# plt.text(r2-0.3, r2_y+0.3, '('+str(round(r2,2))+','+str(0)+')')

# plt.show()
# -----------------------------------------------------------------

# The minimum and maximum values of a quadratic equation in one variable: 3x^2 -12x +10
# -----------------------------------------------------------------
# import matplotlib.pyplot as plt
# from scipy.optimize import minimize_scalar
# import numpy as np

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

# a = 3
# b = -12
# c = 10

# r = minimize_scalar(f, bounds=(0, 4), method='bounded')
# print("when x is %4.2f, minimize is %4.2f" %(r.x, f(r.x)))
# r1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
# r2 = (-b - (b**2-4*a*c)**0.5)/(2*a)
# r1_y = f(r1)
# r2_y = f(r2)
# print('root1 = ', r1)
# print('root2 = ', r2)

# x = np.linspace(0, 4, 100)
# y = 3*x**2 - 12*x + 10
# plt.plot(x, y)

# plt.plot(r.x, f(r.x), '-o')
# plt.text(r.x-0.3, f(r.x)+0.3, '('+str(round(r.x,2))+','+str(round(f(r.x),2))+')')
# plt.plot(r1, r1_y, '-o')
# plt.text(r1-0.3, r1_y+0.3, '('+str(round(r1,2))+','+str(0)+')')
# plt.plot(r2, r2_y, '-o')
# plt.text(r2-0.3, r2_y+0.3, '('+str(round(r2,2))+','+str(0)+')')

# plt.show()
# -----------------------------------------------------------------

# The minimum and maximum values of a quadratic equation in one variable: -3x^2 + 12x -9
# -----------------------------------------------------------------
import matplotlib.pyplot as plt
from scipy.optimize import minimize_scalar
import numpy as np

def fmax(x):
    return (-3*x**2 + 12*x - 9)*(-1)

def f(x):
    return (-3*x**2 + 12*x - 9)

a = -3
b = 12
c = -9

r = minimize_scalar(fmax, bounds=(0, 4), method='bounded')
print("when x is %4.2f, minimize is %4.2f" %(r.x, f(r.x)))
r1 = (-b + (b**2-4*a*c)**0.5)/(2*a)
r2 = (-b - (b**2-4*a*c)**0.5)/(2*a)
r1_y = f(r1)
r2_y = f(r2)
print('root1 = ', r1)
print('root2 = ', r2)

x = np.linspace(0, 4, 100)
y = -3*x**2 + 12*x - 9
plt.plot(x, y)

plt.plot(r.x, f(r.x), '-o')
plt.text(r.x-0.3, f(r.x)+0.3, '('+str(round(r.x,2))+','+str(round(f(r.x),2))+')')
plt.plot(r1, r1_y, '-o')
plt.text(r1-0.3, r1_y+0.3, '('+str(round(r1,2))+','+str(0)+')')
plt.plot(r2, r2_y, '-o')
plt.text(r2-0.3, r2_y+0.3, '('+str(round(r2,2))+','+str(0)+')')

plt.show()
# -----------------------------------------------------------------


### import libraries ###
'''
NOTES:
The Fourier transform can be obtained from the complex notation Fourier series
So imaginary components correspond to sines and real components correspond to cosines
'''
import numpy as np
from mpmath import *
import matplotlib.pyplot as plt
### make MATLAB-like commands for use in Python ###
fft, fftshift, ifft = np.fft.fft, np.fft.fftshift, np.fft.ifft
abs, rndm, exp = np.abs, np.random.normal, np.exp
def sech(x) :  return 1/np.cosh(x)
np.set_printoptions(precision=3)

n,m,pi = 32,3,np.pi
L = pi
t2 = np.linspace(-L,L,n+1).astype('float')
t = t2[0:n]
k = (2*pi/(2*L)) * np.append(np.arange(0,n/2),np.arange(-n/2,0))
ks = fftshift(k)
Cos = np.cos(t*m)
Sin = np.sin(t*m)
FtCos = fft(Cos)
FtSin = fft(Sin)

''' MATLAB (same as above)
L=pi;
n=8;
m=3;
t2=linspace(-L,L,n+1);
t=t2(1:n);
k=(2*pi/(2*L))*[0:(n/2-1) -n/2:-1];
ks = fftshift(k);
Cos=cos(m*t);
Sin=sin(m*t);
FtCos = fft(Cos);
FtSin = fft(Sin);
plot(ks,fftshift(FtCos), ks,fftshift(abs(FtCos)),  ks,fftshift(imag(FtCos)) );
plot(ks,fftshift(FtSin), ks,fftshift(abs(FtSin)),  ks,fftshift(imag(FtSin)) );
xlabel("ks")
'''

f, (ax1, ax2) = plt.subplots(2, 1)
plt.title('$FT(\cos(%dt)), t \in [-\pi,\pi]$' % (m))
ax1.plot(t, Cos, label='$\cos(%dt)$' % (m))
ax1.axvline(x=2*L/m)
ax2.plot(fftshift(k), fftshift(abs(FtCos)), label='abs')
ax2.plot(fftshift(k), fftshift(np.real(FtCos)), label='real')
ax2.plot(fftshift(k), fftshift(np.imag(FtCos)), label='imaginary')
plt.legend()
plt.show()

f, (ax1, ax2) = plt.subplots(2, 1)
plt.title('$FT(\sin(%dt)), t \in [-\pi,\pi]$' % (m))
ax1.plot(t, Cos, label='$\sin(%dt)$' % (m))
ax2.plot(fftshift(k), fftshift(abs(FtSin)), label='abs')
ax2.plot(fftshift(k), fftshift(np.real(FtSin)), label='real')
ax2.plot(fftshift(k), fftshift(np.imag(FtSin)), label='imaginary')
plt.legend()
plt.show()

'''
k
ks
Cos
FtCos
'''
print("\nt",t)
print("\nk",k)
print("\nks",ks)
print("\nCos",Cos)
print("\nFtCos",FtCos)


import math
import matplotlib.pyplot as plt
import scipy.optimize as op
import numpy as np
np.set_printoptions(suppress=True)

def plot_cost_history(cost_history):
	plt.clf()
	plt.title('Cost J x iterations')

	plt.ylabel('Cost J')
	plt.xlabel('Number of iterations')

	plt.plot(cost_history, 'g.')

	plt.show()

# 1.2.1 Warmup exercise: sigmoid function
def sigmoid(z):
	if np.isscalar(z):
		return 1 / (1 + math.e ** (-z))
	else:
		return np.ones(z.size) / (np.ones(z.size) + np.repeat(np.e, z.size) ** (-z))

def hypothesis(theta, X):
	return sigmoid(np.dot(X, theta))

# 1.2.2 Cost function and gradient
def compute_cost(theta, X, y):
	m = X.shape[0]

	h = hypothesis(theta, X)
	error = np.sum(-y * np.log(h) - (1 - y) * np.log(1 - h))

	return error / m

# 1.2.3 Learning parameters using fminunc
def gradient(theta, X, y):
	m = X.shape[0]

	h = hypothesis(theta, X)

	for j in range(theta.shape[0]):
		theta[j] = np.sum((h - y) * X[:, j]) / m

	return theta

if __name__ == '__main__':
	# Import data
	data = np.loadtxt("ex2data1.txt", delimiter=",")

	positive = data[data[:,2] == 1][:,0:2]
	negative = data[data[:,2] == 0][:,0:2]

	# 1.1 Visualizing the data
	plt.ylabel('Exame 2 score')
	plt.xlabel('Exam 1 score')

	plt.plot(positive[:,0], positive[:,1], 'b+', label = 'Admitted')
	plt.plot(negative[:,0], negative[:,1], 'rx', label = 'Not admitted')

	plt.legend()
	plt.show()

	# 1.2 Implementation

	# For large positive values of x, the sigmoid should be close to 1,
	# while for large negative values, the sigmoid should be close to 0.
	# Evaluating sigmoid(0) should give you exactly 0.5.
	assert sigmoid(0) == 0.5
	assert sigmoid(129378) == 1
	assert round(sigmoid(-111)) == 0

	# For a matrix, your function should perform the sigmoid function on every element.
	assert np.all(sigmoid(np.array([0,0])) == 0.5)

	theta = np.zeros(3)

	# Evaluation
	## Create the bias column, set to 1
	X = data[:, 0:2]
	X = np.stack((np.ones(data.shape[0]), X[:, 0], X[:, 1]), axis=-1)

	y = data[:, 2]

	# You should see that the cost is about 0.693.
	assert round(compute_cost(theta, X, y), 3) == 0.693

	result = op.minimize(compute_cost, theta, (X, y), 'TNC', gradient, options = {'maxiter': 400})
	theta = result.x
	cost = result.fun

	try:
		# You should see that the cost is about 0.203.
		assert(round(cost, 3) == 0.203)

		# 1.2.4 Evaluating logistic regression
		# You should expect to see an admission probability of 0.776
		assert(round(hypothesis(theta, np.array([1, 45, 85])), 3) == 0.776)

	# There is no fminunc in scipy, so I have used https://en.wikipedia.org/wiki/Truncated_Newton_method.
	# However, it does not produce correct theta values, which therefore yelds terrible prediction.
	# In this case, I went back to gradient descent with manually tuned alpha and iterations.
	except:
		theta = np.zeros(3)
		alpha = 0.00417
		iterations = 400000
		cost_history = []

		m = X.shape[0]

		for i in range(iterations):
			h = hypothesis(theta, X)

			for j in range(theta.shape[0]):
				theta[j] = theta[j] - alpha * np.sum((h - y) * X[:, j]) / m

			cost_history.append(compute_cost(theta, X, y))

		cost = compute_cost(theta, X, y)

		# You should see that the cost is about 0.203.
		assert(round(cost, 3) == 0.203)

		# 1.2.4 Evaluating logistic regression
		# You should expect to see an admission probability of 0.776
		assert(round(hypothesis(theta, np.array([1, 45, 85])), 3) == 0.776)

		plot_cost_history(cost_history)

	# The predict function will produce “1” or “0” predictions given a dataset
	# and a learned parameter vector θ.
	print('Train Accuracy: ', np.mean((hypothesis(theta, X) >= 0.5).astype(float) == y) * 100)


from sympy import lambdify, diff, latex, symbols
from utils import r

def newton_raphson(fn, xi, iter=1):
    x = symbols('x')

    out_str = ""
    dev_fn = lambdify(x, diff(fn))
    f = lambdify(x, fn)

    if iter == 1:
        out_str += f"Newton-Raphson method is given as:\n"
        out_str += f"$$ x_{{i+1}} = x_i - \\frac{{f(x_i)}}{{f'(x_i)}} $$\n"
        out_str += f"Here $$ f(x) = {latex(fn)} $$\n $$ f'(x)= {latex(diff(fn))} $$\n"

    out_str += f"\\textbf{{Iteration {iter}}}\n\n"
    out_str += f"$$ x_{iter} = x_{iter - 1} - \\frac{{f(x_{iter - 1})}}{{f'(x_{iter - 1})}} $$\n"
    out_str += f"$$ f(x_{iter-1}) = f({r(xi)}) = {r(f(xi))} $$\n"
    out_str += f"$$ f'(x_{iter-1}) = f'({r(xi)}) = {r(dev_fn(xi))} $$\n"
    out_str += f"$$ x_{iter} = {r(xi)} - \\left[\\frac{{{r(f(xi))}}}{{{r(dev_fn(xi))}}}\\right] $$\n"
    x_val = xi - (f(xi) / dev_fn(xi))
    out_str += f"$$ x_{iter} = {r(x_val)} $$\n"

    error = None
    if iter > 1:
        out_str += "\n\\textbf{Error}\n\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|\\text{{latest value}} - \\text{{previous value}}\\right|}}{{\\left|\\text{{latest value}}\\right|}} \\times 100 $$\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|{r(x_val)} - {r(xi)}\\right|}}{{\\left|{r(x_val)}\\right|}} \\times 100 $$\n"
        error = abs(x_val - xi)/abs(x_val) * 100
        out_str += f"$$ \\text{{Error}} = {r(error, 2)} \\% $$\n"

    if error is not None and error < 0.0001:
        return out_str

    out_str += newton_raphson(fn, x_val, iter+1)
    return out_str

if __name__ == '__main__':
    from sympy import exp
    x = symbols('x')

    print(newton_raphson(exp(-1 * x) - x, 0))
    # print(newton_raphson(28.16*x**2 - 3.14*x**3 - 90, 0))


# Assignement on Probability

import numpy as np
'''
1.Three coins are tossed. Find the probability of getting
a)At least one head
b)Exactly 2 heads
'''
print("Assignment 1")
ss=2**3 # Sample Space
print("Probability of at least one Head:",7/ss)
print("Probability of excatly two Heads:",3/ss)
print("\n")

'''
2.An integer is chosen at random out of the integers from 1 to 100. What is the probability that it is
a)Multiple of 5
b)Divisible by 7
c)Greater than 70
'''
print("Assignment 2")
numbers=np.arange(1,101)

#multiple of 5
count=0
for i in numbers:
    if i % 5 == 0:
        count+=1
print("Probability of Muliples of 5:",count/len(numbers))

#Divisible by 7
count=0
for i in numbers:
    if i % 7 == 0:
        count+=1
print("Probability of Divisible by 7:",count/len(numbers))

#Greater than 70
count=0
for i in numbers:
    if i > 70:
        count+=1
print("Probability of greater than 70:",count/len(numbers))
print("\n")

'''
3.Generate Prime Numbers till 1000 and find the following among this population
a)Mean
b)Median
c)Range
d)Quartile (1st , 2nd and 3rd )
e)Inter-Quartile Range
f)Variance
g)Standard Deviation
'''

print("Assignment 3")

# Function to generate Numpy array of Prime numbers

def bha_prime_gen(n):
    primeno=np.array([])
    for num in np.arange(2,n+1):
        flag=0
        for i in np.arange(2,int(num/2)):
            if (num%i==0):
                flag=1
        if(flag==0):
            primeno=np.append(primeno,num)
    return primeno

primes=bha_prime_gen(1000)
#print(primes)

print("Mean:",np.mean(primes))
print("Median:",np.median(primes))
print("Range:",np.max(primes)-np.min(primes))
print("Quartiles")
print("1st Quartiles",np.percentile(primes,25))
print("2st Quartiles",np.percentile(primes,50))
print("3st Quartiles",np.percentile(primes,75))
print("InterQuartile Range",
        np.percentile(primes,75)-np.percentile(primes,25))
print("Variance",np.var(primes))
print("Standard Deviations",np.std(primes))


import numpy as np
import pandas as pd
import random
from collections import Counter
import math

inf = float(1e-20)

def rootX2(x):
    aux = 0
    for i in range(len(x)):
        aux += (x[i])**2
    return np.sqrt(aux)

def dot_product(x, y):
    return np.dot(x, y)

def cosine_distance(x, y):
    return sum(a*b for a, b in zip(x, y))/(inf+float(rootX2(x)*rootX2(y)))

def euc_distance(x, y):
    aux = 0
    for i in range(len(x)):
        aux += (x[i]-y[i])**2
    return np.sqrt(aux)

def moda(s):
    lf = Counter(s).items()
    x = sorted(lf, key=lambda x: x[1])
    return x[-1][0]

def norma(x):
    aux = 0
    for i in range(len(x)):
        aux += (x[i])**2
    return np.sqrt(aux)

def manhattan_distance(x, y):
    return sum(abs(a-b) for a, b in zip(x, y))

x = [6, 1, 3, 2]
y = [3, 1, 1, 4]
z = [6, 2, 2, 8]
a = [1, 2, 1]
b = [3, 1, 2]
cd = [(1, 2), (5, 6), (11, 3)]

print(list(zip(x, y)))
print(list(zip(a, b)))
c, d = zip(* cd)
print('cd:', c, d)

print('Distance X - Y:', euc_distance(x, y))
print('Distance Y - Z:', euc_distance(y, z))

print('Norma X:', norma(x))
print('Norma Y:', norma(y))

print('Moda X:', moda(x))
print('Moda Y:', moda(y))

print('Manhattan Distance X - Y:', manhattan_distance(x, y))
print('Manhattan Distance Y - Z:', manhattan_distance(x, z))

print('Euclidian Distance X - Y:', euc_distance(x, y))
print('Euclidian Distance Y - Z:', euc_distance(y, z))

print('Cosine Distance X - Y:', cosine_distance(x, y))
print('Cosine Distance Y - Z:', cosine_distance(y, z))

print('Distancia De Manhattan: é a distância entre dois pontos medidos ao longo dos eixos em ângulos retos. É frequentemente utilizada em circuitos integrados onde os fios so correm em paralelo as eixos (X, Y).\n')
print('Distancia Euclidiana: represente a distancia ''em linha reta'' entre dois pontos dentro de um espaço euclidiano. É calculada a partir da norma e o quadrado da diferença entre os pontos x-y.\n')
print('A Norma de um Vetor: representa o comprimento desse vetor, que pode ser calculado por meio da distância de seu ponto final até a origem.\n')
print('Produtor Escalar: Dados os vetores u=(a,b) e v=(c,d), definimos o produto escalar entre os vetores u e v, como o número real obtido por: u.v = a.c + b.d .\n')

print('Dot Product X - Y:', dot_product(x, y))

print('Cosine Distance X - X:', cosine_distance(x, x))
print('Manhattan Distance X- X:', manhattan_distance(x, x))
print('Euclidian Distance X - X:', euc_distance(x, x))

print('Arquivos ''Comma Separeted Values'', ou seja, arquivos com valores separados por vírgulas.\n')

print('O leitor (reader) do pacote CSV, retorna uma lista de linhas onde cada linha é uma string (F ou T)? F, segundo a documentação da biblioteca ele retorna um Reader Object, pelo qual é possivel iterar por todas as linhas do arquivo.\n')
print('O leitor (reader) do pacote CSV, retorna uma lista de listas; cada linha é uma lista de campos string (F ou T)? F, ver reposta acima.\n')

answers = ['1.a', '2.e', '3.c', '4.b', '5.a',
           '6.b', '7.a', '8.c', '9.d', '10.b']
student_answers = ['1.a', '2.b', '3.c', '4.b',
                   '5.c', '6.b', '7.a', '8.c', '9.a', '10.b']
acc = 0
for question in student_answers:
    if question in answers:
        acc += 1

print("Accuracy:", acc / len(answers))

print('random.sample(population, k) -> Return a k length list of unique elements chosen from the population sequence. Used for random sampling without replacement.\n')
print('random.shuffle(x, random) -> Shuffle method takes two parameters. Out of the two random is an optional parameter. Shuffle method used to shuffle the sequence in place. i.e., it changes the position of items in a list. We call it a randomizes the elements of a list in place.\n')

print('Range(1,10):',list(range(1, 10)), '\n')

array = [i for i in range(1, 101)]
array = random.sample(array, 100)

lista1, lista2 = array[30:], array[:30]

random.shuffle(lista1)
random.shuffle(lista2)

print('Shuffle Lista 1:', lista1, '\n')
print('Shuffle Lista 2:', lista2, '\n')

# lista1.sort(key=lambda x: x[0])
# lista1.sort(key=lambda x: x[1])
# lista1.sort(key=lambda x: x[2])
print(lista1)

print('A função array.sort() ordena um vetor em ordem crescente ou descrecente (reverse = True).\n')
print('Na função array.sort(key=lambda x: x[0]), key é uma função que será chamada para transformar a coleção de itens antes dele serem comparados, o paramêtro passado para key deve ser algo que seja callable. Na lista criada elas não conseguem iterar com x[0,1,2], no entanto a ideia seria transformar todos os itens da lista para que eles fossem iguais aos 1,2,3º itens.\n')
print('Instance-Based Learning: é uma família de algoritmos de aprendizado que compararam novas instâncias de problemas/testes com as vistas em treinamento, ao invés de comparar com as instâncias armazenadas na memória. Um exemplo de algoritmo IBL é k-nearest neighbors.\n')
print('O ''k'' em IBK significa K-Nearest Neighbors(K-NN), que significa K Vizinhos +Próximos. Nesse caso o K faz relação ao número de vizinhos mais próximos analisados, por exemplo em IBK3 nos procuramos o resultado nos 3 vizinhos mais próximos.\n')

s = [1,2,3,4,1,2,3,3,1,2,3,4,5]

print('Moda S:', moda(s))

print('A moda é necessária quando levamos em conta o conjunto que mais aparece dentro de um DataFrame, já que ela mostra o item que teve maior ocorrência. Desta forma, quando levamos em conta também a relação entre item e dataFrame, a moda é muito útil.\n')

print('A função Counter() retorna os valores de X em dicionários. Os elementos são salvos como as chaves do dicionário e sua contagem (nº de ocorrências) como os valores do dicionário\n')

print('Counter(x).keys():',Counter(x).keys())
print('Counter(x).value():',Counter(x).values())
print('Counter(x).items():',Counter(x).items())
print()

aux = ['aa', 'bb', 'cc', 'dd', 'aa', 'bb', 'cc', 'aa']
print('Moda [''aa'', ''bb'', ..]:', moda(aux))
aux = [1,2,3,1,4,5,6,1,2,3,5,6,6,7,8,4,5,6,7]
print('Moda [1,2,3,..]:', moda(aux))


import numpy as np

######################################################################################################
# Transpose 																						 #
######################################################################################################
#
# Getting the transpose of a matrix is really easy in NumPy. Simply access its T attribute.
# There is also a transpose() function which returns the same thing, but you’ll rarely see that used
# anywhere because typing T is so much easier. :)

# For example:

m = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
print(m)
# displays the following result:
# array([[ 1,  2,  3,  4],
#        [ 5,  6,  7,  8],
#        [ 9, 10, 11, 12]])

print('Transpose: ', m.T)
# displays the following result:
# array([[ 1,  5,  9],
#        [ 2,  6, 10],
#        [ 3,  7, 11],
#        [ 4,  8, 12]])

# NumPy does this without actually moving any data in memory - it simply changes the way it indexes the
# original matrix - so it’s quite efficient.
#
# However, that also means you need to be careful with how you modify objects.
# For example, with the same matrix m from above, look what happens if we modify a value in its transpose:

m_t = m.T
m_t[1][1] = 200

print(m_t)
# displays the following result:
# array([[ 1,    5,  9],
#        [ 2,  200, 10],
#        [ 3,    7, 11],
#        [ 4,    8, 12]])

print(m)
# displays the following result:
# array([[ 1,    2,  3,  4],
#        [ 5,  200,  7,  8],
#        [ 9,   10, 11, 12]])

# Notice how it modified the original matrix! So its best to just consider the transpose as a different
# view of your matrix, rather than a different matrix.
#
# A real use case
# I don't want to get into too many details about neural networks because you haven't covered them yet,
# but there is one place you will almost certainly end up using a transpose, or at least thinking about it.
#
# Let's say you have the following two matrices, called inputs and weights,

inputs = np.array([[-0.27,  0.45,  0.64, 0.31]])
print('Inputs: ', inputs)
# displays the following result:
# array([[-0.27,  0.45,  0.64,  0.31]])

print('Inputs shape: ', inputs.shape)
# displays the following result:
# (1, 4)

weights = np.array([[0.02, 0.001, -0.03, 0.036], \
    [0.04, -0.003, 0.025, 0.009], [0.012, -0.045, 0.28, -0.067]])

print('Weights:', weights)
# displays the following result:
# array([[ 0.02 ,  0.001, -0.03 ,  0.036],
#        [ 0.04 , -0.003,  0.025,  0.009],
#        [ 0.012, -0.045,  0.28 , -0.067]])

print('Weights shape:', weights.shape)
# displays the following result:
# (3, 4)

# I won't go into what they're for because you'll learn about them later, but you're going to end up wanting
# to find the matrix product of these two matrices.
#
# If you try it like they are now, you get an error:

# np.matmul(inputs, weights)
# displays the following error:
# ValueError: shapes (1,4) and (3,4) not aligned: 4 (dim 1) != 3 (dim 0)
#
# If you did the matrix multiplication lesson, then you've seen this error before.
# It's complaining of incompatible shapes because the number of columns in the left matrix, 4, does not equal
# the number of rows in the right matrix, 3.

# So that doesn't work, but notice if you take the transpose of the weights matrix, it will:

print('Inputs * weights.T', np.matmul(inputs, weights.T))
# displays the following result:
# array([[-0.01299,  0.00664,  0.13494]])

# It also works if you take the transpose of inputs instead and swap their order, like we showed in the video:

print(np.matmul(weights, inputs.T))
# displays the following result:
# array([[-0.01299],#
#        [ 0.00664],
#        [ 0.13494]])

# The two answers are transposes of each other, so which multiplication you use really just depends
# on the shape you want for the output.


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

# Calculating Pi using Monte Carlo algorithm.
def montecarlo_pi(times:int):
    inside = 0
    total = times

    for i in range(times):
        x_i = random.random()
        y_i = random.random()
        delta = x_i ** 2 + y_i **2 - 1

        if delta <= 0:
            inside += 1

    approx_pi = 4 * inside / total
    print('\nRandom test: ' + str(times))
    print('Approximation of pi is:{:.8f}'.format(approx_pi))

    return approx_pi

if __name__ == '__main__':
    numlist = [100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000, 5000000, 10000000, 30000000, 50000000, 75000000, 100000000]
    x_list = list(np.log10(numlist))
    pi_ = []
    for times in numlist:
        pi_.append(montecarlo_pi(times))

    plt.figure()
    plt.plot([min(x_list), max(x_list)], [math.pi, math.pi], color='red', label='true value')
    plt.plot(x_list, pi_, 'b.-', label='approximation')

    plt.legend()
    plt.xlabel('log10(n)')
    plt.ylabel('pi')

    my_y_ticks = np.arange(3, 3.4, 0.02)
    plt.yticks(my_y_ticks)
    plt.ylim((min(pi_)-0.1, max(pi_)+0.1))

    plt.show()


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

# Change working Directory
os.chdir('C:/Users/pp9596/Documents/02 ZSP/00 PACKT/Book - Practical Time-Series Analysis/Avishek')

# Load Dataset
ibm_df = pd.read_csv('datasets/ibm-common-stock-closing-prices.csv')
ibm_df.head()

#Rename the second column
ibm_df.rename(columns={'IBM common stock closing prices': 'Close_Price'},
              inplace=True)
ibm_df.head()

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

ibm_df['SES'] = single_exp_smoothing(ibm_df['Close_Price'], 0.8)

### Plot Single Exponential Smoothing forecasted value
fig = plt.figure(figsize=(5.5, 5.5))
ax = fig.add_subplot(2,1,1)
ibm_df['Close_Price'].plot(ax=ax)
ax.set_title('IBM Common Stock Close Prices during 1962-1965')
ax = fig.add_subplot(2,1,2)
ibm_df['SES'].plot(ax=ax, color='r')
ax.set_title('Single Exponential Smoothing')
plt.savefig('plots/ch2/B07887_02_14.png', format='png', dpi=300)

# Plot the forecasted values using multiple alpha values
#Calculate the moving averages using 'rolling' and 'mean' functions
ibm_df['SES2']  = single_exp_smoothing(ibm_df['Close_Price'], 0.2)
ibm_df['SES6']= single_exp_smoothing(ibm_df['Close_Price'], 0.6)
ibm_df['SES8']= single_exp_smoothing(ibm_df['Close_Price'], 0.8)

# Plot the curves
f, axarr = plt.subplots(3, sharex=True)
f.set_size_inches(5.5, 5.5)

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[0])
ibm_df['SES2'].iloc[:45].plot(color='r', linestyle = '--', ax=axarr[0])
axarr[0].set_title('Alpha 0.2')

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[1])
ibm_df['SES6'].iloc[:45].plot(color='r', linestyle = '--', ax=axarr[1])
axarr[1].set_title('Alpha 0.6')

ibm_df['Close_Price'].iloc[:45].plot(color='b', linestyle = '-', ax=axarr[2])
ibm_df['SES8'].iloc[:45].plot(color='r', linestyle = '--', ax=axarr[2])
axarr[2].set_title('Alpha 0.8')
plt.savefig('plots/ch2/B07887_02_15.png', format='png', dpi=300)


import numpy as np

def vec_abs(x):
    """Returns the length of a vector (or list of vectors)"""
    return np.sqrt(np.sum(x**2, axis=x.ndim-1))

def normalise(u):
    return u / vec_abs(u)

def project(u, v):
    return u * (np.dot(u, v) / np.dot(u, u))

def gram_schmidt(vs, normalised=True):
    """Gram-Schmidt Orthonormalisation / Orthogonisation.
    Given a set of vectors, returns an orthogonal set of vectors
    spanning the same subspace.
    Set `normalised` to False to return a non-normalised set of vectors."""
    us = []
    for v in np.array(vs):
        u = v - np.sum([project(x, v) for x in us], axis=0)
        us.append(u)
    if normalised:
        return np.array([normalise(u) for u in us])
    else:
        return np.array(us)

def gram_schmidt_modified(vs, normalised=True):
    """Gram-Schmidt Orthonormalisation / Orthogonisation.
    Given a set of vectors, returns an orthogonal set of vectors
    spanning the same subspace.
    Set `normalised` to False to return a non-normalised set of vectors.

    (Modified version as per p.80 Practical Numerical Algorithms
        - Parker & Chua)"""
    us = []
    for v in np.array(vs):
        u = v
        for x in us:
            u = u - project(x, u)
        us.append(u)
    if normalised:
        return np.array([normalise(u) for u in us])
    else:
        return np.array(us)

if __name__ == '__main__':
    ## testing  Gram-Schmidt works as expected
    print("Testing Gram-Schmidt orthonormalisation.")

    randmat = np.random.random((3,3))
    print("Random matrix:")
    print(randmat)
    gsrm = gram_schmidt(randmat)
    print("Normalised:")
    print(gsrm)
    print("x1 . x2 = ", np.dot(gsrm[0], gsrm[1]))
    print("x1 . x3 = ", np.dot(gsrm[0], gsrm[2]))
    print("x2 . x3 = ", np.dot(gsrm[1], gsrm[2]))
    print("x2 . x2 = ", np.dot(gsrm[1], gsrm[1]))


"""
Newton's method
Author: Daniel Homola
Licence: BSD 3-clause
"""

from scipy.optimize import newton
from sklearn.utils.testing import assert_almost_equal

def f(x):
    return 6*x**5-5*x**4-4*x**3+3*x**2
def df(x):
    return 30*x**4-20*x**3-12*x**2+6*x

def dx(f, x):
    return abs(0-f(x))

def newtons_method(f, df, x0, e, print_res=False):
    delta = dx(f, x0)
    while delta > e:
        x0 = x0 - f(x0)/df(x0)
        delta = dx(f, x0)
    if print_res:
        print 'Root is at: ', x0
        print 'f(x) at root is: ', f(x0)
    return x0

def test_with_scipy(f, df, x0s, e):
    for x0 in x0s:
        my_newton = newtons_method(f, df, x0, e)
        scipy_newton = newton(f, x0, df, tol=e)
        assert_almost_equal(my_newton, scipy_newton, decimal=5)
        print 'Tests passed.'

if __name__ == '__main__':
    # run test
    x0s= [0, .5, 1]
    test_with_scipy(f, df, x0s, 1e-5)

    for x0 in x0s:
        newtons_method(f, df, x0, 1e-10, True)


import numpy as np
import matplotlib.pyplot as plt

def mse(m,b,xv,yv):
    N = len(xv)
    error = 0
    for i in range(N):
        error += (yv[i] - (m*xv[i] + b))**2
    error /= N
    return error

def stepGradient(b_current,m_current,xv,yv,gamma):
    #gradient descent
    b_gradient = 0
    m_gradient = 0
    N = len(xv)
    for i in range(N):
        m_gradient += xv[i]*(yv[i]-(m_current*xv[i] + b_current))
        b_gradient += (yv[i]-(m_current*xv[i] + b_current))
    m_gradient = m_gradient*(-2.0/N)
    b_gradient = b_gradient*(-2.0/N)

    m_new = m_current - gamma*m_gradient
    b_new = b_current - gamma*b_gradient
    #stdout = "dEdm=%f, dEdb=%f"%(m_gradient,b_gradient)
    #print(stdout)
    return [m_new,b_new,m_gradient,b_gradient]

def get_mse_weights(b_init,m_init,xv,yv,gamma,iterations):
    m = m_init
    b = b_init

    mse_vec = []
    m_gradient_vec = []
    b_gradient_vec = []
    for i in range(iterations):
        m,b,m_gradient,b_gradient = stepGradient(b,m,xv,yv,gamma)
        mse_vec.append(mse(m,b,xv,yv))
        m_gradient_vec.append(m_gradient)
        b_gradient_vec.append(b_gradient)

    return [m,b,mse_vec,m_gradient_vec,b_gradient_vec]

def main():
    #Number of points
    N = 100

    #(xi,yi) generate
    #Xv = np.random.randint(1,10,size=N)
    #Yv = np.random.randint(1,10,size=N)
    mu,sigma = 2,5
    Xv = np.random.normal(mu,sigma,size=N)
    Yv = np.random.normal(mu,sigma,size=N)

    for i in range(N):
        stdout = "(X%d,Y%d)=(%d,%d)"%(i,i,Xv[i],Yv[i])
        print(stdout)

    mse_vec = []
    m,b,mse_vec,m_gradient_vec,b_gradient_vec = get_mse_weights(1,1,Xv,Yv,0.01,10000)

    x_min = np.min(Xv)
    x_max = np.max(Xv)
    xl = np.arange(x_min,x_max)
    yl = []
    for x in xl:
        yl.append(x*m + b)

    #print last
    stdout = "mse_final=%f, dEdm_final=%f, dEdb_final=%f"%(mse_vec[-1],m_gradient_vec[-1],b_gradient_vec[-1])
    print(stdout)

    #plot
    plt.figure(1)
    plt.plot(Xv,Yv,'ro')
    plt.plot(xl,yl)

    #figure 2
    plt.figure(2)
    plt.subplot(221)
    plt.plot(mse_vec)
    plt.xlabel('mse')
    plt.subplot(222)
    plt.plot(m_gradient_vec,color='blue')
    plt.xlabel('m_gradient')
    plt.subplot(224)
    plt.plot(b_gradient_vec,color='green')
    plt.xlabel('b_gradient')
    plt.show()

if __name__ == "__main__":
    main()


import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

def covarience_matrix(X):
    #standardizing data
    X_std = StandardScaler().fit_transform(X)

    #sample means of feature columns' of dataset
    mean_vec = np.mean(X_std, axis=0)
    #covariance matrix
    cov_mat = (X_std - mean_vec).T.dot((X_std - mean_vec)) / (X_std.shape[0]-1)
    #if right handside is ( Xstd - mean(Xstd) )^T . ( Xstd - mean(Xstd) )
    #simplyfying X^T.X / ( n - 1 )
    cov_mat = np.cov(X_std.T)
    return cov_mat

def max_min_bi_corel(X):
    a = covarience_matrix(X)
    a[a>=1] = 0
    maxcor = np.argwhere(a.max() == a)[0] # reverse 1

    b = covarience_matrix(x)
    mincor = np.argwhere(b.min() == b)[0] # reverse 1

    return maxcor,mincor

def eign_decomposition(cov_mat):
    #eign_decomposition
    eign_vals, eign_vecs = np.linalg.eig(cov_mat)

    print('Eigenvalues \n%s' %eign_vals)
    print('\nEigenvectors \n%s' %eign_vecs)

    #for i in range(len(eig_vals)):
    #    (np.abs(eig_vals[i]), eig_vecs[:,i] )
    #make a list of (eigenvalue, eigenvector) tuples
    eign_pairs = [ (np.abs(eign_vals[i]), eign_vecs[:,i] ) for i in range(len(eign_vals)) ]
    return eign_pairs

data = pd.read_csv('iris.csv')
data_features = data.ix[:,:-1]
data_label = data.ix[:,4]

#covariance_matrix
cov_mat = covarience_matrix(data_features)

#eign_decomposition
eig_pairs = eign_decomposition(cov_mat)
#Sorting the (eignvalue, eignvector) tuples from high to low
eig_pairs.sort()
eig_pairs.reverse()

#you can select the principle components by eign values, which one can be dropped
#visually confirming about the list is correctly sorted by decreasing eignvaluess
print("Eigenvalues in descending order:")
for i in eig_pairs:
    print(i[0])

#projection_matrix of our concatenated top k eigen vectors
matrix_w = np.hstack(( eig_pairs[0][1].reshape(4,1),
                       eig_pairs[1][1].reshape(4,1),
                       eig_pairs[2][1].reshape(4,1) ))

print("Matrix W:\n", matrix_w)
'''
we can choose how many dimensions we want for our subspace by chosing that amount of eignvectors
to construct our dxk dimensional eignvector matrix w. lastly we will use our projection matrix
to transform our samples onto to the subspaces via a simple dot product operation.
'''


#Fibonacci number

import numpy as np

def __pow(x,n,I):
    if n==0:
        return I
    elif n==1:
        return x
    else:
        y = __pow(x,n//2,I)
        y = y.dot(y)
        if n%2:
            y = x.dot(y)
        return y

def MatrixFibonacci(index):
    F = __pow(np.array(((1,1),(1,0)),dtype=object),index,np.eye(2))
    return F[0][1]

def MatrixFibonacci2(index):
    return np.linalg.matrix_power(np.matrix(((1,1),(1,0)),dtype=object),index)[0,1]

def RecursionFibonacci(index):
    if index==1:
        return 0
    elif index==2:
        return 1
    else:
        return RecursionFibonacci(index-1)+RecursionFibonacci(index-2)

def IterationFibonacci(index):
    a=0
    b=1
    if index == 0:
        return a
    elif index == 1:
        return b
    else:
        for i in range(2,index):
            c=a+b
            a=b
            b=c
        return b

def IterationFibonacciSequence(indexLimit):
    a=0
    b=1
    seq=[0,1]
    if indexLimit == 0:
        return a
    elif indexLimit == 1:
        return [a,b]
    else:
        for i in range(2,indexLimit):
            c=a+b
            a=b
            b=c
            seq.append(b)
        return seq

def Binetformula(index,roundi=True):
    from sympy import Pow as mmmpow
    from sympy import Integer as mmmint
    from sympy import N as mmmn
    b = (mmmpow(1.6185,index)-mmmpow(-0.6185,index))/2.237
    if(not roundi):
        return mmmint(b)
    else:
        return mmmn(b)

def isFibonacci(num):
    from math import log as mlog
    a = 2.07684408521711*mlog(2.237*num)
    return (a % 1 * 100 > 90) or (a % 1 * 100 < 5)

def isFibonacci2(num):
    from math import log as mlog
    a=(mlog(( (num*5**(1/2)+(5*num**2 + 4)**(1/2))/2 ),1.618)+mlog(( (num*5**(1/2)+(5*num**2 - 4)**(1/2))/2 ),1.618))/2
    return (a % 1 * 100 > 99) or (a % 1 * 100 < 1)

def doTest(toPrint=False,toProgress=False,start=0,toEnd=1000,algo="s"):
    s=set()
    KK=10000
    from IPython.display import clear_output
    for i in range(start,toEnd+1):
        if(toProgress and ( i<KK or (i>=KK and i%(KK/100)==0))):
            clear_output(wait=True)
            print(i,end="\t")
        if(algo=="s"):
            fibon=MatrixFibonacci(i)
        elif(algo=="binet"):
            fibon=Binetformula(i)
        elif(algo=="iterat"):
            fibon=IterationFibonacci(i)
        elif(algo=="recursive"):
            fibon=RecursionFibonacci(i)
        if(fibon):
            s.add(fibon)
            if(toPrint and not toProgress):
                print(fibon,end=", ")
        if(toProgress and (i<KK or (i>=KK and i%(KK/100)==0))):
            print(s)
    if(not toPrint):
        return s

def CheckExists(toend=10000):
    for i in {Binetformula(i) for i in range(toend) if i!=0}:
        if(not isFibonacci(i)):
            print(i,end=", ")

def IterationFibonacciWord(index):
    Sn_1="0"
    Sn="01"
    tmp=""
    for i in range(2,index+1):
        tmp=Sn
        Sn+=Sn_1
        Sn_1=tmp
    return Sn

def IterationFibonacciWords(indexLimit):
    Sn_1="0"
    Sn="01"
    tmp=""
    word=[Sn_1,Sn]
    for i in range(2, indexLimit + 1):
        tmp=Sn
        Sn+=Sn_1
        Sn_1=tmp
        word.append(Sn)
    return word

def FibonacciWord(index):
    from math import floor as mmmfloor
    return ((mmmfloor(index*1.618)-mmmfloor((index+1)*1.618))+2)

#RecursionFibonacci(9)
#IterationFibonacci(9)
#MatrixFibonacci(8)
#MatrixFibonacci2(8)
#Binetformula(7)
#isFibonacci(13)
#doTest(True,False,1,5000) #823 ms
#doTest(True,False,1,5000,"binet") #163 ms


import numpy as np
from numpy.linalg import norm

class linearSys:
    def __init__(self, A, b):
        self.A = A
        self.b = b
        self.n = b.shape[0]
        self.x = np.zeros([self.n, 1])

    def JacobiIterative(self, XO, N, TOL):
        k = 1
        while k <= N:
            for i in range(self.n):
                self.x[i, 0] = (self.b[i] - np.dot(self.A[i, :], XO[:, 0]) + self.A[i, i] * XO[i, 0]) / self.A[i, i]

            indicator = norm(self.x - XO)
            if indicator < TOL:
                return self.x.reshape((-1,))

            k += 1
            XO = self.x

        return 'Maximum number of iterations exceeded'

    def GaussSeidelIterative(self, XO, N, TOL):
        k = 1
        while k <= N:
            for i in range(self.n):
                self.x[i, 0] = (self.b[i] - np.dot(self.A[i, 0:i], self.x[0:i, 0]) - np.dot(self.A[i, i+1:], XO[i+1:, 0])) / self.A[i, i]

            indicator = norm(self.x - XO)
            if indicator < TOL:
                return self.x.reshape((-1,))

            k += 1
            XO = self.x

        return 'Maximum number of iterations exceeded'

    def SOR(self, XO, N, TOL, omiga):
        k = 1
        while k <= N:
            for i in range(self.n):
                self.x[i, 0] = (1 - omiga) * XO[i, 0] + omiga * (self.b[i] - np.dot(self.A[i, 0:i], self.x[0:i, 0]) - np.dot(self.A[i, i+1:], XO[i+1:, 0])) / self.A[i, i]

            indicator = norm(self.x - XO, np.inf)
            if indicator < TOL:
                return self.x.reshape((-1,))

            k += 1
            XO = self.x

        return 'Maximum number of iterations exceeded'

if __name__ == '__main__':
    # A = np.array([
    #     [10, -1, 2, 0],
    #     [-1, 11, -1, 3],
    #     [2, -1, 10, -1],
    #     [0, 3, -1, 8]
    # ])
    # b = np.array([6, 25, -11, 15])
    # XO = np.zeros([4, 1])
    A = np.array([
        [4, 3, 0],
        [3, 4, -1],
        [0, -1, 4]
    ])
    b = np.array([24, 30, -24])
    XO = np.ones([3, 1])
    N = 30
    TOL = 1e-6
    sol = linearSys(A, b)
    print('Jacobi:', sol.JacobiIterative(XO, N, TOL))
    print('Gauss-Seidel:', sol.GaussSeidelIterative(XO, N, TOL))
    print('SOR:', sol.SOR(XO, N, TOL, 1.241))


"""
test_numpy.fftshift_example_01.py

This is an example code for numpy.fft.fftshift
https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.fftshift.html#numpy.fft.fftshift

More in general, Discrete Fourier Transform (numpy.fft)
https://docs.scipy.org/doc/numpy/reference/routines.fft.html
"""

import numpy as np

freqs = np.fft.fftfreq(10,0.1)
freqs
#array([ 0.,  1.,  2.,  3.,  4., -5., -4., -3., -2., -1.])

# Shift the zero-frequency component to the center of the spectrum.
np.fft.fftshift( freqs )
#array([-5., -4., -3., -2., -1.,  0.,  1.,  2.,  3.,  4.])

# Shift the zero-frequency component only along the second axis

freqs = np.fft.fftfreq( 9, d=1./9)
freqs
#array([ 0.,  1.,  2.,  3.,  4., -4., -3., -2., -1.])

freqs_reshaped = np.fft.fftfreq( 9, d=1./9).reshape(3,3)
freqs_reshaped
#array([[ 0.,  1.,  2.],
#       [ 3.,  4., -4.],
#       [-3., -2., -1.]])

np.fft.fftshift( freqs_reshaped, axes=(1,) )
#array([[ 2.,  0.,  1.],
#       [-4.,  3.,  4.],
#       [-1., -3., -2.]])


from numpy import array, cos, sin, tan, identity

def Rx(phi):
    return array([
        [1, 0       , 0       ],
        [0, cos(phi),-sin(phi)],
        [0, sin(phi), cos(phi)]
    ])

def Ry(theta):
    return array([
        [ cos(theta), 0, sin(theta)],
        [ 0         , 1, 0         ],
        [-sin(theta), 0, cos(theta)]
    ])

def Rz(psi):
    return array([
        [cos(psi),-sin(psi), 0],
        [sin(psi), cos(psi), 0],
        [0       , 0       , 1]
    ])

def Hrx(phi):
    return array([
        [1, 0       , 0       , 0],
        [0, cos(phi),-sin(phi), 0],
        [0, sin(phi), cos(phi), 0],
        [0, 0       , 0       , 1]
    ])

def Hry(theta):
    return array([
        [ cos(theta), 0, sin(theta), 0],
        [ 0         , 1, 0         , 0],
        [-sin(theta), 0, cos(theta), 0],
        [ 0         , 0, 0         , 1]
    ])

def Hrz(psi):
    return array([
        [cos(psi),-sin(psi), 0, 0],
        [sin(psi), cos(psi), 0, 0],
        [0       , 0       , 1, 0],
        [0       , 0       , 0, 1]
    ])

def Htx(x):
    return array([
        [1, 0, 0, x],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
    ])

def Hty(y):
    return array([
        [1, 0, 0, 0],
        [0, 1, 0, y],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
    ])

def Htz(z):
    return array([
        [1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, z],
        [0, 0, 0, 1]
    ])

def inv(H):
    '''
    Inverse homogenous transformation
    '''
    R = H[0:3,0:3]
    d = H[0:3,3]

    invH = identity(4)

    invH[0:3,0:3] = R.transpose()
    invH[0:3,3] = -R.transpose().dot(d)

    return invH

def DH(theta, d, a, alpha):
    '''
    Deneavit-Hartenberg transformation matrix
    '''
    return array([
        [ cos(theta),-sin(theta)*cos(alpha), sin(theta)*sin(alpha), a*cos(theta)],
        [ sin(theta), cos(theta)*cos(alpha),-cos(theta)*sin(alpha), a*sin(theta)],
        [ 0         , sin(alpha)           , cos(alpha)           , d           ],
        [ 0         , 0                    , 0                    , 1           ]
    ])


import sys

import numpy as np

print('Python : {}'.format(sys.version))

print('Numpy : {}'.format(np.__version__))

from numpy import linalg

# Define an Array
A=np.arange(9) - 3
A

B=A.reshape(3,3)

B

#Ecludian L2 norm - default
print(np.linalg.norm(A))
print(np.linalg.norm(B))

#the frogeniuos norm is the L2 norm for a matrix
print(np.linalg.norm(B,'fro'))

# the max norm ( p = infinity)
print(np.linalg.norm(A,np.inf))
print(np.linalg.norm(B,np.inf))

# Vector normalization - normalization to produce a vector
norm = np.linalg.norm(A)
A_unit = A / norm
print(A_unit)

# the magnitude of a unit vector is equal to 1
np.linalg.norm(A_unit)

#Eigendecomposition

#The eigendecomposition is one form of matrix decomposition.
#Decomposing a matrix means that we want to find a product of matrices that is equal to the initial matrix.
#In the case of eigendecomposition, we decompose the initial matrix into the product of its eigenvectors and eigenvalues.
#Before all, let’s see the link between matrices and linear transformation. Then, you’ll learn what are eigenvectors and eigenvalues.

# find the eigen values and eigen vector for a simple squaematrix

A =np.diag(np.arange(1,4))
A

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

eigenvectors

# eigenvalue w[i] corresponds to the eigenvector v[:,i]
print('Eigenvalue : {}'.format(eigenvalues[2]))
print('Eigenvector : {}'.format(eigenvectors[:,1]))

np.diag(eigenvalues)

np.linalg.inv(eigenvectors)

# verify eigen decomposition

matrix = np.matmul(np.diag(eigenvalues),np.linalg.inv(eigenvectors))
output = np.matmul(eigenvectors,matrix).astype(np.int)
print (output)

# import necessary matplotlib libraries
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline

# plot the eigen vectors
origin = [0,0,0]

fig = plt.figure(figsize=(18,10))
axl = fig.add_subplot(121, projection='3d')

axl.quiver(origin,origin,origin,eigenvectors[0,:],eigenvectors[1,:],eigenvectors[2,:],color='k')
axl.set_xlim([-3,3])
axl.set_ylim([-3,3])
axl.set_zlim([-3,3])
axl.set_xlabel('x axis')
axl.set_ylabel('y axis')
axl.set_zlabel('z axis')
axl.view_init(15,30)
axl.set_title('Before Multiplication')

#show the plot
plt.show()

# plot the eigen vectors
origin = [0,0,0]

fig = plt.figure(figsize=(18,10))
axl = fig.add_subplot(121, projection='3d')

axl.quiver(origin,origin,origin,eigenvectors[0,:],eigenvectors[1,:],eigenvectors[2,:],color='k')
axl.set_xlim([-3,3])
axl.set_ylim([-3,3])
axl.set_zlim([-3,3])
axl.set_xlabel('x axis')
axl.set_ylabel('y axis')
axl.set_zlabel('z axis')
axl.view_init(15,30)
axl.set_title('Before Multiplication')

# now lets multiply origin matrix by eigenvectors
new_eig = np.matmul(A, eigenvectors)
ax2 = plt.subplot(122,projection = '3d')
ax2.quiver(origin,origin,origin,new_eig[0,:],new_eig[1,:],new_eig[2,:],color='k')

#add the eigenvalues to the plot
ax2.plot(eigenvalues[0]*eigenvectors[0],eigenvalues[1]*eigenvectors[1],eigenvalues[2]*eigenvectors[2],'rX')
ax2.set_xlim([-3,3])
ax2.set_ylim([-3,3])
ax2.set_zlim([-3,3])
ax2.set_xlabel('x axis')
ax2.set_ylabel('y axis')
ax2.set_zlabel('z axis')
ax2.view_init(15,30)
ax2.set_title('After Multiplication')
#show the plot
plt.show()