#!/usr/local/bin/python3

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import distance as sd

class Clusterers:
    def __init__(self, data_path):
        self._data_path = data_path
        self._points = list()
        self._clusters = list()
        self._centroids = list()
        self.load_data()

    def load_data(self):
        with open(self._data_path, 'r') as f:
            lines = f.readlines()
            for line in lines:
                point_str = line.strip("\n").split("\t")
                self._points.append([float(point_str[0]), float(point_str[1])])
            self._points = np.array(self._points)
        # print(np.average(self._points, axis=0))

    ''' ################# Following is implementation for K-means clustering ################# '''
    def k_means(self, init_centroids):
        cent = init_centroids
        # print(self.mink_dist(cent[0], cent[1], 3))
        print("=============  (a) Euclidean  ==============")
        self.k_means_train(init_centroids, self.eucl_dist)
        print("=============  (b) Cityblock  ==============")
        self.k_means_train(init_centroids, self.city_dist)
        print("=============  (c) Minkovski  ==============")
        self.k_means_train(init_centroids, self.mink_dist)
        return

    def k_means_train(self, init_cents, dist_m):
        # num_clusters = len(init_cents)
        self._centroids = list(init_cents)
        self._clusters.clear()

        loss = 1000
        epoch = 0
        while loss > 0:
            epoch += 1
            print("=============  epoch", epoch, "==============")
            pre_obj = self.k_means_obj_func(dist_m)
            # assign points
            self.print_centroids()
            self.k_means_assign(dist_m)
            # update centroids
            self.k_means_update()
            self.print_clusters()
            obj = self.k_means_obj_func(dist_m)
            print("result of pre_obj func:", pre_obj)
            print("result of obj func:", obj)
            loss = abs(pre_obj - obj)
            print("loss:", loss)
        print("amount of epoch:", epoch)

        return self._clusters

    def k_means_assign(self, dist_m):
        cents = self._centroids
        self._clusters = [np.zeros([0,2]) for i in range(len(cents))]
        # for cent in cents:
        #     self._clusters.append(np.array([cent]))
        for point in self._points:
            min_dist = 1000
            min_ind = 0
            for cent_ind in range(len(cents)):
                dist = dist_m(point, cents[cent_ind])
                if dist < min_dist:
                    min_dist = dist
                    min_ind = cent_ind
            # print(point, self._clusters[min_ind][0], min_dist)
            self._clusters[min_ind] = np.append(self._clusters[min_ind], np.array([point]), axis=0)
        return self._clusters

    def k_means_update(self):
        self._centroids.clear()
        for clst in self._clusters:
            # print(clst[1:])
            self._centroids.append(np.average(clst[:], axis = 0))
            # print(self._centroids)
        return self._centroids

    def k_means_obj_func(self, dist_m):
        # res = 0
        # for clst_i in range(len(self._clusters)):
        #     cent = self._clusters[clst_i][0]
        #     res += sum(dist_m(cent, self._clusters[clst_i][i]) for i in range(1, len(self._clusters[clst_i])))
        # return res
        return sum( sum(dist_m(self._clusters[clst_i][0], self._clusters[clst_i][i]) for i in range(1, len(self._clusters[clst_i]))) for clst_i in range(len(self._clusters)))

    ''' ################# Following is implementation for Hierarchical clustering ################# '''
    def hierarchical(self):
        self._clusters.clear()
        # print pair-wise proximity matrix for original points
        print("======================= a. initial proximity matrix & clusters =======================")
        pdist = self.hier_pdist()
        self.hier_show_prox_mat(self._points, pdist, "[Point-Point Proximities]", True, "p")
        dists = self.hier_init_clusters()
        self.print_clusters()
        self._clusters.clear()

        # complete linkage
        print("======================= b. complete linkage  =======================")
        dists = self.hier_init_clusters(self.hier_dist_complete)
        self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        while len(self._clusters)>1:
            dists = self.hier_update(dists, self.hier_dist_complete)
            self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        self._clusters.clear()

        # single linkage
        print("======================= c. single linkage  =======================")
        dists = self.hier_init_clusters(self.hier_dist_single)
        self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        while len(self._clusters)>1:
            dists = self.hier_update(dists, self.hier_dist_single)
            self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        self._clusters.clear()

        # group average
        print("======================= d. group average =======================")
        dists = self.hier_init_clusters(self.hier_dist_group)
        self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        while len(self._clusters)>1:
            dists = self.hier_update(dists, self.hier_dist_group)
            self.hier_show_prox_mat(self._clusters, dists, "[Cluster-Cluster Proximities]", True)
        self._clusters.clear()

        return

    def hier_init_clusters(self, dist_m = None):
        # initialize 5 separations (0-3, 4-7, 8-11, 12-15, 16-19)
        # self._clusters = [np.zeros([0,2]) for i in range(5)]
        # for clst_i in range(len(self._clusters)):
        #     for p_i in range(clst_i * 4, clst_i * 4 + 4):
        #         self._clusters[clst_i] = np.append(self._clusters[clst_i], np.array([self._points[p_i]]), axis=0)
        #         # print(np.array([self._points[p_i]]))

        # initialize 5 separations using single-linkage
        for point in self._points:
            self._clusters.append(np.array([point]))
        pdist = self.hier_pdist()
        dists = self.hier_update(pdist, self.hier_dist_single)
        while len(self._clusters) != 5:
            dists = self.hier_update(dists, self.hier_dist_single)
        if dist_m:
            # dists = dist_m(pdist)
            dists = dist_m()
            return dists
        return

    def hier_update(self, dists, merge_m):
        self.hier_merge(dists)
        # return merge_m(dists)
        return merge_m()

    def hier_merge(self, dists):
        # merge clusters based on a certain linkage method
        min_ind = np.argmin(dists)
        # self.print_clusters()
        num_clst = len(self._clusters)
        for clst_1 in range(num_clst-1):
            for clst_2 in range(clst_1+1, num_clst):
                if min_ind == self.hier_rcl(clst_1, clst_2, num_clst):
                    print("[Notes] 'c%02d'" % clst_2, "will be merged into 'c%02d'" % clst_1)
                    # found the closest cluster pair
                    # print(clst_1, clst_2)
                    clusters = list()
                    for clst_i in range(num_clst):
                        if clst_i == clst_2:
                            clusters[clst_1] = np.append(clusters[clst_1], self._clusters[clst_2], axis=0)
                            continue
                        clusters.append(self._clusters[clst_i])
                    self._clusters = clusters
                    break
        # self.print_clusters()
        return self._clusters

    def hier_pdist(self):
        return sd.pdist(self._points)

    # def hier_dist_single(self, pdist):
    def hier_dist_single(self):
        clusters = self._clusters
        dists = np.zeros([0])
        for clst_1 in range(len(clusters)-1):
            for clst_2 in range(clst_1 + 1, len(clusters)):
                min_dist = np.min(sd.cdist(clusters[clst_1], clusters[clst_2]))
                dists = np.append(dists, np.array([min_dist]))
                # print(clst_1, clst_2, min_dist)
        return dists

    # def hier_dist_complete(self, pdist):
    def hier_dist_complete(self):
        clusters = self._clusters
        dists = np.zeros([0])
        for clst_1 in range(len(clusters)-1):
            for clst_2 in range(clst_1 + 1, len(clusters)):
                max_dist = np.max(sd.cdist(clusters[clst_1], clusters[clst_2]))
                dists = np.append(dists, np.array([max_dist]))
        return dists

    # def hier_dist_group(self, pdist):
    def hier_dist_group(self):
        clusters = self._clusters
        dists = np.zeros([0])
        for clst_1 in range(len(clusters)-1):
            for clst_2 in range(clst_1 + 1, len(clusters)):
                avg_dist = np.average(sd.cdist(clusters[clst_1], clusters[clst_2]))
                dists = np.append(dists, np.array([avg_dist]))
        return dists

    def hier_show_prox_mat(self, clusters, pdist, title, if_print = False, label = 'c'):
        if if_print:
            num_p = len(clusters)
            # print table header
            print(title)
            if label == "p":
                print("             ", end="")
            else:
                print("    ", end="")
            for i in range(num_p):
                print(" %s%02d " % (label, i), end="")
            print()
            # print matrix
            row = 0
            for i in sd.squareform(pdist):
                col = 0
                if label == "p":
                    print("%s%02d[%.1f,%.1f] " % (label, row, clusters[row][0], clusters[row][1]), end="")
                else:
                    print("%s%02d " % (label, row), end="")
                for j in i:
                    if row == col:
                        print("0.00 ", end="")
                    else:
                        print("%.2f " % pdist[self.hier_rcl(row,col,num_p)], end="")
                        # if j == pdist[self.hier_rcl(row,col,num_p)]:
                        #     print('true ', end="")
                    col += 1
                row += 1
                print()
        return pdist

    # get dist from condensed pdist, based on #row, #col and len
    def hier_rcl(self, i, j, l):
        if i < j:
            res = (i*(l-2)+j-i*(i-1)/2-1)
        elif i > j:
            res = (j*(l-2)+i-j*(j-1)/2-1)
        # print(res)
        return int(res)

    ''' ################# Following is implementation for SOM clustering ################# '''
    def som(self, init_centroids, a = 0.3, a_nb = 0.2, size_nb = 3, loss_threshold = 1e-16, learning_rate_threshold = 1e-10):
        # self.display_points(init_centroids)

        self._clusters.clear()
        centroids = init_centroids
        print("[b] before training:\n", centroids)
        epoch = 0
        while True:
            epoch += 1
            pre_centroids = np.array(centroids)
            centroids = self.som_train(self._points, centroids, a, a_nb, size_nb, epoch)
            loss = np.linalg.norm(pre_centroids - centroids)
            print("loss: ", loss)
            if loss <= loss_threshold:
                break
        print("after training:\n", centroids, "\nround: ", epoch)
        self._clusters = self.som_assign(self._points, centroids)
        self.print_clusters()

        print("======================================")

        centroids = init_centroids
        print("[c] before training:\n", centroids)
        epoch = 0
        pre_loss = 0
        while True:
            epoch += 1
            pre_centroids = np.array(centroids)
            # TODO: epoch from 0 or 1?
            a -= 0.02
            a_nb -= 0.02
            centroids = self.som_train(self._points, centroids, a, a_nb, size_nb, epoch)
            loss = np.linalg.norm(pre_centroids - centroids)
            pre_loss = loss
            print("loss: ", loss)
            if loss <= loss_threshold or a <= learning_rate_threshold or a_nb <= learning_rate_threshold:
                break
        print("after training:\n", centroids, "\nround: ", epoch, ", a=", a, ", a_nb=", a_nb)
        self._clusters = self.som_assign(self._points, centroids)
        self.print_clusters()

        return self._clusters

    def som_train(self, examples, centroids, a, a_nb, size_nb, cur_round):
        # print("round: ", cur_round)
        for example in examples:
            # select winner centroid
            min_dist = 1000
            closest_cent = 0
            for ind in range(len(centroids)):
                dist = self.eucl_dist(centroids[ind], example)
                if dist < min_dist:
                    min_dist = dist
                    closest_cent = ind
            # print(closest_cent, centroids[closest_cent], min_dist)
            # look for winner's neighbours
            nb_inds = list()
            for nb_i in range(len(centroids)):
                if nb_i != closest_cent:
                    nb_inds.append(nb_i)
            # update winner
            centroids[closest_cent] += a * ( example - centroids[closest_cent] )
            # update winner's neighbours
            for i in range(size_nb):
                centroids[nb_inds[i]] += a_nb * (example - centroids[nb_inds[i]])
        return centroids

    def som_assign(self, examples, centroids):
        self._clusters = list()
        # create clusters
        for ind in range(len(centroids)):
            self._clusters.append(np.array([centroids[ind]]))
        for example in examples:
            min_dist = 1000
            closest_cent = 0
            for ind in range(len(centroids)):
                dist = self.eucl_dist(centroids[ind], example)
                if dist < min_dist:
                    min_dist = dist
                    closest_cent = ind
            # print(closest_cent, min_dist)
            self._clusters[closest_cent] = np.append(self._clusters[closest_cent], np.array([example]), axis=0)
        return self._clusters

    # a special case of minkovski-distance with 2 as lambda
    def eucl_dist(self, vec1, vec2):
        # return self.mink_dist(vec1, vec2, 2)
        return np.linalg.norm(vec1 - vec2)

    # a special case of minkovski-distance with 1 as lambda
    def city_dist(self, vec1, vec2):
        # return self.mink_dist(vec1, vec2, 1)
        return sum(abs(vec1[dim] - vec2[dim]) for dim in range(len(vec1)))

    def mink_dist(self, vec1, vec2, lmd = 3):
        return (sum((abs(vec1[dim] - vec2[dim]) ** lmd) for dim in range(len(vec1))) ** (1 / lmd))

    def display_points(self, points):
        plt.plot(points)
        plt.show()

    def display_clusters(self):
        # plot self._clusters
        return self._clusters

    def print_clusters(self):
        cluster_num = 0
        for cluster in self._clusters:
            cluster_num += 1
            print("cluster", cluster_num)
            print(cluster)

    def print_centroids(self):
        cent_num = 0
        for cent in self._centroids:
            cent_num += 1
            print("centroid", cent_num, ":", cent)

