from math import factorial as fac, sqrt
from scipy.integrate import quad
import numpy as np

def integrand(t):
    """
    Integrand of the cumulative function, used to compute phi(Z).
    """
    return np.exp(-(t**2)/2)

def phi(z):
    """
    Cumulative function as described in Wikipedia, for the computation
    of the probability of a statistic being less than  Z.
    """
    return (1/(sqrt(2*np.pi))) * quad(integrand, -np.inf, z)[0]

def complementary_cumulative(z):
    """
    Returns the complementary cumulative probability for @param Z-score.
    This gives the odds for an event X > Z in the Standard Normal Table.
    """
    return 1 - phi(z)

def r_to_z(r, mu, std):
    """
    Returns the Z-score of some value 'r', normalized with the Standard
    Normal Table.
    """
    return (r - mu) / std

def z_to_r(z, mu, std):
    """
    Returns the 'r' value, corresponding to the original distribution,
    given from a Z-score in the Standard Normal Table.
    """
    return z*std + mu

def chance_exactly_k(k, n, p):
    """
    Returns the success chance of an event with probability 'p' to
    occur exactly 'k' in 'n' trials.
    """
    return (fac(n)/(fac(k)*fac(n-k))) * p**k * (1-p)**(n-k)

def chance_exactly_once(n,p):
    """
    Returns the chance of an event with probability 'p' to occur EXACLTY
    once in 'n' trials.
    """
    chance_exaclty_k(1,n,p)

def at_least_one_in_n(p, n):
    """
    Returns the success probability of an event with independent
    probability 'p' in 'n' trials.
    to
    """
    assert(p > 0 and p < 1 and n > 0)
    return 1 - (1-p)**n

def at_least_one_with_prob(event_p, success_p, threshold=20000):
    """
    Returns the number of trials needed for an independent event with
    probability 'event_p' to occur at least one time with a probability
    of 'success_p'.
    """
    assert(event_p >0 and event_p < 1 and success_p > 0 and success_p < 1)
    n = 1
    while(at_least_one_in_n(event_p, n) < success_p):
        n += 1
    if n == threshold:
        return -1
    return n

def average_chance(p):
    return 1/p

if __name__ == "__main__":

    w = 2**16
    mu = 32774
    std = 4730

    print("mean = %d"%(mu))
    print("std = %d"%(std))
    mui = mu*1.2

    print("For 20 increase, mean = %f"%(mui))
    z = r_to_z(mui,mu,std)
    print("%d to z = %f)"%(mui, z))
    p = complementary_cumulative(z)
    print("p( mu > %d ) = %f"%(mui, p))
    print("In average, event will occur after %f trials"%(average_chance(p)))
    print("The event will occur at least once, with 95 chance, after %d trials"%(at_least_one_with_prob(p, 0.95)))
    print("The event will occur at least once, with 99 chance, after %d trials"%(at_least_one_with_prob(p, 0.99)))

    print("\n ================= \n")
    mui = mu*1.435
    print("For 30 increase, mean = %f"%(mui))
    z = r_to_z(mui,mu,std)
    print("%d to z = %f)"%(mui, z))
    p = complementary_cumulative(z)
    print("p( mu > %d ) = %f"%(mui, p))
    print("In average, event will occur after %f trials"%(average_chance(p)))
    print("The event will occur at least once, with 95 chance, after %d trials"%(at_least_one_with_prob(p, 0.95)))
    print("The event will occur at least once, with 99 chance, after %d trials"%(at_least_one_with_prob(p, 0.99)))

    print("\n ================= \n")

    moment1 = 0
    p = 1/(2**16)
    for i in range(2**16):
        moment1 +=(i*p)
    moment2 = 0
    for i in range(2**16):
        moment2 +=(i**2)*p
    print(moment1, moment2, sqrt(moment2), sqrt(moment2)-moment1)

    first_central=0
    for i in range(2**16):
        first_central += (i-moment1)*p

    print(first_central)

    second_central=0
    for i in range(2**16):
        second_central += ((i-first_central)**2)*p
    print(second_central, sqrt(second_central))

    print("==============")
    m = 0
    p = 1/(2**16)
    for i in range(2**16):
        m +=(i**2 - i)
    print(p*m, sqrt(p*m))

    print("\n ================= \n")

    moment1 = 0
    p = 1/(2**16)
    for i in range(2**16):
        moment1 +=(i*p)
    moment2 = 0
    for i in range(2**16):
        moment2 +=(i**2)*p
    print(sqrt(moment2- moment1**2))

    print("\n ================= Panario\n")

    var = 0
    for i in range(2**16):
        var +=(p-2**15)**2
    v = var*p

    print(sqrt(v))


import numpy as np
from scipy.io import loadmat
import matplotlib.pyplot as plt

mat = loadmat('E:\lessons\ML wu.n.g\coursera-ml-py-master\coursera-ml-py-master\machine-learning-ex7\ex7\ex7data1.mat')
#print(mat.keys())
#print(mat)
X = mat['X']    #(50, 2)
#print(X.shape)
plt.figure(figsize=(8,8))
#facecolor is pots' color
plt.scatter(X[:,0],X[:,1], facecolors = 'none', edgecolors = 'r')
#plt.show()

#====================implementing pca

def featureNormalize(X):
    means = X.mean(axis = 0)
    #std ddof = 1 标准差是除以n - 1（无偏估计）
    stds = X.std(axis = 0, ddof = 1)
    X_norm = (X - means) / stds
    return X_norm, means, stds

def pca(X):
    sigma = (X.T @ X) / len(X)
    U, S, V = np.linalg.svd(sigma)
    return U, S, V

X_norm, means, stds = featureNormalize(X)
U, S, V = pca(X_norm)
print(X_norm.shape)
#change to 1d , get the 1st column
print(U.shape,S.shape,V.shape)
print(U[:,0])
plt.scatter(X[:,0], X[:,1], facecolors='none', edgecolors='b')

plt.plot([means[0], means[0] + 5*S[0]*U[0,0]],
         [means[1], means[1] + 5*S[0]*U[0,1]],
        c='r', linewidth=3, label='First Principal Component')
plt.plot([means[0], means[0] + 1.5*S[1]*U[1,0]],
         [means[1], means[1] + 1.5*S[1]*U[1,1]],
        c='g', linewidth=3, label='Second Principal Component')
plt.grid(True)
plt.legend()
#plt.show()

#======================Dimensionality Reduction with PCA

#======projectData
def projectData(X, U, K):
    Z = X @ U[:,:K]
    return Z

Z = projectData(X_norm, U, 1) #Z[0] 1.481
#print(Z)

#======Reconstructing an approximaion of the data(重现数据维度)

def recData(Z, U, K):
    X_rev = Z @ U[:,:K].T
    return X_rev

X_rec = recData(Z, U, 1) #rec[0] = [-1.04741,-1.04741...]
#print(X_rec)

#======Visualizing

plt.figure(figsize=(8,8))
#x,y比例相同？？
plt.axis("equal")
plt.scatter(X_norm[:,0], X_norm[:,1], s = 50, facecolors = 'none',
            edgecolors='b', label = 'Original Data')
plt.scatter(X_rec[:,0], X_rec[:,1], s=30, facecolors='none',
            edgecolors='r',label='PCA Reduced Data Points')
plt.title("reduced dimension show", fontsize = 20)
plt.xlabel("x1(Normalized)", fontsize = 14)
plt.ylabel("x2(Normalized)", fontsize = 14)
plt.grid(True)

#给对应的点连线
for x in range(X_norm.shape[0]):
    plt.plot([X_norm[x,0],X_rec[x,0]],[X_norm[x,1],X_rec[x,1]], 'k--')
plt.legend()
#plt.show()

#===========================Face Image Dataset
'''
Run PCA in face images
'''
facemat = loadmat('E:\lessons\ML wu.n.g\coursera-ml-py-master\coursera-ml-py-master\machine-learning-ex7\ex7\ex7faces.mat')
#print(facemat.keys())
X_face = facemat['X']   #(5000, 1024)
#print(X_face.shape)

#show the data
def displayData(X, row, col):
    fig, axs = plt.subplots(row, col, figsize=(10,10))
    for r in range(row):
        for c in range(col):
            axs[r][c].imshow(X[r * col + c].reshape(32, 32).T, cmap = 'Greys_r')
            axs[r][c].set_xticks([])
            axs[r][c].set_yticks([])

displayData(X_face, 10, 10)

#PCA
X_face_norm, face_means, face_stds = featureNormalize(X_face)

U_face, S_face, V_face = pca(X_face_norm) #U(1024,1024)  S(1024,)
print(U_face.shape, S_face.shape, V_face.shape)

displayData(X_face_norm, 6, 6)
#plt.show()

'U 的意义？？？？？？？？'
displayData(U_face[:,:100].T,10,10)
#plt.show()

#==================Dimension Redcution

z_face = projectData(X_face_norm, U_face, K = 36)
X_face_rec = recData(z_face, U_face, K = 36)

displayData(X_face_rec, 10, 10)
plt.show()


''' The following python code helps the user to convert Coordinates from polar system to Cartesian system, or vice-versa'''

import numpy as np

def pol2cart(r, theta):         #function for converting polar to cartesian coordinates.
	x = r*np.cos(theta)
	y = r*np.sin(theta)
	return (x,y)

def cart2pol(x, y):             #function for converting cartesian to polar coordinates.
	r = np.sqrt( x**2 + y**2 )
	theta = np.arctan2(y, x)
	return (r,theta)

choice = input("Enter 1 for polar to cartesian conversion, or 2 for cartesian to polar conversion: ")

if choice == '1' :
    r, theta = input("Enter polar coordinates r, theta (will be computed in radian) : ").split()
    print ("Cartesian Coordinates are (x,y) = ",pol2cart(float(r), float(theta)))

elif choice == '2':
    x, y = input("Enter cartesian coordinantes x,y: ").split()
    print ("Polar Coordinates are (r,theta) = ",cart2pol(float(x),float(y)))
# In case a number other than 1 or 2 is entered
else :
    print ("INVALID CHOICE, CHOICE MUST BE 1 OR 2")


import numpy as np
import matplotlib.pyplot as plt

def cart2pol(x, y):
    rho = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)
    return(rho, phi)

def pol2cart(rho, phi):
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)
    return(x, y)

################################
#initial conditions

x_0 = 10
y_0 = -50
v_int = 1/10

r_0 = [(x_0, 0)]
v_0 = [(0, v_int)]

r = r_0
v = v_0

h = x_0*v_int
p = h**2
E_0 = .5*v_int**2-1/x_0
a = -1/(2*E_0)
e = (1-p/a)**.5
P = 2*np.pi*a**(3/2)
t = P/1000

##############################################
#Exact Solution

theta_ex = np.arange(0, 2*np.pi+.01, 0.01)
r_ex = p/(1-e*np.cos((theta_ex)))

theta_comp = np.arange(0, 2*np.pi+.01, 0.1)
r_comp = p/(1-e*np.cos((theta_comp)))

#############################################################
#Graphing
fig=plt.figure(1)

#Exact Solution
ax1=fig.add_subplot(221, projection='polar')
ax1.plot(theta_ex, r_ex)
ax1.set_rmax(.5)
ax1.set_rticks([3, 6, 9, 12])  # less radial ticks
ax1.set_rlabel_position(-22.5)  # get radial labels away from plotted line
ax1.grid(True)
ax1.set_title("Kepler Solution", va='bottom')

##############################################
#Cromer Algorithm

for i in range(0,int(P*100)):
	r_mag = (r[len(r)-1][0]**2+r[len(r)-1][1]**2)**.5
	acc = np.asarray((-r[len(r)-1][0]/r_mag**3,-r[len(r)-1][1]/r_mag**3))

	v.append(tuple(map(sum, zip(v[len(v)-1],acc*t))))
	v_add = np.asarray(v[len(v)-1])
	r.append(tuple(map(sum, zip(r[len(r)-1],v_add*t))))

r = np.asarray(r)
l = []

for i in r:
	l.append(cart2pol(i[0],i[1]))

x_val = [x[0] for x in l]
y_val = [x[1] for x in l]

##############################################
#Graphing Cromer Algorithm

ax2=fig.add_subplot(222, projection='polar')
ax2.plot(y_val,x_val)
ax2.plot(theta_comp, r_comp, 'o', markerfacecolor='none', markeredgecolor='r')
ax2.set_rmax(.5)
ax2.set_rticks([3, 6, 9, 12])  # less radial ticks
ax2.set_rlabel_position(-22.5)  # get radial labels away from plotted line
ax2.grid(True)
ax2.set_title("Cromer Algorithm", va='bottom')

