import numpy as np

# Mathematical functions
a = np.array([0,30,45,60,90])
# Note: all the functions take radian as angle

# sin
print ("print sin: ", np.sin(a*np.pi/180))

# cos
print ("print cos: ", np.cos(a*np.pi/180))

# tan
print ("print tan: ", np.tan(a*np.pi/180))

# arcsin
sin = np.sin(a*np.pi/180)
inv = np.arcsin(sin)
print("inv sin: ", np.degrees(inv))

# arccos
cos = np.cos(a*np.pi/180)
inv = np.arccos(cos)
print("inv sin: ", np.degrees(inv))

# arctan
tan = np.tan(a*np.pi/180)
inv = np.arctan(tan)
print("inv sin: ", np.degrees(inv))

# Rounding ops
# Syantax:
#numpy.around(a,decimals)
# decimals -> The number of decimals to round to. Default is 0. If negative, the integer is rounded to position to the left of the decimal point

a = np.array([1.0,5.55, 123, 0.567, 25.532])
print("Round: ", np.round(a))
print("Round deci = 1 ", np.round(a, decimals  = 1))
print("Round deci = -1 ", np.round(a, decimals = -1))

# floor: This function returns the largest integer not greater than the input parameter.
a = np.array([-1.7, 1.5, -0.2, 0.6, 10])
print("Floor ops: ", np.floor(a))

# ceil: The ceil() function returns the ceiling of an input value, i.e. the ceil of the scalar x is the smallest integer i, such that i >= x
print("Ceil ops: ", np.ceil(a))

# add, subtract, multiple, divide
# Note: all the ops follow broadcasting and element-wise rules
a = np.arange(9, dtype = np.float_).reshape(3,3)
b = np.array([10,10,10])

print("a: \n", a)
print("b: \n", b)
print("add array: ", np.add(a,b))
print("subtract array: ", np.subtract(a,b))
print("add multiply: ", np.multiply(a,b))
print("add divide: ", np.divide(a,b))

# power
a = np.array([10,100,1000])
print("power with scalar: ", np.power(a, 2))
b = np.array([1,2, 3])
print("power with array elementwise: ", np.power(a, b))

# mod or remainder
a = np.array([10,20,30])
b = np.array([3,5,7])

print("mod with scalar: ", np.mod(1, 4))
print("mod with array elementwise: ", np.mod(a,b))
print("remainder with elementwise: ", np.remainder(a,b))


"""

http://cs-people.bu.edu/sbargal/Fall%202016/lecture_notes/Nov_3_3d_geometry_representation
https://en.wikipedia.org/wiki/Quadratic_form
https://en.wikipedia.org/wiki/Conic_section
https://en.wikipedia.org/wiki/Matrix_representation_of_conic_sections

"""

import numpy as np
from sympy import *

def print_implicit(title, Q, p, eig=True):
    M = Matrix(Q)
    v = expand(np.dot(p.T, np.dot(Q, p)))
    n = 2 * np.dot(Q, p)
    print(title)
    print("Matrix")
    pprint(M)
    print("Implicit")
    print(v)
    print("Normal")
    print(n[:len(n)-1])
    print("Determinant")
    print(M.det())
    if eig:
        print("Eigenvalues and Eigenvectors")
        pprint(M.eigenvals())
        pprint(M.eigenvects())
    print()

"""

Conic sections can be viewed as a 2D quadric surface and can be represented by the 3x3 symmetric matrix
M = [A   B/2 D/2
     B/2 C   E/2
     D/2 E/2 F]

where A-F are constants

To test if a point lies on the conic section we can do
p = [x y 1] (homogeneous point)
transpose(p) * M * p = 0

If we symbolically multiply the equation above out, we get
A*x**2 + B*x*y + C*y**2 + D*x + E*y + F = 0

The determinant of the matrix M gives us what "shape" of the solution set is
det(M) < 0 gives ellipse (If A=C, B=0), gives us a circle
det(M) = 0 gives parabola
det(M) > 0 gives hyperbola (if A+C = 0), gives us a rectangular hyperbola

Circle
M = [1 0 0
     0 1 0
     0 0 -r^2]

x^2 + y^2 - r^2

Ellipse
M = [1/a^2 0 0
     0 1/b^2 0
     0 0 -1]

x^2/a^2 + y^2/b^2 - 1

Parabola
M = [4a/x 0 0
     0 -1 0
     0 0 0]

y^2 = 4ax

Hyperbola
M = [1/a^2 0 0
     0 -1/b^2 0
     0 0 -1]

x^2/a^2 - y^2/b^2 = 1

"""
def test_implicit_matrix_form_2d():
    A = Symbol('A')
    B = Symbol('B')
    C = Symbol('C')
    D = Symbol('D')
    E = Symbol('E')
    F = Symbol('F')

    x = Symbol('x')
    y = Symbol('y')
    r = Symbol('r')
    a = Symbol('a')
    b = Symbol('b')
    p = np.array([x, y, 1])

    print("Conic Sections Implicit Matrix Form")

    Q = np.array([[A, B/2, D/2], [B/2, C, E/2], [D/2, E/2, F]])
    print_implicit("General", Q, p, eig=False)

    Q = np.array([[1, 0, 0], [0, 1, 0], [0, 0, -(r*r)]])
    print_implicit("Circle", Q, p)

    Q = np.array([[1/(a*a), 0, 0], [0, 1/(b*b), 0], [0, 0, -1]])
    print_implicit("Ellipse", Q, p)

    Q = np.array([[1/(a*a), 0, 0], [0, -1/(b*b), 0], [0, 0, -1]])
    print_implicit("Hyperbola", Q, p)

    Q = np.array([[4*a/x, 0, 0], [0, -1, 0], [0, 0, 0]])
    print_implicit("Parabola", Q, p)

"""

Implicit equations that describe quadric surfaces can be represented as a 4x4 symmetric matrix
M = [A B C D
     B E F G
     C F H I
     D G I J]

where A-J are constants

To test whether a point (represented in homogenous form)
p = [x y z 1] lies on the quadric surface we have

tranpose(p) * M * p = 0

If we symbolically do the matrix multiplication above, we get this equation:
A*x**2 + 2*B*x*y + 2*C*x*z + 2*D*x + E*y**2 + 2*F*y*z + 2*G*y + H*z**2 + J + 2*I*z = 0

In a sense, M is a matrix of a quadratic form
transpose(x)*A*x + transpose(b)*x = c
and transpose(b)*x and c is 0 in this case
The eigenvalues of A determine the "shape" solution set, such as a ellipsoid or hyperboloid

If all eigenvalues of A are positive, then it is a ellipsoid
If all eigenvalues of A are negative, then it is a imginary ellipsoid
If some eigenvalues are positive and some negative, it is a hyperboloid
If one or more eigenvalues is zero, then the shape depends on the b vector, then it can be elliptic/hyperbolic paraboloid

The normal can be calculated by taking the gradient of equation
gradient(transpose(p) * M * p) = 2*Q*p
This gives us the normal vector for every point that is determined to be on the surface by the implicit form

Depending what we set the constants A-J as, we can get a sphere, hyperbolas, ellipsoid, cones

Sphere
M = [1 0 0 0
     0 1 0 0
     0 0 1 0
     0 0 0 -r^2]

N = [2x 2y 2z]

x^2 + y^2 + z^2 - r^2

Ellipsoid
M = [1/rx^2 0 0 0
     0 1/ry^2 0 0
     0 0 1/rz^2 0
     0 0 0 -1]

N = [2*x/rx^2 2*y/ry^2 2*z/rz^2]

x^2/rx^2 + y^2/ry^2 + z^2/rz^2 - 1

Cylinder
M = [1/rx^2 0 0 0
     0 1/ry^2 0 0
     0 0 0 0
     0 0 0 -1]

N = [2*x/rx^2 2*y/ry^2 0]

x^2/rx^2 + y^2/ry^2 - 1

Cone
M = [1/rx^2 0 0 0
     0 1/ry^2 0 0
     0 0 -1/s^2 0
     0 0 0 0]

N = [2*x/rx^2 2*y/ry^2 -2*z/s^2]

x^2/rx^2 + y^2/ry^2 - z^2/s^2

Plane (Not a quadric surface but it can be represented in this form)
M = [0 0 0 a/2
     0 0 0 b/2
     0 0 0 c/2
     a/2 b/2 c/2 -d]

N = [a b c]

ax + by + cz - d

"""
def test_implicit_matrix_form_3d():
    A = Symbol('A')
    B = Symbol('B')
    C = Symbol('C')
    D = Symbol('D')
    E = Symbol('E')
    F = Symbol('F')
    G = Symbol('G')
    H = Symbol('H')
    J = Symbol('J')

    a = Symbol('a')
    b = Symbol('b')
    c = Symbol('c')
    d = Symbol('d')
    r = Symbol('r')
    s = Symbol('s')
    rx = Symbol('rx')
    ry = Symbol('ry')
    rz = Symbol('rz')

    x = Symbol('x')
    y = Symbol('y')
    z = Symbol('z')
    s = Symbol('s')
    p = np.array([x, y, z, 1])

    print("Quadric Surfaces Implicit Matrix Form")
    print()

    Q = np.array([[A, B, C, D], [B, E, F, G], [C, F, H, I], [D, G, I, J]])
    print_implicit("General", Q, p, eig=False)

    Q = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -(r*r)]])
    print_implicit("Sphere", Q, p)

    Q = np.array([[1/(rx*rx), 0, 0, 0], [0, 1/(ry*ry), 0, 0], [0, 0, 1/(rz*rz), 0], [0, 0, 0, -1]])
    print_implicit("Ellipsoid", Q, p)

    Q = np.array([[1/(rx*rx), 0, 0, 0], [0, 1/(ry*ry), 0, 0], [0, 0, 0, 0], [0, 0, 0, -1]])
    print_implicit("Cylinder", Q, p)

    Q = np.array([[1/(rx*rx), 0, 0, 0], [0, 1/(ry*ry), 0, 0], [0, 0, -1/(s*s), 0], [0, 0, 0, 0]])
    print_implicit("Cone", Q, p)

    Q = np.array([[0, 0, 0, a/2], [0, 0, 0, b/2], [0, 0, 0, c/2], [a/2, b/2, c/2, -d]])
    print_implicit("Plane", Q, p)

init_printing()
test_implicit_matrix_form_2d()
print("")
test_implicit_matrix_form_3d()


import numpy as np
from scipy.interpolate import CubicSpline

# calculate 5 natural cubic spline polynomials for 6 points
# (x,y) = (0,12) (1,14) (2,22) (3,39) (4,58) (5,77)
x = np.array([0, 1, 2, 3, 4, 5])
y = np.array([12,14,22,39,58,77])

# calculate natural cubic spline polynomials
cs = CubicSpline(x,y,bc_type='natural')

# show values of interpolation function at x=1.25
print('S(1.25) = ', cs(1.25))

## Aditional - find polynomial coefficients for different x regions

# if you want to print polynomial coefficients in form
# S0(0<=x<=1) = a0 + b0(x-x0) + c0(x-x0)^2 + d0(x-x0)^3
# S1(1< x<=2) = a1 + b1(x-x1) + c1(x-x1)^2 + d1(x-x1)^3
# ...
# S4(4< x<=5) = a4 + b4(x-x4) + c5(x-x4)^2 + d5(x-x4)^3
# x0 = 0; x1 = 1; x4 = 4; (start of x region interval)

# show values of a0, b0, c0, d0, a1, b1, c1, d1 ...

cs.c
coeffsAll = cs.c
print(cs.c)
print("a0 is " + str(coeffsAll[3][0]) )

# Polynomial coefficients for 0 <= x <= 1
a0 = cs.c.item(3,0)
print("a0 is " + str(a0) )

b0 = cs.c.item(2,0)
c0 = cs.c.item(1,0)
d0 = cs.c.item(0,0)

# Polynomial coefficients for 1 < x <= 2
a1 = cs.c.item(3,1)
b1 = cs.c.item(2,1)
c1 = cs.c.item(1,1)
d1 = cs.c.item(0,1)

# Polynomial coefficients for 2 < x <= 3
a2 = cs.c.item(3,2)
b2 = cs.c.item(2,2)
c2 = cs.c.item(1,2)
d2 = cs.c.item(0,2)

# Polynomial coefficients for 3 < x <= 4
a3 = cs.c.item(3,3)
b3 = cs.c.item(2,3)
c3 = cs.c.item(1,3)
d3 = cs.c.item(0,3)

# Polynomial coefficients for 4 < x <= 5
a4 = cs.c.item(3,4)
b4 = cs.c.item(2,4)
c4 = cs.c.item(1,4)
d4 = cs.c.item(0,4)

# Print polynomial equations for different x regions
print('S0(0<=x<=1) = ', a0, ' + ', b0, '(x-0) + ', c0, '(x-0)^2  + ', d0, '(x-0)^3')
print('S1(1< x<=2) = ', a1, ' + ', b1, '(x-1) + ', c1, '(x-1)^2  + ', d1, '(x-1)^3')