def main():
    # configuration
    data_path = "./data_points"
    # create my clusterers
    my_clusterers = Clusterers(data_path)

    # run k-means
    init_centroids_kmeans = np.array([[1.8, 2.3], [2.3, 1.4]])
    lambda_kmeans_mink = 3
    my_clusterers.k_means(init_centroids_kmeans)

    # run hierarchical
    my_clusterers.hierarchical()

    # run som
    init_centroids_som = np.array([[1, 3.1], [2, 2.2], [1.5, 2.1], [3.1, 1.1]])
    my_clusterers.som(init_centroids_som)

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pylab as plt

def funcion(x):
    return x**5 - 2*(x**4) - 10*(x**3) + 20*(x**2) + 9*x - 18

xx=np.linspace(-3.5,3.5,100)

plt.figure()
plt.plot(xx,funcion(xx))
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Gráfica de la función')
plt.grid()
plt.savefig('NRpoli.pdf')
plt.close()

def NR(x,funcion,precision=1*(10**-5)):
    N=0
    dx=3**-1
    for i in range(10000):
        F=funcion(x)
        if(abs(F)<precision):
            break
        df= (funcion(x+(dx/2))-funcion(x-(dx/2)))/dx
        dx=-F/df
        x+=dx
        N+=1
    return x,N

# x_guess = -3
print('')

xmenos3,Nmenos3=NR(-3,funcion)

print('Con un x_guess inicial de -3 el valor de la raíz X0r da: ',xmenos3,'y el valor de f(X0r) da: ',funcion(xmenos3))

# x_guess = -1
print('')

xmenos1,Nmenos1=NR(-1,funcion)

print('Con un x_guess inicial de -1 el valor de la raíz X1r da: ',xmenos1,'y el valor de f(X1r) da: ',funcion(xmenos1))

# Número de iteraciones

xx2=np.linspace(-4,4,1000)
Ns=np.zeros(len(xx2))
raices=np.zeros(len(xx2))

for i in range(len(xx2)):
    raiz,N=NR(xx2[i],funcion,precision=1*(10**-10))
    Ns[i]=N
    raices[i]=raiz

plt.figure()
plt.scatter(xx2,Ns,s=15)
plt.xlabel('X_guess')
plt.ylabel('No. Iteraciones')
plt.title('Número de iteraciones')
plt.grid()
plt.savefig('NR_itera.pdf')
plt.close()

plt.figure()
plt.scatter(xx2,raices,s=15)
plt.xlabel('X_guess')
plt.ylabel('Raíz')
plt.title('Raíces encontradas')
plt.grid()
plt.savefig('NRxguess.pdf')
plt.close()

print('')
print('Se puede observar claramente que los puntos de X_guess en los que el método necesita una mayor cantidad de iteraciones (Gráfica NR_itera.pdf) está correlacionado con las partes de la función (Gráfica NRpoli.pdf) en las que su derivada es cercana a cero. Esto se debe a que el método busca puntos en el eje x que se crucen con la pendiente de la funcion de x0, lo que significa que esos nuevos puntos de x serán muy lejanos del x0 y el método necesitará mucho más tiempo para encontrar una raíz en la que la función de 0.')
print('')
print('Además, se puede observar que también hay una correlación con los tramos de X_guess (Gráfica NRxguess.pdf) en los que la raiz encontrada es variable. Esto se da por la misma razón de que la distancia entre nuevos puntos de x y el x anterior es muy grande y los ceros encontrados son de una parte lejana de la función.')


# %%

#!/anaconda3/bin/python

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from mpl_toolkits.mplot3d import Axes3D  # for surface plot

# %%
def featureNormaliz(X):
    X_norm = X
    mu = np.mean(X, axis=0)
    sigma = np.std(X, axis=0, ddof=1)  # ddof=1 除法使用N-1
    X_norm = (X - mu) / sigma
    return X_norm, mu, sigma

def gradientDescentMulti(X, y, theta, alpha, num_iters):
    m = len(y)
    n = np.size(X, axis=1)
    J_history = np.zeros([num_iters, 1])
    for iter in np.arange(1, num_iters + 1):
        delta = 1 / m * (X.T.dot(X).dot(theta) - X.T.dot(y).reshape(n, 1))
        theta = theta - alpha * delta
        J_history[iter - 1] = computeCostMulti(X, y, theta)
    return theta, J_history

def computeCostMulti(X, y, theta):
    m = len(y)
    J = sum((X.dot(theta) - y.reshape(m, 1))**2) / 2 / m
    return J

def normalEqn(X,y):
    theta = np.linalg.pinv(X.T.dot(X)).dot(X.T).dot(y) # pinv(X' * X) * X' * y
    return theta

data = pd.read_csv('ex1data2.txt', header=-1)
data = np.asarray(data)
X = data[:, (0, 1)]
y = data[:, 2]
m = len(y)

#   %% ================ Part 1: Feature Normalization ================

#   对所有特征做归一化

X, mu, sigma = featureNormaliz(X)

#   在第一行添加1，用于thete0的计算
X = np.append(np.ones((m, 1)), X, axis=1)

#   %% ================ Part 2: Gradient Descent ================
alpha = 0.1
num_iters = 50
theta = np.zeros((3, 1))  #  如果要扩展至任意维度也是可以的，这里已经有提示了n

theta, J_history0_01 = gradientDescentMulti(X, y, theta, alpha, num_iters)

alpha = 0.3
num_iters = 50
theta = np.zeros((3, 1))  #  如果要扩展至任意维度也是可以的，这里已经有提示了n

theta, J_history0_03 = gradientDescentMulti(X, y, theta, alpha, num_iters)

alpha =  1
num_iters = 50

theta = np.zeros((3, 1))
theta, J_history0_1 = gradientDescentMulti(X, y, theta, alpha, num_iters)

fig, ax = plt.subplots()

ax.plot(J_history0_01, 'r-')
ax.hold
ax.plot(J_history0_03, 'g-')
ax.hold
ax.plot(J_history0_1, 'b-')

ax.set(xlabel='Number of iterations', ylabel='Cost J')
plt.legend(('alpha = 0.01','alpha = 0.03','alpha = 0.1'))

plt.show()

# %% ================ Part 3: Normal Equations ================

data = pd.read_csv('ex1data2.txt', header=-1)
data = np.asarray(data)
X = data[:, (0, 1)]
y = data[:, 2]
m = len(y)

X = np.append(np.ones((m, 1)), X, axis=1)

theta = normalEqn(X,y)

print(theta)

# %%


# solutions.py
"""Volume 1A: Linear Systems. Solutions file."""

import numpy as np
from time import time
from scipy import sparse
from scipy import linalg as la
from scipy.sparse import linalg as spla
from matplotlib import pyplot as plt

# Problem 1
def ref(A):
    """Reduce the square matrix A to REF. You may assume that A is invertible
    and that a 0 will never appear on the main diagonal. Avoid operating on
    entries that you know will be 0 before and after a row operation.
    """
    A = np.array(A, dtype=np.float, copy=True)
    m,n = A.shape
    for j in xrange(n):
        for i in xrange(j+1, m):
            A[i,j:] -= A[j,j:] * A[i,j] / A[j,j]
    return A

# Problem 2
def lu(A):
    """Compute the LU decomposition of the square matrix A. You may assume the
    decomposition exists and requires no row swaps.

    Returns:
        L ((n,n) ndarray): The lower-triangular part of the decomposition.
        U ((n,n) ndarray): The upper-triangular part of the decomposition.
    """
    m, n = A.shape
    U = np.array(A, dtype=np.float, copy=True)
    L = np.eye(n)
    for j in xrange(n):
        for i in xrange(j+1, m):
            L[i,j] = U[i,j]/U[j,j]
            U[i,j:] -= L[i,j]*U[j,j:]
    return L,U

# Problem 3
def solve(A, b):
    """Use the LU decomposition and back substitution to solve the linear
    system Ax = b. You may assume that A is invertible (hence square).
    """
    m, n = A.shape
    L, U = lu(A)

    # First solve Ly = Pb (assume P = I).
    y = np.zeros(n)
    for k in xrange(n):
        y[k] = b[k] - np.dot(L[k,:k], y[:k])

    # Now solve Ux = y.
    x = np.zeros(n)
    for k in reversed(xrange(n)):
        x[k] = (y[k] - np.dot(U[k,k:], x[k:])) / U[k,k]

    return x

# Problem 4
def prob4(N=11):
    """Time different scipy.linalg functions for solving square linear systems.
    Plot the system size versus the execution times. Use log scales if needed.
    """
    domain = 2**np.arange(1,N+1)
    inv, solve, lu_factor, lu_solve = [], [], [], []

    for n in domain:
        A = np.random.random((n,n))
        b = np.random.random(n)

        start = time()
        la.inv(A).dot(b)
        inv.append(time()-start)

        start = time()
        la.solve(A, b)
        solve.append(time()-start)

        start = time()
        x = la.lu_factor(A)
        la.lu_solve(x, b)
        lu_factor.append(time()-start)

        start = time()
        la.lu_solve(x, b)
        lu_solve.append(time()-start)

    plt.subplot(121)
    plt.plot(domain, inv, '.-', lw=2, label="la.inv()")
    plt.plot(domain, solve, '.-', lw=2, label="la.solve()")
    plt.plot(domain, lu_factor, '.-', lw=2,
                                    label="la.lu_factor() and la.lu_solve()")
    plt.plot(domain, lu_solve, '.-', lw=2, label="la.lu_solve() alone")
    plt.xlabel("n"); plt.ylabel("Seconds")
    plt.legend(loc="upper left")

    plt.subplot(122)
    plt.loglog(domain, inv, '.-', basex=2, basey=2, lw=2)
    plt.loglog(domain, solve, '.-', basex=2, basey=2, lw=2)
    plt.loglog(domain, lu_factor, '.-', basex=2, basey=2, lw=2)
    plt.loglog(domain, lu_solve, '.-', basex=2, basey=2, lw=2)
    plt.xlabel("n")

    plt.suptitle("Problem 4 Solution")
    plt.show()

# Problem 5
def prob5(n):
    """Return a sparse n x n tridiagonal matrix with 2's along the main
    diagonal and -1's along the first sub- and super-diagonals.
    """
    return sparse.diags([2,-1,2], [-1,0,1], shape=(n,n))

# Problem 6
def prob6(N=10):
    """Time regular and sparse linear system solvers. Plot the system size
    versus the execution times. As always, use log scales where appropriate.
    """
    domain = 2**np.arange(2,N+1)
    solve, spsolve = [], []

    for n in domain:
        A = prob5(n).tocsr()
        b = np.random.random(n)

        start = time()
        spla.spsolve(A, b)
        spsolve.append(time()-start)

        A = A.toarray()
        start = time()
        la.solve(A, b)
        solve.append(time()-start)

    plt.subplot(121)
    plt.plot(domain, spsolve, '.-', lw=2, label="spla.spsolve()")
    plt.plot(domain, solve, '.-', lw=2, label="la.solve()")
    plt.xlabel("n"); plt.ylabel("Seconds")
    plt.legend(loc="upper left")

    plt.subplot(122)
    plt.loglog(domain, spsolve, '.-', basex=2, basey=2, lw=2)
    plt.loglog(domain, solve, '.-', basex=2, basey=2, lw=2)
    plt.xlabel("n")

    plt.suptitle("Problem 6 Solution")
    plt.show()

# Additional Material =========================================================

def ref_fast(A):
    """Alternate REF using an outer product. Fast, but not very intuitive."""
    for i in xrange(A.shape[0]):
        A[i+1:,i:] -= np.outer(A[i+1:,i]/A[i,i], A[i,i:])

def lu2_fast(A):
    """Alternative LU decomposition using an outer product."""
    U = A.copy()
    L = np.eye(A.shape[0])
    for i in xrange(A.shape[0]-1):
        L[i+1:,i] = U[i+1:,i] / U[i,i]
        U[i+1:,i:] -= np.outer(L[i+1:,i], U[i,i:])
    return L, U

def lu_inplace(A):
    """Compute the LU decomposition of the square matrix A *IN PLACE*."""
    for j in xrange(A.shape[0]-1):
        for i in xrange(j+1, A.shape[0]):
            A[i,j] /= A[j,j]
            A[i,j+1:] -= A[i,j] * A[j,j+1:]

def lu_det(A):
    """Compute det(A) using the LU decomposition (via la.lu_factor())."""
    lu, piv = la.lu_factor(A)

    # Determine if there were an even or odd number of row swaps.
    s = (piv != np.arange(A.shape[0])).sum() % 2
    return ((-1)**s) * lu.diagonal().prod()

def cholesky(A):
    L = np.zeros_like(A)
    for i in xrange(A.shape[0]):
        for j in xrange(i):
            L[i,j]=(A[i,j] - np.inner(L[i,:j], L[j,:j])) / L[j,j]
        sl = L[i,:i]
        L[i,i] = sqrt(A[i,i] - np.inner(sl, sl))
    return L

def cholesky_inplace(A):
    for i in xrange(A.shape[0]):
        A[i,i+1:] = 0.
        for j in range(i):
            A[i,j] = (A[i,j] - np.inner(A[i,:j],A[j,:j])) / A[j,j]
        sl = A[i,:i]
        A[i,i] = sqrt(A[i,i] - np.inner(sl, sl))