###################################################
#Cromer Energy

r_mag = []
v_mag = []
E_t = []
time = []
timePeriod = []
E_rat = []
for i in r:
	r_mag.append((i[0]**2+i[1]**2)**.5)
for i in v:
	v_mag.append((i[0]**2+i[1]**2)**.5)
for i in range(len(r_mag)):
	E_t.append(.5*v_mag[i]**2-1/r_mag[i])
for i in range(0,int(P*100)+1):
	x = i*t
	time.append(x)
for i in time:
	x = i/P
	timePeriod.append(x)
for i in range(len(E_t)):
	E_rat.append(E_t[i]/E_0-1)

E_rat = E_rat[450:550]
timePeriod = timePeriod[450:550]

fig2, ax5 = plt.subplots()
ax5.set_ylabel('E(t)/E_0-1')
ax5.set_xlabel('Time/Period')
ax5.set_title("Energy Ratio")
ax5.plot(timePeriod,E_rat)

##############################################
#Runge-Kutta
x_0 = 10
y_0 = -50
v_int = 1/10

r_0 = [(x_0, 0)]
v_0 = [(0, v_int)]

r = r_0
v = v_0

for i in range(0,int(P*100)):
	r_mag = (r[len(r)-1][0]**2+r[len(r)-1][1]**2)**.5
	acc = np.asarray((-r[len(r)-1][0]/r_mag**3,-r[len(r)-1][1]/r_mag**3))

	v_add = np.asarray(v[len(v)-1])
	r_add = np.asarray(r[len(r)-1])

	r.append(tuple(map(sum, zip(r[len(r)-1],v_add*t,.5*t**2*acc))))

	r_2 = r_add+.5*t*v_add
	r_mag2 = ((r_add[0]+.5*t*v_add[0])**2+(r_add[1]+.5*t*v_add[1])**2)**.5
	acc_2 = np.asarray((-r_2[0]/r_mag2**3,-r_2[1]/r_mag2**3))

	v.append(tuple(map(sum, zip(v[len(v)-1],t*acc_2))))

r = np.asarray(r)
l = []

for i in r:
	l.append(cart2pol(i[0],i[1]))

x_val = [x[0] for x in l]
y_val = [x[1] for x in l]

#Graphing

ax3=fig.add_subplot(223, projection='polar')
ax3.plot(y_val,x_val)
ax3.plot(theta_comp, r_comp, 'o', markerfacecolor='none', markeredgecolor='r')
ax3.set_rmax(.5)
ax3.set_rticks([3, 6, 9, 12])  # less radial ticks
ax3.set_rlabel_position(-22.5)  # get radial labels away from plotted line
ax3.grid(True)
ax3.set_title("Runge-Kutta", va='bottom')

###################################################
#Runge-Kutta Energy

r_mag = []
v_mag = []
E_t = []
time = []
timePeriod = []
E_rat = []
for i in r:
	r_mag.append((i[0]**2+i[1]**2)**.5)
for i in v:
	v_mag.append((i[0]**2+i[1]**2)**.5)
for i in range(len(r_mag)):
	E_t.append(.5*v_mag[i]**2-1/r_mag[i])
for i in range(0,int(P*100)+1):
	x = i*t
	time.append(x)
for i in time:
	x = i/P
	timePeriod.append(x)
for i in range(len(E_t)):
	E_rat.append(E_t[i]/E_0-1)

E_rat = E_rat[450:550]
timePeriod = timePeriod[450:550]
# fig2, ax5 = plt.subplots()
ax5.plot(timePeriod,E_rat)

##############################################
#Velocity Verlet
x_0 = 10
y_0 = -50
v_int = 1/10

r_0 = [(x_0, 0)]
v_0 = [(0, v_int)]

r = r_0
v = v_0

for i in range(0,int(P*100)):
	r_mag = (r[len(r)-1][0]**2+r[len(r)-1][1]**2)**.5
	acc = np.asarray((-r[len(r)-1][0]/r_mag**3,-r[len(r)-1][1]/r_mag**3))
	v_add = np.asarray(v[len(v)-1])
	r_add = np.asarray(r[len(r)-1])

	r.append(tuple(map(sum, zip(r[len(r)-1],v_add*t,.5*t**2*acc))))

	r_mag2 = (r[len(r)-1][0]**2+r[len(r)-1][1]**2)**.5
	acc_2 = np.asarray((-r[len(r)-1][0]/r_mag2**3,-r[len(r)-1][1]/r_mag2**3))

	v.append(tuple(map(sum, zip(v[len(v)-1],.5*t*acc,.5*t*acc_2))))

r = np.asarray(r)
l = []

for i in r:
	l.append(cart2pol(i[0],i[1]))

x_val = []
y_val = []

x_val = [x[0] for x in l]
y_val = [x[1] for x in l]

#Graphing
ax4=fig.add_subplot(224, projection='polar')
ax4.plot(y_val,x_val)
ax4.plot(theta_comp, r_comp, 'o', markerfacecolor='none', markeredgecolor='r')
ax4.set_rmax(.5)
ax4.set_rticks([3, 6, 9, 12])  # less radial ticks
ax4.set_rlabel_position(-22.5)  # get radial labels away from plotted line
ax4.grid(True)
ax4.set_title("Velocity Verlet", va='bottom')

###################################################
#Velocity Verlet Energy

r_mag = []
v_mag = []
E_t = []
time = []
timePeriod = []
E_rat = []
for i in r:
	r_mag.append((i[0]**2+i[1]**2)**.5)
for i in v:
	v_mag.append((i[0]**2+i[1]**2)**.5)
for i in range(len(r_mag)):
	E_t.append(.5*v_mag[i]**2-1/r_mag[i])
for i in range(0,int(P*100)+1):
	x = i*t
	time.append(x)
for i in time:
	x = i/P
	timePeriod.append(x)
for i in range(len(E_t)):
	E_rat.append(E_t[i]/E_0-1)

E_rat = E_rat[450:550]
timePeriod = timePeriod[450:550]
# fig2, ax5 = plt.subplots()
ax5.plot(timePeriod,E_rat)

plt.legend(('Cromer', 'Runge-Kutta', 'Velocity Verlet'), loc='upper right')
plt.show()
#################################################################


from numpy import *
#from pylab import *

def normgauss(x, sigma, x0=0):
    return 1.0/sqrt(2*pi*sigma**2) * exp(-(x-x0)**2 / (2.0*sigma**2))

def FWHM_to_sigma(FWHM):
    # from FWHM to sigma parameter:
    return FWHM / sqrt(8.0 * log(2.0))

def rotxz(xp, zp, theta_deg):
    theta = theta_deg * pi / 180.0
    x = xp*cos(theta) - zp*sin(theta)
    z = xp*sin(theta) + zp*cos(theta)
    return x, z

def gengauss(sigma, x0=0, threshold=0.001, npts = 200):
    """ when gaussian drops below 0.001 of max amplitude, cut off x """
    cutoff = sqrt( -2.0 * sigma**2 * log( threshold ) )
    x = linspace( x0 - cutoff, x0 + cutoff, npts )
    y = normgauss( x, sigma, x0 )
    return x, y

def plot_intersection():
    pass

def gengaussFWHM(FWHM, x0=0, threshold=0.001, npts = 200):
    sigma = FWHM_to_sigma(FWHM)
    return gengauss(sigma, x0, threshold, npts)

#y_offset + amplitude * exp( - ( x - center )**2 * 4 * log(2) / FWHM**2


from numpy import array,zeros,append
import numpy as np

def gausselim(A,b):
   #AUGMENTED MATRIX
    augA = np.c_[A,b]
    p1 = augA [1,:] - augA [0,:] * (augA [1,0]/augA [0,0])
    p2 = augA [2,:] - augA [0,:] * (augA [2,0]/augA [0,0])
    temp = append(augA[0,:],p1)
    augA1 = append(temp,p2).reshape(3,4)
    p3 = augA1[2,:] - augA1[1,:] * (augA1[2,1]/augA1[1,1])
    augA2 = augA1.copy()
    augA2[2] = p3
    A = augA2[:,0:3]
    b = augA2[:,-1]
    print("A = ",A)
    print("b = ",b)
    # BACK SUBSTITUTION
    x = zeros((3))
    x[2] = b[2]/A[2,2]
    x[1] = (b[1] - A[1,2] * x[2])/A[1,1]
    x[0] = (b[0] - A[0,2] * x[2] - A[0,1] * x[1])/A[0,0]
    return x

A = array([[5,-1,1],[-1,3,-1],[1,-1,4]])
b = array([6,2,11])
xg = gausselim(A,b)

print('x1 = %8.4f' % xg[0])
print('x2 = %8.4f' % xg[1])
print('x3 = %8.4f' % xg[2])


# Import libraries
from numpy import array, cos, sin, vstack
from numpy.linalg import norm
from lib.convert import rad2deg, deg2rad
from lib.rotation import euler2dcm, dcm2euler, euler2quat, quat2euler, dcm2axis_ang, axis_ang2dcm, dcm2quat, \
    quat2dcm, qinv, qmult

# euler angles (example)
roll = deg2rad(10.0)
pitch = deg2rad(20.0)
yaw = deg2rad(30.0)

# euler to dcm
C = euler2dcm(roll, pitch, yaw)
print("euler2dcm:")
print(C, norm(C, 2))

# dcm to euler
roll, pitch, yaw = dcm2euler(C)
print("dcm2euler:")
print(rad2deg(roll), rad2deg(pitch), rad2deg(yaw))

# euler to quaternion
q = euler2quat(roll, pitch, yaw)
print("euler2quat:")
print(q, norm(q, 2))

# quaternion to euler
roll, pitch, yaw = quat2euler(q)
print("quat2euler:")
print(rad2deg(roll), rad2deg(pitch), rad2deg(yaw))

# dcm to axis_angle
theta, r = dcm2axis_ang(C)
print("DCM2axis_ang:")
print(theta, r.T)

# axis_angle to dcm
C = axis_ang2dcm(theta, r)
print("axis_ang2DCM:")
print(C, norm(C, 2))

# DCM to quaternion
q = dcm2quat(C)
print("dcm2quat:")
print(q, norm(q, 2))

# quaternion to DCM
C = quat2dcm(q)
print("quat2dcm:")
print(C, norm(C, 2))

# Rotation by quaternions (full example)

# Rotation angle
theta = deg2rad(30)

# Rotation axis (normalized)
r = array([[0],
           [0],
           [1]])

# Point
p1 = array([[1],
            [0],
            [0]])

# Point (quaternion representation)
p1q = vstack([0,
              p1])
print("Point p1:")
print(p1q, norm(p1q, 2))

# Define rotation quaternion
q = vstack([cos(theta/2),
           sin(theta/2)*r])
print("Rotation quaternion:")
print(q, norm(q, 2))

# Rotate p1 to p2
p2q = qmult(qmult(q, p1q), qinv(q))
print("Point p2:")
print(p2q, norm(p2q, 2))

# Corresponding dcm rotation

# Quaternion to dcm
C = quat2dcm(q)
print("quat2dcm:")
print(C, norm(C, 2))

# Rotate p1 to p2
p2 = C@p1
print("Point p2:")
print(p2, norm(p2, 2))


import numpy as np
from scipy import linalg as lg
from numpy import pi,sin,cos,tan,sqrt, e
import matplotlib.pyplot as plt
from scipy.integrate import odeint

# units in MHz
TRANSITON_FREQUENCY = 177
BIG_OMEGA = 2

DELTA = 3

# BIG OMEGA IS NOT RABI FREQUENCY

## density matrix (much faster)
def equation_system(r,t,Omega,w0,w):
    rho_00, rho_01_r, rho_01_i = r

    rhodot_00 = -2*rho_01_i * Omega*cos(w*t)

    rhodot_01_r = -w0*rho_01_i

    rhodot_01_i = +w0*rho_01_r + (2*rho_00 - 1) * Omega*cos(w*t)

    return rhodot_00, rhodot_01_r, rhodot_01_i

# solution
t = np.linspace(0,1/BIG_OMEGA,2000) # time units in terms of microseconds

r_init = np.array([1,0,0]) # initial starting state of the DEs

w,w0 = 2*pi*TRANSITON_FREQUENCY,2*pi*177 # forced oscillation frequency vs energy level frequency
Omega = 2*pi*BIG_OMEGA

solution = odeint(equation_system, r_init, t, args=(Omega,w,w0))
fig, axes = plt.subplots(nrows=1)
axes.plot(t,1-solution[:,0],lw=2,label=r"$\rho_{11}$",color='C0')