print('...')
#print('S5(4< x<=5) = ', a4, ' + ', b4, '(x-4) + ', c4, '(x-4)^2  + ', d4, '(x-4)^3')
#print(str(a0)+", "+str(b0)+", "+str(c0)+", "+str(d0)+", ")
#print(str(a1)+", "+str(b1)+", "+str(c1)+", "+str(d1)+", ")
#print(str(a2)+", "+str(b2)+", "+str(c2)+", "+str(d2)+", ")
#print(str(a3)+", "+str(b3)+", "+str(c3)+", "+str(d3)+", ")
#print(str(a4)+", "+str(b4)+", "+str(c4)+", "+str(d4)+", ")

# So we can calculate S(1.25) by using equation S1(1< x<=2)
#print('S(1.25) = ', a1 + b1*0.25 + c1*(0.25**2) + d1*(0.25**3))

x = np.array([0, 1, 2, 3, 4, 5])
y[0:3] = y[3:6]
y[3:6] = [56,745,346]   # New values.

# calculate natural cubic spline polynomials from 0 to 5
cs = CubicSpline(x,y,bc_type='natural')

# send c4,c3,c2s to stm and interpolate according to those vals.
# Split those vals in stm according to the timestamps.

# Cubic spline interpolation calculus example
#  https://www.youtube.com/watch?v=gT7F3TWihvk


import numpy as np
import matplotlib.pylab as plt

########### Differentiation ##################

print('Differentiation')
print('\n')

def fun(x):
	return np.sin(x)

x = np.linspace(0,np.pi,1000)
y = fun(x)

plt.figure()
plt.plot(x,y)
plt.grid(1)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.savefig('sin(x).png')

def fun_prime(x):
	return np.cos(x)

y_prime = fun_prime(x)

plt.figure()
plt.plot(x,y_prime)
plt.grid(1)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.savefig('sin_prime(x).png')