def cholesky_solve(A, B):
    for j in xrange(A.shape[0]):
        B[j] /= A[j,j]
        for i in xrange(j+1, A.shape[0]):
            B[i] -= A[i,j] * B[j]
    for j in xrange(A.shape[0]-1, -1, -1):
        B[j] /= A[j,j]
        for i in xrange(j):
            B[i] -= A[j,i] * B[j]


from numpy import *
from scipy import *
from pylab import *

import numpy.random as random

import pdb

ion()

i = 1j

def my_slow_fft(f):
    # a slow ifft that **CAN'T** interpolate!
    N = len(f)
    F = zeros(N, dtype=complex)
    for k in range(N):
        for n in range(N):
            F[k] += f[n]*e**(-2.0*pi*i*k*n/N)
    return F

def my_slow_ifft(F):
    # a slow ifft that **CAN'T** interpolate!
    N = len(F)
    f = zeros(N, dtype=complex)
    for n in range(N):
        for k in range(N):
            f[n] += F[k]*e**(2.0*pi*i*k*n/N)
    return (1.0/N)*f

def my_slow_ifft_interp(F,t):
    # a slow ifft that **CAN'T** interpolate!
    N = len(F)
    f = zeros(len(t), dtype=complex)
    for n in range(len(t)):
        for k in range(N):
            f[n] += F[k]*e**(2.0*pi*i*k*t[n])
    return (1.0/N)*f

def my_correct_slow_ifft_interp(F,t):
    # an ifft that **CAN** interpolate
    N = len(F)
    f = zeros(len(t), dtype=complex)
    for n in range(len(t)):
        for k in range(int(N/2)):
            f[n] += F[k]*e**(2.0*pi*i*k*t[n])
        for k in range(int(N/2),N):
            f[n] += F[k]*e**(-2.0*pi*i*(N-k)*t[n])
    return (1.0/N)*f

if __name__ == "__main__":
    # Sample measurements
    ts = linspace(0,1,num=21)
    f = zeros(len(ts))
    hf = int(len(ts)/2)
    f[0:hf] = ts[0:hf]
    #f[hf:] = e**-ts[hf:] + 0.5 - e**-0.5
    f[hf:] = 1.0/ts[hf:] - 1.5
    #f = ts*(ts-1)
    #f = sin(ts*(2.*pi)) + cos(ts*(2.*pi))#-1.0*ts**2#exp(ts)

    F = fftshift(fft(f))
    F_unshifted = fft(f)
    ff = fftshift(ifftshift(f))

    # plot where we are so far
    # this includes F^-1{F(f)} for now
    figure(1)
    plot(ts,f, 'bo-',markerfacecolor='none', mec='blue')
    #ylim(-0.6,0.6)
    title("Original Function")

    figure(2)
    plot(ts,F.real,ts,F.imag)
    legend(('real', 'imaginary'))
    title("Shifted Fourier Transform")

    figure(22)
    plot(ts,F_unshifted.real,ts,F_unshifted.imag)
    legend(('real', 'imaginary'))
    title("Unshifted Fourier Transform")

    figure(3)
    plot(ts,ff)
    #ylim(-0.6,0.6)
    title("Getting Back The Original")

    # now interpolate
    # new timestamps, m samples between former samples.
    Nf = len(ts)

    t = linspace(0,1,num=len(ts)*3)

    Fs = my_slow_fft(f)
    fm = my_slow_ifft(Fs)

    # using the Fourier Coefficients "sas-is"
    fwi = my_slow_ifft_interp(F_unshifted,t)
    fci = my_correct_slow_ifft_interp(F_unshifted,t)

    figure(4)
    plot(ts,Fs.real,ts,Fs.imag)
    legend(('real', 'imaginary'))
    title("My Unshifted Fourier Transform")

    figure(5)
    plot(ts,f, 'bo-',markerfacecolor='none', mec='blue')
    plot(ts,fm,'g^-',markerfacecolor='none', mec='green')
    #ylim(-0.6,0.6)
    title("Original Using My Inverse")

    figure(500)
    plot(ts,f, 'bo-',markerfacecolor='none', mec='blue')
    plot(ts[:-1],my_correct_slow_ifft_interp(fft(f[:-1]),ts[:-1]),'g^-',markerfacecolor='none', mec='green')
    #ylim(-0.6,0.6)
    title("Original Using My Inverse Interp ")

    figure(6)
    plot(ts,f, 'bo-',markerfacecolor='none', mec='blue')
    plot(t,fwi,'go-',markerfacecolor='none', mec='green')
    #ylim(-0.6,0.6)
    legend(('original', 'interpolatation'))
    title("Interpolating with Fourier Coefficients, No Change")

    figure(7)
    plot(ts,f,'bo-',markerfacecolor='none', mec='blue')
    plot(t[:-1],my_correct_slow_ifft_interp(fft(f[:-1]),t[:-1]),'g^-',markerfacecolor='none', mec='green')
    #ylim(-0.6,0.6)
    legend(('original', 'interpolatation'))
    title("Interpolating with Fourier Coefficients Accounting for Aliasing")

    show()


#!/usr/bin/env python
# coding: utf-8

import numpy as np
import random
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
from scipy.stats import t,norm,uniform
from matplotlib.offsetbox import AnchoredText
plt.style.use('ggplot')

def Markov_Monte_Carlo(x0, delta, num_samples):
    """
    Returns samples from t-distribution
    using Metropolis-Hastings algorithim
    and an uniform distribution.
    """
    X = np.zeros(num_samples)  # Samples
    X[0] = x0
    for i in range(1,num_samples):
        r = np.random.uniform(-delta,delta)
        y = X[i-1] + r
        acc_ratio = min(np.log(t.pdf(y,2))-np.log(t.pdf(X[i-1],2)), 0)  # Acceptance ratio
        u = np.random.uniform(0,1)
        if np.log(u) <= acc_ratio:
            X[i] = y
        else:
            X[i] = X[i-1]
    return X

def MCMC_Plots(x0, delta_vec, num_samples):
    half = len(delta_vec)//2
    fig1 = plt.figure(figsize=(16,6), constrained_layout=True)
    gs1 = fig1.add_gridspec(2,3)
    ax = {}
    for i, delta in enumerate(delta_vec[:half+1]):
        ax[i] = fig1.add_subplot(gs1[0, i])
        samples = Markov_Monte_Carlo(x0, delta, num_samples)
        x = np.linspace(-8,8,num_samples)
        z = t.pdf(x,2)
        ax[i].plot(x, z, label='true density', color='cornflowerblue')
        sns.kdeplot(samples, label='MCMC simulation', color='orchid', ax=ax[i])
        ax[i].legend(fontsize=8)
        at = AnchoredText('$\delta$={}'.format(delta), loc='center left', frameon=True)
        ax[i].add_artist(at)
        ax[0].set_ylabel('Density')
        ax[i].set_xlim([-8,8])
    for i, delta in enumerate(delta_vec[half+1:]):
        ax[i] = fig1.add_subplot(gs1[1, i])
        samples = Markov_Monte_Carlo(x0, delta, num_samples)
        x = np.linspace(-8,8,num_samples)
        z = t.pdf(x,2)
        ax[i].plot(x, z, label='true density', color='cornflowerblue')
        sns.kdeplot(samples, label='MCMC simulation', color='orchid', ax=ax[i])
        ax[i].legend(fontsize=8)
        at = AnchoredText('$\delta$={}'.format(delta), loc='center left', frameon=True)
        ax[i].add_artist(at)
        ax[0].set_ylabel('Density')
        ax[i].set_xlim([-8,8])

def Quotient(n, mu, sigma, c):
    return t.pdf(n,2)/(c*norm.pdf(n, mu, sigma))

def Supremum(mu, sigma):
    x = np.linspace(-10,10,100)
    q = norm.pdf(x, mu, sigma) / t.pdf(x,2)
    c = max(q)
    return c

def Rejection_Sampling1(mu, sigma, num_sim):
    """
    Returns samples of a t-distribution using
    rejection sampling method and a fixed
    prob of acceptance. Candidate density is
    a normal distribution.
    """
    c = Supremum(mu, sigma)
    sample1 = []
    for i in range(num_sim):
        u = np.random.uniform(0,1)
        n = np.random.normal(mu, sigma)
        if u <= Quotient(n, mu, sigma, c):
            sample1.append(n)
    return [c, sample1]

def Rejection_Sampling2(mu, sigma, c0, num_sim):
    """
    An alternative method. Prob of acceptance
    is updated during the simulations.
    """

    c_list = []
    sample2 = []
    c = c0
    for i in range(num_sim):
        u = np.random.uniform(0,1)
        n = np.random.normal(mu, sigma)
        if u <= Quotient(n, mu, sigma, c):
            sample2.append(n)
            c = max(c, norm.pdf(n, mu, sigma)/t.pdf(n,2))
            c_list.append(c)
    return [c_list, sample2]

def Rejection_Comparison_Plots(mu, var_vec, c0, num_sim):

     """It compares both methods for rejection sampling."""

    fig, ax = plt.subplots(len(var_vec),2, figsize=(12,10))
    x = np.linspace(-8,8,num_sim)
    for i, v in enumerate(var_vec):
        [c, sample1] = Rejection_Sampling1(mu, np.sqrt(v), num_sim)
        [c_list, sample2] = Rejection_Sampling2(mu, np.sqrt(v), c0, num_sim)
        ax[i,0].plot(x, t.pdf(x,2), label='true density', color='black')
        sns.kdeplot(sample1, label='first approach', color='cornflowerblue', ax=ax[i,0])
        sns.kdeplot(sample2, label='alternative method', color='orchid', ax=ax[i,0])
        ax[0,0].set_title('KDE plots')
        ax[i,0].legend(fontsize=10)
        ax[-1,0].set_xlabel('x', fontsize=14)
        ax[i,0].set_xlim([-8,8])
        ax[i,0].text(-6,0.2,'Candidate density:\n$\mu=0,\;\sigma^2$={}'.format(v))
        ax[i,1].axhline(y=c, label='4', color='r', linestyle='--', alpha=0.6)
        ax[i,1].plot(np.arange(len(c_list)), c_list, label='5', color='b', alpha=0.6)
        ax[0,1].set_title('Plots for $c$', fontsize=12)
        ax[i,1].legend(['$c$ in the first approach','$c$ in the second approach'], fontsize=10)
        ax[-1,1].set_xlabel('steps', fontsize=14)
        ax[i,1].set_xlim(0,50)
        plt.suptitle('Rejection sampling method with fixed $c$ and its alternative', y=1.05)
        plt.tight_layout()

def MCMC_Rejection_Plots(x0, delta, mu, v, s:list):

    """It compares Monte-Carlo Narkov chain with rejection sampling method."""

    fig, ax = plt.subplots(len(s), figsize=(10,10))
    for i, num_samples in enumerate(s):
        first_mathod = Markov_Monte_Carlo(x0, delta, num_samples)
        [c, second_method] = Rejection_Sampling1(mu, np.sqrt(v), num_samples)
        x = np.linspace(-8,8,num_samples)
        ax[i].plot(x, t.pdf(x,2), label='1', color='black')
        sns.kdeplot(first_mathod, label='1', color='cornflowerblue', ax=ax[i])
        sns.kdeplot(second_method, label='2', color='orchid', ax=ax[i])
        ax[i].legend(['true density','MCMC','rejection sampling'], fontsize=10)
        ax[i].text(-6,0.25,'number of simulations={}'.format(num_samples), fontsize=12)
        ax[i].set_ylabel('KDE', fontsize=14)
        ax[i].set_xlim([-8,8])
        plt.suptitle('Comparison between MCMC and rejection sampling,\n for MCMC:'\
                     r' $\delta=1$ and for rejection sampling: $\mu=0,\,\sigma^2=3$'\
                     ' and $c\approx1.25$', y=1.05)
        plt.tight_layout()

# An Example
x0 = 0
delta = 1
mu = 0
v = 3
s = [5000, 10000, 50000]

MCMC_Rejection_Plots(x0, delta, mu, v, s)


"""
Author : Achintya Gupta
Date Created : 22-09-2020
"""

"""
Problem Statement
------------------------------------------------
Q)  The following iterative sequence is defined for the set of positive integers:
                n → n/2 (n is even)
                n → 3n + 1 (n is odd)
    Using the rule above and starting with 13, we generate the following sequence:
    13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
    It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem),
    it is thought that all starting numbers finish at 1.
    Which starting number, under one million, produces the longest chain?
    NOTE: Once the chain starts the terms are allowed to go above one million.
"""
from utils import timing_decorator, find_factors
import numpy as np

chain_mapping={}

def get_chainlen(num):
    global chain_mapping
    chain_len=0
    chain=[num]
    while num!=1:
        if num in chain_mapping:
            return len(chain)+chain_mapping[num]-1
        if num%2==0:
            num=int(num/2)
        else:
            num=int(3*num+1)
        chain.append(num)
    chain_len = len(chain)
    for i in range(len(chain)):
        if i not in chain_mapping:
            chain_mapping[chain[i]] = len(chain[i:])
    return chain_len

@timing_decorator
def logest_collatz(N=1e6):
    num = 0
    cno = 0
    clen = 0

    for i in range(1,int(N)):
        curr_clen = get_chainlen(i)
        if curr_clen>clen:
            cno = i
            clen=curr_clen
    print(f'Starting number with longest collatz chain under < {N} is {cno}, with chain length of {clen}')

logest_collatz()