w0 = 2*pi*(177+DELTA) # forced oscillation frequency vs energy level frequency
solution = odeint(equation_system, r_init, t, args=(Omega,w,w0))
axes.plot(t,1-solution[:,0],lw=2,label=f"+{DELTA}",color='C1')

w0 = 2*pi*(177-DELTA) # forced oscillation frequency vs energy level frequency
solution = odeint(equation_system, r_init, t, args=(Omega,w,w0))
axes.plot(t,1-solution[:,0],lw=2,label=f"-{DELTA}",color='C2')

# predict analytic solution
amp_factor = 1/(1+(DELTA/BIG_OMEGA)**2)
time_factor = sqrt(1+(DELTA/BIG_OMEGA)**2) # YES I DID IT

prediction = amp_factor*(sin(2*pi*time_factor*BIG_OMEGA/2*t))**2
axes.plot(t,prediction, "--",lw=2,label="prediction",color='C3')

axes.margins(0,0.1)
plt.legend()
plt.show()

fig, axes = plt.subplots(nrows=1)
DELTAS = np.linspace(-20, 20, 60)
# predict analytic solution

prediction = []

T = pi/BIG_OMEGA

for DELTA in DELTAS:
    amp_factor = 1/(1+(DELTA/BIG_OMEGA)**2)
    time_factor = sqrt(1+(DELTA/BIG_OMEGA)**2) # YES I DID IT
    prediction.append(amp_factor*(sin(2*pi*time_factor*BIG_OMEGA/2*T))**2)
axes.plot(DELTAS,prediction,lw=2)

axes.margins(0,0.1)
plt.show()


#!/usr/bin/env python
#
# Created: 20170227

# Note: In a python program or function, if the first statement
# encountered is a string literal like this, it will be taken as the
# documentation (__doc__) of the program/function.

"""
matmul1.py

Demo program to read two matrices, performs multiplication, and prints
the output to standard output.

This program has some pedagogical value in demonstrating the effect of
hand-crafting loops in python as opposed to using
matrix-multiplication kernel (canned routine).

"""

import sys
import numpy
from np_helper import loadmatrix1, printmatrix1

def matmul_manual(A, B):
    """Performs manual matrix multiplication with python.
    Avoid doing this in python because of low performance!
    """
    from numpy import asarray, sum
    A = asarray(A)
    B = asarray(B)
    M, K = A.shape
    N = B.shape[1]
    assert A.shape[1] == B.shape[0]
    # Caveat: works only if A & B dtypes are the same:
    C = numpy.zeros((M,N), dtype=A.dtype)
    for i in range(M):
        for j in range(N):
            Cij = 0
            for k in range(K):
                Cij += A[i,k] * B[k,j]
            C[i,j] = Cij
    return C

def matmul_vecdot(A, B):
    """Performs semi-manual matrix multiplication with python,
    using dot product in the innnermost loop.
    """
    from numpy import asarray, sum
    A = asarray(A)
    B = asarray(B)
    M, K = A.shape
    N = B.shape[1]
    assert A.shape[1] == B.shape[0]
    # Caveat: works only if A & B dtypes are the same:
    C = numpy.zeros((M,N), dtype=A.dtype)
    for i in range(M):
        for j in range(N):
            C[i,j] = sum(A[i,:] * B[:,j])
    return C

def matmul_matdot(A, B):
    """Performs matrix multiplication with python using numpy.dot.

    Note that numpy.dot is equivalent to matrix-matrix multiplication
    for 2-D arrays; we explot this fact here!
    """
    from numpy import asarray, sum
    A = asarray(A)
    B = asarray(B)
    M, K = A.shape
    N = B.shape[1]
    assert A.shape[1] == B.shape[0]
    C = numpy.dot(A, B)
    return C

def matmul1(argv):
    if len(argv) < 3:
        print >> sys.stderr, "Needs an input file name on arg1"
        sys.exit(1)

    matfile1 = argv[1]
    matfile2 = argv[2]
    A = loadmatrix1(matfile1)
    B = loadmatrix1(matfile2)
    C = matmul_matdot(A,B)
    printmatrix1(C, float_fmt=" %12.6f")
    #printmatrix1(C)

if __name__ == "__main__":
    matmul1(sys.argv)


import numpy as np
import pytest
from numpy.testing import assert_almost_equal, assert_array_almost_equal

from reservoirpy.activationsfunc import identity, relu, sigmoid, softmax, softplus, tanh

@pytest.mark.parametrize(
    "value, expected",
    [
        ([1, 2, 3], np.exp([1, 2, 3]) / np.sum(np.exp([1, 2, 3]))),
        (1, np.exp(1) / np.sum(np.exp(1))),
        ([0, 0], [0.5, 0.5]),
    ],
)
def test_softmax(value, expected):
    result = softmax(value)

    assert_almost_equal(np.sum(result), 1.0)
    assert_array_almost_equal(result, expected)

@pytest.mark.parametrize(
    "value, expected",
    [
        (0, np.log(1 + np.exp(0))),
        ([0, 1, 2], np.log(1 + np.exp([0, 1, 2]))),
        ([-2, -1], np.log(1 + np.exp([-2, -1]))),
    ],
)
def test_softplus(value, expected):
    result = softplus(value)

    assert np.any(result > 0.0)
    assert_array_almost_equal(result, expected)

@pytest.mark.parametrize(
    "value",
    [
        ([1, 2, 3]),
        ([1]),
        (0),
        ([0.213565165, 0.1, 1.064598495615132]),
    ],
)
def test_identity(value):
    result = identity(value)
    val = np.asanyarray(value)

    assert np.any(result == val)

@pytest.mark.parametrize(
    "value, expected", [([1, 2, 3], np.tanh([1, 2, 3])), (0, np.tanh(0))]
)
def test_tanh(value, expected):
    result = tanh(value)

    assert_array_almost_equal(result, expected)

@pytest.mark.parametrize(
    "value, expected",
    [
        ([1, 2, 3], 1 / (1 + np.exp(-np.array([1, 2, 3])))),
        (0, 1 / (1 + np.exp(0))),
        ([-1000, -2], [0.0, 1.35e-1]),
    ],
)
def test_sigmoid(value, expected):
    result = sigmoid(value)
    assert_array_almost_equal(result, expected, decimal=1)

@pytest.mark.parametrize(
    "value, expected",
    [
        ([1, 2, 3], np.array([1, 2, 3])),
        ([-1, -10, 5], np.array([0, 0, 5])),
        (0, np.array(0)),
        ([[1, 2, 3], [-1, 2, 3]], np.array([[1, 2, 3], [0, 2, 3]])),
    ],
)
def test_relu(value, expected):
    result = relu(value)
    assert_array_almost_equal(result, expected)


import numpy as np

def opt_mult_recurse(a,p,q):
    if q-p<2:
        return 0
    minn = np.inf
    for i in range(p+1,q):
        minn = min(minn,\
            opt_mult_recurse(a,p,i)+opt_mult_recurse(a,i,q)\
                +a[p]*a[i]*a[q])
    return minn

def opt_mult_top_dwn(a,p,q,m):
    if q-p<2:
        return 0
    if m[p,q] < np.inf:
        return m[p,q]
    minn = np.inf
    for i in range(p+1,q):
        minn = min(minn,\
                opt_mult_top_dwn(a,p,i,m)+opt_mult_top_dwn(a,i,q,m)\
                    +a[p]*a[i]*a[q])
    m[p,q] = minn
    return m[p,q]

def opt_mult_top_dwn_v2(a,p,q,m,s):
    if q-p<2:
        return 0, s
    if m[p,q] < np.inf:
        return m[p,q], s
    minn = np.inf; min_ix=0
    for i in range(p+1,q):
        minn1 = min(minn,\
                opt_mult_top_dwn_v2(a,p,i,m,s)[0]+\
                opt_mult_top_dwn_v2(a,i,q,m,s)[0]\
                    +a[p]*a[i]*a[q])
        if minn1<minn:
            minn = minn1; min_ix = i
    m[p,q] = minn
    s[p,q] = int(min_ix)
    return m[p,q], s

def opt_mult_top_dwn_strt(a):
    n = len(a)
    m=np.ones((n,n+1))*np.inf
    return opt_mult_top_dwn(a,0,len(a)-1,m)

def opt_mult_top_dwn_strt_v2(a):
    n = len(a)
    m=np.ones((n,n+1))*np.inf
    s=np.zeros((n,n+1)).astype(int)
    return opt_mult_top_dwn_v2(a,0,len(a)-1,m,s)

def print_opt_parenth(s,p,q):
    if q-p<2:
        print("A_"+str(q)+' ',end='')
    else:
        print("(",end='')
        print_opt_parenth(s,p,s[p,q])
        print_opt_parenth(s,s[p,q],q)
        print(")",end='')

# https://wbd.ms/share/v2/aHR0cHM6Ly93aGl0ZWJvYXJkLm1pY3Jvc29mdC5jb20vYXBpL3YxLjAvd2hpdGVib2FyZHMvcmVkZWVtLzA2YjJmZTQ0YWJkYTQ1ZjNiM2NkMTI0OWZmMjExYmY3X0JCQTcxNzYyLTEyRTAtNDJFMS1CMzI0LTVCMTMxRjQyNEUzRA==
def opt_mult_bottom_up(a):
    # The number of matrices
    n = len(a)-1
    m = np.zeros((n,n+1))
    for d_i in range(2,n+1):
        for p in range(n):
            q=p+d_i
            if q>n:
                break
            minn = np.inf
            for i in range(p+1,q):
                minn=min(minn,m[p,i]+m[i,q]+a[p]*a[i]*a[q])
            m[p,q] = minn
    return int(m[0,n])

if __name__=="__main__":
    a = np.arange(4)+2
    res = opt_mult_bottom_up(a)
    print(res)
    res = opt_mult_recurse(a,0,len(a)-1)
    print(res)
    res = opt_mult_top_dwn_strt(a)
    print("Top down version: " + str(res))
    a = [30,35,15,5,10,20,25]
    res=opt_mult_bottom_up(a)
    print(res)
    res = opt_mult_top_dwn_strt(a)
    print(res)
    res, s = opt_mult_top_dwn_strt_v2(a)
    print(s)
    print_opt_parenth(s,0,len(a)-1)


'''
Original credits: http://www.kostasalexis.com/lqr-control.html
Modifications by Avik De
'''

from __future__ import division, print_function

import autograd.numpy as np
import scipy.linalg

def lqr(A,B,Q,R, eigs=False):
    """Solve the continuous time lqr controller.

    dx/dt = A x + B u

    cost = integral x.T*Q*x + u.T*R*u
    """
    #ref Bertsekas, p.151

    #first, try to solve the ricatti equation
    X = scipy.linalg.solve_continuous_are(A, B, Q, R)

    #compute the LQR gain
    K = np.linalg.inv(R) @ (B.T @ X)

    if eigs:
        eigVals, eigVecs = np.linalg.eig(A - B @ K)
        return K, X, eigVals
    else:
        return K, X

def dlqr(A,B,Q,R, eigs=False):
    """Solve the discrete time lqr controller.

    x[k+1] = A x[k] + B u[k]

    cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k]
    """
    #ref Bertsekas, p.151

    #first, try to solve the ricatti equation
    X = scipy.linalg.solve_discrete_are(A, B, Q, R)

    #compute the LQR gain
    K = np.linalg.inv(B.T @ X @ B + R) @ (B.T @ X @ A)

    if eigs:
        eigVals, eigVecs = np.linalg.eig(A - B @ K)
        return K, X, eigVals
    else:
        return K, X


import numpy as np
import matplotlib.pyplot as plt