def forward_difference(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	prime = (y[1:]-y[0:-1])/float(h)
	return prime

y_prime_forward = forward_difference(x,y)

plt.figure()
plt.plot(x[:-1],y_prime_forward)
plt.grid(1)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.savefig('sin_prime_forward(x).png')

def backward_difference(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	prime = (y[1:]-y[0:-1])/float(h)
	return prime

y_prime_backward = backward_difference(x,y)

plt.figure()
plt.plot(x[1:],y_prime_backward)
plt.grid(1)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.savefig('sin_prime_backward(x).png')

def central_difference(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	prime = (y[2:]-y[0:-2])/float(2*h)
	return prime

y_prime_central = central_difference(x,y)

plt.figure()
plt.plot(x[1:-1],y_prime_central)
plt.grid(1)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.savefig('sin_prime_central(x).png')

def complete_prime(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	prime_0 = float(y[1]-y[0])/float(h)
	prime_last = float(y[-1]-y[-2])/float(h)
	prime = (y[2:]-y[0:-2])/float(2*h)
	complete_prime = np.concatenate([[prime_0],prime,[prime_last]])
	return complete_prime

print('Error associated with forward difference: ' + str(np.sum(np.square(np.subtract(y_prime[:-1],y_prime_forward))))) #Mean squared error
print('Error associated with backward difference: ' + str(np.sum(np.square(np.subtract(y_prime[1:],y_prime_backward)))))
print('Error associated with central difference: ' + str(np.sum(np.square(np.subtract(y_prime[1:-1],y_prime_central)))))
print('Error associated with complete difference: ' + str(np.sum(np.square(np.subtract(y_prime,complete_prime(x,y))))))
print('\n')

########### Integration ##################

from scipy import integrate

print('Integration')
print('\n')

def int_trap(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	y *= h
	integral = np.sum(y[1:-1]) + ((y[0]+y[-1])/2.0)
	return integral

trapezoids = int_trap(x,fun(x))
trapezoids_scipy = integrate.trapz(fun(x), x)

print('Integral of sin(x)[0,pi] using trapezoids:' + '\n' + 'Using our implementation: ' + str(trapezoids) + '\n' 'Using SciPy: ' +str(trapezoids_scipy))
print('\n')

x_simp = np.linspace(0,np.pi,1001)

def int_simpson(x,y):
	h = (max(x)-min(x))/float(len(x)-1)
	y *= h
	integral = np.sum(y[1:-1:2]*4.0/3.0) + np.sum(y[2:-2:2]*2.0/3.0) + ((y[0]+y[-1])/3.0)
	return integral

simpson = int_simpson(x_simp,fun(x_simp))
simpson_scipy = integrate.simps(fun(x_simp), x_simp)

print('Integral of sin(x)[0,pi] using Simpson\'s Method:' + '\n' + 'Using our implementation: ' + str(simpson) + '\n' 'Using SciPy: ' + str(simpson_scipy))
print('\n')

x_mc = np.linspace(0,2*np.pi,1000)
y_mc = fun(x_mc)
y_random = np.random.uniform(-1,1,1000)
plt.figure()
plt.scatter(x_mc,y_random)
plt.plot(x_mc,y_mc,c='r')
plt.plot(x_mc,np.zeros(len(x)),c='b')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid(1)
plt.savefig('monte_carlo.png')

def int_mc(x_min,x_max,y,N):
	counter = []
	y_max = max(y)
	y_min = min(y)
	area = (x_max-x_min)*(y_max-y_min)
	y_ran = np.random.uniform(y_min,y_max,N)
	for i in range(N):
		if(y_ran[i]>0 and y[i]>0 and abs(y_ran[i])<=abs(y[i])):
			counter.append(1)
		elif(y_ran[i]<0 and y[i]<0 and abs(y_ran[i])<=abs(y[i])):
			counter.append(-1)
		else:
			counter.append(0)
	return (np.mean(counter)*area)

monte_carlo_1000 = int_mc(0,np.pi,fun(np.random.uniform(0,2*np.pi,1000)),1000)
monte_carlo_10000 = int_mc(0,np.pi,fun(np.random.uniform(0,2*np.pi,10000)),10000)
monte_carlo_100000 = int_mc(0,np.pi,fun(np.random.uniform(0,2*np.pi,100000)),100000)

print('Integral of sin(x)[0,2pi] using Monte Carlo\'s Method:' + '\n' + 'Using 1000 points: ' + str(monte_carlo_1000) + '\n' 'Using 10000 points: ' + str(monte_carlo_10000) + '\n' 'Using 100000 points: ' + str(monte_carlo_100000))
print('\n')

########### Filters ##################

from scipy.fftpack import fft, ifft, fftfreq
from scipy import signal

our_signal = np.genfromtxt('signal.dat', delimiter = ',')

signal_x = our_signal[:,0]
signal_y = our_signal[:,1]

plt.figure()
plt.plot(signal_x,signal_y)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid(1)
plt.savefig('noisy_signal.png')

fourier_transform = np.real(fft(signal_y))
frequencies = fftfreq(len(signal_x),signal_x[1]-signal_x[0])

plt.figure()
plt.plot(frequencies,fourier_transform)
plt.xlabel('$f$')
plt.ylabel('Amplitude')
plt.grid(1)
plt.savefig('fourier_transform.png')

def filter_lowpass(frequencies,transform,n):
	for i in range(0,len(frequencies)):
		if abs(frequencies[i])>n:
			transform[i] = 0

	return transform

def filter_highpass(frequencies,transform,n):
	for i in range(0,len(frequencies)):
		if abs(frequencies[i])<n:
			transform[i] = 0

	return transform

signal_y_lowpass = np.real(ifft(filter_lowpass(frequencies, fourier_transform, 1000)))
signal_y_highpass = np.real(ifft(filter_highpass(frequencies, fourier_transform, 1000)))

b_low, a_low = signal.butter(3, 1000/((1/(signal_x[1]-signal_x[0]))/2), 'low')
scipy_y_lowpass = signal.filtfilt(b_low, a_low, signal_y)

b_high, a_high = signal.butter(3, 1000/((1/(signal_x[1]-signal_x[0]))/2), 'high')
scipy_y_highpass = signal.filtfilt(b_high, a_high, signal_y)

plt.figure()
plt.plot(signal_x,signal_y_lowpass, label = 'Ours')
plt.plot(signal_x,scipy_y_lowpass, label = 'Scipy')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid(1)
plt.legend()
plt.savefig('lowpass.png')

plt.figure()
plt.plot(signal_x,scipy_y_highpass, label = 'Scipy')
plt.plot(signal_x,signal_y_highpass, label = 'Ours')
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.legend()
plt.grid(1)
plt.savefig('highpass.png')

########### Last Integral ##################

def last_fun(x):
	fun = np.sin(x)/x
	fun[np.isnan(fun)] = 1
	return fun

x_last = np.linspace(-10**6,10**6,101)
fun_last = last_fun(x_last)

plt.figure()
plt.plot(x_last,fun_last)
plt.xlabel('$x$')
plt.ylabel('$y$')
plt.grid(1)
plt.savefig('improper.png')

integral = int_simpson(np.linspace(-10**6,10**6,10**6 +1),last_fun(np.linspace(-10**6,10**6,10**6+1)))

print('The integral of sin(x)/x [-infinity,infinity] using numerical methods is: ' + str(integral))
print('With an error of ' + str(100*(np.pi-integral)/np.pi) + '%')

########### Example ##################

boat_data = np.genfromtxt('boat_data.txt')

boat_t = boat_data[:,0]
boat_acce = boat_data[:,1]
boat_pitch = boat_data[:,2]

plt.figure()
plt.plot(boat_t,boat_acce)
plt.xlabel('t $(s)$')
plt.ylabel('A $(m/s^2)$')
plt.grid(1)
plt.savefig('acce_unfiltered.png')

plt.figure()
plt.plot(boat_t,boat_pitch)
plt.xlabel('t $(s)$')
plt.ylabel('P $(^\circ/s)$')
plt.grid(1)
plt.savefig('pitch_unfiltered.png')

a_acce, b_acce = signal.butter(2,(0.27/50), 'low')
a_pitch, b_pitch = signal.butter(2,(0.6341/50), 'low')

acce_filt = signal.filtfilt(a_acce,b_acce,boat_acce)
pitch_filt = signal.filtfilt(a_pitch,b_pitch,boat_pitch)

plt.figure()
plt.plot(boat_t,acce_filt)
plt.xlabel('t $(s)$')
plt.ylabel('A $(m/s^2)$')
plt.grid(1)
plt.savefig('acce_filtered.png')

plt.figure()
plt.plot(boat_t,pitch_filt)
plt.xlabel('t $(s)$')
plt.ylabel('P $(^\circ/s)$')
plt.grid(1)
plt.savefig('pitch_filtered.png')

def cum_trapz(x,d_y,y_0):
	y = np.empty(len(x))
	y[0] = y_0
	for i in range(0,len(x)-1):
		y[i+1] = (x[i+1]-x[i])*(((d_y[i+1]+d_y[i]))/2.0)+y[i]
	return y

x_boat = cum_trapz(boat_t,cum_trapz(boat_t,acce_filt,0),0)
angle_boat = cum_trapz(boat_t,pitch_filt,0)

plt.figure()
plt.plot(boat_t,x_boat)
plt.xlabel('t $(s)$')
plt.ylabel('x $(m)$')
plt.grid(1)
plt.savefig('x_boat.png')

plt.figure()
plt.plot(boat_t,angle_boat)
plt.xlabel('t $(s)$')
plt.ylabel('Angle $(^\circ)$')
plt.grid(1)
plt.savefig('angle_boat.png')


import numpy as np

def power_method(A, ini_vec, tol=1e-9, Max_iter=100):
    """ Calculate maximum eigenvalue of matrix A by power method.

    Args:
        A: ndarray, the matrix to be solved
        ini_vec: list/ndarray, the initial eigenvector
        tol: double, iteration accuracy
        Max_iter: int, maximum iteration number

    Returns:
        k: int, iteration number
        eig: double, main eigenvalue(maximum) of matrix A
        eigv: ndarray, corresponding eigenvector
    """
    # initialization
    # iteration number
    k = 0
    # initial eigenvalue
    eig = 0
    # initial eigenvector
    eigv = np.array(ini_vec)

    # iteration
    while k < Max_iter and np.linalg.norm(A @ eigv - eig * eigv) >= tol:
        k += 1
        x = A @ eigv
        eig = x[np.argmax(x)]
        eigv = x / eig

    return k, eig, eigv

def acceleration_power_method(A, alpha, tol=1e-9, Max_iter=100):
    """ Calculate maximum eigenvalue of matrix A by origin translation acceration power method.

    Args:
        A: ndarray, the matrix to be solved
        alpha: double, acceleration parameter
        tol: double, iteration accuracy
        Max_iter: int, maximum iteration number

    Returns:
        k: int, iteration number
        eig: double, main eigenvalue(maximum) of matrix A
        eigv: ndarray, corresponding eigenvector
    """
    # initialization
    B = A - alpha * np.eye(A.shape[0])

    # power method
    k, eig, eigv = power_method(B, np.ones(A.shape[0]), tol, Max_iter)

    eig += alpha

    return k, eig, eigv

if __name__ == '__main__':
    A = np.array([[4, -1, 1], [-1, 3, -2], [1, -2, 3]])
    # power method
    ini_vecs = [[1, 1, 1], [2.001, 1.999, 0], [0, 1, 1]]
    for ini_vec in ini_vecs:
        n, eig, eigv = power_method(A, ini_vec, 1e-6)
        print(f"When selecting the initial values {ini_vec},")
        print(f"main eigenvalue is {eig:.7f}, corresponding eigenvector is {eigv},")
        print(f"the iteration number is {n}.")

    # acceleration power method
    alpha_lst = np.linspace(1, 3, 5)
    for alpha in alpha_lst:
        n, eig, eigv = acceleration_power_method(A, alpha, 1e-6)
        print(f"Whene selection the parameter alpha = {alpha},")
        print(f"main eigenvaluue is {eig:.7f}, corresponding eigenvector is {eigv},")
        print(f"the iteration number is {n}")


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

## Gaussians
def gaussian_density(x, mu, sigma):
    return (1/np.sqrt(2*np.pi*np.power(sigma, 2.))) * np.exp(-np.power(x - mu, 2.) / (2 * np.power(sigma, 2.)))

def gaussian_probability(mean, stdev, x_low, x_high):
    return norm(loc = mean, scale = stdev).cdf(x_high) - norm(loc = mean, scale = stdev).cdf(x_low)

def plot_gaussian(x, mu, sigma):

    # y = gaussian_density(x, mu, sigma)
    y = mlab.normpdf(x,mu,sigma)
    plt.plot(x, y)
    plt.title('Gaussian Probability Density Function')
    plt.xlabel('x variable')
    plt.ylabel('probability density function')
    plt.show()

def plot_gaussian2(x, mu1, sigma1,mu2,sigma2):

    # y = gaussian_density(x, mu, sigma)
    y1 = norm.pdf(x,mu1,sigma1)
    y2 = norm.pdf(x,mu2,sigma2)
    plt.plot(x, y1,'b')
    plt.plot(x, y2,'r--')
    # y3 = y1*y2
    # y3 = ((y1.sum() + y2.sum()) / 2.0) * (y3.astype(float) / y3.astype(float).sum())

    # plt.plot(x, y3,'r--')
    plt.title('Gaussian Probability Density Function')
    plt.xlabel('x variable')
    plt.ylabel('probability density function')
    plt.show()

# mu1 = 120
# mu2 = 200
# sigma1 = 40
# sigma2 = 20
# x = np.linspace(1,280,1000)
# plot_gaussian2(x,mu1,sigma1,mu2,sigma2)


import sys
import numpy as np
from numpy import pi

a = np.arange(15).reshape(3, 5)  # Create array 3x5 of integers from 0 to 14
print(a)  # It is a 2-d array
print(a.shape)  # A tuple of the dimensions of the array:  (3, 5)
print(a.ndim)  # An integer representing the number of dimensions: 2
print(a.dtype.name)  # A string representing the name of the variables in the array: int32
print(a.itemsize)  # An integer = 32 / 8 = 4
print(a.size)  # An integer representing the size of the array: 15
print(type(a))  # A string representing the class of the array: "<class 'numpy.ndarray'>"

# Creation of the array
print("\narray\n")

print(np.array([2, 3, 4]))  # Create numpy array of int64 from Python list
print(np.array([1.2, 3.5, 5.1]))  # Array of float64
print(np.array([(1.5, 2, 3), (4, 5, 6)]))  # 2-d array from sequence of sequences
print(np.array([[1, 2], [3, 4]], dtype=complex))  # Explicitly specify a type of the array during creation (complex)
print(np.zeros((3, 4)))  # Create an array 3x4 filled with zeroes
print(np.ones((2, 3, 4), dtype=np.int16))  # Create an array of int16 2x3x4 filled with ones
print(np.empty((2, 3)))  # Create an array 2x3 filled with random data

# Creation of ranges with arange
print("\narange\n")

print(np.arange(10, 30, 5))  # Same as range(10, 30, 5) in Python, byt returns array
print(np.arange(0, 2, 0.3))  # It accepts float arguments

# Creation of ranges with linspace
print("\nlinspace\n")

print(np.linspace(0, 2, 9))  # 9 numbers from 0 to 2
x = np.linspace(0, 2 * pi, 100)  # Useful to evaluate function at lots of points
print(np.sin(x))

# Printing arrays
print("\nPrinting arrays\n")

print(np.arange(6))  # 1d array looks as a row
print(np.arange(12).reshape(4, 3))  # 2d array looks as a matrix
print(np.arange(24).reshape(2, 3, 4))  # 3d array looks as a list of matrices

print(np.arange(10000))  # NumPy automatically skips the central part of the array and only prints the corners
print(np.arange(10000).reshape(100, 100))

# Basic Operations
print("\nBasic Operations\n")

a = np.array([20, 30, 40, 50])
b = np.arange(4)
print(b)  # array([0, 1, 2, 3])
print(a - b)  # array([20, 29, 38, 47])
print(b**2)  # array([0, 1, 4, 9])
print(10 * np.sin(a))  # array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
print(a < 35)  # array([ True,  True, False, False])

# Matrices multiplication
print("\nMatrices multiplication\n")

A = np.array([[1, 1],
              [0, 1]])
B = np.array([[2, 0],
              [3, 4]])
print(A * B)     # elementwise product: array([[2, 0], [0, 4]])
print(A @ B)     # matrix product: array([[5, 4], [3, 4]])
print(A.dot(B))  # another matrix product: array([[5, 4], [3, 4]])

# +=

print("\n+=\n")
rg = np.random.default_rng(1)  # create instance of default random number generator
a = np.ones((2, 3), dtype=int)
b = rg.random((2, 3))
a *= 3
print(a)  # array([[3, 3, 3],  [3, 3, 3]])
b += a
print(b)  # array([[3.51182162, 3.9504637 , 3.14415961],  [3.94864945, 3.31183145, 3.42332645]])
try:
    a += b  # b is not automatically converted to integer type
except Exception as e:
    print(e)

# When operating with arrays of different types, the type of the resulting array corresponds
# to the more general or precise one (a behavior known as upcasting).

print("\nUpcasting\n")
a = np.ones(3, dtype=np.int32)
b = np.linspace(0, pi, 3)
print(b.dtype.name)  # 'float64'
c = a + b  # Trying to sum int with float
print(c)  # array([1.        , 2.57079633, 4.14159265])
print(c.dtype.name)  # 'float64'
d = np.exp(c * 1j)  # Creating array of complexes
print(d)  # array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j, -0.54030231-0.84147098j])
print(d.dtype.name)  # 'complex128'

# Unary operators
print("\nUnary operators\n")

a = rg.random((2, 3))
print(a)  # array([[0.82770259, 0.40919914, 0.54959369], [0.02755911, 0.75351311, 0.53814331]])
print(a.sum())  # 3.1057109529998157
print(a.min())  # 0.027559113243068367
print(a.max())  # 0.8277025938204418

# By default, these operations apply to the array as though it were a list of numbers, regardless of its shape.
# However, by specifying the axis parameter you can apply an operation along the specified axis of an array:
print("\nAxis operators\n")

b = np.arange(12).reshape(3, 4)
print(b)
"""array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])"""
print(b.sum(axis=0))     # sum of each column: array([12, 15, 18, 21])
print(b.min(axis=1))     # min of each row: array([0, 4, 8])
print(b.cumsum(axis=1))  # cumulative sum along each row
"""array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])"""

# Universal functions
"""NumPy provides familiar mathematical functions such as sin, cos, and exp. In NumPy, these are called “universal
functions” (ufunc). Within NumPy, these functions operate elementwise on an array, producing an array as output."""
print("\nUniversal functions\n")

B = np.arange(3)
print(B)  # array([0, 1, 2])
print(np.exp(B))  # array([1.        , 2.71828183, 7.3890561 ])
print(np.sqrt(B))  # array([0.        , 1.        , 1.41421356])
C = np.array([2., -1., 4.])
print(np.add(B, C))  # array([2., 0., 6.])

# Indexing, Slicing and Iterating
"""One-dimensional arrays can be indexed, sliced and iterated over, much like lists and other Python sequences."""
print("\nIndexing, Slicing and Iterating\n")

a = np.arange(10)**3
print(a)  # array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
print(a[2])  # Get by index: 8
print(a[2:5])  # Get slice : array([ 8, 27, 64])
# equivalent to a[0:6:2] = 1000;
# from start to position 6, exclusive, set every 2nd element to 1000
a[:6:2] = 1000
print(a)  # array([1000,    1, 1000,   27, 1000,  125,  216,  343,  512,  729])
print(a[::-1])  # reversed a: array([ 729,  512,  343,  216,  125, 1000,   27, 1000,    1, 1000])
for i in a:
    print(i**(1 / 3.))

"""9.999999999999998
1.0
9.999999999999998
3.0
9.999999999999998
4.999999999999999
5.999999999999999
6.999999999999999
7.999999999999999
8.999999999999998"""

# Indices
"""Multidimensional arrays can have one index per axis. These indices are given in a tuple separated by commas:"""
print("\nIndices\n")

def f(x, y):
    return 10 * x + y  # Will it use as lambda function

b = np.fromfunction(f, (5, 4), dtype=int)  # Construct an array by indexing the function
print(b)
"""array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])"""
print(b[2, 3])  # 23
print(b[0:5, 1])  # each row in the second column of b: array([ 1, 11, 21, 31, 41])
print(b[:, 1])    # equivalent to the previous example: array([ 1, 11, 21, 31, 41])
print(b[1:3, :])  # each column in the second and third row of b
"""array([[10, 11, 12, 13],
       [20, 21, 22, 23]])"""

# Autocompletion
"""When fewer indices are provided than the number of axes, the missing indices are considered complete slices:"""
print("\nAutocompletion\n")

print(b[-1])   # the last row. Equivalent to b[-1, :] : array([40, 41, 42, 43])

"""The dots (...) represent as many colons as needed to produce a complete indexing tuple"""
c = np.array([[[  0,  1,  2],  # a 3D array (two stacked 2D arrays)
               [ 10, 12, 13]],
              [[100, 101, 102],
               [110, 112, 113]]])
print(c.shape)  # (2, 2, 3)
print(c[1, ...])  # same as c[1, :, :] or c[1]
"""array([[100, 101, 102],
       [110, 112, 113]])"""
print(c[..., 2])  # same as c[:, :, 2]:
"""array([[  2,  13],
       [102, 113]])"""

# Iterating over multidimensional arrays is done with respect to the first axis:
print("\nIterating over multidimensional arrays\n")

for row in b:
    print(row)

"""[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]"""

# However, if one wants to perform an operation on each element in the array,
# one can use the flat attribute which is an iterator over all the elements of the array:

for element in b.flat:
    print(element)  # 0  1  2  3


"""Largest prime factor

The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143?
"""

import math
import numpy as np

def largest_prime_factor_naive(number):
    """
    Let the given number be n and let k = 2, 3, 4, 5, ... .

    For each k, if it is a factor of n then we divide n by k and completely divide out each k before moving to the next k.

    It can be seen that when k is a factor it will necessarily be prime, as all smaller factors have been removed,
    and the final result of this process will be n = 1.
    """
    factor = 2
    factors = []
    while number > 1:
        if number % factor == 0:
            factors.append(factor)
            number = number // factor  # Remainder guarenteed to be zero
            while number % factor == 0:
                number = number // factor  # Remainder guarenteed to be zero
        factor += 1
    return factors

def largest_prime_factor_even_optimized(number):
    """
    We know that, excluding 2, there are no even prime numbers.
    So we can increase factor by 2 per iteration after having found the
    """
    factors = []
    factor = 2
    if number % factor == 0:
        number = number // factor
        factors.append(factor)
        while number % factor == 0:
            number = number // factor

    factor = 3
    while number > 1:
        if number % factor == 0:
            factors.append(factor)
            number = number // factor  # Remainder guarenteed to be zero
            while number % factor == 0:
                number = number // factor  # Remainder guarenteed to be zero
        factor += 2
    return factors

def largest_prime_factor_square_optimized(number):
    """
    Every number n can at most have one prime factor greater than n.

    If we, after dividing out some prime factor, calculate the square root of the remaining number
    we can use that square root as upper limit for factor.

    If factor exceeds this square root we know the remaining number is prime.
    """
    factors = []
    factor = 2
    if number % factor == 0:
        number = number // factor
        factors.append(factor)
        while number % factor == 0:
            number = number // factor

    factor = 3
    max_factor = math.sqrt(number)
    while number > 1 and factor <= max_factor:
        if number % factor == 0:
            factors.append(factor)
            number = number // factor
            while number % factor == 0:
                number = number // factor
                max_factor = math.sqrt(number)
        factor += 2

    return factors

def idx_sieve(length):
    """Static length sieve-based prime generator"""
    primes = []
    is_prime = np.array([True]*length)
    i = 2
    while i < length:
        if is_prime[i]:
            is_prime[np.arange(i, length, i)] = False
            primes.append(i)
        else:
            i += 1
    return primes

def prime_factor(n, primes):
    i = 0
    factors = []
    while n != 1:
        while (n % primes[i]) != 0:
            i += 1
        factors.append(primes[i])
        n = n / primes[i]
    return factors

if __name__ == '__main__':
    number = 600851475143
    print(largest_prime_factor_naive(number))
    print(largest_prime_factor_even_optimized(number))
    print(largest_prime_factor_square_optimized(number))

    number = 600851475143
    primes = idx_sieve(20000)

    print(max(prime_factor(number, primes)))


# Optimize a polynomial function to find minima/maxima using 2nd order Newton's method.

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

# Function is: f(x) = 6x^5 - 5x^4 - 4x^3 + 3x^2

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 d2f(x):
    return 120*x**3-60*x**2-24*x+6

def delta_fn(x):
    return abs(f(x)-0)

def newton_root_finding(x0, epsilon):
    # Finding root of a function using Newton's Method

    x_root = np.zeros(len(x0))
    for i, x in enumerate(x0):
        delta = delta_fn(x)
        while delta > epsilon:
            x = x - f(x) / float(df(x))
            delta = delta_fn(x)
        x_root[i] = x

    return list(x_root)

def newton_optimization(x1, epsilon):
    # Finding minima/maxima using Newton's Method

    x_critical = np.zeros(len(x1))
    minmax = {}

    for i, x in enumerate(x1):
        n = 0
        delta = delta_fn(x)

        while delta > epsilon:
            x = x - df(x) / float(d2f(x))
            delta = delta_fn(x)
            n += 1
            if n == 10000:
                break
        x_critical[i] = x
    print "\nCritical points: "
    pprint(list(x_critical))

    for x in x_critical:
        if d2f(x) > 0:
            minmax[x] = 'minima'
        elif d2f(x) < 0:
            minmax[x] = 'maxima'
        elif d2f(x) == 0:
            minmax[x] = 'saddle'

    return minmax

def plot_graph():
    x_points = np.arange(-0.8,1.1,0.01)
    fn_points = [f(x) for x in x_points]
    plt.plot(x_points, fn_points, 'ro')
    plt.show()

def main():
    epsilon = 1e-10
    x0 = [-1, 0, 0.5, 1]

    x_root = newton_root_finding(x0,epsilon)
    print "\nRoots are: "
    pprint(x_root)

    x1 = [-0.5, 0.1, 0.5, 1.5, 3]
    minmax = newton_optimization(x1, epsilon)
    print "\nMinima/Maxima points are: "
    pprint(minmax)

    plot_graph()

if __name__ == '__main__':
    main()

'''
Result-
Roots are:
[-0.7953336454431276, 0.0, 0.6286669787778999, 1.0]

Critical points:
[-0.5889879839687899,
 -8.969446052179515e-10,
 0.39415736752083946,
 0.8614972831146172,
 0.861497283114617]

Minima/Maxima points are:
{-0.5889879839687899: 'maxima',
 -8.969446052179515e-10: 'minima',
 0.39415736752083946: 'maxima',
 0.861497283114617: 'minima',
 0.8614972831146172: 'minima'}
'''


import numpy as np
import matplotlib.pyplot as plt

#Continuous Uniform Distribution
def probability_uniform(low_range, high_range, minimum, maximum):

    if( isinstance(low_range,str) or
        isinstance(high_range,str) or
        isinstance(minimum,str) or
        isinstance(maximum,str)):

        print('Inputs should be numbers not string')
        return None

    if (low_range < minimum or low_range > maximum):
        print('Your low range value must be between minimum and maximum')
        return None

    if (high_range < minimum or high_range > maximum):
        print('The high range value must be between minimum and maximum')
        return None

    probability = abs(high_range-low_range)/abs(maximum-minimum)
    return probability

## Gaussians
def gaussian_density(x, mu, sigma):
    return (1/np.sqrt(2*np.pi*np.power(sigma, 2.))) * np.exp(-np.power(x - mu, 2.) / (2 * np.power(sigma, 2.)))

def plot_gaussian(x, mu, sigma):

    y = gaussian_density(x, mu, sigma)

    plt.plot(x, y)
    plt.title('Gaussian Probability Density Function')
    plt.xlabel('x variable')
    plt.ylabel('probability density function')
    plt.show()

#Calculating Area Under the Curve in Python
from scipy.stats import norm

print(gaussian_density(50, 50, 10))
#using scipy
print(norm(loc = 50, scale = 10).pdf(50))

#Calculating Area Under the Curve Solution
def gaussian_probability(mean, stdev, x_low, x_high):
    return norm(loc = mean, scale = stdev).cdf(x_high) - norm(loc = mean, scale = stdev).cdf(x_low)


import numpy as np

def integrate_rec(f, a, b, n):
    # Implementation of the rectanlge method
    h = (b-a)/n
    x = np.linspace(a, b, n+1)
    i=0
    area=0
    while i<n:
        sup_rect =  f(x[i])*h
        area += sup_rect
        i += 1
    return area
'''
We test the Rectangle method on the sine funcion were the definite integral in the interval [0, %*\textcolor{codeviolet}{$\pi$}*)/2] is equal to 1.
'''

sup_5 = integrate_rec(np.sin, 0, np.pi/2, 5)
sup_10 = integrate_rec(np.sin, 0, np.pi/2, 10)
sup_100 = integrate_rec(np.sin, 0, np.pi/2, 100)

print('Using n=5, the rectangle method returns a value of {:.2f}'.format(sup_5))
print('Using n=10, the rectangle method returns a value of {:.2f}'.format(sup_10))
print('Using n=100, the rectangle method returns a value of {:.2f}'.format(sup_100))

'''
Output:
Using n=5, the rectangle method returns a value of 0.83
Using n=10, the rectangle method returns a value of 0.92
Using n=100, the rectangle method returns a value of 0.99
'''


import numpy as np
from numpy.fft import fft, ifft, fftshift, ifftshift
import matplotlib.pyplot as plt

x = np.random.rand(100)
X = fft(x)
y = ifft(X)
np.c_[x, y]
print("Maximum Absolute Error for Random Data: ", np.abs(x - y).max())

x = np.linspace(0, 2 * np.pi, 128)
y = np.sin(5 * x)
Y = fft(y)

plt.figure()
plt.subplot(2, 1, 1)
plt.title("Spectrum of $\sin(5t)$ without Phase Wrapping")
plt.ylabel("$|Y|$")
plt.plot(np.abs(Y), lw=2)
plt.grid(True)

plt.subplot(2, 1, 2)
plt.xlabel("$\omega$")
plt.ylabel("Phase of $Y$")
plt.plot(np.unwrap(np.angle(Y)), lw=2)
plt.grid(True)

plt.savefig("Assignment_08/LaTeX/eg1.png")

def plotter(w, Y, title, lim, out, xlabel="$\omega$", ylabels=("$|Y|$", r"$\angle Y$")):
    plt.figure(figsize=(8, 10))
    plt.subplot(2, 1, 1)
    plt.title(title, size=16)
    plt.ylabel(ylabels[0], size=14)
    plt.plot(w, abs(Y), lw=2)
    plt.xlim(-lim, lim)
    plt.grid(True)

    plt.subplot(2, 1, 2)
    plt.xlabel(xlabel, size=14)
    plt.ylabel(ylabels[1], size=14)
    ii = np.where(abs(Y) > 1e-3)
    plt.plot(w[ii], np.unwrap(np.angle(Y[ii])), "go", lw=2)
    plt.xlim(-lim, lim)
    plt.grid(True)

    plt.savefig("Assignment_08/LaTeX/" + out)

x = np.linspace(0, 2 * np.pi, 128, endpoint=False)
w = np.linspace(-64, 64, 128, endpoint=False)

Y = fftshift(fft(np.sin(5 * x))) / 128
plotter(w, Y, "Spectrum of $\sin(5t)$", 10, "eg2", "$k$")

Y = fftshift(fft((1 + 0.1 * np.cos(x)) * np.cos(10 * x))) / 128
plotter(w, Y, "Spectrum of $(1 + 0.1\cos(t))\cdot\cos(10t)$", 15, "eg3")

x = np.linspace(-4 * np.pi, 4 * np.pi, 512, endpoint=False)
w = np.linspace(-64, 64, 512, endpoint=False)

Y = fftshift(fft((1 + 0.1 * np.cos(x)) * np.cos(10 * x))) / 512
plotter(w, Y, "Spectrum of $(1 + 0.1\cos(t))\cdot\cos(10t)$ (Improved)", 15, "eg4")

Y = fftshift(fft(np.sin(x) ** 3)) / 512
plotter(w, Y, "Spectrum of $\sin^3(t)$", 15, "q2a")

Y = fftshift(fft(np.cos(x) ** 3)) / 512
plotter(w, Y, "Spectrum of $\cos^3(t)$", 15, "q2b")

Y = fftshift(fft(np.cos(20 * x + 5 * np.cos(x)))) / 512
plotter(w, Y, "Spectrum of $\cos(20t + 5\cos(t))$", 30, "q3")

T = 2 * np.pi
N = 128
iter_n = 0
error = None
threshold = 1e-6  # 6 decimals of precision

while error is None or error > threshold:
    t = np.linspace(-T / 2, T / 2, N, endpoint=False)
    w = np.linspace(-np.pi, np.pi, N, endpoint=False) * N / T
    y = np.exp(-0.5 * t ** 2)
    Y = fftshift(fft(ifftshift(y))) * T / (2 * np.pi * N)

    Y_true = np.exp(-0.5 * w ** 2) / np.sqrt(2 * np.pi)
    error = np.max(np.abs(Y - Y_true))

    T *= 2
    N *= 2
    iter_n += 1
    print(f"Iteration {iter_n}:   Total Error = {error:.2e}")

T /= 2
N /= 2

print(f"Samples = {int(N)},   Time Period = {int(T / np.pi)} pi")

plotter(w, Y, "Spectrum of Approximated Gaussian", 5, "q4a")
plotter(w, Y_true, "Spectrum of True Gaussian", 5, "q4b")

plt.show()


# Author: OMKAR PATHAK

# This program illustrates how to create an adarray from numerical ranges

import numpy as np

# ndarray.arange(start, stop, step, dtype)
# Creates a numpy array from 1 to 20
myArray = np.arange(1, 21)
print(myArray)                      # [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20]

# Specifying data type of each element
myArray = np.arange(10, dtype = 'float')
print(myArray)                      # [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9.]

# Specifying steps to jump in between two elements
myArray = np.arange(1, 21, 2)
print(myArray)                      # [ 1  3  5  7  9 11 13 15 17 19]

# ndarray.linspace(start, stop, num, endpoint,  retstep, dtype)
# Shows 5 equal intervals between 10 to 20
myArray = np.linspace(10, 20, 5)
print(myArray)                      # [ 10.   12.5  15.   17.5  20. ]

# if endpoint is set to false the last number inn STOP parameter is not executed
myArray = np.linspace(10, 20, 5, endpoint = False)
print(myArray)                      # [ 10.  12.  14.  16.  18.]

# ndarray.lopspace returns an ndarray object that contains the numbers that are evenly spaced
# on a log scale.
# ndarray.logscale(start, stop, num, endpoint, base, dtype)
# default base is 10
myArray = np.logspace(1.0, 3.0, num = 10)
print(myArray)

# [   10.            16.68100537    27.82559402    46.41588834    77.42636827
#    129.1549665    215.443469     359.38136638   599.48425032  1000.        ]

myArray = np.logspace(1.0, 3.0, num = 10, base = 2)
print(myArray)

# [ 2.          2.33305808  2.72158     3.1748021   3.70349885  4.32023896
#   5.0396842   5.87893797  6.85795186  8.        ]


import numpy as np
#Creation of Evenly Spaced Values
import numpy as np
a = np.arange(1, 10)
print(a)
# compare to range:
x = range(1,10)
print(x)    # x is an iterator
print(list(x))
# some more arange examples:
x = np.arange(10.4)
print(x)
x = np.arange(0.5, 10.4, 0.8)
print(x)
x = np.arange(0.5, 10.4, 0.8, int)
print(x)
# 50 values between 1 and 10:
print(np.linspace(1, 10))
# 7 values between 1 and 10:
print(np.linspace(1, 10, 7))
# excluding the endpoint:
print(np.linspace(1, 10, 7, endpoint=False))
"""
The syntax of arange:

arange([start,] stop[, step,], dtype=None)

arange returns evenly spaced values within a given interval. The values are generated within the half-open interval '[start, stop)' If the function is used with integers, it is nearly equivalent to the Python built-in function range, but arange returns an ndarray rather than a list iterator as range does. If the 'start' parameter is not given, it will be set to 0. The end of the interval is determined by the parameter 'stop'. Usually, the interval will not include this value, except in some cases where 'step' is not an integer and floating point round-off affects the length of output ndarray. The spacing between two adjacent values of the output array is set with the optional parameter 'step'. The default value for 'step' is 1. If the parameter 'step' is given, the 'start' parameter cannot be optional, i.e. it has to be given as well. The type of the output array can be specified with the parameter 'dtype'. If it is not given, the type will be automatically inferred from the other input arguments.

The syntax of linspace:

linspace(start, stop, num=50, endpoint=True, retstep=False)

linspace returns an ndarray, consisting of 'num' equally spaced samples in the closed interval [start, stop] or the half-open interval [start, stop). If a closed or a half-open interval will be returned, depends on whether 'endpoint' is True or False. The parameter 'start' defines the start value of the sequence which will be created. 'stop' will the end value of the sequence, unless 'endpoint' is set to False. In the latter case, the resulting sequence will consist of all but the last of 'num + 1' evenly spaced samples. This means that 'stop' is excluded. Note that the step size changes when 'endpoint' is False. The number of samples to be generated can be set with 'num', which defaults to 50. If the optional parameter 'endpoint' is set to True (the default), 'stop' will be the last sample of the sequence. Otherwise, it is not included.

"""


#T# exponentiation is the operation of multiplying a number by itself several times

#T# the exponentiation ** operator can be used to raise the first number to the power of the second
num1 = 2 ** 4  # 16 #| 16 is 2 to the 4th power
num1 = 5 ** -1 # 0.2

#T# the pow function is used to raise the first number to the power of the second
num1 = pow(2, 4) # 16 #| 16 is 2 to the 4th power

#T# to raise a list or array to the power of another list or array element-wise, the numpy package is used
import numpy as np

#T# the power function raises the first array to the power of the second element-wise, it supports array broadcasting
arr1 = np.array([[2, 3, 2, 4], [3, 2, 2, 5]])
arr2 = np.array([6, 4, 5, 2])
arr3 = np.power(arr1, arr2)
# array([[ 64,  81,  32,  16], [729,  16,  32,  25]])

#T# the exponentiation ** operator can be used to raise the first array to the power of the second element-wise, it supports array broadcasting
arr1 = np.array([[2, 3, 2, 4], [3, 2, 2, 5]])
arr2 = np.array([6, 4, 5, 2])
arr3 = arr1 ** arr2
# array([[ 64,  81,  32,  16], [729,  16,  32,  25]])


from scipy.optimize import newton, bisect, fsolve, root

f = lambda x: 2*x**2 - 5*x + 3

# Newton-Raphson Function
print('newton(f, 0) = %f' % newton(f, 0))
print('newton(f, 2) = %f' % newton(f, 2), end='\n\n')

# Bisection Function
print('bisect(f, 0, 1.3) = %f' % bisect(f, 0, 1.3))
print('bisect(f, 2, 1.3) = %f' % bisect(f, 2, 1.3), end='\n\n')

# FSolve Function
print('fsolve (f, 0) =', fsolve(f, 0))
print('fsolve (f, 2) =', fsolve(f, 2), end='\n\n')

x0 = [-1, 0, 1, 2, 3, 4]
print('           x0 =', x0)
print('fsolve(f, x0) =', fsolve(f, x0), end='\n\n')

# Root Function
print('root(f, 2) =', root(f, 2), sep='\n', end='\n\n')

print('root (f, 0).x =', root(f, 0).x)
print('root (f, 2).x =', root(f, 2).x, end='\n\n')

print('           x0 =', x0)
print('root(f, x0).x =', root(f, x0).x)

# Default print parameters:
# print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

1. Roots of High-Degree Equations/0. Root finding functions of SciPy.py


'''
This problem was asked by Google.

The area of a circle is defined as πr^2. Estimate π to 3 decimal places using a Monte Carlo method.

Hint: The basic equation of a circle is x2 + y2 = r2.
'''

import numpy as np
import matplotlib.pyplot as plt

'''
we have a circle of radius 1, enclosed by a 2 × 2 square. The area of the circle is πr**2=π(1)**2 = π,
the area of the square is 4. If we divide the area of the circle, by the area of the square we get π/4.

We then generate a large number of uniformly distributed random points and plot them on the graph.
These points can be in any position within the square i.e. between (-1,-1) and (1,1).
If they fall within the circle(x2 + y2 = r2),
they are coloured red, otherwise they are coloured green.
We keep track of the total number of points, and the number of points that are inside the circle.
If we divide the number of points within the circle, N_inner by the total number of points, N_total,
we should get a value that is an approximation of the ratio of the areas we calculated above, π/4.

In other words,

π/4 ≈ Ninner/Ntotal
π ≈4* (Ninner/Ntotal)

When we only have a small number of points,
the estimation is not very accurate, but when we have hundreds of thousands of points,
we get much closer to the actual value - to within around 2 decimal places of accuracy.
'''

def MC_pi():
    # pi = 3.141592
    n = 1000000 # number of data points
    #data = np.random.rand(n, 2)  # generate uniform random x,y points b/w [0,1)
    data = np.random.uniform(low=-1, high=1, size=(n,2))
    inside = data[ np.sqrt(data[:,0]**2 + data[:,1]**2) < 1]
    pi =  4* len(inside)/len(data)
    plt.figure(figsize=(8,8))
    plt.scatter(data[:,0], data[:,1], s=.5, c='red')
    plt.scatter(inside[:, 0], inside[:, 1], s=.5, c='green')
    plt.show()
    return pi

if __name__ == '__main__':
    print(MC_pi())


import matplotlib.pyplot as plt
import numpy as np

# ## Approximation

def f(x):
    return np.sin(x) + 0.5 * x

x = np.linspace(-2 * np.pi, 2 * np.pi, 50)

plt.plot(x, f(x), 'b')
plt.grid(True)
plt.xlabel('x')
plt.ylabel('f(x)')

# ### Regression

# #### Linear regresion

reg = np.polyfit(x, f(x), deg=1)
ry = np.polyval(reg, x)

plt.plot(x, f(x), 'b', label='f(x)')
plt.plot(x, ry, 'r.', label='regression')
plt.legend(loc=0)
plt.grid(True)
plt.xlabel('x')
plt.ylabel('f(x)')

# ## Higher order polynomials
reg = np.polyfit(x, f(x), deg=5)
ry = np.polyval(reg, x)

plt.plot(x, f(x), 'b', label='f(x)')
plt.plot(x, ry, 'r.', label='regression')
plt.legend(loc=0)
plt.grid(True)
plt.xlabel('x')
plt.ylabel('f(x)')

np.allclose(f(x), ry) # check if are numerically identical (change deg to 7 to get true)

np.sum((f(x) - ry) ** 2) / len(x)

# ### Constrained Optimization

# function to be minimized
# EXAMPLE
# An investor wants to maximize the expected utility by investing in two risky securities
# a,b that cost 10 today. After one year, on state u they get payoffs (15,5) and on state
# d they get payoffs (5,12). The budget of the investor is 100 and has an utility function
# utility(wealth) = sqrt(wealth)

def Eu(p):
    a, b = p
    return -(0.5 * np.sqrt(a * 15 + b * 5) + 0.5 * np.sqrt(a * 5 + b * 12))

# constraints
cons = ({'type': 'ineq', 'fun': lambda p: 100 - p[0] * 10 - p[1] * 10})
  # budget constraint
bnds = ((0, 1000), (0, 1000))  # uppper bounds large enough

result = spo.minimize(Eu, [5, 5], method='SLSQP',bounds=bnds, constraints=cons)

result['x']@[10,10] # Check if the investor depletes budget
-result['fun']

# ## Integration
import scipy.integrate as sci

def f(x):
    return np.sin(x) + 0.5 * x

a = 0.5  # left integral limit
b = 9.5  # right integral limit
x = np.linspace(0, 10)
y = f(x)

sci.fixed_quad(f, a, b)[0] # Fixed Gaussian quadrature rule

sci.quad(f, a, b)[0] # Adaptive quadrature

sci.romberg(f, a, b)

# ### Integration by Simulation (compare with values above)

# Monte Carlo integration: simulate a path, sum the function values, average
for i in range(1, 20):
    np.random.seed(1000)
    x = np.random.random(i * 10) * (b - a) + a
    print(np.sum(f(x)) / len(x) * (b - a))


#py_numpy.py

"""This script contains code to demonstrate basic operations
   using numpy arrays
"""

#import the library:
import numpy as np

#create an array simply by populating it
a=np.zeros((3,4)); b=np.ones(3)
arr = np.array([1, 2, 3])
print(arr)
print(type(arr))
print()

#the array knows a lot about itself and can be sliced like a list
print(arr.shape)
print(arr[-1])
print(arr[1:3])
print()

#you can create a 2d array like a list of lists
arr2d = np.array([[1,2,3],[4,5,6], [7, 8, 9]])
print(arr2d)
print (arr2d.shape)
print(arr2d[2, 1])
print()

#now, you can address elements with 2d slicking
new2d=arr2d[1:, 1:]
print(new2d)
print()

#you can address one 'dimension' at a time
new1d_row=arr2d[1,]
print(new1d_row)
new1d_col=arr2d[:,1]
print(new1d_col)
print()

#scalar options are easy
double=arr2d*2
print(double)
square=arr2d**2
print(square)
print()

#you can make "deep copies" just like lists
copy_arr2d=arr2d[:]
copy_arr2d[1,]=copy_arr2d[1,]*2
print(copy_arr2d)
print()

#stats
print("max", arr2d.max())
print("mean", arr2d.mean())
print("min", arr2d.min())
print("sd", arr2d.std())
print("sum", arr2d.sum())
print("transpose\n", arr2d.T)
print()

#row, column masks for a diagional
row_mask=np.array([0,1,2])
col_mask=np.array([0,1,2])
diagonal=arr2d[row_mask, col_mask]
print(diagonal)

#or, just use the built-in
print(arr2d.diagonal())
print()

#it's possible to create a boolean mask
boolean_ix=arr2d % 2 == 0  #filter evens
print(boolean_ix)

evens=arr2d*boolean_ix
print(evens)
print()

#extract evens
just_evens=arr2d[boolean_ix]
print(just_evens)
print()

#matrix ops
a_arr = np.array([[1,2],[3,4]])
b_arr = np.array([[5,6],[7,8]])
c_arr = np.array([9,10])
d_arr = np.array([11, 12])

# Scalar Products
print("scalar product of:\n", c_arr, "\nand\n", d_arr, '\n')
print (c_arr.dot(d_arr))
print (np.dot(c_arr, d_arr))
print()

# Matrix Products
print("matrix product of:\n", a_arr, "\nand\n", c_arr, '\n')
print (a_arr.dot(c_arr))
print (np.dot(a_arr, c_arr))
print()

print("matrix product of:\n", a_arr, "\nand\n", b_arr, '\n')
print (a_arr.dot(b_arr))
print (np.dot(a_arr, b_arr))
print()


import numpy as np
import matplotlib.pyplot as plt

def sample_expt():
	mu = 0.0
	sigma_2 = 1.0
	N = 5

	samples = np.random.normal(mu, sigma_2, N)

	# print samples

	mu_MLE = np.sum(samples)/N

	sigma_2_MLE = np.sum(np.power((samples - mu_MLE*np.ones_like(samples)),2))/N

	# print 'my_MLE = %f, sigma_2_MLE = %f' % (mu_MLE, sigma_2_MLE)
	return mu_MLE, sigma_2_MLE

mu_MLE, sigma_2_MLE = sample_expt()
print 'MLE estimate of mean based on 5 samples = %f' % mu_MLE
print 'MLE estimate of variance based on 5 samples = %f' % sigma_2_MLE
# print 'mu_MLE = %f, sigma_2_MLE = %f' % (mu_MLE, sigma_2_MLE)

sigma_2_arr = np.empty([10000,1], dtype=float)
mu_arr = np.empty([10000,1], dtype=float)

for i in range(10000):
	mu_MLE, sigma_2_MLE = sample_expt()
	sigma_2_arr[i,:] = sigma_2_MLE
	mu_arr[i,:] = mu_MLE

mean_sigma_2_MLE_freq = np.mean(sigma_2_arr)
var_sigma_2_MLE_freq = np.mean(np.power((sigma_2_arr - mean_sigma_2_MLE_freq*np.ones_like(sigma_2_arr)),2))

variance_true = 1.0
N = 5

bias_of_variance_estimate = abs(mean_sigma_2_MLE_freq - variance_true)
var_of_variance_estimate = var_sigma_2_MLE_freq
# print 'mean_sigma_2_MLE_freq = %f, var_sigma_2_MLE_freq = %f' % (mean_sigma_2_MLE_freq, var_sigma_2_MLE_freq)

print 'Bias of variance estimate = %f' % bias_of_variance_estimate
print 'Variance of variance estimate = %f' % var_of_variance_estimate

bias_of_variance_estimate_th = abs(variance_true/N)
var_of_variance_estimate_th = 2*(N-1)*(variance_true**2)/(N**2)

print 'Theoretical Bias of variance estimate = %f' % bias_of_variance_estimate_th
print 'Theoretical Variance of variance estimate = %f' % var_of_variance_estimate_th

diff_bias_variance = (bias_of_variance_estimate - bias_of_variance_estimate_th)*100/bias_of_variance_estimate_th
diff_var_variance = (var_of_variance_estimate - var_of_variance_estimate_th)*100/var_of_variance_estimate_th

print 'percentage difference between Theoretical and Frequentist result for bias of variance estimate = %f ' % diff_bias_variance
print 'percentage difference between Theoretical and Frequentist result for variance of variance estimate = %f' % diff_var_variance

plt.hist(sigma_2_arr,'auto')
plt.title("Histogram of variance values")
plt.xlabel("variance estimate")
plt.ylabel("Number of samples")
plt.show()


# Q2: find median for entire sample
# Q1: find median for first half of the sample
# Q3: find median for second half of the sample

# Before doing any calculation of Quartiles we need to order the sequence from lowest to highest

"""

https://towardsdatascience.com/what-are-quartiles-c3e117114cf1

Quartiles are position indicators that divide a sequence of numbers into 4 equal parts.

Quartiles analysis is part of descriptive statistics and consequently, helps us better understand
the data at hand. With these, specifically, we will understand what is called central tendency or :

“A measure of central tendency is a single value that attempts to describe a set of data by identifying
the central position within that set of data”

"""

import numpy as np

from day0.p01mean_median_mode import median

def quartiles(sample: list) -> list:
    sample.sort()
    size = len(sample)
    mid_index = size // 2
    if size % 2:
        q3 = median(sample[mid_index + 1:])
    else:
        q3 = median(sample[mid_index:])
    return [median(sample[:mid_index]), median(sample), q3]

if __name__ == '__main__':
    data = [4, 17, 7, 14, 18, 12, 3, 16, 10, 4, 4, 12]
    result = quartiles(data)
    print(result)

    # using numpy
    print('%d' % np.quantile(np.array(data), 0.25))
    print('%d' % np.quantile(np.array(data), 0.5))
    print('%d' % np.quantile(np.array(data), 0.75, interpolation='midpoint'))


"""

Linear Regression Finder : Given a csv file of the following format calculates the linear regression for the given data.

    Data format:

    x     y

    1	  2
    2	  3
    3	  4
    4	  5
    5	  6
    6	  7
    7	  8
    8	  9
    9	  10
    10	  11

    Formula :

    y = a + (b * x)

    b = r * (sy / sx)

    a = y1 - (b * x1)

    r = sigma( (x - x1) * (y - y1) ) / sqrt( sigma( (x - x1)**0.5) * sigma( (y - y1)**0.5) )

"""
from sys import stdin, stdout
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as lines
import time
import csv

def load_csv():

    # Getting the name of the csv file to be loaded

    file_name = input("enter the name of the csv file : ")

    x = []

    y = []

    # Loading csv file

    try:

        with open(file_name+".csv") as csvfile:

            reader = csv.DictReader(csvfile)

            for row in reader:

                x.append(float(row['x']))

                y.append(float(row['y']))

        return x, y, file_name

    except:

        print("\nThe entered csv file dosent exixt.\n\nPlease check the name and try again.\n")

def show_data(x, y):

    # Displaying the content of the csv file

    for i in range(len(x)):

        print(str(x[i])+"\t"+str(y[i]))

def linear_reg(x, y):

    # Finding means of x and y as x1 and y1

    x1 = sum(x)/len(x)

    y1 = sum(y)/len(y)

    # Finding the numerator part to calculate r

    x = [i-x1 for i in x]

    y = [i-y1 for i in y]

    z = [x[i]*y[i] for i in range(len(x))]

    # Finding the denominator part to calculate r

    x = [i*i for i in x]

    y = [i*i for i in y]

    # Calculation of r, sy and sx

    r = sum(z)/((sum(x)*sum(y))**0.5)

    sy = (sum(y)/(len(y)-1))**0.5

    sx = (sum(x)/(len(x)-1))**0.5

    # Calculating a and b

    b = r * (sy / sx)

    a = y1 - (b * x1)

    # Finding and displaying y

    val = str(a)+" + ( "+str(b)+" * x )"

    return val, a, b

def scatter_plot(x, y, a, b):

    # Ploting the scatter plot

    area = 100

    colors = np.random.rand(len(x))

    fig, ax = plt.subplots()

    data = plt.scatter(x, y, s=area, c=colors, alpha=1)

    # Finding ends of the regression line

    start = [min(x), max(x)]

    end = [(b * min(x)) + a, (b * max(x)) + a]

    # Plotting the regression line

    reg_line = lines.Line2D(start, end,  lw=2, color='black', axes=ax)

    ax.add_line(reg_line)

    # Displaying the plots

    ax.grid(True)

    plt.axis('equal')

    plt.tight_layout()

    plt.show()

def predict(a, b):

    test_x = float(input("Enter the value of x for which y is to be predicted : "))

    test_y = a + (b * test_x)

    print("\nFor x = "+str(test_x)+" y = "+str(test_y)+"\n")

    return test_x, test_y

def update_csv(test_x, test_y, file_name):

    # Updating the CSV file with the predicted value

    fields = [test_x, test_y]

    with open(file_name+".csv", 'a') as f:

        writer = csv.writer(f)

        writer.writerow(fields)

def clear_data_set():

    # Clearing the data set by resetting the values

    x = []

    y = []

    a = 0

    b = 0

    val = ""

    return x, y, a, b, val

def main():

    try:

        op = 0

        while op != 8:

            op = int(input("Choose your option :\n\n1. Load csv\n2. Show data\n3. Find Linear regression equ\n4. Show scatter plot\n5. Predict\n6. Update CSV\n7. Clear Data Set\n8. Exit\n\n"))

            print()

            if op == 1:

                x, y, file_name = load_csv()

                x = np.array(x)

                y = np.array(y)

                print()

            elif op == 2:

                show_data(x, y)

                print()

            elif op == 3:

                val, a, b = linear_reg(x, y)

                print("y = "+val)

                print()

            elif op == 4:

                scatter_plot(x, y, a, b)

            elif op == 5:

                test_x, test_y = predict(a, b)

            elif op == 6:

                update_csv(test_x, test_y, file_name)

            elif op == 7:

                x, y, a, b, val = clear_data_set()

            elif op == 8:

                exit()

            else:

                print("\ninvalid input\n\nplease select a valid input\n")

    except:

        print("Something went wrong try again.")

if __name__ == "__main__":

    main()


import numpy as np
from scipy.linalg import solve
from helpers import Vehicle, show_trajectory

A = np.random.random((3, 3))
b = np.random.random(3)

x = solve(A, b)

def JMT(start, end, T):

	si = start[0]
	si_dot = start[1]
	si_double_dot = start[2]

	sf = end[0]
	sf_dot = end[1]
	sf_double_dot = end[2]

	A = [
		 [T**3	, T**4	 , T**5	  ],
		 [3*T**2, 4*T**3 ,  5*T**4],
		 [6*T	, 12*T**2, 20*T**3]
		]

	b = [sf - (si + si_dot*T + 0.5 * si_double_dot * T**2 ),
		 sf_dot - (si_dot + si_double_dot*T),
		 sf_double_dot - si_double_dot]

	x = solve(A,b)

	r = np.array([si, si_dot, 0.5*si_double_dot, x[0], x[1], x[2]])

	return r
	#return [si, si_dot, 0.5*si_double_dot, x[0], x[1], x[2]]

def close_enough(poly, target_poly, eps=0.01):
	if len(poly) != len(target_poly):
		print("your solution didn't have the correct number of terms")
		return False

	for i in range(len(poly)):
		diff = poly[i]-target_poly[i]
		if(abs(diff) > eps):
			print("at least one of your terms differed from target by more than ", eps )
			return False

	return True

answers = [[0.0, 10.0, 0.0, 0.0, 0.0, 0.0],[0.0,10.0,0.0,0.0,-0.625,0.3125],[5.0,10.0,1.0,-3.0,0.64,-0.0432]];

# create test cases

tc1 = [[0,10,0], [10,10,0], [1]]

tc2 = [[0,10,0],[20,15,20], [2]]

tc3 = [[5,10,2],[-30,-20,-4], [5]]

tc = [tc1,tc2,tc3]

i = 0;
total_correct = True
for test in tc:
	jmt = JMT(test[0], test[1], test[2][0])
	correct = close_enough(jmt, answers[i])
	total_correct &= correct
	i += 1

if total_correct:
	print("Nice work!")
else:
	print("Try again!")

jmt = JMT(tc1[0], tc1[1], tc1[2][0])
show_trajectory(jmt[0], jmt[1], jmt[2])


"""Bures distance metric."""
import numpy as np

from toqito.state_metrics import fidelity

def bures_distance(rho_1: np.ndarray, rho_2: np.ndarray, decimals: int = 10) -> float:
    r"""
    Compute the Bures distance of two density matrices [WikBures]_.

    Calculate the Bures distance between two density matrices :code:`rho_1` and :code:`rho_2`
    defined by:

    .. math::
        \sqrt{2 (1 - F(\rho_1, \rho_2))},

    where :math:`F(\cdot)` denotes the fidelity between :math:`\rho_1` and :math:`\rho_2`. The
    return is a value between :math:`0` and :math:`\sqrt{2}`,with :math:`0` corresponding to
    matrices: :code:`rho_1 = rho_2` and :math:`\sqrt{2}` corresponding to the case: :code:`rho_1`
    and :code:`rho_2` with orthogonal support.

    Examples
    ==========

    Consider the following Bell state

    .. math::
        u = \frac{1}{\sqrt{2}} \left( |00 \rangle + |11 \rangle \right) \in \mathcal{X}.

    The corresponding density matrix of :math:`u` may be calculated by:

    .. math::
        \rho = u u^* = \frac{1}{2} \begin{pmatrix}
                         1 & 0 & 0 & 1 \\
                         0 & 0 & 0 & 0 \\
                         0 & 0 & 0 & 0 \\
                         1 & 0 & 0 & 1
                       \end{pmatrix} \in \text{D}(\mathcal{X}).

    In the event where we calculate the Bures distance between states that are identical, we
    should obtain the value of :math:`0`. This can be observed in :code:`toqito` as follows.

    >>> from toqito.state_metrics import bures_distance
    >>> import numpy as np
    >>> rho = 1 / 2 * np.array(
    >>>     [[1, 0, 0, 1],
    >>>      [0, 0, 0, 0],
    >>>      [0, 0, 0, 0],
    >>>      [1, 0, 0, 1]]
    >>> )
    >>> sigma = rho
    >>> bures_distance(rho, sigma)
    0

    References
    ==========
    .. [WikBures] Wikipedia: Bures metric
        https://en.wikipedia.org/wiki/Bures_metric

    :param rho_1: Density operator.
    :param rho_2: Density operator.
    :param decimals: Number of decimal places to round to (default 10).
    :return: The Bures distance between :code:`rho_1` and :code:`rho_2`.
    """
    # Perform some error checking.
    if not np.all(rho_1.shape == rho_2.shape):
        raise ValueError("InvalidDim: `rho_1` and `rho_2` must be matrices of the same size.")
    # Round fidelity to only 10 decimals to avoid error when :code:`rho_1 = rho_2`.
    return np.sqrt(2.0 * (1.0 - np.round(fidelity(rho_1, rho_2), decimals)))


import numpy as np
from scipy import stats

# In[]:
# Defining 2 random distributions with sample size 10
# Gaussian distributed data with mean = 2 and var = 1
Nx = 10
x = np.random.randn(Nx) + 2
# Gaussian distributed data with with mean = 0 and var = 1
Ny = 10
y = np.random.randn(Ny)

# In[]:

# Calculate the std dev now
# ddof : int, optional
# “Delta Degrees of Freedom”: the divisor used in the calculation is N - ddof,
# where N represents the number of elements. By default ddof is zero.
var_x = x.var(ddof=1)
var_y = y.var(ddof=1)

# In[]:
# Calculate the t-statistics
t = (x.mean() - y.mean()) / (np.sqrt( (var_x/Nx) + (var_y/Ny) ))

# In[]:
# Compare with the critical t-value

# Degrees of freedom
df = Nx + Ny - 2
# p-value after comparison with the t
p = 1 - stats.t.cdf(t,df=df)

# In[]:
print("t = " + str(t))
print("p = " + str(2*p))
# You can see that after comparing the t statistic with the critical t value (computed internally)
# we get a good p value of 0.0005 and thus we reject the null hypothesis and thus it proves that
# the mean of the two distributions are different and statistically significant.

# Cross Checking with the internal scipy function, this is a 2tailed p value
t2, p2 = stats.ttest_ind(x, y)
print("t = " + str(t2))
print("p = " + str(p2))


#1. Write a NumPy program to add, subtract, multiply, divide arguments element-wise.
import numpy as np
print("Add:")
print(np.add(1.0, 4.0))
print("Subtract:")
print(np.subtract(1.0, 4.0))
print("Multiply:")
print(np.multiply(1.0, 4.0))
print("Divide:")
print(np.divide(1.0, 4.0))
#2. Write a NumPy program to compute logarithm of the sum of exponentiations of the inputs, sum of exponentiations of the inputs in base-2.
import numpy as np
l1 = np.log(1e-50)
l2 = np.log(2.5e-50)
print("Logarithm of the sum of exponentiations:")
print(np.logaddexp(l1, l2))
print("Logarithm of the sum of exponentiations of the inputs in base-2:")
print(np.logaddexp2(l1, l2))
#3. Write a NumPy program to get true division of the element-wise array inputs.
import numpy as np
x = np.arange(10)
print("Original array:")
print(x)
print("Division of the array inputs, element-wise:")
print(np.true_divide(x, 3))
#4. Write a NumPy program to get the largest integer smaller or equal to the division of the inputs.
import numpy as np
x = [1., 2., 3., 4.]
print("Original array:")
print(x)
print("Largest integer smaller or equal to the division of the inputs:")
print(np.floor_divide(x, 1.5))
#5. Write a NumPy program to get the powers of an array values element-wise.
import numpy as np
x = np.arange(7)
print("Original array")
print(x)
print("First array elements raised to powers from second array, element-wise:")
print(np.power(x, 3))
#6. Write a NumPy program to get the element-wise remainder of an array of division.
import numpy as np
x = np.arange(7)
print("Original array:")
print(x)
print("Element-wise remainder of division:")
print(np.remainder(x, 5))
#7. Write a NumPy program to calculate the absolute value element-wise.
import numpy as np
x = np.array([-10.2, 122.2, .20])
print("Original array:")
print(x)
print("Element-wise absolute value:")
print(np.absolute(x))
#8. Write a NumPy program to round array elements to the given number of decimals.
import numpy as np
x = np.round([1.45, 1.50, 1.55])
print(x)
x = np.round([0.28, .50, .64], decimals=1)
print(x)
x = np.round([.5, 1.5, 2.5, 3.5, 4.5]) # rounds to nearest even value
print(x)
#9. Write a NumPy program to round elements of the array to the nearest integer.
import numpy as np
x = np.array([-.7, -1.5, -1.7, 0.3, 1.5, 1.8, 2.0])
print("Original array:")
print(x)
x = np.rint(x)
print("Round elements of the array to the nearest integer:")
print(x)
#10. Write a NumPy program to get the floor, ceiling and truncated values of the elements of a numpy array.
import numpy as np
x = np.array([-1.6, -1.5, -0.3, 0.1, 1.4, 1.8, 2.0])
print("Original array:")
print(x)
print("Floor values of the above array elements:")
print(np.floor(x))
print("Ceil values of the above array elements:")
print(np.ceil(x))
print("Truncated values of the above array elements:")
print(np.trunc(x))
#11. Write a NumPy program to multiply a 5x3 matrix by a 3x2 matrix and create a real matrix product.
import numpy as np
x = np.random.random((5,3))
print("First array:")
print(x)
y = np.random.random((3,2))
print("Second array:")
print(y)
z = np.dot(x, y)
print("Dot product of two arrays:")
print(z)
#12. Write a NumPy program to multiply a matrix by another matrix of complex numbers and create a new matrix of complex numbers.
import numpy as np
x = np.array([1+2j,3+4j])
print("First array:")
print(x)
y = np.array([5+6j,7+8j])
print("Second array:")
print(y)
z = np.vdot(x, y)
print("Product of above two arrays:")
print(z)
#13. Write a NumPy program to create an inner product of two arrays.
import numpy as np
x = np.arange(24).reshape((2,3,4))
print("Array x:")
print(x)
print("Array y:")
y = np.arange(4)
print(y)
print("Inner of x and y arrays:")
print(np.inner(x, y))
#14. Write a NumPy program to generate inner, outer, and cross products of matrices and vectors.
import numpy as np
x = np.array([1, 4, 0], float)
y = np.array([2, 2, 1], float)
print("Matrices and vectors.")
print("x:")
print(x)
print("y:")
print(y)
print("Inner product of x and y:")
print(np.inner(x, y))
print("Outer product of x and y:")
print(np.outer(x, y))
print("Cross product of x and y:")
print(np.cross(x, y))
#15. Write a NumPy program to generate a matrix product of two arrays.
import numpy as np
x = [[1, 0], [1, 1]]
y = [[3, 1], [2, 2]]
print("Matrices and vectors.")
print("x:")
print(x)
print("y:")
print(y)
print("Matrix product of above two arrays:")
print(np.matmul(x, y))
#16. Write a NumPy program to find the roots of the following polynomials.
import numpy as np
print("Roots of the first polynomial:")
print(np.roots([1, -2, 1]))
print("Roots of the second polynomial:")
print(np.roots([1, -12, 10, 7, -10]))
#17. Write a NumPy program to compute the following polynomial values
import numpy as np
print("Polynomial value when x = 2:")
print(np.polyval([1, -2, 1], 2))
print("Polynomial value when x = 3:")
print(np.polyval([1, -12, 10, 7, -10], 3))
#19. Write a NumPy program to calculate mean across dimension, in a 2D numpy array.
import numpy as np
x = np.array([[10, 30], [20, 60]])
print("Original array:")
print(x)
print("Mean of each column:")
print(x.mean(axis=0))
print("Mean of each row:")
print(x.mean(axis=1))
#20. Write a NumPy program to create a random array with 1000 elements and compute the average, variance, standard deviation of the array elements.
import numpy as np
x = np.random.randn(1000)
print("Average of the array elements:")
mean = x.mean()
print(mean)
print("Standard deviation of the array elements:")
std = x.std()
print(std)
print("Variance of the array elements:")
var = x.var()
print(var)


import numpy as np
np.set_printoptions(linewidth=1000)

def softmax(vector: np.array) -> np.array:
    """
    My own implementation of the softmax function

    Parameters
    ----------
    vector

    Returns
    -------

    """

    # Notice that
    # exp(x_i)/sum_i exp(x_i)
    # = [exp(-m)/exp(-m)]exp(x_i)/sum_i exp(x_i)
    # = exp(x_i - m)/sum_i exp(x_i-m)

    # I add some gymnastics to allow this to work on vectors as well as arrays
    if len(vector.shape) == 1:
        vector = vector.reshape(1, -1)
        reshape = True
    else:
        reshape = False

    nominator = np.exp(vector - np.max(vector))
    denominator = nominator.sum(axis=1, keepdims=True)

    result = nominator / denominator

    if reshape:
        result = result.reshape(-1,)
    return result

def sigmoid(x):
    return 1/(1 + np.exp(-x))

def cross_entropy(predictions, targets, epsilon=1e-12):
    """

    Parameters
    ----------
    predictions: np.array(N, k)
    targets: np.array(N, k)
    epsilon: float
        Correction factor for numerical stability

    Returns
    -------

    """
    predictions = np.clip(predictions, epsilon, 1. - epsilon)
    N = predictions.shape[0]
    result = -np.sum(targets*np.log(predictions+1e-9))/N
    return result

def relu(X):
    # np.maximum keeps the shape of X! np.max returns less dimensional objects
    return np.maximum(X, 0)


import numpy as np

def homography2d(x1, x2):
    """
    Direct Linear Transform

    Input:
    x1: 3xN set of homogeneous points
    x2: 3xN set of homogeneous points such that x1<->x2

    Returns:
    H: the 3x3 homography such that x2 = H*x1
    """
    [x1, T1] = normalise2dpts(x1)
    [x2, T2] = normalise2dpts(x2)

    Npts = x1.shape[1]

    A = np.zeros((3*Npts,9))

    O = np.zeros(3);

    for i in range(0, Npts):
        X = x1[:,i].T
        x = x2[0,i]
        y = x2[1,i]
        w = x2[2,i]
        A[3*i,:] = np.array([O, -w*X, y*X]).reshape(1, 9)
        A[3*i+1,:] = np.array([w*X, O, -x*X]).reshape(1, 9)
        A[3*i+2,:] = np.array([-y*X, x*X, O]).reshape(1, 9)

    [U,D,Vh] = np.linalg.svd(A)
    V = Vh.T

    H = V[:,8].reshape(3,3)

    H = np.linalg.solve(T2, H)
    H = H.dot(T1)

    return H

def normalise2dpts(pts):
    """
    Function translates and normalises a set of 2D homogeneous points
    so that their centroid is at the origin and their mean distance from
    the origin is sqrt(2).  This process typically improves the
    conditioning of any equations used to solve homographies, fundamental
    matrices etc.

    Inputs:
    pts: 3xN array of 2D homogeneous coordinates

    Returns:
    newpts: 3xN array of transformed 2D homogeneous coordinates.  The
            scaling parameter is normalised to 1 unless the point is at
            infinity.
    T: The 3x3 transformation matrix, newpts = T*pts
    """
    if pts.shape[0] != 3:
        print("Input shoud be 3")

    finiteind = np.nonzero(abs(pts[2,:]) > np.spacing(1));

    if len(finiteind) != pts.shape[1]:
        print('Some points are at infinity')

    dist = []
    for i in finiteind:
        pts[0,i] = pts[0,i]/pts[2,i]
        pts[1,i] = pts[1,i]/pts[2,i]
        pts[2,i] = 1;

        c = np.mean(pts[0:2,i].T, axis=0).T

        newp1 = pts[0,i]-c[0]
        newp2 = pts[1,i]-c[1]

    	dist.append(np.sqrt(newp1**2 + newp2**2))

    meandist = np.mean(dist[:])

    scale = np.sqrt(2)/meandist

    T = np.array([[scale, 0, -scale*c[0]], [0, scale, -scale*c[1]], [0, 0, 1]])

    newpts = T.dot(pts)

    return [newpts, T]


import streamlit as st
import pandas as pd
import math
import numpy as np

st.title('Explore Prime Numbers')
st.header('Find if a number is prime, prime numbers under a particular integer, odd/even prime numbers')

#to tell if a number is prime
def is_prime(n):
    """"pre-condition: n is a nonnegative integer
    post-condition: return True if n is prime and False otherwise."""
    if n < 2:
         return False;
    if n % 2 == 0:
         return n == 2  # return False
    k = 3
    while k*k <= n:
         if n % k == 0:
             return False
         k += 2
    return True
isprime = st.number_input("Enter n an integer",3)
isprime = int(isprime)
Answer =is_prime(isprime)
if Answer:
    s = " is a prime number"
else:
    s = "is not a prime number"
st.write('{}'.format(isprime),s  )
def even_numbers(lst):
    l = lst[:]
    for x in l:
        if not x%2 == 0:
            l.remove(x)
    return l

def primes(n):
    #https://radiusofcircle.blogspot.com/2016/06/problem-50-project-euler-solution-with-python.html
    is_prime = [True]*n
    is_prime[0] = False
    is_prime[1] = False
    is_prime[2] = True
    # even numbers except 2 have been eliminated
    for i in np.arange(3, int(n**0.5+1), 2):
        index = i*2
        while index < n:
            is_prime[index] = False
            index = index+i
    prime = [2]
    for i in np.arange(3, n, 2):
        if is_prime[i]:
            prime.append(i)
    return prime

L = 0
K = [[50,50]]
for i in range(1, 100):
    for j in range(100,i,-1):
        if i+j == 100:
            K.append([i,j])

            L+= 1

n = isprime

primesundern =primes(n)

if(st.button('Find prime numbers under n')):

    # print the BMI INDEX
    st.text("The prime numbers under n are {}".format(primesundern ))
    st.text("There are {} prime numbers under n".format(len(primesundern)))

if(st.button('Find odd prime numbers under n')):

    # print the BMI INDEX
    st.text("The prime numbers under n are {}".format(primesundern ))
    st.text("There are {} prime numbers under n".format(len(primesundern)))

if(st.button('Find even prime numbers under n')):

    # print the BMI INDEX
    st.text("The prime numbers under n are {}".format(even_numbers(primesundern )))
    st.text("There are {} prime numbers under n".format(len(primesundern)))

if(st.button('Find prime numbers divisble by n under n')):

    # print the BMI INDEX
    st.text("The prime numbers under n are {}".format(primesundern ))
    st.text("There are {} prime numbers under n".format(len(primesundern)))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan 26 19:11:24 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return x*(x-1)

def derv(func,x, delta=1.e-2):
    return (func(x+delta)-func(x))/delta

def fprime(x):
    return 2*x-1

print("f(x)=x(x-1)\nAt x=1,")
print("Numerical derivative (Forward Difference): %f"%derv(f,1))
print("Analytical Derviative: %f"%fprime(1))
print("Relative Error: %e"%np.abs((derv(f,1)-fprime(1))/fprime(1)))
print("This arises as a truncation error as an approximation for the derivative using Forward difference.")

y = []
for delta in np.array([2,4,6,8,10,12,14]):
    y.append(np.abs((derv(f,1,delta=1/10**delta)-fprime(1))/fprime(1)))

plt.plot(-np.array([2,4,6,8,10,12,14]),np.log10(y))
plt.xlabel(r'$\log_{10}(h)$', size=20)
plt.ylabel(r'$\log_{10}\left(\left|\frac{f^{(1)}_{analytic}-f^{(1)}_{numerical}}{f^{(1)}_{analytic}}\right|\right)$', size=20)
plt.title('Relative Error as a function of step size',size=20)
plt.grid()
#plt.savefig('error.jpg')
plt.show()

"""
Output
f(x)=x(x-1)
At x=1,
Numerical derivative (Forward Difference): 1.010000
Analytical Derviative: 1.000000
Relative Error: 1.000000e-02
This arises as a truncation error as an approximation for the derivative using Forward difference.
"""


'''
Bootstrap replicates of the mean and the SEM

In this exercise, you will compute a bootstrap estimate of the probability distribution function of the
mean annual rainfall at the Sheffield Weather Station. Remember, we are estimating the mean annual rainfall
we would get if the Sheffield Weather Station could repeat all of the measurements from 1883 to 2015 over
and over again. This is a probabilistic estimate of the mean. You will plot the PDF as a histogram, and
you will see that it is Normal.

In fact, it can be shown theoretically that under not-too-restrictive conditions, the value of the
mean will always be Normally distributed. (This does not hold in general, just for the mean and a
few other statistics.) The standard deviation of this distribution, called the standard error of the
mean, or SEM, is given by the standard deviation of the data divided by the square root of the number
of data points. I.e., for a data set, sem = np.std(data) / np.sqrt(len(data)). Using hacker statistics,
you get this same result without the need to derive it, but you will verify this result from your
bootstrap replicates.

The dataset has been pre-loaded for you into an array called rainfall.

INSTRUCTIONS
100XP
-Draw 10000 bootstrap replicates of the mean annual rainfall using your draw_bs_reps() function and the
rainfall array. Hint: Pass in np.mean for func to compute the mean.
-As a reminder, draw_bs_reps() accepts 3 arguments: data, func, and size.
-Compute and print the standard error of the mean of rainfall.
-The formula to compute this is np.std(data) / np.sqrt(len(data)).
-Compute and print the standard deviation of your bootstrap replicates bs_replicates.
-Make a histogram of the replicates using the normed=True keyword argument and 50 bins.
-Hit 'Submit Answer' to see the plot!
'''
import matplotlib.pyplot as plt
import numpy as np

def draw_bs_reps(data, func, size=1):
    """Draw bootstrap replicates."""

    # Initialize array of replicates: bs_replicates
    bs_replicates = np.empty(size)

    # Generate replicates
    for i in range(size):
        bs_replicates[i] = bootstrap_replicate_1d(data, func)

    return bs_replicates

# Take 10,000 bootstrap replicates of the mean: bs_replicates
bs_replicates = draw_bs_reps(rainfall, np.mean, size=10000)

# Compute and print SEM
sem = np.std(rainfall) / np.sqrt(len(rainfall))
print(sem)

# Compute and print standard deviation of bootstrap replicates
bs_std = np.std(bs_replicates)
print(bs_std)

# Make a histogram of the results
_ = plt.hist(bs_replicates, bins=50, normed=True)
_ = plt.xlabel('mean annual rainfall (mm)')
_ = plt.ylabel('PDF')

# Show the plot
plt.show()


"""
Example: Cafe Java Arrival Time Distribution and Process Generator.

This example develops a continuous process generator for arrival times
observed in Cafe Java.

@author: Paul T. Grogan <pgrogan@stevens.edu>
"""

# import the python3 behavior for importing, division, and printing in python2
from __future__ import absolute_import, division, print_function

# import the numpy package and refer to it as `np`
# see http://docs.scipy.org/doc/numpy/reference/ for documentation
import numpy as np

# import the matplotlib pyplot package and refer to it as `plt`
# see http://matplotlib.org/api/pyplot_api.html for documentation
import matplotlib.pyplot as plt

# import the scipy stats package and refer to it as `stats`
# see http://docs.scipy.org/doc/scipy-0.14.0/reference/stats.html for docs
import scipy.stats as stats

_lambda = 1/2 # customers per minute

# define a linear space between 0 and 10
plot_x = np.linspace(0,10)
# define PDF and CDF functions from derived formulas
pdf = _lambda*np.exp(-_lambda*plot_x)
cdf = 1-np.exp(-_lambda*plot_x)

# create a new figure for a PDF plot
plt.figure()
# plot the PDF using a blue line (-b)
plt.plot(plot_x, pdf, '-b')
# plot the PDF using the built-in function using a dashed black line (--k)
plt.plot(plot_x, stats.expon.pdf(plot_x, scale=1./_lambda), '--k')
plt.xlabel('Inter-arrival Time x (minutes)')
plt.ylabel('f(x)')
plt.title('PDF for Cafe Java Inter-arrival Time')

# create a new figure for a CDF plot
plt.figure()
# plot the CDF using a blue line (-b)
plt.plot(plot_x, cdf, '-b')
# plot the CDF using the built-in function using a dashed black line (--k)
plt.plot(plot_x, stats.expon.cdf(plot_x, scale=1./_lambda), '--k')
plt.xlabel('Inter-arrival Time x (minutes)')
plt.ylabel('F(x)')
plt.title('CDF for Cafe Java Inter-arrival Time')

# define a function to compute arrival times using inverse transform method
def generate_arrival_ivt():
    """Generates an arrival time following the inverse transform method.

    Returns:
        arrival (float): the time until the next arrival
    """
    r = np.random.rand()
    return -np.log(1-r)/_lambda
    """
    note: the code above could be replaced with the built-in process generator:

    return np.random.exponential(1/_lambda)
    """

# define number of samples
num_samples = 1000

# fill the samples arrays with samples from the generators
samples_ivt = [generate_arrival_ivt() for i in range(num_samples)]

# create a new figure to display a histogram of results
plt.figure()
# plot a histogram of samples, using bins between 0 and 10
plt.hist(samples_ivt, bins=range(10), color='blue', label='IVT Samples')
# overlay a plot of the theoretical number of samples computed from PDF
plt.plot(plot_x, stats.expon.pdf(plot_x, scale=1./_lambda)*num_samples,
         '-k', label='Theoretical')
plt.xlabel('Inter-arrival Time Bin (minutes)')
plt.ylabel('Count')
plt.title('Histogram for Inter-arrival Time Samples (n={})'.format(num_samples))
plt.legend()


import numpy as np
import numpy.linalg as linalg

def compute_Z(X, centering=True, scaling=False):
  Z = X.astype(np.double)
  if (centering == True):
    # calculate the mean of each column (axis = 0)
    col_averages = np.mean(X, axis=0)
    for i in range(X.shape[1]):
      Z[:,i] = X[:,i] - col_averages[i]

  if (scaling == True):
    # calculate the standard deviation by column (axis = 0)
    col_std = np.std(X, axis=0)

    # print(X.shape[1])
    # print(X.shape[0])
    for j in range(X.shape[1]):
      for m in range(X.shape[0]):
        Z[m][j] = Z[m][j] / col_std[j]
  return Z

def compute_covariance_matrix(Z):
  cov = np.matmul(Z.transpose(),Z)
  # print(Z.transpose())
  return cov

def find_pcs(COV):
  eigenValues, eigenVectors = linalg.eig(COV)
  idx = eigenValues.argsort()[::-1]
  eigenValues = eigenValues[idx]
  eigenVectors = eigenVectors[:,idx]
  return eigenValues, eigenVectors

def project_data(Z, PCS, L, k, var):
  if ((k==0) and (var ==0)):
    print("Error inputs")
    return 0

  if (not(k==0) and not(var ==0)):
    print("Error inputs")
    return 0

  if (var == 0):
    if ((k < 0) or (k>len(PCS[0]))):
      print("Error input parameters due to k")
      return 0
    space = np.zeros((len(PCS),k), dtype=complex)
    for i in range(len(PCS)):
      for j in range(k):
        space[i][j] = PCS[i][j]
    pro_data = np.matmul(Z,space)
    return pro_data

  if (k == 0):
    if ((var < 0.0) or (var>1.0)):
      print("Error input parameters due to var")
      return 0
    S = np.sum(L)
    check = 0.0
    for m in range(len(L)):
      check += L[m]
      if (check/S >= var):
        space = np.zeros((len(PCS),m+1), dtype=complex)
        for i in range(len(PCS)):
          for j in range(m+1):
            space[i][j] = PCS[i][j]
        pro_data = np.matmul(Z,space)
        return pro_data

# if __name__ == '__main__':

  # X = np.array([[-1,-1], [-1,1], [1,-1], [1,1]])
  X = np.array([[-1,-1], [-1,1], [1,-1], [1,1], [2,6]])

  Z = compute_Z(X)
  COV = compute_covariance_matrix(Z)
  L, PCS = find_pcs(COV)
  Z_star = project_data(Z,PCS, L, 0, 1)
  print(Z_star)


from math import sqrt

import numpy as np
import pandas as pd

def squared_error(prediction, observation):
    """
    Calculates the squared error.

    Args:
        prediction - the prediction from our linear regression model
        observation - the observed data point
    Returns:
        The squared error
    """
    return (observation - prediction) ** 2

def sgd_fit(x, y, learning_rate, epochs):
    """
    Calculates the intercept and slope parameters using SGD for
    a linear regression model.

    Args:
        x - feature array
        y - response array
        learning_rate - learning rate
        epochs - the number of epochs to use in the SGD loop
    Returns:
        The intercept and slope parameters and the sum of
        squared error for the last epoch
    """

    # initialize the slope and intercept
    slope = 0.0
    intercept = 0.0

    # set the number of observations in the data
    N = float(len(y))

    # loop over the number of epochs
    for i in range(epochs):

        # calculate our current predictions
        predictions = (slope * x) + intercept

        # calculate the sum of squared errors for this epoch
        error = 0.0
        for idx, p in enumerate(predictions):
            error += squared_error(p, y[idx])
        error = error/N

        # calculate the gradients for the slope and intercept
        slope_gradient = -(2/N) * sum(x * (y - predictions))
        intercept_gradient = -(2/N) * sum(y - predictions)

        # update the slope and intercept
        slope = slope - (learning_rate * slope_gradient)
        intercept = intercept - (learning_rate * intercept_gradient)

    return intercept, slope, error

def ols_fit(x, y):
    """
    Calculates the intercept and slope parameters using OLS for
    a linear regression model.

    Args:
        x - feature array
        y - response array
    Returns:
        The intercept and slope parameters
    """

    # calculate the mean of x and y
    mean_x = np.mean(x)
    mean_y = np.mean(y)

    # Using the derived OLS formula to calculate
    # the intercept and slope.
    numerator = 0
    denominator = 0
    for i in range(len(x)):
        numerator += (x[i] - mean_x) * (y[i] - mean_y)
        denominator += (x[i] - mean_x) ** 2
    slope = numerator / denominator
    intercept= mean_y - (slope * mean_x)

    return intercept, slope

def main():

    # import the data
    data = pd.read_csv('../data/training.csv')

    # fit our model using our various implementations
    int_sgd, slope_sgd, _ = sgd_fit(data['TV'].values, data['Sales'].values, 0.1, 1000)
    int_ols, slope_ols = ols_fit(data['TV'].values, data['Sales'].values)

    # output the results
    delim = "-----------------------------------------------------------------"
    print("\nOLS\n{delim}\n intercept: {intercept}, slope: {slope}"
            .format(delim=delim, intercept=int_ols, slope=slope_ols))
    print("\nSGD\n{delim}\n intercept: {intercept}, slope: {slope}"
            .format(delim=delim, intercept=int_sgd, slope=slope_sgd))
    print("")

if __name__ == "__main__":
    main()


import numpy as np
from numpy.linalg import eig

if __name__ == "__main__":
    A1 = np.array([[4, -2],
                   [1, 1]])
    eigenvalues1, eig_en_vectors1 = eig(A1)
    print(eigenvalues1)
    print(eig_en_vectors1)
    print()

    # 关于y=x翻转
    A2 = np.array([[0, 1],
                   [1, 0]])
    eigenvalues2, eig_en_vectors2 = eig(A2)
    print(eigenvalues2)
    print(eig_en_vectors2)
    print()

    # 旋转90°
    A3 = np.array([[0, -1],
                   [1, 0]])
    eigenvalues3, eig_en_vectors3 = eig(A3)
    print(eigenvalues3)
    print(eig_en_vectors3)
    print()

    # 几何重数为1
    A4 = np.array([[3, 1],
                   [0, 3]])
    eigenvalues4, eig_en_vectors4 = eig(A4)
    print(eigenvalues4)
    print(eig_en_vectors4)
    print()


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.]