'''
logsumexp.py

Provides a numerically implementation of logsumexp function,
such that no matter what 1-dimensional input array is provided,
we return a finite floating point answer that does not overflow or underflow.

References
----------
See the math here:
https://www.cs.tufts.edu/comp/135/2020f/hw2.html#logsumexp
'''

import numpy as np

# No other imports allowed

def my_logsumexp(scores_N):
    ''' Compute logsumexp on provided array in numerically stable way.

    This function only handles 1D arrays.
    The equivalent scipy function can handle arrays of many dimensions.

    Args
    ----
    scores_N : 1D NumPy array, shape (N,)
        An array of real values

    Returns
    -------
    a : float
        Result of the logsumexp computation

    Examples
    --------
    >>> _ = np.seterr(all='raise') # Make any numerical issue raise error

    # Example 1: an array without overflow trouble, so you get the basic idea
    >>> easy_arr_N = np.asarray([0., 1., 0.1])

    # Show that your code does OK on Example 1
    >>> easy_ans = my_logsumexp(easy_arr_N)
    >>> print("%.5f" % (easy_ans))
    1.57349

    # Show that naive implementation does OK on Example 1
    >>> naive_ans = np.log(np.sum(np.exp(easy_arr_N)))
    >>> print("%.5f" % (naive_ans))
    1.57349

    # Example 2: an array where overflow would occur in bad implementation
    >>> tough_arr_N = [1000., 1001., 1002.]

    # Show that naive implementation suffers from overflow on Example 2
    >>> naive_ans = np.log(np.sum(np.exp(tough_arr_N)))
    Traceback (most recent call last):
    ...
    FloatingPointError: overflow encountered in exp

    # Show that your implementation does well on Example 2
    >>> ans_that_wont_overflow = my_logsumexp(tough_arr_N)
    >>> np.isfinite(ans_that_wont_overflow)
    True
    >>> print("%.5f" % (ans_that_wont_overflow))
    1002.40761
    '''
    scores_N = np.asarray(scores_N, dtype=np.float64)

    # TODO compute logsumexp in numerically stable way
    # See math on HW2 instructions page for the correct approach
    return 0.0


###################Tools and metrics##################

'''The functions in this python module compute
algebric metrics from adjacency matrices used in MANIA'''

import numpy as num

def AR(A):
	# Computes Assymetry ratio
	B=A-A.transpose()
	total_edges=sum(sum(A-num.diag(num.diag(A))))
	eu=float(sum(sum(abs(B))))/2
	if total_edges==0:
		return 1.0
	return eu/total_edges

def NAR(A):
	# Computes the normalized asymmetry ratio
	l1,l2=num.shape(A)
	if l1!=l2:
		print 'Error: Input matrix not square'
		return None
	l=l1
	total_edges=sum(sum(A-num.diag(num.diag(A))))
	AR_rand=1-(float(total_edges)/(l*(l-1)))
	if AR_rand==0:
		return float('inf')
	return AR(A)/AR_rand

def sim(A1,A2,mode='jac'):
	# Jaccard similarity on edges
	l1,l2=num.shape(A1)
	if l1!=l2:
		print 'Error: Input matrix not square'
		return None
	l=l1
	l1,l2=num.shape(A1)
	if l1!=l or l2!=l:
		print 'Error: Matrices must be of the same size'
		return None
	if mode=='jac':
		P=A1+A2
		P=P-num.diag(num.diag(P))
		P[P>0]=1
		S=A1*A2
		S=S-num.diag(num.diag(S))
		return float(sum(sum(S)))/sum(sum(P))
	else:
		S=A1*A2
		S=S-num.diag(num.diag(S))
		A1=A1-num.diag(num.diag(A1))
		A2=A2-num.diag(num.diag(A2))
		denom=min(sum(sum(A1)),sum(sum(A2)))
		return float(sum(sum(S)))/denom

def density(A):
	# returns the density of edges in A
	l1,l2=num.shape(A)
	if l1!=l2:
		print 'Error: Input matrix not square'
		return None
	l=l1
	total_edges=sum(sum(A-num.diag(num.diag(A))))
	return float(total_edges)/(l*(l-1))


# -*- coding: utf-8 -*-

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

def gauss(mu,std,n=1000):
    noise = np.random.normal(mu,std,n)
    x = np.linspace(mu-3*std,mu+3*std,n)
    y = st.norm.pdf(x,mu,std)

    #Timeseries
    plt.figure()
    plt.title(f"Gaussian Time Series   μ={mu}   σ={std}")
    plt.xlabel("Index")
    plt.ylabel("Value")
    plt.plot(range(len(noise)),noise)
    plt.savefig(f"Gaussian_Timeseries_{mu}_{std}.pdf")
    plt.savefig(f"Gaussuan_Timeseries_{mu}_{std}.png")

    #Histogram
    plt.figure()
    plt.title(f"Gaussian   μ={mu}   σ={std}   bins={int(n/30)}")
    plt.xlabel("Value")
    plt.ylabel("Probability")
    plt.hist(noise,bins=int(n/30),density=True,label='Data')
    plt.plot(x,y,color='red',label='Gaussian')
    plt.legend()
    plt.savefig(f"Gaussian_{mu}_{std}.pdf")
    plt.savefig(f"Gaussian_{mu}_{std}.png")

def poisson(mu,n=1000):
    noise = np.random.poisson(mu,n)
    bins = np.arange(min(noise),max(noise)+1,1)

    #Timeseries
    plt.figure()
    plt.title(f"Poisson Time Series   μ={mu}")
    plt.xlabel("Index")
    plt.ylabel("Value")
    plt.plot(range(len(noise)),noise)
    plt.savefig(f"Poisson_Timeseries_{mu}.pdf")
    plt.savefig(f"Poisson_Timeseries_{mu}.png")

    #Sharp Histogram
    x = np.arange(0,max(noise),0.01)
    y = st.poisson.pmf(x,mu)

    plt.figure()
    plt.title(f"Poisson Sharp   μ={mu}   n={1000}")
    plt.xlabel("Value")
    plt.ylabel("Probability")
    plt.hist(noise,bins=bins,align='left',density=True,label='Data')
    plt.xticks(bins)
    plt.plot(x,y,color='red',label='Poisson')
    plt.legend()
    plt.savefig(f"Poisson_Sharp_{mu}.pdf")
    plt.savefig(f"Poisson_Sharp_{mu}.png")

    #Fuzzy Histogram
    x = np.arange(0,max(noise),1)
    y = st.poisson.pmf(x,mu)

    plt.figure()
    plt.title(f"Poisson Fuzzy   μ={mu}   n={1000}")
    plt.xlabel("Value")
    plt.ylabel("Probability")
    plt.hist(noise,bins=bins,align='left',density=True,label='Data')
    plt.xticks(bins)
    plt.plot(x,y,color='red',label='Poisson')
    plt.legend()
    plt.savefig(f"Poisson_Fuzzy_{mu}.pdf")
    plt.savefig(f"Poisson_Fuzzy_{mu}.png")

#1
gauss(0,1)

#2
gauss(10.345,2.338)

#3
poisson(2)

#4
poisson(3.45)


# # https://www.geeksforgeeks.org/implement-sigmoid-function-using-numpy/
import copy
import numpy as np
np.random.seed(0)

# compute sigmoid nonlinearity
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output

# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):
    return output*(1-output)

# sigmoid_test = sigmoid([22,44])

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

# x = np.linspace(-10, 10, 100)
x =1

x = [-1,0,0.5,1]
# x = [-1.33,0.33,0.533,1.33]
# z = 1/(1 + np.exp(-x))

# for val in x:
#     print(val,sigmoid(val))

for val in x:
    print(val,sigmoid(val),sigmoid_output_to_derivative(sigmoid(val)))

# 1 0.7310585786300049
# 0.5 0.6224593312018546
# 0 0.5
# -1 0.2689414213699951

# plt.plot(x, z)
# plt.xlabel("x")
# plt.ylabel("Sigmoid(X)")

# plt.show()


import numpy as np
import scipy.optimize as op
import scipy.linalg as la
import numdifftools as nd
from matplotlib import pyplot as plt
from mpl_toolkits import mplot3d
from time import perf_counter

#returns the minimum of a multivariable function using gradient descent
def gradient_descent(f, xk, delta = 0.01, plot=False, F = None, axlim = 10):
    """
    f: multivariable function with 1 array as parameter
    xk : a vector to start descent
    delta : precision of search
    plot : option to plot the results or not
    F : the function f expressed with 2 arrays in argument (X,Y) representing the colomns xk[0] and xk[1] for ploting issues. used only if plot == True
    axlim : limit of the plot 3 axis (x,y,z)
    """
    if plot : ax = plt.axes(projection='3d')
    A = []
    t = perf_counter()
    dk = nd.Gradient(f)(xk)
    while la.norm(dk) > delta :
        if plot and len(A) < 10 : A.append(xk)
        xt = xk
        phi = lambda s : f(xk - s * dk)
        alpha = op.newton(phi, 1)
        xk -= alpha * dk
        if plot and len(A) < 10 : A.append(xk)
        dk = nd.Gradient(f)(xk)
        if la.norm(xk - xt) < delta : break
    t = perf_counter() - t
    print("execution time: ",t)
    if plot :
        for u in A:
            ax.scatter(u[0], u[1], f(u), c = 'b', s = 50)
        ax.scatter(xk[0], xk[1], f(xk), c = 'r', s = 50,label="optimum")
        x = np.arange(-axlim, axlim, axlim/100)
        y = np.arange(-axlim, axlim, axlim/100)
        X, Y = np.meshgrid(x, y)
        Z = F(X,Y)
        ax.set_xlabel('x', labelpad=20)
        ax.set_ylabel('y', labelpad=20)
        ax.set_zlabel('z', labelpad=20)
        surf = ax.plot_surface(X, Y, Z, cmap = plt.cm.cividis)
        plt.legend()
        plt.title("optimizition with Gradient Descent")
        plt.show()
    return xk

#returns the minimum of a multivariable function using conjugate gradient
def conjugate_gradient(f, x, plot=False, F = None,axlim = 10):
    """
    f: multivariable function with 1 array as parameter
    x : a vector to start descent
    plot : option to plot the results or not
    F : the function f expressed with 2 arrays in argument (X,Y) representing the colomns x[0] and x[1] for ploting issues. used only if plot == True
    axlim : limit of the plot 3 axis (x,y,z)
    """
    if plot : ax = plt.axes(projection='3d')
    A = []
    t = perf_counter()
    d = -nd.Gradient(f)(x)
    q = nd.Hessian(f)(x)
    n = len(x)
    for k in range(1, n):
        if plot and len(A) < int(n/3) : A.append(x)
        alpha = (d.T@d)/(d.T@q@d)
        x += alpha * d
        beta = (nd.Gradient(f)(x).T@q@d)/(d.T@q@d)
        d = beta * d - nd.Gradient(f)(x)
    t = perf_counter() - t
    print("execution time: ",t)
    if plot :
        for u in A:
            ax.scatter(u[0], u[1], f(u), c = 'b', s = 50)
        ax.scatter(x[0], x[1], f(x), c = 'r', s = 50,label="optimum")
        x = np.arange(-axlim, axlim, axlim/100)
        y = np.arange(-axlim, axlim, axlim/100)
        X, Y = np.meshgrid(x, y)
        Z = F(X,Y)
        ax.set_xlabel('x', labelpad=20)
        ax.set_ylabel('y', labelpad=20)
        ax.set_zlabel('z', labelpad=20)
        surf = ax.plot_surface(X, Y, Z, cmap = plt.cm.cividis)
        plt.legend()
        plt.title("optimizition with Conjugate Gradient")
        plt.show()
    return x

#verifies if a function is defined positive at a point x
def is_pos_def(f, x):
    """
    f: multivariable function with 1 array as parameter
    x : a vector where to verify if f is definite positive
    """
    m = nd.Hessian(f)(x)
    return np.all(np.linalg.eigvals(m) > 0)

#returns the minimum of a multivariable function using Newton descent
def newton_descent(f, x, delta = 0.01, plot=False, F = None, axlim = 10):
        """
    f: multivariable function with 1 array as parameter
    x : a vector to start descent
    delta : precision of search
    plot : option to plot the results or not
    F : the function f expressed with 2 arrays in argument (X,Y) representing the colomns x[0] and x[1] for ploting issues. used only if plot == True
    axlim : limit of the plot axis (x,y,z)
    """
    d = -la.inv(nd.Hessian(f)(x))@nd.Gradient(f)(x)
    m = nd.Hessian(f)(x)
    if la.det(m) == 0:
        return ValueError
    else :
        if plot : ax = plt.axes(projection='3d')
        A = []
        t = perf_counter()
        while la.norm(d) > delta:
            if plot and len(A) < 10 : A.append(x)
            phi = lambda s : f(x - s * d)
            alpha = op.newton(phi, 1)
            x -= alpha * d
            dt = nd.Hessian(f)(x)
            if is_pos_def(f, x) : d = -la.inv(nd.Hessian(f)(x))@nd.Gradient(f)(x)
            else :
                u = la.eigvals(f)(x)
                epsilon = min(u)
                n = len(x)
                d = -la.inv((epsilon * np.identity(n) + nd.Hessian(f)(x))) @ nd.Gradient(f)(x)
        t = perf_counter() - t
        print("execution time: ",t)
        if plot :
            for u in A:
                ax.scatter(u[0], u[1], f(u), c = 'b', s = 50)
            ax.scatter(x[0], x[1], f(x), c = 'r', s = 50,label="optimum")
            x = np.arange(-axlim, axlim, axlim/100)
            y = np.arange(-axlim, axlim, axlim/100)
            X, Y = np.meshgrid(x, y)
            Z = F(X,Y)
            ax.set_xlabel('x', labelpad=20)
            ax.set_ylabel('y', labelpad=20)
            ax.set_zlabel('z', labelpad=20)
            surf = ax.plot_surface(X, Y, Z, cmap = plt.cm.cividis)
            plt.legend()
            plt.title("optimizition with Newton descent")
            plt.show()
    return x


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