class LinearRegression:

    def __init__(self,csv_file):
        data = np.loadtxt(csv_file , delimiter = ',')
        X = data[: , :-1]
        self.y = data[: , -1]
        self.X = np.insert(X,0,values = np.ones(X.shape[0]) , axis =1)
        self.mu = np.mean(self.X[:,1:] , axis = 0)
        self.sigma = np.std(self.X[:,1:] , axis = 0)

    def head(self,n):
        print("Feature Data Is")
        print(self.X[:n,:])
        print("Target Data Is")
        print(np.array(self.y[:n]))

    def normalise(self):
        X_norm = (self.X[:,1:]-self.mu)/self.sigma
        X_norm = np.insert(X_norm,0,values = np.ones(self.X.shape[0]) , axis =1)
        return X_norm

    def computeCost(self,theta):
        """m represents number of training examples
           X is feature set with n features and is matrix of shape m*(n+1)
           theta is parameter set of shape (n+1)*1
           y is target data set of shape m*1
           X*theta represents our predicted value
           (X*theta-y)^2 is the squared error value
           J is sum of all the squared errors over 2*m
        """
        m = self.y.shape[0]
        HTheta_X = np.dot(self.X,theta)
        Error = HTheta_X-self.y
        J = (np.sum(Error**2))/(2*m)
        return J

    def gradientDescent(self,theta,alpha,iterations):
        theta = theta.copy()
        m = self.y.shape[0]
        J_values = []

        for num in range(iterations):
            for j in range(theta.size):
                Error = np.dot(self.X,theta)-self.y
                Derivative = np.dot(Error, self.X[:,j])
                theta[j] = theta[j] - (np.sum(Derivative))*(alpha/m)

            J_values.append(self.computeCost(theta))

        return theta,J_values

    def predict_value(self,values,theta , norm=False):
        if(norm == True):
            values = (values-self.mu)/self.sigma
        values = np.insert(values , 0 , np.ones(1) , axis = 0)
        predict = np.dot(values,theta)
        return predict

if __name__ == '__main__':

    LR = LinearRegression('ex1data2.txt')
    LR.X = LR.normalise()
    theta, J_history = LR.gradientDescent(np.zeros(LR.X.shape[1]), 0.15, 1000)
    print(LR.predict_value([1650,3],theta , norm = True)) #Prediction for 1650 sq. feet and 3 bedrooms

    '''For ex1data1 file , no need for normalisation'''
    LR2 = LinearRegression('ex1data1.txt')
    theta , J_history = LR2.gradientDescent(np.zeros(2) , 0.01 ,1500)
    print(LR2.predict_value([7] , theta))


import numpy as np
import pymc3 as pm
import matplotlib.pyplot as plt
from scipy.stats import binom
from scipy.stats import mode
import seaborn as sns
import collections

birth1 = np.array(
    [1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1,
     0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0,
     1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1])
birth2 = np.array([0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0,
                   1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                   1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1,
                   0, 0, 0, 1, 1, 1, 0, 0, 0, 0])

boys = np.sum(birth1) + np.sum(birth2)

g1 = collections.Counter(birth1)
g2 = collections.Counter(birth2)

girls = g1[0]+g2[0]
print('overall boys:' + str(boys))
print('overall girls:' + str(girls))

size = len(birth1)+len(birth2)

priors = np.random.uniform(0.,1.,size)
p_grid = np.linspace(0.,1.,size)
likehood = binom.pmf(boys,size, p=p_grid)
posterior = likehood*priors
posterior = posterior/posterior.sum()
plt.plot(p_grid,posterior)
plt.show()

print('maximum posteriori %f at prob = %f '%(max(posterior),p_grid[posterior == max(posterior)]))

# likehood = binom.pmf(np.sum(birth2),len(birth2), p=p_grid)
# posterior = likehood*posterior
# posterior = posterior/posterior.sum()
#
# plt.plot(p_grid,posterior)
# plt.show()
sample_size=int(10000)
samples = np.random.choice(a=p_grid,p=posterior,size=sample_size)
sns.kdeplot(samples)
plt.show()

print('50% hpd: '+str(pm.hpd(samples,alpha=0.5)))
print('89% hpd: '+str(pm.hpd(samples,alpha=0.89)))
print('97% hpd: '+str(pm.hpd(samples,alpha=0.97)))

#model fits well, max is around 111/200 as is the observation
dummy_w = binom.rvs(n=200,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(200)]

print('boys = %f from number of births %f '%(boys,size))
plt.plot(means)
plt.show()

dummy_w = binom.rvs(n=100,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(100)]

print('boys = %f from number of births %f '%(np.sum(birth1),len(birth1)))
plt.plot(means)
plt.show()

#simulating boys(second born) folowing girls born first
dummy_w = binom.rvs(n=g1[0],p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(200)]

print('boys = %f from number of births %f '%(boys,size))
plt.plot(means)
plt.show()

#simulating 51 boys from 1st birth
size = len(birth1)
priors = np.random.uniform(0.,1.,size)
p_grid = np.linspace(0.,1.,size)
likehood = binom.pmf(np.sum(birth1),size, p=p_grid)
posterior = likehood*priors
posterior = posterior/posterior.sum()
plt.plot(p_grid,posterior)
plt.show()

sample_size=int(10000)
samples = np.random.choice(a=p_grid,p=posterior,size=sample_size)
sns.kdeplot(samples)
plt.show()

print('50% hpd: '+str(pm.hpd(samples,alpha=0.5)))
print('89% hpd: '+str(pm.hpd(samples,alpha=0.89)))
print('97% hpd: '+str(pm.hpd(samples,alpha=0.97)))

dummy_w = binom.rvs(n=100,p=samples,size=sample_size)
_,(ax0,ax1) = plt.subplots(1,2)
ax0.plot(posterior)
ax1.hist(dummy_w,bins=50)
plt.show()
means = [(dummy_w == i).mean() for i in range(100)]

print('boys = %f from number of births %f '%(np.sum(birth1),len(birth1)))
plt.plot(means)
plt.show()


import numpy as np
from numpy import linalg as LA
import matplotlib.pyplot as plt
from pdb import set_trace
from scipy.linalg import eig

def get_laplacian(E, n_vertices, directed=False):
    L = np.zeros((n_vertices, n_vertices))
    for e in E:
        if directed:
            L[e[1]][e[1]] += 1
            L[e[1]][e[0]] = -1
        else:
            L[e[1]][e[1]] += 1
            L[e[0]][e[0]] += 1
            L[e[1]][e[0]] = -1
            L[e[0]][e[1]] = -1
    return L

def get_cycle_graph(N):
    n_vertices = N
    E = []

    for i in range(N-1):
        E.append([i, i+1])
    E.append([N-1, 0])

    return E, n_vertices

def simulate_consensus(x_0, T, L, dt=0.001):
    x_current = x_0
    t = 0
    ts = [t]
    xs = x_0
    converged = False
    t_converged = np.inf
    while t <= T:
        x_next = x_current - np.matmul(L, x_current) * dt
        xs = np.hstack((xs, x_next))
        x_current = x_next
        t += dt
        ts.append(t)

        if not converged and np.amax(x_current) - np.amin(x_current) <= 0.01:
            converged = True
            t_converged = t

    return xs, np.array(ts), t_converged

def get_complete_graph(N):
    n_vertices = N
    E = []
    for i in range(N):
        for j in range(N-i-1):
            E.append([i, j+i+1])

    return E, n_vertices

def main():
    # Exercise 1
    E = [[0, 1], [1, 2], [2, 0]]
    n_vertices = 3

    laplacian = get_laplacian(E, n_vertices, False)
    print(laplacian)
    laplacian = get_laplacian(E, n_vertices, True)
    print(laplacian)

    num = [5, 15, 199]

    for n in num:
        E, n_vertices = get_cycle_graph(n)
        laplacian = get_laplacian(E, n_vertices, False)
        eigvals = LA.eigvals(laplacian)
        eigvals = np.sort(eigvals)
        print("C_{}: {}, {}, {}, {}".format(n, eigvals[0], eigvals[1], eigvals[-2], eigvals[-1]))

    # Exercise 2
    x_0 = np.transpose(np.array([[10, 20, 12, 5, 30, 12, 15, 16, 25]]))
    n_vertices = 9
    E = [[0, 2], [1, 2], [2, 3], [2, 4], [2, 6], [4, 5], [4, 6], [4, 7], [5, 6], [6, 7], [6, 8]]
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    # 2.b
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Original Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/original_graph_sim.png')

    # 2.c
    E = [[0, 2], [1, 2], [2, 3], [4, 5], [4, 6], [4, 7], [5, 6], [6, 7], [6, 8]]
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Disconnected Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/disconnected_graph_sim.png')

    # 2.d
    E, n_vertices = get_complete_graph(9)
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Complete Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/complete_graph_sim.png')

    # 2.e
    E, n_vertices = get_cycle_graph(9)
    L = get_laplacian(E, n_vertices, False)
    set_trace()
    xs, ts, t_converge = simulate_consensus(x_0, 20, L)

    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Cycle Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/cycle_graph_sim.png')

    # Exercise 3
    # 3.c
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[1, 0], [1, 2], [2, 4], [3, 2], [4, 1], [4, 3]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 10, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Original Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/unchanged_graph_sim.png')

    # 3.d
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[0, 4], [1, 0], [2, 4], [3, 2], [4, 1], [4, 3]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 10, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Converge to Average Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/average_graph_sim.png')

    # 3.e
    x_0 = np.transpose(np.array([[10, 5, 1, -5, -10]]))
    n_vertices = 5
    E = [[0, 4], [1, 0], [2, 4], [3, 2], [4, 1]]
    L = get_laplacian(E, n_vertices, True)
    vals, vl, vr = eig(L, left=True)
    xs, ts, t_converge = simulate_consensus(x_0, 30, L)
    plt.figure()
    for i in range(xs.shape[0]):
        plt.plot(ts, xs[i, :])
    plt.title("Leader Graph, t_converge = {}".format(t_converge))
    plt.xlabel('t / sec')
    plt.ylabel('state')
    plt.savefig('/home/bolun/Documents/swarmrobotics/series2/leader_graph_sim.png')

if __name__ == "__main__":
    main()


# -*- coding: utf-8 -*-
"""
Created on Mon Jan  4 09:58:31 2021

@author: Larisa
"""

import numpy as np
import sympy as sym
import matplotlib.pyplot as plt
import math

### Proceduri -> Ex1
def difFinProg(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x+h) - f(x)) / h

    pt discretizare xi  ->  f'(xi) = (f(xi+1) - f(xi)) / (xi+1 - xi), unde
    xi + 1  => nodul i + 1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n - 1, 1))

    for i in range(n - 1):
        df[i] = (Y[i+1] - Y[i]) / (X[i+1] - X[i])

    return df

def difFinReg(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x) - f(x-h)) / h

    pt discretizare xi  ->  f'(xi) = (f(xi) - f(xi-1)) / (xi - xi-1), unde
    xi-1  => nodul i-1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n, 1))

    for i in range(1, n):
        df[i] = (Y[i] - Y[i - 1]) / (X[i] - X[i - 1])

    return df

def difFinCen(X, Y):
    """
    x oarecare  ->  f'(x) = (f(x+h) - f(x-h)) / (2*h)

    pt discretizare xi  ->  f'(xi) = (f(xi+1) - f(xi-1)) / (xi+1 - xi-1), unde
    xi-1  => nodul i-1 al vectorului x

    """
    n = len(X)
    df = np.zeros((n - 1, 1))

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

    return df

### Exercițiul 1

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

a = 0
b = np.pi
n = 100
x_graf = np.linspace(a, b, n)
y_graf = f(x_graf)

x = sym.symbols('x')
f_expr = sym.sin(x)
df = sym.diff(f_expr, x)
dfFunc = sym.lambdify(x, df)

plt.plot(x_graf, dfFunc(x_graf), linewidth = 2)
plt.grid(True)

dfaprox = difFinProg(x_graf, y_graf)
plt.plot(x_graf[0:n-1], dfaprox, linewidth = 2)
plt.show()

err = np.zeros((n - 1, 1))
for i in range(n - 1):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox[i])

plt.plot(x_graf[0:n-1], err, linewidth = 2)
plt.grid(True)
plt.show()

# Pasul
print(x_graf[1] - x_graf[0])

# Metoda Reg
dfaprox2 = difFinReg(x_graf, y_graf)
plt.plot(x_graf[1:n], dfaprox2[1:n], linewidth = 2)
plt.grid(True)
plt.show()

err = np.zeros((n, 1))
for i in range(1, n):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox2[i])

plt.plot(x_graf[1:n], err[1:n], linewidth = 2)
plt.grid(True)
plt.show()

# Metoda Cen
dfaprox3 = difFinCen(x_graf, y_graf)
plt.plot(x_graf[1:n-1], dfaprox3[1:n-1], linewidth = 2)
plt.grid(True)
plt.show()

err = np.zeros((n-1, 1))
for i in range(1, n-1):
    err[i] = abs(dfFunc(x_graf[i]) - dfaprox3[i])

plt.plot(x_graf[1:n-1], err[1:n-1], linewidth = 2)
plt.grid(True)
plt.show()

### Proceduri -> Ex2
def MetRichardson(phi, x, h, n):
    """
    Parameters
    ----------
    phi : formula de aproximare a derivatei cu un ordin inferior.
    x : punctul în care calculez derivata.
    h : pasul.
    n : ordinul de aproximare al derivatei (superior).

    Returns
    -------
    df = derivata aproximativă

    """
    Q = np.zeros((n, n))
    for i in range(n):
        Q[i, 0] = phi(x, h / 2 ** i)

    for i in range(1, n):
        for j in range(1, i + 1):
            Q[i, j] = Q[i, j - 1] + 1 / (2 ** j - 1) * (Q[i, j - 1] - Q[i - 1, j - 1])

    return Q[n - 1 , n - 1]

# Exercițiul 2
def phi(x, h):
    return (f(x + h) - f(x)) / h

df_richardson = np.zeros((n, 1))
N = 3 # ordinul de aproximare la care dorim să ajungem cu met Richardson

for i in range(len(x_graf)):
    # pas echidistant
    df_richardson[i] = MetRichardson(phi, x_graf[i], x_graf[1] - x_graf[0], N)

plt.plot(x_graf, df_richardson, linewidth = 2)
plt.show()

err = np.zeros((n, 1))
for i in range(n):
    err[i] = abs(dfFunc(x_graf[i]) - df_richardson[i])
plt.plot(x_graf, err, linewidth = 2)
plt.show()

# d.
# Aproximeaza a doua derivata si are ordinul de aproximare h^2
def phi2(x, h):
    return (f(x + h) - 2 * f(x) + f(x - h)) / h ** 2

N = 5 # eroarea creste din cauza rotunjirilor făcute de pc (erori interne)
d2f_richardson = np.zeros((n, 1))
for i in range(len(x_graf)):
    d2f_richardson[i] = MetRichardson(phi2, x_graf[i], (x_graf[1] - x_graf[0]), N - 1)

plt.figure(9)
plt.plot(x_graf, d2f_richardson, linewidth=3)
plt.show()

d2f = sym.diff(df, x)
d2f_func = sym.lambdify(x, d2f)

err2 = np.zeros((n, 1))
for i in range(n):
    err2[i] = np.abs(d2f_func(x_graf[i]) - d2f_richardson[i])

plt.figure(10)
plt.plot(x_graf, err2, linewidth=3)
plt.show()

Calcul Numeric (CN)/Laborator/Laborator 12/lab12.py


import numpy as np

def hms2dec(h,m,s):
  return 15*(h + (m/60) + (s/3600))

def dms2dec(d,m,s):
  if d>=0:
    return (d + (m/60) + (s/3600))
  return (d - (m/60) - (s/3600))

def angular_dist(r1, d1, r2, d2):
  r1= np.radians(r1)
  r2= np.radians(r2)
  d1= np.radians(d1)
  d2= np.radians(d2)
  a = (np.sin(np.abs(d1 - d2)/2))**2
  b = np.cos(d1)*np.cos(d2)*np.sin(np.abs(r1 - r2)/2)**2
  d = np.degrees(2*np.arcsin(np.sqrt(a + b)))
  return d

def import_bss():
  cat = np.loadtxt('bss.dat', usecols=range(1, 7))
  loaded = []
  count = 1
  for i in cat:
    loaded.append((count, hms2dec(i[0],i[1],i[2]), dms2dec(i[3],i[4],i[5])))
    count+=1
  return loaded

def import_super():
  cat = np.loadtxt('super.csv', delimiter=',', skiprows=1, usecols=[0, 1])
  loaded = []
  count = 1
  for i in cat:
    loaded.append((count, i[0], i[1]))
    count+=1
  return loaded

def find_closest(catalogue,target_ra, target_dec):
  min_distance = 9999999
  ID = 0
  for i in catalogue:
    distance = angular_dist(i[1], i[2], target_ra, target_dec)
    if distance<min_distance:
      min_distance = distance
      ID = i[0]
  return (ID,min_distance)

def crossmatch(bss_cat, super_cat, max_dist):
  matches = []
  no_matches = []

  for i in bss_cat:
      best = (0,0,max_dist+1)

      for j in super_cat:
        dist = angular_dist(i[1], i[2], j[1], j[2])
        if dist<=best[2]:
          best = (i[0],j[0],dist)

      if best[2]<=max_dist:
         matches.append(best)
      else:
         no_matches.append(i[0])

  return (matches, no_matches)

if __name__ == '__main__':
  bss_cat = import_bss()
  super_cat = import_super()

  max_dist = 40/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))

  max_dist = 5/3600
  matches, no_matches = crossmatch(bss_cat, super_cat, max_dist)
  print(matches[:3])
  print(no_matches[:3])
  print(len(no_matches))


#!/bin/env python

"""
Module for approximating sqrt
"""

def sqrt(x,debug=False):
   """
   Module implementing newton's method for approximating sqrt
   """
   from numpy import nan

   if x == 0.:
       return 0.
   elif x<0:
       print "***Error, x must be positive"
       return nan
   assert x>0, "Should not get here!"
   s = 1.
   kmax = 100
   tol = 1.e-14

   for k in range(kmax):
       if debug:
           print "Before iteration {}, s = {:20.15f}".format(k,s)
       s0 = s
       s = 0.5 * ( s + x/s )
       delta_s = s - s0
       if abs(delta_s/x) < tol:
           break
   if debug:
       print "After {} iterations, s = {:20.15f}".format(k+1,s)
   return s


#!/usr/bin/env python3

import numpy as np

import problems as pb
import plotter

#################################################
### Public Variables
#################################################

ENABLE_ERRORS_CALCULATION = True
ENABLE_SOLUTION_PLOTTING = False

ENABLE_GRAPHS_VIEW = False
SAVE_GRAPHS_IMAGE = True

#################################################
### Functions Definitions
#################################################

def matrix_decomposition(a_matrix_diag, a_matrix_subdiag):
    """
    Decompõe uma matrix A tridiagonal simétrica em três matrizes
    L, D e Lt, retornando apenas dois vetores que representam as
    matrizes L e D.
    """

    array_size = len(a_matrix_diag)

    l_matrix_array = np.zeros(array_size, dtype=float)
    d_matrix_array = np.zeros(array_size, dtype=float)

    l_matrix_array[0] = 0
    d_matrix_array[0] = a_matrix_diag[0]

    for i in range(1, array_size):
        l_matrix_array[i] = a_matrix_subdiag[i] / d_matrix_array[i - 1]

        d_matrix_array[i] = a_matrix_diag[i] - d_matrix_array[i - 1] * ((l_matrix_array[i])**2)

    return l_matrix_array, d_matrix_array

def solve_system(a_matrix_diag, a_matrix_subdiag, b_array):
    """
    Soluciona um sistema Ax = b, onde A é uma matrix tridiagonal
    simétrica.

    Para a resolução do sistema, é feita a decomposição de A para L*D*Lt.

    É feita a divisão do problema em três sistemas menores:
    L * y = b
    D * z = y
    Lt * x = z
    """

    array_size = len(a_matrix_diag)

    l_matrix_array, d_matrix_array = matrix_decomposition(a_matrix_diag, a_matrix_subdiag)

    # First system solution -> L * y = b
    y_array = np.zeros(array_size, dtype=float)

    y_array[0] = b_array[0]

    for i in range(1, array_size):
        y_array[i] = b_array[i] - l_matrix_array[i] * y_array[i - 1]

    # Second system solution -> D * z = y
    z_array = np.zeros(array_size, dtype=float)

    for i in range(0, array_size):
        z_array[i] = y_array[i] / d_matrix_array[i]

    # Third system solution -> Lt * x = z
    x_array = np.zeros(array_size, dtype=float)

    x_array[-1] = z_array[-1]

    for i in reversed(range(0, array_size - 1)):
        x_array[i] = z_array[i] - l_matrix_array[i + 1] * x_array[i + 1]

    return x_array