def loadDataset(filename, split, trainingSet=[] , testSet=[]):
    with open(filename, 'r') as csvfile:
        lines = csv.reader(csvfile)
        dataset = list(lines)
        for x in range(len(dataset)):
            for y in range(2):
                dataset[x][y] = float(dataset[x][y])
            if random.random() < split:
                trainingSet.append(dataset[x])
            else:
                testSet.append(dataset[x])

def pca(X,n):

    #1st Step : subtract mean
    avg = np.mean(X,axis=0)
    avg = np.tile(avg,(X.shape[0],1))
    X -= avg;
    #print(avg)

    #2nd Step : covariance matrix
    C = np.dot(X.transpose(),X)/(X.shape[0]-1)

    #3rd Step : Eigen Value, Eigen Vector
    eig_values,eig_vecs = np.linalg.eig(C)

    #4rd Step : Select n개의 PC
    idx = np.argsort(eig_values)[-n:][::-1]
    eig_values = eig_values[idx]
    eig_vecs = eig_vecs[:,idx]

    #print(eig_values.argsort())
    #print(eig_values.argsort()[-n:])
    #print(idx)
    #print(eig_values)
    #print(eig_vecs)

    #5th Step : new coordinate in new space
    Y = np.dot(X,eig_vecs)

    #6th Step : reconstruction
    rec=np.dot(eig_vecs,Y.transpose())
    Score=np.linalg.norm(X.transpose()-rec,axis=0)
    #print(rec)
    #print(Score)

    return (X.transpose(), rec, Score.transpose(), eig_vecs, eig_values)

def main():

	# prepare data
    trainingSet=[]
    testSet=[]
    split = 0.8
    random.seed(100)
    loadDataset('ex_pca5.csv', split, trainingSet, testSet)
    #print('Train set: ' + repr(len(trainingSet)))
    #print('Test set: ' + repr(len(testSet)))

    # n=PC개수 (Hyper parameter)
    n=1
    trainX=np.array(trainingSet)
    pca_result=pca(trainX[:,:-1].astype(np.float),n)

   # print('pca result:',pca_result)

    x=pca_result[0][0]
    y=pca_result[0][1]
    Score=pca_result[2]*100

    print('Eigen Value : ', pca_result[4])
    print('Eigen Vector : ', pca_result[3])
    print('Data X : ', np.transpose(pca_result[0])[:10])
    print('Reconstruction : ', np.transpose(pca_result[1])[:10])
    print('Novelty Score : ', np.transpose(pca_result[2])[:10])

    x_rec=pca_result[1][0]
    y_rec=pca_result[1][1]

    plt.figure(figsize=(6, 6), dpi=80)
    plt.scatter(x,y,s=Score);
    plt.scatter(x_rec,y_rec,s=20);
    plt.xticks(np.arange(-3,3,0.5))
    plt.yticks(np.arange(-3,3,0.5))

main()

03 Novelty Detection/Tutorial 09 - Distance and reconstruction-based novelty detection/PCA/PCA_ND.py


# https://projecteuler.net/problem=14
# Run with: 'python solve14.py'
# using Python 3.6.9
# by Zack Sargent

# Prompt:

# The following iterative sequence is defined for the set of positive integers:
#   n → n/2 (n is even)
#   n → 3n + 1 (n is odd)
# Using the rule above and starting with 13, we generate the following sequence:
#   13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
# It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms.
# Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
# Which starting number, under one million, produces the longest chain?
# NOTE: Once the chain starts the terms are allowed to go above one million.

# numba is only used to improve performance.
# remove lines 20 and 22 if you don't want it.
from numba import njit

@njit
def collatz_sequence(n: int, length: int) -> int:
    length += 1
    if n == 1:
        return length
    elif n % 2 == 0:
        return collatz_sequence(n / 2, length)
    else:
        return collatz_sequence((3*n)+1, length)

max_num: int = 0
max_size: int = 0