def run(letter, task_result_dir):
    """
    Soluciona o problema da equação de calor a partir de dois métodos implícitos,
    Euler implícito e Crank-Nicolson.
    """

    # Input parameters
    method = (input("Qual método executar: Euler implícito ou (e) ou Crank-Nicolson (c)? ")).lower()

    while method not in ("e", "c"):
        print("Método não encontrado, escolha entre 'e' e 'c'")
        method = (input("Qual método executar: Euler implícitou (e) ou Crank-Nicolson (c)? ")).lower()

    if method == "e":
        method_name = "EULER_IMP"
    else:
        method_name = "CN"

    N = int(input("Insira o valor de N: "))
    M = N

    Δx = 1 / float(N)
    Δt = Δx

    λ = Δt / (Δx**2)

    print(f"N: {N}, λ: {λ}, Δx: {Δx} e Δt: {Δt}")

    # Resuls file
    results_file_name = f"{task_result_dir}/2{letter.capitalize()}_{method_name}_{N}_ERRORS.txt"

    if letter != "c" and ENABLE_ERRORS_CALCULATION == True:
        results_file = open(results_file_name, 'w')

    # A Matrix creation
    if method == "e":
        diag_value = 1 + 2 * λ
        subdiag_value = -λ
    else:
        diag_value = 1 + λ
        subdiag_value = -λ / 2

    a_matrix_diag = np.full(N - 1, diag_value, dtype=float)
    a_matrix_subdiag = np.full(N - 1, subdiag_value, dtype=float)

    # B array creation
    b_array = np.zeros(N - 1, dtype=float)

    # Create and initializes scale array
    scale_array = np.zeros(N + 1, dtype=float)

    for i in range(0, N + 1):
        scale_array[i] = i / N

    # Create U matrix
    U = pb.create_u(scale_array, scale_array, letter)

    # Inside points calculation
    for k in range(0, M):
        # b array calculation
        for i in range(1, N):
            if method == "e":
                b_array[i - 1] = U[k][i] + Δt * pb.heat_source(scale_array[k + 1], scale_array[i], N, letter)
            else:
                b_array[i - 1] = (U[k][i] + (λ / 2) * (U[k][i - 1] - 2 * U[k][i] + U[k][i + 1])
                               + (Δt / 2) * (pb.heat_source(scale_array[k], scale_array[i], N, letter)
                               + pb.heat_source(scale_array[k + 1], scale_array[i], N, letter)))

        g1, g2 = pb.boundary_conditions(scale_array[k + 1], letter)

        if method == "e":
            b_array[0] += λ * g1
            b_array[-1] += λ * g2
        else:
            b_array[0] += (λ / 2) * g1
            b_array[-1] += (λ / 2) * g2

        solution = solve_system(a_matrix_diag, a_matrix_subdiag, b_array)

        for i in range(1, N):
            U[k + 1][i] = solution[i - 1]

    # Plotting u(t, x)
    approx_image_name = f"2{letter.capitalize()}_{method_name}_{N}_APPROX"

    plotter.u_2d_graph(U, scale_array, scale_array, 11, approx_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

    plotter.u_3d_graph(U, scale_array, scale_array, N, approx_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

    if letter != "c":
        if ENABLE_SOLUTION_PLOTTING == True:
            # Plotting the u solution
            u_sol = np.zeros((M + 1, N + 1))

            for k in range(0, M + 1):
                for i in range(0, N + 1):
                    u_sol[k][i] = pb.u_solution(scale_array[k], scale_array[i], letter)

            sol_image_name = f"2{letter.capitalize()}_{method_name}_{N}_SOL"

            plotter.u_2d_graph(u_sol, scale_array, scale_array, 11, sol_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

            plotter.u_3d_graph(u_sol, scale_array, scale_array, N, sol_image_name, ENABLE_GRAPHS_VIEW, SAVE_GRAPHS_IMAGE, task_result_dir)

        if ENABLE_ERRORS_CALCULATION == True:
            if method == "e":
                # Truncation error calculation
                max_truncation_error = 0

                for k in range(0, M):
                    for i in range(1, N):
                        first_term = (pb.u_solution(scale_array[k + 1], scale_array[i], letter)
                                   - pb.u_solution(scale_array[k], scale_array[i], letter)) / Δt
                        second_term = (pb.u_solution(scale_array[k + 1], scale_array[i - 1], letter)
                                    - 2 * pb.u_solution(scale_array[k + 1], scale_array[i], letter)
                                    + pb.u_solution(scale_array[k + 1], scale_array[i + 1], letter)) / (Δx**2)

                        current_truncation_error = abs(first_term - second_term - pb.heat_source(scale_array[k + 1], scale_array[i], N, letter))

                        if current_truncation_error > max_truncation_error:
                            max_truncation_error = current_truncation_error

                max_truncation_error_result = f"O erro máximo de truncamento é {max_truncation_error}"
                print(max_truncation_error_result)
                results_file.write(max_truncation_error_result + "\n")

            # Approximation error calculation for T = 1
            max_approx_error = 0

            for i in range(0, N):
                current_approx_error = abs(pb.u_solution(scale_array[M], scale_array[i], letter) - U[M][i])

                if current_approx_error > max_approx_error:
                    max_approx_error = current_approx_error

            max_approx_error_result = f"O erro máximo de aproximação é {max_approx_error}"
            print(max_approx_error_result)
            results_file.write(max_approx_error_result)

        # End task
        results_file.close()


import probayes as pb
import numpy as np
"""
Consider a disease with a prevalence 1\% in a given population.
Of those with the disease, 98\% manifest a particular symptom,
that is present only in 10\% of those without the disease.
What is the probability someone with symptoms has the disease?
Answer: \approx 9%
"""

# PARAMETERS
prevalence = 0.01
sym_if_dis = 0.98
sym_if_undis = 0.1

# SET UP RANDOM VARIABLES
dis = pb.RV('dis', prob=prevalence)
sym = pb.RV('sym')

# SET UP STOCHASTIC CONDITION
sym_given_dis = sym | dis
sym_given_dis.set_prob(np.array([1-sym_if_undis, 1-sym_if_dis, \
                                 sym_if_undis,   sym_if_dis]).reshape((2,2)))

# APPLY BAYES' RULE
p_dis = dis()
p_sym_given_dis = sym_given_dis()
p_dis_and_sym = p_dis * p_sym_given_dis
p_sym = p_dis_and_sym.marginal('sym')
p_dis_given_sym = p_dis_and_sym / p_sym
inference = p_dis_given_sym({'dis': True, 'sym': True})
print(inference)
assert abs(inference.prob-0.09) < 0.01, \
    "Expected around 0.09 but evaluated {}".format(inference.prob)


import numpy as np

def relu(x):
    """
    Calculate element-wise Rectified Linear Unit (ReLU)
    :param x: Input array
    :return: Rectified output
    """
    return np.maximum(x, 0)

def relu_bw(x):
    """
    Calculate element-wise ReLU derivative
    :param x: Input array
    :return: For each element in x: 1 if x>0, 0 if x<0, N length array
    """
    return np.greater(x, 0).astype(int)

def sigmoid(x):
    """
    Calculate element-wise sigmoid
    :param x: N length array
    :return: Output 1/(1+exp(-x)), N length array
    """
    return 1 / (1 + np.exp(-x))

def sigmoid_bw(x):
    """
    Calculate element-wise sigmoid derivative
    :param x: N length array
    :return: Output sigm(x)*(1-sigm(x)), N length array
    """
    return sigmoid(x) * (1 - sigmoid(x))

def softmax(x):
    """
    Compute softmax for each value in x
    :param x: N length array
    :return: Softmax for ach value between 0-1, N length array
    """
    max_val = 700
    # this custom implementation gives overflow errors when used with momentum, so we clip...
    np.clip(x, -max_val, max_val, out=x)
    return np.exp(x) / np.sum(np.exp(x), axis=0)

def softmax_bw(softmax_output, label):
    """
    Cross entropy error with softmax output, backpropagation of deritivative w.r.t the inputs to the softmax function.
    dE/dzk = softmax_output_k - label_k
    :param softmax_output: Softmax output vector of length 10.
    :param label: One-hot encoded label vector of length 10. Example: [0 0 0 0 0 0 1 0 0 0] = 7
    :return: softmax_output - label
    """
    return softmax_output - label

def cross_entropy(predictions, label):
    """
    Calculate the cross entropy.
    :param predictions: Predicted values, N length array
    :param label: One-hot encoded label vector. Example: [0 0 0 0 0 0 1 0 0 0] = 7
    :return: Cross-entropy
    """
    n = np.size(label)
    eps = 1e-12 # for numerical stability
    ce = -np.dot(label, np.log(predictions + eps)) / n
    return ce


import numpy as np

# Gradient based methods for linear systems

def compute_mse(y, tx, w):
    """
    Compute the Mean Square Error as defined in class.
    Takes as input the targeted y, the sample matrix X and the feature fector w.
    """
    e = y - tx@w
    mse = e.T.dot(e) /(2*len(e))
    return mse

def least_squares(y, tx):
    """
    Compute an esimated solution of the problem y = tx @ w, and the associated error. This method is equivalent
    to the minimization problem of finding w such that |y-tx@w||^2 is minimal. Note that this methods provides the global optimum.
    The error is the mean square error of the targeted y and the solution produced by the least square function.
    Takes as input the targeted y, and the sample matrix X.
    """
    w = np.linalg.solve (tx.T.dot(tx),tx.T.dot(y))
    mse = compute_mse(y, tx, w)
    return w, mse

def least_squares_gradient(y, tx, w):
    """
    Compute the gradient of the mean square error with respect to w, and the current error vector e.
    Takes as input the targeted y, the sample matrix w and the feature vector w.
    This function is used when solving gradient based method, such that least_squares_GD() and least_squares_SGD().
    """
    e = y - tx.dot(w)
    grad = -tx.T.dot(e) / len(e)
    return grad, e

def least_squares_GD(y, tx, initial_w=None, max_iters=50, gamma=0.1):
    """
    Compute an estimated solution of the problem y = tx @ w and the associated error using Gradient Descent.
    This method is equivalent to the minimization problem of finding w such that |y-tx@w||^2 is minimal. Note that
    this method may output a local minimum, while least_squares() provides the global minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Gradient Descent
        * the learning rate gamma
    """
    # Define parameters to store w and loss
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    ws = [initial_w] # Initial guess w0 generated randomly
    losses = []
    w = ws[0]
    for n_iter in range(max_iters):
        # compute loss, gradient
        grad, err = least_squares_gradient(y, tx, w)
        loss = compute_mse(y,tx,w)
        # gradient w by descent update
        w = w - gamma * grad
        # store w and loss
        ws.append(w)
        losses.append(loss)
        #if (n_iter % int(max_iters/5)) == 0:
            #print("Gradient Descent({bi}/{ti}): loss={l}".format(bi=n_iter, ti=max_iters,l=loss))
    return w,loss

def batch_iter(y, tx, batch_size, num_batches=1, shuffle=True):
    """
    Generate a minibatch iterator for a dataset.
    Takes as input two iterables (here the output desired values 'y' and the input data 'tx')
    Outputs an iterator which gives mini-batches of `batch_size` matching elements from `y` and `tx`.
    Data can be randomly shuffled to avoid ordering in the original data messing with the randomness of the minibatches.
    Example of use :
    for minibatch_y, minibatch_tx in batch_iter(y, tx, 32):
        <DO-SOMETHING>
    """
    data_size = len(y)

    if shuffle:
        shuffle_indices = np.random.permutation(np.arange(data_size))
        shuffled_y = y[shuffle_indices]
        shuffled_tx = tx[shuffle_indices]
    else:
        shuffled_y = y
        shuffled_tx = tx
    for batch_num in range(num_batches):
        start_index = batch_num * batch_size
        end_index = min((batch_num + 1) * batch_size, data_size)
        if start_index != end_index:
            yield shuffled_y[start_index:end_index], shuffled_tx[start_index:end_index]

def least_squares_SGD(y, tx, initial_w=None, batch_size=1, max_iters=50, gamma=0.00005):
    """
    Compute an estimated solution of the problem y = tx @ w and the associated error using Stochastic Gradient Descent.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is identifical to least_squares_GD().
        * the number of iterations for Gradient Descent
        * the learning rate gamma
    """
    # Define parameters to store w and loss
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    losses = []
    w = initial_w
    for n_iter in range(max_iters):
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            # compute a stochastic gradient and loss
            grad, _ = least_squares_gradient(y_batch, tx_batch, np.array(w))
            # update w through the stochastic gradient update
            w = w - gamma * grad
            # calculate loss
            loss = compute_mse(y, tx, w)
            # store w and loss
            losses.append(loss)

        #if n_iter % int(max_iters/5) == 0:
            #print("SGD({bi}/{ti}): loss={l}".format(bi=n_iter, ti=max_iters - 1, l=loss))
    return w,loss
# RIDGE REGRESSION

def ridge_regression(y, tx, lambda_):
    """
    Compute an esimated solution of the problem y = tx @ w , and the associated error. Note that this method
    is a variant of least_square() but with an added regularization term lambda_.
    This method is equivalent to the minimization problem of finding w such that |y-tx@w||^2 + lambda_*||w||^2 is minimal.
    The error is the mean square error of the targeted y and the solution produced by the least square function.
    Takes as input the targeted y, the sample matrix X and the regulariation term lambda_.
    """
    x_t = tx.T
    lambd = lambda_ * 2 * len(y)
    w = np.linalg.solve (np.dot(x_t, tx) + lambd * np.eye(tx.shape[1]), np.dot(x_t,y))
    loss = compute_mse(y, tx, w)

    return w,loss

#LOGISTIC REGRESSION

def sigmoid(t):
    """
    Apply the sigmoid function on t.
    """
    return np.exp(t)/(1+np.exp(t))

def calculate_loss(y, tx, w):
    """
    Compute the negative log likelihood as defined in class.
    Takes as input the targeted y, the sample matrix X and the feature fector w.
    """
    return np.sum(np.log(1+np.exp(tx@w))-y*(tx@w))

def calculate_gradient(y, tx, w):
    """
    Compute the gradient of the negative log likelihood with respect to w, and the current error vector e.
    Takes as input the targeted y, the sample matrix w and the feature vector w.
    This function is used when solving gradient based method, such that logistic_regression() and reg_logistic_regression().
    """
    return tx.T@(sigmoid(tx@w)-y)

def learning_by_gradient_descent(y, tx, w, gamma):
    """
    Compute one step of gradient descent for logistic regression.
    Takes as input the targeted y, the sample matrix w, the feature w and the learning rate gamma.
    Return the feature vector w and the error defined as the negative log likelihood.
    """
    loss = calculate_loss(y,tx,w)
    grad = calculate_gradient(y,tx,w)
    w = w-gamma*grad
    return w, loss

def logistic_regression(y, tx, initial_w=None, max_iters=100, gamma=0.009, batch_size=1):
    """
    Compute an estimated solution of the problem y = sigmoid(tx @ w) and the associated error using Gradient Descent.
    This method is equivalent to the minimization problem of finding w such that the negative log likelihood is minimal. Note that
    this method may output a local minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Stochastic Gradient Descent
        * the learning rate gamma
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is Gradient Descent.
    """
    # init parameters
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    threshold = 1e-8
    losses = []
    y = (1 + y) / 2
    # build tx
    w = initial_w

    # start the logistic regression
    for i in range(max_iters):
        # get loss and update w.
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            w, _ = learning_by_gradient_descent(y_batch, tx_batch, w, gamma)
            # converge criterion
            losses.append(calculate_loss(y,tx,w))
            if len(losses) > 1 and np.abs(losses[-1] - losses[-2]) < threshold:
                break
            #if i % int(max_iters/5) == 0:
                #print(losses[-1],i,'/{tot}'.format(tot=max_iters))

    return w,losses[-1]

# Regularized LOGISTIC REGRESSION

def learning_by_penalized_gradient_descent(y, tx, w, gamma, lambda_):
    """
    Compute one step of gradient descent for regularized logistic regression.
    Takes as input the targeted y, the sample matrix w, the feature w and the learning rate gamma.
    Return the feature vector w and the error defined as the negative log likelihood.
    """
    loss = calculate_loss(y, tx, w) + lambda_ * np.squeeze(w.T.dot(w))
    grad = calculate_gradient(y, tx, w) + 2 * lambda_ * w
    w = w-gamma*grad
    return w, loss

def reg_logistic_regression(y, tx, lambda_, initial_w=None, max_iters=100, gamma=0.009, batch_size=1):
    """
    Compute an estimated solution of the problem y = sigmoid(tx @ w) and the associated error using Gradient Descent.
    Note that this method is a variant of logistic_regression() but with an added regularization term lambda_.
    This method is equivalent to the minimization problem of finding w such that the negative log likelihood is minimal. Note that
    this method may output a local minimum.
    Takes as input:
        * the targeted y
        * the sample matrix w
        * the initial guess for w, by default set as a vector of zeros
        * the number of iterations for Stochastic Gradient Descent
        * the learning rate gamma
        * the batch_size, which is the number of samples on which the new gradient is computed. If set to 1 it corresponds
        to Stochastic Gradient Descent, to the full number of samples it is Gradient Descent.
    """
    # init parameters
    if np.all(initial_w == None): initial_w = np.zeros(tx.shape[1])
    threshold = 1e-8
    losses = []
    y = (1 + y) / 2
    # build tx
    w = initial_w

    # start the logistic regression
    for iter in range(max_iters):
        # get loss and update w.
        for y_batch, tx_batch in batch_iter(y, tx, batch_size=batch_size, num_batches=1):
            w, loss = learning_by_penalized_gradient_descent(y_batch, tx_batch, w, gamma, lambda_)
            # converge criterion
            loss = calculate_loss(y, tx, w) + lambda_ * np.squeeze(w.T.dot(w))
            losses.append(loss)
            if len(losses) > 1 and np.abs(losses[-1] - losses[-2]) < threshold:
                break
            #if iter % int(max_iters/5) == 0:
                #print(losses[-1],iter,'/{tot}'.format(tot=max_iters))

    return w,losses[-1]


from numpy import exp, cos, linspace, zeros_like
import matplotlib.pyplot as plt
import os, time, glob, math

def damped_vibrations(t, A, b, w):
    return A*exp(-b*t)*cos(w*t)

def compute_vib(A, b, w, T, resolution=500):
    """Return filename of plot of the damped_vibration function."""
    t = linspace(0, T, resolution+1)
    y = damped_vibrations(t, A, b, w)
    plt.figure()  # needed to avoid adding curves in plot
    plt.plot(t, y)
    plt.title('A=%g, b=%g, w=%g' % (A, b, w))

    # Make Matplotlib write to BytesIO file object and grab
    # return the object's string
    from io import BytesIO
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)  # rewind to beginning of file
    import base64
    figdata_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_svg = unicode(figdata_svg,'utf-8')
    return figdata_png, figdata_svg

def gamma_density(x, a, h, A):
    # http://en.wikipedia.org/wiki/Gamma_distribution
    xA = x/float(A)
    return abs(h)/(math.gamma(a)*A)*(xA)**(a*h-1)*exp(-xA**h)

def gamma_cumulative(x, a, h, A):
    # Integrate gamma_density using the Trapezoidal rule.
    # Assume x is array.
    g = gamma_density(x, a, h, A)
    r = zeros_like(x)
    for i in range(len(r)-1):
        r[i+1] = r[i] + 0.5*(g[i] + g[i+1])*(x[i+1] - x[i])
    return r

def compute_gamma(a=0.5, h=2.0, A=math.sqrt(2), resolution=500):
    """Return plot and mean/st.dev. value of the gamma density."""
    gah = math.gamma(a + 1./h)
    mean = A*gah/math.gamma(a)
    stdev = A/math.gamma(a)*math.sqrt(
        math.gamma(a + 2./h)*math.gamma(a) - gah**2)
    x = linspace(0, 7*stdev, resolution+1)
    y = gamma_density(x, a, h, A)
    plt.figure()  # needed to avoid adding curves in plot
    plt.plot(x, y)
    plt.title('a=%g, h=%g, A=%g' % (a, h, A))
    # Make Matplotlib write to BytesIO file object and grab
    # return the object's string
    from io import BytesIO
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)  # rewind to beginning of file
    import base64
    figdata_density_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_density_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_density_svg = unicode(figdata_density_svg,'utf-8')

    y = gamma_cumulative(x, a, h, A)
    plt.figure()
    plt.plot(x, y)
    plt.grid(True)
    figfile = BytesIO()
    plt.savefig(figfile, format='png')
    figfile.seek(0)
    figdata_cumulative_png = base64.b64encode(figfile.getvalue())
    figfile = BytesIO()
    plt.savefig(figfile, format='svg')
    figfile.seek(0)
    figdata_cumulative_svg = '<svg' + figfile.getvalue().split('<svg')[1]
    figdata_cumulative_svg = unicode(figdata_cumulative_svg,'utf-8')
    return figdata_density_png, figdata_cumulative_png, \
           figdata_density_svg, figdata_cumulative_svg, \
           '%.2f' % mean, '%.2f' % stdev


import numpy as np

def compute_cost(x, y, size, theta):
    """
        Compute the cost function for linear regression.

        Parameters
        ----------
        x : array_like
            Shape (m, n+1), where m is the number of examples, and n is the number of features
            including the vector of ones for the zeroth parameter.

        y : array_like
            Shape (m,), where m is the value of the function at each point.

        size : int
            Number of total training points.

        theta : array_like
            Shape (n+1, 1). Starting parameters of the regression function.

        Returns
        -------
        cost : float
            The value of the regression cost function.
    """

    cost = np.sum((1 / (2 * size)) * (((np.dot(theta.T, x)) - y) ** 2))
    return cost


# importing modules
import numpy as np
import pandas as pd

#Matrix creation
arr1 = np.array([[5, 9, 3], [3, 7, 5]])
arr2 = np.array([[3, -9], [5, 4], [7, -8]])
#printing matrix
print(arr1)
print(arr2)

# Randomly initialize an array
print(np.random.rand(3, 4))

# Perform matrix multiplication
print(np.dot(arr1, arr2))

# Perform element-wise matrix multiplication
# For storing answer there is a --> res
res = [[0 for x in range(len(arr1))] for y in range(len(arr2[0]))]

for i in range(len(arr1)):
    for j in range(len(arr2[0])):
        for k in range(len(arr2)):
            res[i][j] += arr1[i][k] * arr2[k][j]

print(res)

# Find the mean of the first matrix
mean = np.mean(arr1)
print(mean)

main_data = pd.read_csv("mtcars.csv")
del main_data['model']

# shwing the meancenter
meancenter = main_data.apply(lambda e: e - e.mean())
print(meancenter.head())


"""
Demonstrate Root Finding of Polynomials, Including Complex Roots
Learning Example for Dr. Stanier's classes
File: E8_show_numpy_roots

Author: Charles Stanier, charles-stanier@uiowa.edu
Date:  August 27, 2019
Written/Tested In: Python 3.7.3

Program Objective: Demonstrate how to solve for the roots of a polynomial
such as a parabola or a cubic equation

Modifications: none so far

"""

import numpy as np  # this is used for math functions
import pylab as pl  # this is used for plotting functions

# let's make a polynomial - starting with a line

p_array = [ 2, 1]  # [ 2 1 ] would be y = 2x + 1.  slope 2 and crosses x=0 at y=1

# and plot is from x -10 to 10
x_array = np.linspace(-10,10,100)
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a parabola with real roots

p_array = [ 2, 1, -3 ]  # [ 2, 4, -3 ] would be y = 2x^2 + 4x - 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a parabola with complex conjugate roots

p_array = [ 2, 4, 3 ]  # [ 2, 4, 3 ] would be y = 2x^2 + 4x + 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1

# let's make a polynomial - now a cubic

p_array = [ 1, 2, 4, 3 ]  # [ 1, 2, 4, 3 ] would be y = x^3 + 2x^2 + 4x + 3.

# and plot is from x -10 to 10
y_array = np.polyval(p_array,x_array)

pl.plot(x_array,y_array)
pl.plot([-10,10],[ 0,0 ]) # x=0 line
pl.plot([0,0],[-10,10]) # y=0 line
pl.xlim([-10,10])
pl.ylim([-10,10])
pl.show()

r = np.roots(p_array)
# print out the roots
ic=1 # counter
for rval in r:
    print('Root ',ic,'x= ',rval)
    ic+=1


import numpy as np
from scipy.linalg import solve

def gauss_elim(A, b):
    # don't destroy the old arrays
    A = A.copy()
    b = b.copy()
    # there are n equations and variables
    n = b.size
    eq_num = n
    var_num = n
    # the equations are the rows and each column is a variable
    # eliminate all variables i in 0..n-1 in the equations i+1..n-1
    for var_index_to_elim in range(0, var_num):
        # find the pivot (max abs) in the row below (and including the main diagonal
        avl = A[var_index_to_elim:, var_index_to_elim]
        best_index = np.argmax(np.abs(avl))
        best_index_in_A = var_index_to_elim + best_index
        # if no pivot != 0 was found fail
        best_pivot_val = A[best_index_in_A, var_index_to_elim]
        if best_pivot_val == 0.0:
            raise ValueError("SINGULAR MATRIX")
        # swap pivot in A and b
        best_row = A[best_index_in_A, :]
        original_row = A[var_index_to_elim, :]
        A[best_index_in_A, :] = best_row
        A[var_index_to_elim, :] = original_row
        # now is var_index_to_elim also the index of the equation to elimintate with
        eq_index_to_elim_with = var_index_to_elim
        # eliminate the variable `var_index_to_elim` in the equations var_index_to_elim+1..n-1
        for eq_index_to_elim_in in range(var_index_to_elim + 1, eq_num):
            # elimintate the variable var_index_to_elim in eq_index_to_elim_in
            # I: equation to eliminate with = A[eq_index_to_elim_with, :]
            eq_to_elim_with = A[eq_index_to_elim_with, :]
            # II: equation to eliminate in = A[eq_index_to_elim_in, :]
            eq_to_elim_in = A[eq_index_to_elim_in, :]
            # the variable to use to elimintate with is at I[var_index_to_elim] =
            # A[eq_index_to_elim_with, var_index_to_elim] = A[var_index_to_elim, var_index_to_elim]
            # = coeff_to_elim_with
            coeff_to_elim_with = eq_to_elim_with[var_index_to_elim]
            # the variable to elimintate is at II[var_index_to_elim] = coeff_to_elim =
            # A[eq_index_to_elim_in, var_index_to_elim]
            coeff_to_elim = eq_to_elim_in[var_index_to_elim]
            # alpha = coeff_to_elim/coeff_to_elim_with
            alpha = coeff_to_elim/coeff_to_elim_with
            # II <- II - alpha*I
            eliminated_eq = eq_to_elim_in - alpha*eq_to_elim_with
            # but the equation back
            A[eq_index_to_elim_in, :] = eliminated_eq
            # b[eq_index_to_elim_in] -= b[eq_index_to_elim_with]*alpha
            b[eq_index_to_elim_in] -= alpha*b[eq_index_to_elim_with]
    return A, b

def backsubst(A, b):
    n = b.size
    x = np.zeros(n)
    for var_to_solve in range(n - 1, -1, -1):
        other_subs = 0.0
        for var_to_sub in range(var_to_solve + 1, n):
            other_subs += x[var_to_sub]*A[var_to_solve, var_to_sub]
        x[var_to_solve] = (b[var_to_solve] - other_subs)/A[var_to_solve, var_to_solve]
    return x

A = np.random.rand(3, 3)
b = np.random.rand(3)
print("A:")
print(A)
print("b:")
print(b)
A, b = gauss_elim(A, b)
print("Gaussian Elimination:")
print(A)
print(b)
x = backsubst(A, b)
print("solution x:")
print(x)
print("solution using scipy:")
print(solve(A, b))


import numpy as np

def rotation_matrix2d_ccw(angle:float):
    """
    Function that returns the 2d rotation matrix (counter-clockwise)

    Args:
    -----
        angle(float)    : The angle passed in degrees

    Returns:
    --------
        rot_mat(np.ndarray) : The rotation matrix returned in (2,2)
    """
    rot_mat = np.zeros((2,2))
    angle2rad = np.pi/180

    theta   = angle*angle2rad
    costheta = np.cos(theta)
    sintheta = np.sin(theta)
    rot_mat[0,0] = costheta
    rot_mat[0,1] = -sintheta
    rot_mat[1,0] = sintheta
    rot_mat[1,1] = costheta

    return rot_mat

def rotation_matrix2d_cw(angle:float):
    """
    Function that returns the 2d rotation matrix (clockwise)

    Args:
    -----
        angle(float)    : The angle passed in degrees

    Returns:
    --------
        rot_mat(np.ndarray) : The rotation matrix returned in (2,2)
    """
    rot_mat = rotation_matrix2d_ccw(angle)
    rot_mat[1,0] = -rot_mat[1,0]
    rot_mat[0,1] = -rot_mat[0,1]

    return rot_mat


from math import gamma  # * see also scipy.special
from random import random

import numpy as np
import numpy.linalg as la

from sdia_python.lab2.utils import get_random_number_generator

class BallWindow:
    """Creates a Ball window, meaning a volume around a center point with a radius"""

    def __init__(self, center, radius=1.0):
        """Initialize the BallWindows from the center and the radius

        Args:
            center (list) : Gives the coordinates of the center
            radius (float) : Gives the radius of the ball. Default to 1.
        """

        self.center = np.array(center)
        self.radius = radius

    def __str__(self):
        """Display the BallWindow as a string

        Returns:
            string: BallWindows center and radius
        """

        description = (
            f"BallWindow: center = {list(self.center)} & radius = {self.radius}"
        )
        return description

    def __len__(self):
        """Returns the dimension of the space of the BallWindow

        Returns:
            int: Size of the space containing the BallWindow
        """

        return self.center.size

    def __contains__(self, point):
        """Indicates whether the argument given is inside the Ball Window of not.
        Assertion error if the dimension of the point is not equal to the dimension of the BallWindow

        Args:
            point (np.array): [list of coordinates]

        Returns:
            boolean: True if the point is inside, else returns False
        """

        # ? readability: len(self) => self.dimension()
        assert (
            len(point) == self.dimension()
        )  ##Test if the point has the same dimension
        return la.norm(self.center - point) <= self.radius

    def dimension(self):
        """Gives the dimension of the BallWindows, see __len__

        Returns:
            int: The dimension of the BallWindow
        """

        return len(self)

    def volume(self):
        """Gives the volume of the BallWindow

        Returns:
            int: The volume of the BallWindow
        """
        n = self.dimension()
        R = self.radius
        return (np.pi ** (n / 2) * R ** (n)) / gamma(1 + n / 2)

    def indicator_function(self, array_points):
        """Gives the result of the indicator function of the BallWindows given some points of the same dimension

        Args:
            args ([int]): 1 if the argument is inside the BallWindow, else 0
        """
        # * same remarks as in BoxWindow.indicator_function
        if array_points.ndim > 1:
            return np.array([int(p in self) for p in array_points], dtype=int)
        return int(array_points in self)

    def rand(self, n=1, rng=None):
        """Generate ``n`` points uniformly at random inside the :py:class:`BallWindow`.

        Args:
            n (int, optional): Number of random points to generate. Defaults to 1.
            rng (type, optional): Defaults to None.

        Returns: Array which contains n points randomly uniformly generated

        """
        dim = self.dimension()
        r = self.radius
        rng = get_random_number_generator(rng)

        points_l = np.empty((n, dim))
        for index in range(n):
            # an array of dim normally distributed random variables
            u = np.random.normal(0, 1, dim)
            point_radius = r * random() ** (1.0 / dim)
            points_l[index] = np.reshape(point_radius * u / la.norm(u), (1, 2))
        return points_l + self.center


"""
Problem Statement:
Say that you are a traveler on a 2D grid.
You begin in the top-left corner and your goal is to travel to the bottom-right corner. You may only move down or right.
You may only move down or right.

In how many ways can you travel to the goal on a grid with dimensions m*n?
"""
from functools import lru_cache, partial
from utils.decorators import time_this
import numpy as np

class GridTraveller:
    def __init__(self, m, n):
        self.solutions = {
            "recursive": partial(self.recursive, m, n),
            "dp_traverse_child": partial(self.dp_traverse_child, m, n),
            "dp_reduce_grid": partial(self.dp_reduce_grid, m, n),
            "dp_lru_cache": partial(self.dp_lru_cache, m, n),
            "dp_tabulation": partial(self.dp_tabulation, m, n),
        }

    @staticmethod
    def recursive(m, n):
        """
        Time complexity: O(2^(n+m))
        Space Complexity: O(n+m)

        :param m:
        :param n:
        :return:
        """
        if m == 1 and n == 1:
            return 1
        if m == 0 or n == 0:
            return 0
        return GridTraveller.recursive(m - 1, n) + GridTraveller.recursive(m, n - 1)

    @staticmethod
    def dp_traverse_child(m, n, current=None, grid_value=None):
        start = [0, 0]
        goal = [m - 1, n - 1]
        actions = ('d', 'r')
        if current is None:
            current = start
            grid_value = {}
        if tuple(current) in grid_value.keys():
            return grid_value[tuple(current)]
        value = 0
        if current == goal:
            return 1
        for action in actions:
            if action == "d":
                child = [current[0] + 1, current[1]]
            else:
                child = [current[0], current[1] + 1]

            if child == goal:
                return 1
            if child[0] >= m or child[1] >= n:
                continue
            else:
                value += GridTraveller.dp_traverse_child(m, n, child, grid_value)

        grid_value[tuple(current)] = value
        return grid_value[tuple(current)]

    @staticmethod
    def dp_reduce_grid(m, n, grid_value=None):
        """
        grid_traveller(m,n) == grid_traveller(n,m) (Symmetric)
        Time Complexity: O(nm)
        Space Complexity: O(nm)
        """
        if grid_value is None:
            grid_value = {}
        value = grid_value.get((m, n))
        if value:
            return value
        value = grid_value.get((n, m))
        if value:
            return value
        if m == 1 and n == 1:
            grid_value[(m, n)] = 1
            return 1
        if m == 0 or n == 0:
            grid_value[(m, n)] = 0
            return 0
        grid_value[(m, n)] = GridTraveller.dp_reduce_grid(m - 1, n, grid_value) + GridTraveller.dp_reduce_grid(m, n - 1, grid_value)
        return grid_value[(m, n)]

    @staticmethod
    @lru_cache
    def dp_lru_cache(m, n):
        if m == 1 and n == 1:
            return 1
        if m == 0 or n == 0:
            return 0
        return GridTraveller.dp_lru_cache(min(m - 1, n), max(m - 1, n)) + \
               GridTraveller.dp_lru_cache(min(m, n - 1), max(m, n - 1))

    @staticmethod
    def dp_tabulation(m, n, grid_value=None):
        """
        grid_traveller(m,n) == grid_traveller(n,m) (Symmetric)
        Time Complexity: O(nm)
        Space Complexity: O(nm)
        """
        grid_value_table = np.zeros((m + 1, n + 1), int)
        grid_value_table[1][1] = 1

        for i in range(m + 1):
            for j in range(n + 1):
                if i + 1 <= m:
                    grid_value_table[i + 1][j] += grid_value_table[i][j]
                if j + 1 <= n:
                    grid_value_table[i][j + 1] += grid_value_table[i][j]
        # print(grid_value_table)
        return grid_value_table[m][n]

    @staticmethod
    @time_this()
    def run(func):
        print(f"Solution: {func()}")

    def execute_all(self):

        print("\nSolutions to Grid Traveller\n")

        for name, solution in self.solutions.items():
            print(f'Algo-Name: {name} {" -" * 90}')
            self.run(solution)
            print('-' * 100)

GridTraveller(10, 10).execute_all()


'''
2D Arithmetic
100xp
Remember how you calculated the Body Mass Index for all baseball players?
numpy was able to perform all calculations element-wise (i.e. element by element).
For 2D numpy arrays this isn't any different! You can combine matrices with single
numbers, with vectors, and with other matrices.

Execute the code below in the IPython shell and see if you understand:

import numpy as np
np_mat = np.array([[1, 2],
                   [3, 4],
                   [5, 6]])
np_mat * 2
np_mat + np.array([10, 10])
np_mat + np_mat
np_baseball is coded for you; it's again a 2D numpy array with 3 columns representing
height, weight and age.

Instructions
-You managed to get hold of the changes in weight, height and age of all baseball players.
It is available as a 2D numpy array, updated. Add np_baseball and updated and print out the result.
-You want to convert the units of height and weight. As a first step, create a numpy array
with three values: 0.0254, 0.453592 and 1. Name this array conversion.
-Multiply np_baseball with conversion and print out the result.
'''
# baseball is available as a regular list of lists
# updated is available as 2D numpy array

# Import numpy package
import numpy as np

# Create np_baseball (3 cols)
np_baseball = np.array(baseball)

# Print out addition of np_baseball and updated
print(np_baseball + updated)

# Create numpy array: conversion
conversion = np.array([0.0254, 0.453592, 1.0])

# Print out product of np_baseball and conversion
print(np_baseball * conversion)

Intro to Python for Data Science/NumPy/2d-arithmetic.py


import numpy as np

# Change False to True for this block of code to see what it does

# NumPy axis argument
if True:
    a = np.array([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ])

    print(a.sum())
    print(a.sum(axis=0))
    print(a.sum(axis=1))

# Subway ridership for 5 stations on 10 different days
ridership = np.array([
    [0, 0, 2, 5, 0],
    [1478, 3877, 3674, 2328, 2539],
    [1613, 4088, 3991, 6461, 2691],
    [1560, 3392, 3826, 4787, 2613],
    [1608, 4802, 3932, 4477, 2705],
    [1576, 3933, 3909, 4979, 2685],
    [95, 229, 255, 496, 201],
    [2, 0, 1, 27, 0],
    [1438, 3785, 3589, 4174, 2215],
    [1342, 4043, 4009, 4665, 3033]
])

def min_and_max_riders_per_day(ridership):
    """
    Fill in this function. First, for each subway station, calculate the
    mean ridership per day. Then, out of all the subway stations, return the
    maximum and minimum of these values. That is, find the maximum
    mean-ridership-per-day and the minimum mean-ridership-per-day for any
    subway station.
    """
    mean_ridership_per_day = ridership.mean(axis=0)
    max_daily_ridership = mean_ridership_per_day.max()  # Replace this with your code
    min_daily_ridership = mean_ridership_per_day.min()  # Replace this with your code

    return max_daily_ridership, min_daily_ridership

print(min_and_max_riders_per_day(ridership))


#Determining the Principal Axis of Inertia & the Principal Moments of Inertia from the Inertia Tensor

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

I = np.array([[30,5,5],
             [5,20,5],
             [5,5,10]])
print("Inertia Tensor = \n",I)

I_p, P = np.linalg.eig(I) #This function is used to calculate EigenValues & EigenVectors (I_p = EigenValues, P = EigenVectors)

print("Principal Moments of Inertia using EigenValues = \n",I_p)
print("Principal Axis of Inertia = \n",P.T)

#Determining Principal Moment of Inertia vai Diagonalization

I_pp = np.linalg.inv(P)@I@P # @ Operator represents the product of the matrix. (In NumPy * operator attention to become a product of the elements to each other.)
I_pp = np.diag(I_pp) #Using the diag function and retrieve only the diagonal components.
print("Principal Moments of Inertia using Diagonalization = \n",I_pp)


import matplotlib.pyplot as plt
import numpy as np
while True:
    try:
        print('-'*111) # for decoration purpose
        x = input('<< TO CONTINUE PRESS "ENTER" OR TO KILL THE APPLICATION PRESS "0" >>     ')
        if x == '':
            print()
            print('<< THIS APPLICATION CALCULATES ROOTS FOR A GIVEN QUADRATIC EQUATION >>')
            print()
            print('>>> THE EQUATION WILL BE IN FORM OF << a.X^2 + b.X + c >>')
            print()
            a = int(input('enter value of a: '))
            b = int(input('enter value of b: '))
            c = int(input('enter value of c: '))
            d = ((b**2)-4*a*c)**(1/2)
            discriminant = ((b**2)-4*a*c)

            roots_1= ((-b)+d)/(2*a)
            roots_2 = ((-b)-d)/(2*a)
            root_declare = f'>>> The roots are  X = {roots_1}  and  X = {roots_2}.'
            print()
            print(f'>>> THE EQUATION IS ( {a}.X^2 + {b}.X + {c} ).')
            print()
            print(root_declare)
            print()
            print(f'>>> The value of discriminant is {discriminant}.')
            print()

            def root_indicator():                     # tell's root charateristic
                if discriminant > 0:
                    return('The roots of given equation are real.')
                elif discriminant < 0:
                    return('The roots of given equation are imaginary.')
                elif discriminant == 0:
                    return('There is one real root.')
            print(f'>>> {root_indicator()}')
            print()

            print('')
            plot_enable = input('<<< To show the plot press " y " OR To cancel plotting press ENTER >>>     ')
            if plot_enable == 'y':
                def plot_show():
                    # 100 linearly spaced numbers
                    x = np.linspace(-10**2,10**2,10**2)
                    y = np.linspace(-10**2,10**2,10**2)

                    # the function, which is y = x^2 here
                    y = (a*(x**2))+(b*x)+c

                    # setting the axes at the centre
                    fig = plt.figure()
                    ax = fig.add_subplot(1, 1, 1)
                    ax.spines['left'].set_position('center')
                    ax.spines['bottom'].set_position('zero')
                    ax.spines['right'].set_color('none')
                    ax.spines['top'].set_color('none')
                    ax.xaxis.set_ticks_position('bottom')
                    ax.yaxis.set_ticks_position('left')

                    # plot the function
                    plt.plot(x,y, 'r')

                    # show the plot
                    plt.show()
                plot_show()
            elif plot_enable == '':
                print()
                print('>>> Graphing Cancelled')
        elif int(x) == 0:
            break
    except:
        if a == 0:
            print(f'>> a value must be larger than "zero" and enter numerals only.  <<')
        else:
            print()
            print(f'>> Please check the entered value, enter numerals only. <<')