GOAL: int = 1_000_000
# divide by 1.5 to limit the range we check
for i in range(round(GOAL // 1.5), GOAL + 1):
    size = collatz_sequence(i, 0)
    if size > max_size:
        max_size = size
        max_num = i

print(max_num)
# -> 837799


## module gaussNodes
''' x,A = gaussNodes(m,tol=10e-9)
    Returns nodal abscissas {x} and weights {A} of
    Gauss-Legendre m-point quadrature.
'''
import math
import numpy as np

def gaussNodes(m,tol=10e-9):

    def legendre(t,m):
        p0 = 1.0; p1 = t
        for k in range(1,m):
            p = ((2.0*k + 1.0)*t*p1 - k*p0)/(1.0 + k )
            p0 = p1; p1 = p
        dp = m*(p0 - t*p1)/(1.0 - t**2)
        return p,dp

    A = np.zeros(m)
    x = np.zeros(m)
    nRoots = int((m + 1)/2)         # Number of non-neg. roots
    for i in range(nRoots):
        t = math.cos(math.pi*(i + 0.75)/(m + 0.5))# Approx. root
        for j in range(30):
            p,dp = legendre(t,m)    # Newton-Raphson
            dt = -p/dp; t = t + dt  # method
            if abs(dt) < tol:
                x[i] = t; x[m-i-1] = -t
                A[i] = 2.0/(1.0 - t**2)/(dp**2) # Eq.(6.25)
                A[m-i-1] = A[i]
                break
    return x,A


import numpy as np

def polykernel(x1, x2, offset=0, degree=2):
    """
    polynomial kernel of the form:
    k(x1, x2) = ((x1' * x2) + offset) ^ degree
    """
    return (np.dot(x1, x2) + offset)**degree

def rbf(x1, x2, sigma=1):
    """
    rbf kernel. sigma is the smoothing parameter
    k(x1, x2) = exp(-||x1 - x2||^2 / 2 * sigma^2)
    """
    return np.exp(-(np.linalg.norm(x1 - x2) / (2 * sigma**2)))


from time import perf_counter
from math import sqrt, ceil, log
from matplotlib import pyplot as plt
import numpy as np

#unrestricted search with fixed step
def usfs(f, a, b, p = 0.01, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    p : step size
    plot : a parameter to choose either to plot or not the results
    """
    if plot :
        X = np.linspace(a,b,1000)
        plt.plot(X, f(X))
    t = perf_counter()
    iter = 0
    A = []
    while f(a) > f(a + p) and a <= b :
        iter += 1
        a += p
        if plot and len(A) < 10 : A.append(a)
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        for e in A :
            plt.plot(e,f(e),"o")
    if a > b :
        plt.plot(a-p, f(a-p),"s",label="optimum")
        plt.show()
        return a - p
    if plot :
        plt.plot(a,f(a),"s",label="optimum")
        plt.title("optimization with fixed step search")
        plt.legend()
        plt.show()
    return a

#unrestricted search with accelerated step
def usas(f, a, b, p = 0.01, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    p : step size
    plot : a parameter to choose either to plot or not the results
    """
    t = perf_counter()
    it , step = a, 0
    A = []
    while  step != p :
        if it > b : it  -= step / 2
        step , i1, i2 = p, f(it), f(it + p)
        while i1 > i2 and it < b :
            if plot and len(A) < 10 : A.append(it)
            it += step
            i1 = i2
            step *= 2
            i2 = f(it + step)
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot(it,i1,"s",label="optimum")
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
        for e in A :
            plt.plot(e,f(e),"o")
        plt.legend()
        plt.title("optimization with accelerated step search")
        plt.show()
    return it

#exhaustive search
def bf(f, a, b, n = 1000, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    n : number of divisions of the interval
    plot : a parameter to choose either to plot or not the results
    """
    t = perf_counter()
    p = (b - a) / (n - 1)
    it = a
    L = []
    A = []
    while a <= b :
        L.append(f(a))
        a += p
        if plot : A.append(a)
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot(it + L.index(min(L))*p,f(it + L.index(min(L))*p),"s",label="optimum")
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
        for e in A :
            plt.plot(e,f(e),"o")
        plt.legend()
        plt.title("optimization with Brute Force method")
        plt.show()
    return it + L.index(min(L))*p

#binary search
def bs(f, a, b, e = 0.01, delta = 0.001, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    e : search precision
    delta : a factor relied to the binary search
    plot : a parameter to choose either to plot or not the results
    """
    t = perf_counter()
    A = []
    mid = (a + b) / 2
    n = ceil(log((b - a - delta) / (2 * e * (b - a) - delta)) * (2 / log(2)))
    if n%2 : n+=1
    if plot :
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
    for i in range(n) :
        y1, y2 = f(mid - (delta / 2)), f(mid + (delta / 2))
        if y2 > y1 : b = mid + (delta / 2)
        else : a = mid - (delta / 2)
        mid = (a + b) / 2
        if plot : A.append(mid)
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot(mid,f(mid),"s",label="optimum")
        for u in A :
            plt.plot(u,f(u),"o")
        plt.legend()
        plt.title("optimization with Binary Search method")
        plt.show()
    return mid

#interval halving method
def halv(f, a, b, e = 0.01, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    e : search precision
    plot : a parameter to choose either to plot or not the results
    """
    t = perf_counter()
    A = [[],[],[]]
    n = ceil((2 * log(2 * e))/ log(1 / 2)) + 1
    if (n%2)==0 : n += 1
    if plot :
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
    for i in range(n):
        x0, x1, x2 = (a+b)/2, (a+b)/3, (2*(a+b))/3
        y0, y1, y2 = f(x0), f(x1), f(x2)
        if plot :
            A[0].append(x0)
            A[1].append(x1)
            A[2].append(x2)
        if y1 < y0 < y2 : b = x0
        elif y1 > y0 > y2 :	a = x0
        else :
            a = x1
            b = x2
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot((a+b)/2, f((a+b)/2),"s",color="yellow",label="optimum")
        for i in range(n):
            plt.plot(A[0][i],f(A[0][i]),"o",color='red')
            plt.plot(A[1][i],f(A[1][i]),"o",color='blue')
            plt.plot(A[2][i],f(A[2][i]),"o",color='green')
        plt.legend()
        plt.title("optimization with Interval Halving method")
        plt.show()
    return (a + b) / 2

#fibonacci method
def fibo(f, a, b, n = 20, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    n : number of iterations (deepth)
    plot : a parameter to choose either to plot or not the results
    """
    A = [[],[]]
    if plot :
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
    t = perf_counter()
    f0, f1, f2 = 1, 1, 2
    for i in range(n-2) :
        f0 = f1
        f1 = f2
        f2 = f1 + f0
    c = a + (f0 / f2) * (b - a)
    d = a + (f1 / f2) * (b - a)
    y1 = f(c)
    y2 = f(d)
    for i in range(n):
        if plot and len(A[0]) < 10 :
            A[0].append(c)
            A[1].append(d)
        if y1 < y2 :
            b = d
            d = c
            c = a + (b - d)
            y2 = y1
            y1 = f(c)
        else :
            a = c
            c = d
            d = a + (b - c)
            y1 = y2
            y2 = f(d)
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot(c,f(c),"s",label="optimum")
        for i in range(len(A[0])):
            plt.plot(A[0][i],f(A[0][i]),"o",color='red')
            plt.plot(A[1][i],f(A[1][i]),"o",color='blue')
        plt.legend()
        plt.title("optimization with Fibonacci method")
        plt.show()
    return c

#golden ratio method
def golden(f, a, b, e = 0.01, plot=False):
    """
    f: monovariable function
    a : start of search interval
    b : end of search interval
    e : search precision
    plot : a parameter to choose either to plot or not the results
    """
    A = [[],[]]
    if plot and len(A[0]) < 10 :
        X = np.linspace(a,b,1000)
        plt.plot(X,f(X))
    t = perf_counter()
    phi = (sqrt(5) - 1) / 2
    Ln = 1
    f0, f1, f2 = 1, 1, 2
    for i in range(100) :
        f0 = f1
        f1 = f2
        f2 = f1 + f0
    c = a + (f0 / f2) * (b - a)
    d = a + (f1 / f2) * (b - a)
    y1 = f(c)
    y2 = f(d)
    while Ln > e:
        if plot :
            A[0].append(c)
            A[1].append(d)
        if y1 < y2 :
            b = d
            d = c
            c = a + (b - d)
            y2 = y1
            y1 = f(c)
        else :
            a = c
            c = d
            d = a + (b - c)
            y1 = y2
            y2 = f(d)
        Ln *= phi
    t = perf_counter() - t
    print("time execution: ",t)
    if plot :
        plt.plot(c,f(c),"s",label="optimum")
        for i in range(len(A[0])):
            plt.plot(A[0][i],f(A[0][i]),"o",color='red')
            plt.plot(A[1][i],f(A[1][i]),"o",color='blue')
        plt.title("optimization with Golden section")
        plt.legend()
        plt.show()
    return c


import numpy as np

__all__ = ['cart2pol', 'pol2cart', 'cart2sphere', 'sphere2cart']

def _fix_angle(a):
    while a > np.pi:
        a -= np.pi
    while a < -np.pi:
        a += np.pi
    return a

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

def pol2cart(r, ang):
    x = r * np.cos(ang)
    y = r * np.sin(ang)
    return x, y

def cart2sphere(x, y, z):
    r = np.sqrt(x**2 + y**2 + z**2)
    # theta = np.arctan2(np.sqrt(x**2 + y**2), z)
    theta = np.arccos(z / r)
    phi = np.arctan2(y, x)
    return r, theta, phi

def sphere2cart(r, theta, phi):
    theta = _fix_angle(theta)
    phi = _fix_angle(phi)
    x = r * np.sin(theta) * np.cos(phi)
    y = r * np.sin(theta) * np.sin(phi)
    z = r * np.cos(theta)
    return x, y, z


from math import pi

from jax import vmap
from jax.config import config
import jax.numpy as jnp
import numpy.random as np_random

from diffjeom import (
    check_diff_bianchi,
    check_riemann_sym,
    get_christoffel2,
    get_ricci_scalar,
    get_ricci_tensor,
    get_riemann,
)

# Necessary for these tests
config.update("jax_enable_x64", True)

"""
Test differential geometry functions on S^2, the unit 2-sphere.
"""

def get_xs(n):
    return jnp.stack(
        [
            np_random.rand(n) * pi,
            np_random.rand(n) * 2 * pi,
        ],
        axis=1,
    )

def get_g(x):
    th, _ = x
    return jnp.diag(jnp.array([1, jnp.cos(th) ** 2]))

def get_christoffel2_true(x):
    """
    See https://math.stackexchange.com/questions/3057630/calculation-of-christoffel-symbol-for-unit-sphere.
    """
    th, _ = x
    c_th = jnp.cos(th)
    s_th = jnp.sin(th)
    Gamma_th = jnp.diag(jnp.array([0, s_th * c_th]))
    Gamma_ph = jnp.array([[0, -s_th / c_th], [-s_th / c_th, 0]])
    return jnp.stack([Gamma_th, Gamma_ph], axis=0)

def get_ricci_tensor_true(x):
    th, _ = x
    return jnp.diag(jnp.array([1, jnp.cos(th) ** 2]))

def test_test_christoffel2(n=10):
    xs = get_xs(n)
    Gammas = vmap(lambda x: get_christoffel2(x, get_g))(xs)
    Gamma_trues = vmap(get_christoffel2_true)(xs)

    jnp.allclose(Gammas, Gamma_trues)

def test_riemann(n=10):
    xs = get_xs(n)
    g = vmap(get_g)(xs)
    get_riemann_pt = lambda x: get_riemann(x, get_g)
    R = vmap(get_riemann_pt)(xs)

    check_riemann_sym(g, R)
    check_diff_bianchi(xs, get_riemann_pt)

def test_ricci_tensor(n=10):
    xs = get_xs(n)
    Rt_trues = vmap(get_ricci_tensor_true)(xs)
    Rts = vmap(lambda x: get_ricci_tensor(x, get_g))(xs)

    jnp.allclose(Rts, Rt_trues)

def test_ricci_scalar(n=10):
    """
    See https://math.stackexchange.com/questions/3057630/calculation-of-christoffel-symbol-for-unit-sphere.
    """
    xs = get_xs(n)
    Rss = vmap(lambda x: get_ricci_scalar(x, get_g))(xs)

    jnp.allclose(Rss, 2.0)


# Code was created by M. Heriyanto, 2020/01/13
# https://github.com/ezygeo-ai/machine-learning-and-geophysical-inversion/blob/master/scripts/derrivatives_in_python.py

import sympy as sym
# https://scipy-lectures.org/packages/sympy.html

# === example
x = sym.Symbol('x')
y = sym.Symbol('y')

print(sym.diff(3*x**2 + 3*y, x))
print(sym.diff(3*x**2 + 3*y, y))

def func(vx, vy):
    return 3*vx**2 + 3*vy

print(sym.diff(func(x, y), x))
print(sym.diff(func(x, y), y))

# === SP case
x = sym.Symbol('x')
x0 = sym.Symbol('x0')
alpa = sym.Symbol('alpa')
h = sym.Symbol('h')
K = sym.Symbol('K')

# forward function
def func_SP(vx, vx0, va, vh, vk):
    return vk * (((vx-vx0)*sym.cos(va)-vh*sym.sin(va))/((vx-vx0)**2 + vh**2)**3/2)

# x0
print(sym.diff(func_SP(x, x0, alpa, h, K), x0))

# alpa
print(sym.diff(func_SP(x, x0, alpa, h, K), alpa))

# h
print(sym.diff(func_SP(x, x0, alpa, h, K), h))

# K
print(sym.diff(func_SP(x, x0, alpa, h, K), K))


import numpy as np
import re
import random

H_matrix = np.mat([
    [1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1],
    [0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1],
    [0 , 0 , 0 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1],
    [0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1],
])

G_matrix = np.mat([
    [1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0],
    [1 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0],
    [0 , 1 , 0 , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0],
    [1 , 1 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0],
    [1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0],
    [0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0],
    [1 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0],
    [0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0],
    [1 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0],
    [0 , 1 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 0],
    [1 , 1 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1]
])

def hamming_encode(data: str) -> str:
    d = []
    for x in data:
        d.append(int(x))
    res = (np.mat(d) * G_matrix) % 2
    return re.sub(r'[^0-1]', '', str(res))
    # return str(res).replace(' ', '').replace('[', '').replace(']', '')

def error_occur(data: str) -> str:
    tmp = random.randint(0, 14)
    return data[:tmp] + ('0' if data[tmp] == '1' else '1') + data[tmp + 1:]

def hamming_decode(data: str, error = False) -> str:
    if error:
        data = error_occur(data)
    d = []
    for x in data:
        d.append(int(x))
    diff = (np.mat(d) * H_matrix.T) % 2
    diff = int(re.sub(r'[^0-1]', '', str(diff)[::-1]), 2)
    if diff != 0:
        diff -= 1
        data = data[:diff] + ('0' if data[diff] == '1' else '1') + data[diff + 1:]
    return data[2] + data[4:7] + data[8:]

if __name__ == "__main__":
    file_lines = []
    f = open('lab2_data/hamming_15_11.txt')
    file_lines = f.readlines()
    f.close()
    print (len(file_lines))
    for line in file_lines:
        data = line.split(',')
        data[0], data[1] = data[0].strip(), data[1].strip()
        assert (hamming_encode(data[0]) == data[1])
        assert (hamming_decode(data[1]) == data[0])
        assert (hamming_decode(data[1], True) == data[0])


"""
Exercise 2
Implement the Gradient Descent
Your challenge is to implement a generic prgram using the gradient
descent algorithm to find the minimum value of a single-variable
function specified as input by the user. The program should also create
a graph of the function and show all the intermediate values it found
before finding the minimum.
"""

from sympy import Derivative, Symbol, sympify, solve
from sympy.core.sympify import SympifyError
import matplotlib.pyplot as plt

epsilon = 1e-6
step_size = 1e-4

def grad_descent(init_val, function, x):

    if not solve(function):
        print("Cannot continue, solution for {0}=0 does not exist".format(function))
        return None

    x_new = init_val - step_size * function.subs({x: init_val}).evalf()
    x_old = init_val

    x_traversed = []

    while abs(x_old - x_new) > epsilon:
        x_traversed.append(x_new)
        x_old = x_new
        x_new = x_old - step_size * function.subs({x: x_old}).evalf()

    return x_new, x_traversed

def frange(start, final, interval):

    numbers = []
    while start < final:
        numbers.append(start)
        start += interval

    return numbers

def create_plot(x_traversed, function, var):
    x_val = frange(-10, 10, 0.01)
    y_val = [function.subs({var: x}) for x in x_val]
    plt.plot(x_val, y_val, "black")

    y_traversed = [function.subs({var: x}) for x in x_traversed]
    plt.plot(x_traversed, y_traversed, "green")

    plt.legend(["Function", "Intermediate points"])
    plt.show()

def validate_function(function):

    try:
        return sympify(function)
    except SympifyError:
        print("Invalid function entered")
        sys.exit(1)

    return None

def main():
    function = input("Provide a function of one variable: ")
    function = validate_function(function)

    variable = input("Enter the variable to differentiate with respect to: ")
    variable = Symbol(variable)

    init_val = input("Enter the initial value of the variable: ")
    init_val = float(init_val)

    derivative = Derivative(function, variable).doit()
    var_min, x_traversed = grad_descent(init_val, derivative, variable)

    if var_min:
        print("{} : {}".format(variable.name, var_min))
        create_plot(x_traversed, function, variable)

if __name__ == "__main__":
    main()


#Author: Daniel Moraes dos Santos
import numpy as np
import algebra

def multiply_matrix(A, B):
    if A.shape[1] != B.shape[0]:
        raise NameError('deu ruim')

    C = np.zeros(shape=(A.shape[0], B.shape[1]))

    for i in range(C.shape[0]):
        for j in range(C.shape[1]):
            for k in range(B.shape[0]):
                C[i, j] += A[i, k] * B[k, j]

    return C

def transpose_matrix(A):
    t = np.zeros(shape=(A.shape[1], A.shape[0]))

    for i in range(t.shape[0]):
        for j in range(t.shape[1]):
            t[i, j] = A[j, i]

    return t

def generate_A(points):
    A = np.zeros(shape=(len(points), k + 1))

    for i, point in enumerate(points):
        for j in range(k + 1):
            A[i, j] = point[0] ** abs(k - j)

    return A

def generate_b(points):
    b = np.zeros(shape=(len(points), 1))

    for i, point in enumerate(points):
        b[i, 0] = point[1]

    return b

if __name__ == '__main__':
    points_list = [(-2., -3.), (-1., 0.), (1., 0.), (2., -3.), (2., -1.), (4., 0.), (5., 2.)]
    # points_list = [(-2., 0.), (-1., 4.), (0., 5.), (1., 4.), (2., 0.)] # ex 1
    # points_list = [(-4, 0), (3, 0), (0, 0), (-3, -576), (4, -576), (-1, 144), (2, 144)] # ex 2
    # points_list = [(0., 1.), (2., 1.), (2., 5.), (6., 2.), (6., 4.), (6., 6.)] # ex 3

    k = 3  # ax3+bx2+cx1+d=y
    # k = 4
    # k = 6
    # k = 2

    A = generate_A(points_list)
    b = generate_b(points_list)
    print A
    print b

    At = transpose_matrix(A)

    An = multiply_matrix(At, A)
    bn = multiply_matrix(At, b)
    # An = At.dot(A)
    # bn = At.dot(b)

    print An
    print bn

    solver = algebra.Algebra()

    print solver.solve_system(An, bn)

Trabalho 6 (Daniel M.)/polynomial_interpolation.py


import numpy as np
from scipy import linalg as la
from scipy import optimize as opt
from matplotlib import pyplot as plt

# Problem 1
def conjugateGradient(b,x0,Q,tol = .0001):
    """Use the Conjugate Gradient Method to find the solution to the linear
    system Qx = b.

    Parameters:
        b  ((n, ) ndarray)
        x0 ((n, ) ndarray): An initial guess for x.
        Q  ((n,n) ndarray): A positive-definite square matrix.
        tol (float)

    Returns:
        x ((n, ) ndarray): The solution to the linear systm Qx = b, according
            to the Conjugate Gradient Method.
    """
    xk = x0
    rk = Q.dot(x0)-b
    dk = -rk
    k = 0
    while la.norm(rk) > tol:
        alphak = rk.T.dot(rk)/dk.T.dot(Q.dot(dk))
        xk1 = xk + alphak*dk
        rk1 = rk + alphak*Q.dot(dk)
        betak1 = rk1.T.dot(rk1)/rk.T.dot(rk)
        dk1 = -rk1 + betak1*dk
        k = k+1
        xk = xk1
        rk = rk1
        dk = dk1
    return xk1

# Problem 2
def prob2(filename = 'linregression.txt'):
    """Use conjugateGradient() to solve the linear regression problem with
    the data from linregression.txt.
    Return the solution x*.
    """
    data = np.loadtxt(filename)
    m,n = data.shape
    b = data[:,0]
    A = np.column_stack((np.ones(m),data[:,1:]))
    x0 = np.random.random(n)
    return conjugateGradient(A.T.dot(b),x0,A.T.dot(A))

    '''Correct Answer:
    [   -3482258.6159527,   15.06187214,    -0.03581918,    -2.0202298
        -1.03322686,        -0.05110411,    1829.15145504               ]

    or

    [ -3.48225866e+06   1.50618728e+01  -3.58191800e-02  -2.02022981e+00
      -1.03322687e+00  -5.11041030e-02   1.82915148e+03 ]
    '''

# Problem 3
def prob3(filename = 'logregression.txt'):
    """Use scipy.optimize.fmin_cg() to find the maximum likelihood estimate
    for the data in logregression.txt.
    """
    def objective(b):
        return (np.log(1+np.exp(x.dot(b))) - y*(x.dot(b))).sum()

    data = np.loadtxt(filename)
    m,n = data.shape
    y = data[:,0]
    x = np.empty_like(data)
    x[:,0] = np.ones_like(data[:,0])
    x[:,1:] = data[:,1:]
    y = data[:,0]

    guess = np.ones(4)
    b = opt.fmin_cg(objective, guess)

    return b

    '''Correct Answer:
    [-0.41307717, 0.92181585, 0.21007539, -0.55791808]
    '''

if __name__ == '__main__':
    n = 10
    A = np.random.random((n,n))
    Q = A.T.dot(A)
    b = np.random.random(n)
    x0 = np.random.random(n)
    x = conjugateGradient(b, x0, Q)
    # np.set_printoptions(suppress=True)
    if not np.allclose(x, la.solve(Q,b)):
        raise ValueError("Problem 1 Failed")
    print prob2()
    print prob3()
    np.set_printoptions()
    # test()


import math
import numpy as np
from random import randint

def PrimeGen(n=10000):
    """ Returns  a list of primes < n. Used Robert William method of high speed prime generation. """
    assert n>=2
    sieve = np.ones(n/2, dtype=np.bool)
    for i in range(3,int(n**0.5)+1,2):
        if sieve[i/2]:
            sieve[i*i/2::i] = False
    return np.r_[2, 2*np.nonzero(sieve)[0][1::]+1]

def lcm(p,q):
    ''' Computes LCM between two Integers p,q'''
    return (p*q)/math.gcd(p,q)

def extended_gcd(aa, bb):
    lastremainder, remainder = abs(aa), abs(bb)
    x, lastx, y, lasty = 0, 1, 1, 0
    while remainder:
        lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
        x, lastx = lastx - quotient*x, x
        y, lasty = lasty - quotient*y, y
    return lastremainder, lastx * (-1 if aa < 0 else 1), lasty * (-1 if bb < 0 else 1)

def modinv(a, m):
    g, x, y = extended_gcd(a, m)
    if g != 1:
        raise ValueError
    return x % m

def mult_inverse(e, lamda):
    ''' Using the Euclidian extended algorithm '''
    d = 0
    x1 = 0
    x2 = 1
    y1 = 1
    temp_lamda = lamda

    while e > 0:
        temp1 = temp_lamda/e
        temp2 = temp_lamda - temp1 * e
        temp_lamda = e
        e = temp2

        x = x2- temp1* x1
        y = d - temp1 * y1

        x2 = x1
        x1 = x
        d = y1
        y1 = y

    if temp_lamda == 1:
        return d + lamda

def keyGen():
    ''' Generate  Keypair '''
    i_p=randint(0,20)
    i_q=randint(0,20)
    # Instead of Asking the user for the prime Number which in case is not feasible,
    # generate two numbers which is much highly secure as it chooses higher primes
    while i_p==i_q:
        continue
    primes=PrimeGen(100)
    p=primes[i_p]
    q=primes[i_q]
    #computing n=p*q as a part of the RSA Algorithm
    n=p*q
    #Computing lamda(n), the Carmichael's totient Function.
    # In this case, the totient function is the LCM(lamda(p),lamda(q))=lamda(p-1,q-1)
    # On the Contrary We can also apply the Euler's totient's Function phi(n)
    #  which sometimes may result larger than expected
    lamda_n=int(lcm(p-1,q-1))
    e=randint(1,lamda_n)
    #checking the Following : whether e and lamda(n) are co-prime
    while math.gcd(e,lamda_n)!=1:
        e=randint(1,lamda_n)
    #Determine the modular Multiplicative Inverse
    d=modinv(e,lamda_n)
    #return the Key Pairs
    # Public Key pair : (e,n), private key pair:(d,n)
    return ((e,n),(d,n))

def encrypt(pk,message):
    """ Perform RSA Encryption Algorithm"""
    key, n = pk
    #Convert each letter in the plaintext to numbers based on the character using a^b mod m
    cipher = [(ord(char) ** key) % n for char in message]
    #Return the array of bytes
    return cipher

def decrypt(pk,cipher):
    '''Perform RSA Decryption Algorithm '''
    key, n = pk
    #Generate the plaintext based on the ciphertext and key using a^b mod m
    message = [chr((int(char) ** key) % n) for char in cipher]
    #Return the array of bytes as a string
    return ''.join(message)


import numpy as np

# Mathematical source for both Haar and Schauder(-Faber) bases: https://en.wikipedia.org/wiki/Haar_wavelet

# Numpy implementation of Haar wavelet generation on [0,1]
def haar_wavelet(time_grid):
    index_set1 = np.where( (0   <= time_grid) & (time_grid < 0.5) )
    index_set2 = np.where( (0.5 <= time_grid) & (time_grid < 1  ) )
    wavelet = np.zeros_like( time_grid )
    wavelet[ index_set1 ] = 1
    wavelet[ index_set2 ] = -1
    return wavelet

def haar_basis_element(n, k, time_grid):
    return (2.0**(0.5*n)) * haar_wavelet( (2**n)*time_grid- k)

# Returns a total of 2**n_max basis functions corresponding to
#   the Haar wavelets (n,k) corresponding to
#   n=0, ..., n_max-1 and
#   k=0, ..., 2**n - 1
def haar_basis( n_max, time_grid):
    basis = []
    # Constant function
    basis.append( np.ones_like(time_grid) )
    # The next ones (n>0)
    for n in range(n_max):
        basis = basis + [ haar_basis_element(n,k, time_grid) for k in range(2**n)]
    return basis

# Numpy implementation of Schauder basis generation on [0,1]

# Schauder elements are integrals of the Haar elements
def schauder_basis_element(n, k, time_grid):
    haar_element =  haar_basis_element(n, k, time_grid)
    element = np.zeros_like( haar_element )
    element[1:] = (2**(1+0.5*n)) * 0.5* ( haar_element[:-1] + haar_element[1:] )* (time_grid[1:] - time_grid[:-1])
    return element.cumsum()

def schauder_basis( n_max, time_grid):
    basis = []
    # Constant function
    basis.append( np.ones_like(time_grid) )
    # Identity map
    basis.append( np.copy(time_grid) )
    # The next ones (n>0)
    for n in range(n_max):
        basis = basis + [ schauder_basis_element(n,k, time_grid) for k in range(2**n)]
    return basis


import numpy as np
#import matplotlib.pyplot as plt

def Gradient_descent(x,y):
  m = 0
  c = 0
  L = 0.0001  # The learning Rate
  epochs = 1000  #The number of iterations to perform gradient descent
  n = len(X) # Number of elements in X
  for i in range(epochs):
    Y_pred = m*X + c  # The current predicted value of Y
    D_m = (-2/n) * sum(X * (Y - Y_pred))  # Derivative wrt m
    D_c = (-2/n) * sum(Y - Y_pred)  # Derivative wrt c
    m = m - L * D_m  # Update m
    c = c - L * D_c  # Update c
  # print(m,c,i)
  # print('Independent ',X)
  # print('Dependent ',Y)
  print('predicted values ',Y_pred)

if __name__ == '__main__':
  X = np.array([1,2,4,3,5])
  Y = np.array([1,3,3,2,5])
  Gradient_descent(X,Y)

'''
After completing the gradient descent algorithms we found the optimal m & c respectively .
m = 0.7652159238030152 c = 0.21788387785300492
'''


from typing import List
import sympy as sp
from sympy.matrices.dense import MutableDenseMatrix

import logging
logging.basicConfig(level=logging.WARN)

class InverseSolver:

    def __init__(self,mat) -> None:
        self.mat: MutableDenseMatrix = mat
        self.dim = self.mat.shape[0]
        self.course: List[MutableDenseMatrix] = []
        self.is_Invertible = False if self.mat.det() == 0 else True #可逆标志

    def getCourse(self) -> List[MutableDenseMatrix]:
        if self.is_Invertible == False:
            raise Exception('矩阵不可逆')

        self.course.clear()
        mat = self.mat.copy()
        AE: MutableDenseMatrix = mat.row_join(sp.eye(self.dim)) #将原矩阵与单位矩阵连接，用Gauss-Jordan消元法求逆
        self.course.append(AE)

        #Gauss消元
        for pivot_rowInd in range(self.dim-1): #最后一行不用选主元
            pivot = AE[pivot_rowInd, pivot_rowInd]
            #选主元，如果待消元的列不为0，则选为主元，否则选最开始一行的元素为主元
            if pivot == 0:
                for rowInd in range(pivot_rowInd+1, self.dim):
                    if AE[rowInd, pivot_rowInd] != 0:
                        AE = AE.elementary_row_op(op="n<->m", row1=pivot_rowInd, row2=rowInd)
                        self.course.append(AE.copy())
                        break
            #用主元所在的行消元
            for rowInd in range(pivot_rowInd+1, self.dim):
                if AE[rowInd, pivot_rowInd] != 0:
                    k = -sp.Rational(AE[rowInd, pivot_rowInd], pivot)
                    AE = AE.elementary_row_op(op='n->n+km', k=k, row1=rowInd, row2=pivot_rowInd)
                    self.course.append(AE.copy())

        #Jordan消元
        for pivot_rowInd in range(self.dim-1, 0, -1): #第一行不用选主元
            pivot = AE[pivot_rowInd, pivot_rowInd]
            #先将主元化为1
            if pivot != 1:
                AE = AE.elementary_row_op(op='n->kn', k=sp.Rational(1, pivot), row=pivot_rowInd)
                self.course.append(AE.copy())
            #用主元所在的行消元
            for rowInd in range(pivot_rowInd-1, -1, -1):
                if AE[rowInd, pivot_rowInd] != 0:
                    k = -AE[rowInd, pivot_rowInd]
                    AE = AE.elementary_row_op(op='n->n+km', k=k, row1=rowInd, row2=pivot_rowInd)
                    self.course.append(AE.copy())
        return self.course

    def getInverse(self) -> MutableDenseMatrix:
        return self.mat.inv()

if __name__ == '__main__':
    mat = sp.Matrix([[1,0,0],[0,1,0],[0,0,3]])
    logging.info(mat)
    solver = InverseSolver(mat)
    co = solver.getInverse()
    logging.info(co)
    logging.info(mat.inv())


## 1. Individual Values ##

import pandas as pd
houses = pd.read_table('AmesHousing_1.txt')

import matplotlib.pyplot as plt

houses['SalePrice'].plot.kde(xlim = (houses['SalePrice'].min(),
                                    houses['SalePrice'].max()
                                    )
                            )

st_dev = houses['SalePrice'].std(ddof = 0)
mean = houses['SalePrice'].mean()
plt.axvline(mean, color = 'Black', label = 'Mean')
plt.axvline(mean + st_dev, color = 'Red', label = 'Standard deviation')
plt.axvline(220000, color = 'Orange', label = '220000')
plt.legend()
very_expensive = False

## 2. Number of Standard Deviations ##

distance = 220000 - houses['SalePrice'].mean()
st_devs_away = distance / houses['SalePrice'].std(ddof=0)
print(distance)
print(st_devs_away)

## 3. Z-scores ##

min_val = houses['SalePrice'].min()
mean_val = houses['SalePrice'].mean()
max_val = houses['SalePrice'].max()
def z_score(value, array, bessel = 0):
    mean = sum(array) / len(array)

    from numpy import std
    st_dev = std(array, ddof = bessel)

    distance = value - mean
    z = distance / st_dev

    return z

min_z = z_score(min_val, houses['SalePrice'])
mean_z = z_score(mean_val, houses['SalePrice'])
max_z = z_score(max_val, houses['SalePrice'])

## 4. Locating Values in Different Distributions ##

def z_score(value, array, bessel = 0):
    mean = sum(array) / len(array)

    from numpy import std
    st_dev = std(array, ddof = bessel)

    distance = value - mean
    z = distance / st_dev

    return z

north_ames = houses[houses['Neighborhood']=='NAmes']
College_creek = houses[houses['Neighborhood']=='CollgCr']
old_town = houses[houses['Neighborhood']=='OldTown']
edwards = houses[houses['Neighborhood']=='Edwards']
somerset = houses[houses['Neighborhood']=='Somerst']

north_ames_z = z_score(200000,north_ames['SalePrice'])
College_creek_z = z_score(200000, College_creek['SalePrice'])
old_town_z = z_score(200000, old_town['SalePrice'])
edwards_z = z_score(200000, edwards['SalePrice'])
somerset_z = z_score(200000, somerset['SalePrice'])
print('north_ames ' + str(north_ames_z))
print('College_creek '+ str(College_creek_z))
print('old_town ' + str(old_town_z))
print('edwards ' + str(edwards_z))
print('somerset '+ str(somerset_z))

best_investment = 'College Creek'

## 5. Transforming Distributions ##

mean = houses['SalePrice'].mean()
st_dev = houses['SalePrice'].std(ddof = 0)
houses['z_prices'] = houses['SalePrice'].apply(
    lambda x: ((x - mean) / st_dev)
    )

z_mean_price = houses['z_prices'].mean()
print(z_mean_price)
z_stdev_price = houses['z_prices'].std(ddof=0)
print(z_stdev_price)

mean = houses['Lot Area'].mean()
st_dev = houses['Lot Area'].std(ddof=0)
houses['z_area'] = houses['Lot Area'].apply(
    lambda x: ((x - mean)/st_dev)
    )
z_mean_area = houses['z_area'].mean()
z_stdev_area = houses['z_area'].std(ddof=0)
print(z_mean_area)
print(z_stdev_area)

## 6. The Standard Distribution ##

from numpy import std, mean
population = [0,8,0,8]
mean_pop = mean(population)
stdev_pop = std(population, ddof = 0)

standardized_pop = []
for value in population:
    z = (value - mean_pop) / stdev_pop
    standardized_pop.append(z)

mean_z = mean(standardized_pop)
stdev_z = std(standardized_pop, ddof = 0)

## 7. Standardizing Samples ##

from numpy import std, mean
sample = [0,8,0,8]

x_bar = mean(sample)
s = std(sample, ddof = 1)

standardized_sample = []
for value in sample:
    z = (value - x_bar) / s
    standardized_sample.append(z)
stdev_sample = std(standardized_sample, ddof = 1)

## 8. Using Standardization for Comparisons ##

mean_index1 = houses['index_1'].mean()
stdev_index1 = houses['index_1'].std(ddof = 0)
houses['z_1'] = houses['index_1'].apply(lambda x:
                                      (x - mean_index1) / stdev_index1
                                     )

mean_index2 = houses['index_2'].mean()
stdev_index2 = houses['index_2'].std(ddof = 0)
houses['z_2'] = houses['index_2'].apply(lambda x:
                                      (x - mean_index2) / stdev_index2
                                     )

print(houses[['z_1', 'z_2']].head(2))
better = 'first'

## 9. Converting Back from Z-scores ##

mean = 50
st_dev = 10
houses['transformed'] = houses['z_merged'].apply(
                                lambda z: (z * st_dev + mean)
                                )
mean_transformed = houses['transformed'].mean()
stdev_transformed = houses['transformed'].std(ddof = 0)

5. Probability and Statistics/statistics-intermediate/Z-scores-309.py


# zeros
# The zeros tool returns a new array with a given shape and type filled with 0's.

# import numpy
# print numpy.zeros((1,2))   # Default type is float
# #Output : [[ 0.  0.]]

# print numpy.zeros((1,2), dtype = numpy.int)   # Type changes to int
# #Output : [[0 0]]

# ones
# The ones tool returns a new array with a given shape and type filled with 1's.

# import numpy
# print numpy.ones((1,2))   # Default type is float
# #Output : [[ 1.  1.]]

# print numpy.ones((1,2), dtype = numpy.int) #Type changes to int

# #Output : [[1 1]]

# Task
# You are given the shape of the array in the form of space-separated integers, each integer representing the size
# of different dimensions, your task is to print an array of the given shape and integer type using the tools numpy.zeros
# and numpy.ones.

# Input Format
# A single line containing the space-separated integers.

import numpy as np

abc = list(map(int, input().split()))

print(np.array(np.zeros(abc), dtype=np.int))
print(np.array(np.ones(abc), dtype=np.int))

# Output Format
# First, print the array using the numpy.zeros tool
# and then print the array with the numpy.ones tool.

# Sample Input 0
# 3 3 3

# Sample Output 0
# [[[0 0 0]
#   [0 0 0]
#   [0 0 0]]

#  [[0 0 0]
#   [0 0 0]
#   [0 0 0]]

#  [[0 0 0]
#   [0 0 0]
#   [0 0 0]]]
# [[[1 1 1]
#   [1 1 1]
#   [1 1 1]]

#  [[1 1 1]
#   [1 1 1]
#   [1 1 1]]

#  [[1 1 1]
#   [1 1 1]
#   [1 1 1]]]

# Explanation 0
# Print the array built using numpy.zeros and numpy.ones tools
# and you get the result as shown.


import matplotlib.pyplot as plt
import numpy as np

print("Formally we say that the Taylor series of a function is its representation as a power series. Let's see what this really means.")

print("\nConsider the cosine function from trigonometry. It has a nice smooth shape but if I asked you the cosine of some random number you'd probably have only a rough idea what the answer is. Traditionally it had to be worked out geometrically.")

print("\nWe can be more clever than that. There is one that we know for sure: cos(0) = 1. We will make an approximation starting with this requirement that approx(0) = 1.")

print("\nWhat should be used to make the approximation? Polynomials are very easy to evaluate and they can take lots of different shapes.")

print("\nSo lets start with the simplest polynomial... a horizontal line. There aren't many options here.")

print("\nObviously this isn't a very good quality approximation.")

print("\nTo make a better approximation we need to fit the curve of the cosine function. Fortunately that is a well known concept in calculus: the derivate.")

print("\nSo lets check the derivative of cos(0)... its zero. Our approximation also has a derivative of zero so we're matching!")

print("\n(Of course that means our approximation isn't any better.)")

print("\nBut the derivative doesn't tell the whole story. Lets check the second derivative of cos(0). That is -1!")

print("\nSo now we need to change our polynomial to give it a second derivative that is equal to -1. In order to have a second derivative it will have to be quadratic.")

print("\nSince polynomials are so well behaved this isn't a hard requirement to match. The power rule tells us that the second derivative of a quadratic polynomial is 2b, where b is the second coefficient. To make 2b = -1 we just have to say that b = -1/2.")

print("\nWe can continue in this way as long as desired. All of the odd terms will have a derivative of zero and so don't contribute. But each even term will make the approximation better.")

x = np.linspace(-5,5,100)
y = np.cos(x)

approx1 = [1]*len(x)
approx2 = [1-(i**2)/2 for i in x]
approx3 = [1-(i**2)/2+(i**4)/24 for i in x]
approx4 = [1-(i**2)/2+(i**4)/24-(i**6)/720 for i in x]

fig = plt.figure()
ax=fig.add_axes([0,0,1,1])
ax.set_axis_off()

plt.ylim(-2,2)
plt.plot(x,y)
plt.plot(x,approx1)
plt.title("First Approximation: 1")

fig = plt.figure()
ax=fig.add_axes([0,0,1,1])
ax.set_axis_off()

plt.ylim(-2,2)
plt.plot(x,y)
plt.plot(x,approx2)
plt.title("Second Approximation: 1 - (x^2)/2")

fig = plt.figure()
ax=fig.add_axes([0,0,1,1])
ax.set_axis_off()

plt.ylim(-2,2)
plt.plot(x,y)
plt.plot(x,approx3)
plt.title("Third Approximation: 1-(x^2)/2 + (x^4)/24")

fig = plt.figure()
ax=fig.add_axes([0,0,1,1])
ax.set_axis_off()

plt.ylim(-2,2)
plt.plot(x,y)
plt.plot(x,approx4)
plt.title("Fourth Approximation: 1-(x^2)/2 + (x^4)/24 - (x^6)/720")


import numpy as np
import math as m
import csv, sys

# Returns a new dataset that normalizes all the data based on Z-scores.
def normalize_data(data):
	# Transposed for ease and possibly better spacial locality.
	newData = np.transpose(data)
	normalizedData = []
	for dataPoint in newData:
		mean = calc_mean(dataPoint)
		deviation = calc_standard_deviation(dataPoint)
		normalizedData.append([(x - mean) / deviation for x in dataPoint])
	return np.transpose(normalizedData)

def calc_mean(data):
	return sum(data) / len(data)

# Returns the standard deviation for a 1-D array.
def calc_standard_deviation(data):
	mean = calc_mean(data)
	standard_deviation = m.sqrt(sum([(x - mean) ** 2 for x in data]) / (len(data) - 1))
	return standard_deviation

# Opens the file, returns the data array and the target array.
def open_file_discretized(filename):
	data, target = [], []
	with open(filename, newline='') as csvfile:
		spamreader = csv.reader(csvfile, delimiter=',')
		for row in spamreader:
			data_elem, target_elem = np.split(row, [-1])
			data.append(data_elem)
			target.append(target_elem[0])
	return data, target

# Opens the file, returns the data array and the target array.
def open_file_normalized(filename):
	data, target = [], []
	with open(filename, newline='') as csvfile:
		spamreader = csv.reader(csvfile, delimiter=',')
		for row in spamreader:
			data_elem, target_elem = np.split(row, [-1])
			data.append([float(elem) for elem in data_elem])
			target.append(target_elem[0])
	data = normalize_data(data)
	return data, target


from os import stat
import numpy as np
import cv2 # OpenCV
import math

from numpy.linalg.linalg import qr

class Rotations:
    def __init__(self):

        return

    @staticmethod
    def rot_mat_2_quat(R):
        """
        Convert a rotation from rotation matrix to quaternion representation. Assumes that the columns of the rotation matrix are orthonomal!
        """

        i, j, k = 0, 1, 2
        if R[1,1] > R[0,1]:
            i, j, k = 1, 2, 0

        if R[2,2] > R[i,i]:
            i, j, k = 2, 0, 1

        t = R[i,i] - (R[j,j] + R[k,k]) + 1
        q = np.array([0, 0, 0, 0])
        q[0] = R[k,j] - R[j,k]
        q[i+1] = t
        q[j+1] = R[i,j] + R[j,i]
        q[k+1] = R[k,i] + R[i,k]
        q = np.multiply(q, 0.5 / math.sqrt(t))

        return q

    @staticmethod
    def quat_2_rot_mat(q):
        """
        Convert a rotation from rotation matrix to quaternion representation.
        """

        s = np.linalg.norm(q) # s = 1 if the quaternion has unit length

        R = np.zeros((3,3))
        R[0,0] = 1 - 2 * s * (q[2]**2 + q[3]**2)
        R[0,1] = 2 * s * (q[1]*q[2] - q[3]*q[0])
        R[0,2] = 2 * s * (q[1]*q[3] + q[2]*q[0])
        R[1,0] = 2 * s * (q[1]*q[2] + q[3]*q[0])
        R[1,1] = 1 - 2 * s * (q[1]**2 + q[3]**2)
        R[1,2] = 2 * s * (q[2]*q[3] - q[1]*q[0])
        R[2,0] = 2 * s * (q[1]*q[3] - q[2]*q[0])
        R[2,1] = 2 * s * (q[2]*q[3] + q[1]*q[0])
        R[2,2] = 1 - 2 * s * (q[1]**2 + q[2]**2)

        R = Rotations.orthonormal_mat(R)

        return R

    @staticmethod
    def rot_mat_2_angle_axis(rot_mat):
        theta = math.acos((np.trace(rot_mat) - 1) / 2)

        return 1 / (2 * math.sin(theta)) * np.array([rot_mat[2,1] - rot_mat[1,2], rot_mat[0,2] - rot_mat[2,0], rot_mat[1,0] - rot_mat[0,1]]).reshape((3,1))

    @staticmethod
    def orthonormal_mat(mat):
        # Perform SVD on rotation matrix to make the rows and columns orthonormal
        U, _, V_transpose = np.linalg.svd(mat, full_matrices=True)
        return np.matmul(U, V_transpose)


'''
A robot is located at the top-left corner of a m x n grid
(marked 'Start' in the diagram below).

The robot can only move either down or right at any point in time.
The robot is trying to reach the bottom-right corner of the
grid (marked 'Finish' in the diagram below).

How many possible unique paths are there?

Note: m and n will be at most 100.

Example 1:

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:

Input: m = 7, n = 3
Output: 28
'''
import numpy as np

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        grid = np.array([[0 for i in range(m)] for j in range(n)])
        grid[0, :] = 1
        grid[:, 0] = 1
        for i in range(1, m):
            for j in range(1, n):
                grid[j, i] = grid[j-1, i] + grid[j, i-1]
        print(grid)
        return grid[n-1, m-1]

sol = Solution()
m, n = [7,3]
print(sol.uniquePaths(m, n))


import numpy as np

x = np.array([[1, 3, 5], [7, 9, 11]])
y = np.array([[1, 2], [3, 4], [5, 6]])

print(np.dot(x, y)) #行列積

'''
[[ 35  44]
 [ 89 116]]
'''

print(x.dot(y)) #行列積

'''
[[ 35  44]
 [ 89 116]]
'''

print(x @ y) #行列積

'''
[[ 35  44]
 [ 89 116]]
'''


import torch
import numpy as np

def decayconst(tau, dt):
    if type(tau) == torch.Tensor:
        return torch.exp(-dt/tau)
    return float(np.exp(-dt/tau))

def sigmoid_project(value, bounds, framework = torch):
    return bounds[0] + (bounds[1]-bounds[0])/(1 + framework.exp(-value))

def expfilt(target, filtered, alpha):
    return alpha*filtered + (1 - alpha)*target

# Sunflower seed pattern:
# https://stackoverflow.com/questions/28567166/uniformly-distribute-x-points-inside-a-circle
phi = (np.sqrt(5)+1)/2 # The golden ratio
def sunflower(n, alpha=1):
    '''
    Returns a sunflower seed pattern of n points. alpha>1 causes truncation of
    radii at the outer edge, making a smoother boundary.
    '''
    b = torch.tensor(np.round(alpha * np.sqrt(n)))
    k = torch.arange(1,n+1)
    r = torch.sqrt(k-1/2) / torch.sqrt(n - (b+1)/2)
    r[r>1] = 1
    theta = 2*np.pi*k / phi**2
    return r, theta

def broadcast_outer(a,b):
    assert a.dim() == b.dim() == 1
    a = a.reshape(-1,1).expand(-1,len(b))
    b = b.reshape(1,-1)
    return a,b

def polar_dist(r1, th1, r2, th2):
    r1,r2 = broadcast_outer(r1,r2)
    th1,th2 = broadcast_outer(th1,th2)
    return torch.sqrt(r1**2 + r2**2 - 2*r1*r2*torch.cos(th1-th2))

def cartesian(r, theta):
    return r*theta.cos(), r*theta.sin()