import argparse
import random
import numpy as np
import math

MONTE_CARLO = 0
RIEMANN = 1
random.seed(1)

def integrate_string(functionstring, lower_boundry, upper_boundry, method=1, iterations=10000):
    function = _parse_Function(functionstring)

    if method == 0:
        return monte_carlo_integrate(function, lower_boundry, upper_boundry, iterations)
    if method == 1:
        return riemann_integrate(function, lower_boundry, upper_boundry, iterations)

def riemann_integrate(function, lower_boundry, upper_boundry, iterations=10000):
    step_size = (upper_boundry - lower_boundry)/iterations
    steps = np.arange(lower_boundry, upper_boundry, step_size)
    function_array = np.vectorize(function)
    results = function_array(steps)
    return np.mean(results) * (upper_boundry - lower_boundry)

def monte_carlo_integrate(function, lower_boundry, upper_boundry, iterations=10000, min_height = None, max_height= None):

    if min_height is None or max_height is None:
        min_height, max_height = _generate_y_boundries(function, lower_boundry, upper_boundry, iterations)

    results = 0
    for step in range(1, iterations):
        random_point = _generate_random_point(lower_boundry, upper_boundry, min_height, max_height)
        stepresult = _monte_Carlostep(function, random_point)
        if stepresult:
            results += 1

    minresult = (upper_boundry - lower_boundry) * (min_height)
    return minresult + (upper_boundry - lower_boundry) * (max_height - min_height) * results / iterations

def _generate_random_point(lower_boundry, upper_boundry, min_height, max_height):
    return [random.uniform(lower_boundry,upper_boundry), random.uniform(min_height, max_height)]

def _parse_Function(expression):
    funcstr= '''\
def f(x):
    return {e}'''.format(e=expression)
    exec(funcstr, globals())
    return f

def _generate_y_boundries(function, lower_x_boundry, upper_x_boundry, iterations=1000):
    step_size = (upper_x_boundry - lower_x_boundry) / iterations
    steps = np.arange(lower_x_boundry, upper_x_boundry, step_size)
    function_array = np.vectorize(function)
    results = function_array(steps)
    return np.min(results), np.max(results)

def _monte_Carlostep(function, point):
    if point[1] < function(point[0]):
        return True
    else:
        return False

if __name__ == '__main__':
    argumentparser = argparse.ArgumentParser()
    argumentparser.add_argument("function", type=str)
    argumentparser.add_argument("lower_boundry", type=float)
    argumentparser.add_argument("upper_boundry", type=float)
    argumentparser.add_argument('-i', help='number of iterations (10000 default)', type=int, default=10000)
    argumentparser.add_argument("-m", type=int, choices=[0, 1, 2], default=2,
                        help="which approximation method used: 0 = Monte Carlo, 1 = Riemann sum, 2 = both (default)")

    args=argumentparser.parse_args()

    iterations = 10000
    if args.m in [0,2]:
        print(f"Monte Carlo approximation: {integrate_string(args.function, args.lower_boundry, args.upper_boundry, MONTE_CARLO, args.i)}")
    if args.m in [1,2]:
        print(f"Riemann approximation: {integrate_string(args.function, args.lower_boundry, args.upper_boundry, RIEMANN, args.i)}")


from scipy.constants import c
from scipy.optimize import curve_fit
from numpy import loadtxt, real, imag, pi, sqrt, diag, where
from bokeh.plotting import *

output_notebook()

def lorentz(x,*p):
#     p = amp, f0, kappa
    return (real(p[0]/(1-1j*(2*pi*x - 2*pi*p[1])/(p[2]/2)))**2 + imag(p[0]/(1-1j*(2*pi*x - 2*pi*p[1])/(p[2]/2)))**2)**.5

def analyze(filename):
    fig1, x,y = plotS21(filename, showPlot=False)
    fig1.plot_width = 450
#     Find Resonance
    f0 = x[where(y == y.max())][0]

# Convert db to Voltage
    yvolt = 10**(y/20.)
    fig=figure(
        x_axis_label="Frequency (GHz)",
        y_axis_label="Amplitude (V)",
        width=450)
    fig.circle(x*1e-9,yvolt, fill_color=None)
    p, pcov = curve_fit(lorentz, x, yvolt, [yvolt.max(),f0,.01])

    f0 = round(p[1]*1e-9, 6)
    kappa = p[2]
    q = p[1]/(kappa/(2*pi))

#     Calculate fit error

    perr = sqrt(diag(pcov))

    f0err = perr[1]*1e-9
    kappaerr = perr[2]
    qerr = perr[1]/(2*pi*perr[2])

    print 'f0 = {} +/- {:.5f} ({:.2}%) GHz'.format(f0, f0err, (f0err/f0)*100)
    print 'kappa = {:.4E} +/- {:.4E} ({:.2}%)'.format(kappa, kappaerr, (kappaerr/kappa)*100)
    print 'Q = {:.4E} +/- {:.4E} ({:.2}%)'.format(q, qerr, (qerr/q)*100)

    fig.line(x*1e-9,lorentz(x,*p))

    grid = gridplot([[fig1, fig]], title=filename)
    show(grid)

#     Create results dict
    results = {
        'filename': filename,
        'freq': x,
        'dBm': y,
        'amp': yvolt,
        'f0': p[1],
        'kappa': kappa,
        'Q': q,
        'fig': grid,
        'f0err': f0err,
        'kappaerr': kappaerr,
        'qerr': qerr
    }
    return results

def plotS21(filename, showPlot=True):
    print(filename)
    x, y = loadtxt(filename, comments='!', skiprows=10, usecols=(0, 3), unpack=True)
    fig = figure(
        x_axis_label="Frequency (GHz)",
        y_axis_label="|S21| (dBm)")
    fig.circle(x*1e-9,y, fill_color=None)
    if showPlot:
        show(fig)
    else:
        return fig,x,y


from math import ceil, floor
from typing import Callable, Tuple
import numpy
import pandas

def lower_discretization(x: int, y: int, n: int, continuous_implication: Callable[[float, float], float]) -> int:
    return int(floor(n*continuous_implication(x/n, y/n)))

def upper_discretization(x: int, y: int, n: int, continuous_implication: Callable[[float, float], float]) -> int:
    return int(ceil(n*continuous_implication(x/n, y/n)))

def check_law_importation(tnorm_matrix: numpy.ndarray, n: int,
                          continuous_implication: Callable[[float, float], float],
                          discretization_method: int) -> Tuple[bool, pandas.DataFrame]:
    law_importation_summmary = pandas.DataFrame(columns=["N", "VERIFIED", "X", "Y", "Z",
                                                         "T(X,Y)", "N*I(T(X,Y)/N,Z/N)", "I_D(T(X,Y),Z)",
                                                         "N*I(Y/N, Z/N)", "I_D(Y,Z)", "N*I(X/N, I_D(Y,Z)/N)", "I_D(X,I_D(Y,Z))"])
    verified_law_importation = True

    if discretization_method == 1:
        discretization_method = lower_discretization
    else:
        discretization_method = upper_discretization

    for x in range(0, n+1):
        for y in range(0, n+1):
            for z in range(0, n+1):

                t_xy = tnorm_matrix[x, y]
                disc1 = n*continuous_implication(t_xy/n, z/n)
                left = discretization_method(x=tnorm_matrix[x, y], y=z, n=n, continuous_implication=continuous_implication)

                disc2 = n*continuous_implication(y/n, z/n)
                disc3 = discretization_method(x=y, y=z, n=n, continuous_implication=continuous_implication)
                disc4 = n*continuous_implication(x/n, disc3/n)
                right = discretization_method(x=x, y=discretization_method(x=y, y=z, n=n, continuous_implication=continuous_implication),
                                              n=n, continuous_implication=continuous_implication)

                if left != right:
                    law_importation_summmary.loc[len(law_importation_summmary)] = [n, False, x, y, z, t_xy, disc1, left, disc2, disc3, disc4, right]
                    verified_law_importation = False
                else:
                    law_importation_summmary.loc[len(law_importation_summmary)] = [n, True, x, y, z, t_xy, disc1, left,
                                                                                disc2, disc3, disc4, right]
    return verified_law_importation, law_importation_summmary

def implication(x: float, y: float) -> float:
    # Fodor implication
    if x <= y:
        return 1
    else:
        return max(1-x, y)

def generate_nilpotent_minimum(n):
    mat = numpy.zeros((n+1, n+1), dtype=int)
    for x in range(0, n+1):
        for y in range(0, n+1):
            if x+y <= n:
                mat[y, x] = 0
            else:
                mat[y, x] = min(x, y)
    return mat

if __name__ == "__main__":

    saving_path = r"C:\Users\Usuario\OneDrive - Universitat de les Illes Balears\UIB\Tesi\Experiments\E4 (Python)\IMPLICATION=fodor_TNORM=nilpotentmin"

    for n in range(2, 91):
        nilpotent_minimum_matrix = generate_nilpotent_minimum(n)
        verifies_li, result = check_law_importation(tnorm_matrix=nilpotent_minimum_matrix,
                                                    n=n,
                                                    continuous_implication=implication,
                                                    discretization_method=1)

        result.to_csv(saving_path+f"/N={n}_VERIFIED={verifies_li}.txt", index=False)


import numpy as np

def st_var(x: np.ndarray) -> float:
    """ Short-term Poincare variability
    """
    return np.std(np.diff(x)) / np.sqrt(2)

def lt_var(x: np.ndarray) -> float:
    """ Long-term Poincare variability
    """
    st_var = np.std(np.diff(x)) / np.sqrt(2)
    lt_var = np.sqrt(2 * np.var(x) - st_var**2)

    return lt_var


import cv2
import numpy as np

def DCTConstant(u, v):

    if u == 0 and v == 0:
        return 1 / 2
    elif u == 0 or v == 0:
        return 1 / np.sqrt(2)
    else:
        return 1

def DCT(img, block=8):

    Ver, Hor, Col = img.shape

    result = np.zeros((Ver, Hor, Col)).astype(np.float)
    x = np.tile(np.arange(block), (block, 1))
    y = np.arange(block).repeat(block).reshape([block, block])

    for c in range(Col):
        for u in range(Hor):
            #block内での画像位置
            cu = u % block
            #何番目のブロックか
            iu = u // block
            for v in range(Ver):
                #block内での画像位置
                cv = v % block
                #何番目のブロックか
                iv = v // block
                result[v, u, c] = np.sum(img[iv*block:(iv+1)*block, iu*block:(iu+1)*block, c] * np.cos((2*x+1)*cu*np.pi/(2*block)) * np.cos((2*y+1)*cv*np.pi/(2*block)))
                result[v, u, c] *= DCTConstant(cu, cv)
    result *= (2/block)

    return result

def InvDCT(dct, block=8, K=8):
    Ver, Hor, Col = img.shape

    result = np.zeros((Ver, Hor, Col)).astype(np.float)

    u = np.tile(np.arange(K), (K, 1))
    v = np.arange(K).repeat(K).reshape([K, K])
    c_uv = np.zeros((K, K))

    for x in range(K):
        for y in range(K):
            c_uv[y, x] = DCTConstant(y, x)

    for c in range(Col):
        for x in range(Hor):
            cx = x % block
            ix = x // block
            for y in range(Ver):
                cy = y % block
                iy = y // block
                result[y, x, c] = np.sum(dct[iy*block:iy*block+K, ix*block:ix*block+K, c] * np.cos((2*cx+1)*u*np.pi/(2*block)) * np.cos((2*cy+1)*v*np.pi/(2*block)) * c_uv)
    result *= (2/block)

    return result

img = cv2.imread("../imori.jpg")

dct = DCT(img, block=8)
cv2.imshow("dct", dct)
result = InvDCT(dct, block=8, K=8)
result = np.clip(result, 0, 255).astype(np.uint8)

cv2.imwrite("myans_36.jpg", result)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()


import scipy
import numpy as np
from scipy.optimize import minimize
from scipy import spatial
import seaborn as sns
import matplotlib.pyplot as plt

__SAVE_FIG__ = True
__FIG_DIR__ = '04_figs/'
__FORMAT__ = 'eps'

def laplacian(W):
    return np.diag(np.sum(W, axis=0)) - W

def normalized_laplacian(W):
    N = np.alen(W) + 1
    D_diag = np.sum(W, axis=0)
    D_minus12 = np.diag([d**(-0.5) for d in D_diag])
    return np.eye(N - 1) - D_minus12@W@D_minus12

def buildW(flat_w_sup, N):
    W = np.zeros((N, N))
    shift = 0
    for i in range(N-1):
        row_length = N-i-1
        W[i, i+1:] = flat_w_sup[shift:shift+row_length]
        shift += row_length
    W = W+W.T
    return W

def O_LU(W, U):
    """W is a matrix NxN"""
    L = laplacian(W)
    return L@U

def O_LambdaU(lambda_, U):
    return U @ np.diag(lambda_)

def loss_function(Wsup_and_lambda, U, N):
    flat_w_sup = Wsup_and_lambda[:-N]
    W = buildW(flat_w_sup, N)
    lambda_ = Wsup_and_lambda[-N:]
    return np.linalg.norm(O_LU(W, U) - O_LambdaU(lambda_, U), ord='fro')

def harmonics(thetas):
    N = len(thetas)
    assert N % 2 == 1, 'N has to be odd to construct the last eigenvector'
    U = np.zeros((N, N))
    k = 0
    for i in range(N):
        if i == 0:
            U[:, 0] = 1. / (np.sqrt(2)*np.pi)
        elif i % 2:
            k += 1
            U[:, i] = np.sin(k * thetas) / (np.pi)
        else:
            U[:, i] = np.cos(k * thetas) / (np.pi)
    return U

def main():

    # number of vertices on the ring graph
    # WARNING: it has to be an odd number for the construction of the correct harmonics
    N = 21

    # building the pointset
    thetas = np.linspace(0, 2 * np.pi, N, endpoint=False)
    thetas += np.random.rand(N)/(30*np.pi/N)
    thetas.sort()
    plt.plot(thetas)
    plt.show()
    assert len(thetas) == N, len(thetas)
    pointset = np.zeros((N, 2))
    pointset[:, 0] = np.cos(thetas)
    pointset[:, 1] = np.sin(thetas)
    plt.plot(pointset[:, 0], pointset[:, 1], 'ro')
    plt.axis('equal')
    title = 'Sampling'
    plt.title(title)
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

    # buiding the initial graph
    distance_matrix = spatial.distance.cdist(pointset, pointset)
    W0 = np.exp(-distance_matrix / np.mean(distance_matrix))
    for i in range(np.alen(W0)):
        W0[i, i] = 0
    print("Initial adjacency matrix:\n", W0)

    W0 = np.random.rand(N, N)

    # calculating the circle armonics of the circle
    U = harmonics(thetas)

    # storing the upper triangular adjacency matrix in a flat vector
    w = np.ndarray(0)
    for i in range(np.alen(W0)-1):
        w = np.append(w, W0[i, i+1:])
    assert len(w) == N*(N-1)/2

    # initializing the lambdas
    lambdas = np.ones(N)/N

    # initializing the vector of w, lambdas
    X0 = np.concatenate([w, lambdas])

    # defining the bounds on the variables w, lambda
    lb = np.zeros(len(w) + N)
    ub = np.ones(len(w) + N) * np.inf
    bounds = scipy.optimize.Bounds(lb=lb, ub=ub)

    # defining the constraint on the lambdas
    A = np.zeros(len(w) + N)
    A[len(w):] = 1
    b = 1
    cons = [{"type": "eq", "fun": lambda x: A @ x - b}]  # Ax == b

    # performing the minimization step
    result = minimize(loss_function, X0, args=(U, N), bounds=bounds,
                      constraints=cons)

    # retrieving the results
    Wsup_and_lambda = result.x

    flat_w_sup = Wsup_and_lambda[:-N]
    W = buildW(flat_w_sup, N)
    lambda_ = Wsup_and_lambda[-N:]

    # printing the results
    print("Final adjacency matrix:\n", W)
    print("Final lambdas:\n", lambda_)
    print("Initial Loss = ", loss_function(X0, U, N))
    print("Final Loss = ", loss_function(Wsup_and_lambda, U, N))

    sns.heatmap(W0)
    title = 'initial weights'
    plt.title(title)
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

    sns.heatmap(W)
    title = 'optimal weights'
    plt.title(title)
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

    eigvalues0, eigenvectors0 = np.linalg.eig(laplacian(W0))
    eigvalues, eigenvectors = np.linalg.eig(laplacian(W))
    eigenvectors = eigenvectors[:, np.argsort(eigvalues)]
    eigenvectors0 = eigenvectors0[:, np.argsort(eigvalues0)]

    sns.heatmap(abs(eigenvectors.T @ U))
    title = 'harmonics @ final U, ordered by eigenvalues'
    plt.title(title)
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

    print("||initial eigenvectors @ harmonics.T|| = ",
          np.linalg.norm(eigenvectors0 @ (U.T), ord='fro'))
    print("||final eigenvectors @ harmonics.T|| = ",
          np.linalg.norm(eigenvectors @ (U.T), ord='fro'))
    # ---------------------------------------------------
    # Does comparing the two quantities above make sense?
    # ---------------------------------------------------

    plt.plot(eigenvectors[:, :3])
    title = 'First three eigenvectors of the optimal Laplacian'
    plt.title(title)
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

    plt.plot(np.sort(eigvalues), 'bo', label='eigenvalues of the optimal solution')
    plt.plot(np.sort(lambda_), 'ro', label='Lambdas')
    title = 'confront eigenvalues'
    plt.legend()
    if __SAVE_FIG__:
        plt.savefig(__FIG_DIR__+title, format=__FORMAT__)
    plt.show()

if __name__ == '__main__':
    main()


import sympy

def return_T_U_config_velocities():
    phi1, phi2, tht = sympy.symbols('phi_1 phi_2 theta', real=True)
    vphi1, vphi2, vtht = sympy.symbols('v_phi1 v_phi2 v_theta', real=True)
    location = [phi1, phi2, tht]
    velocity = [vphi1, vphi2, vtht]
    T = (vtht**2)/4 + (vphi1**2+vphi2**2)/10
    U = -(
        sympy.cos(phi1 - phi2)
        +3*sympy.cos(phi1 + phi2 - 2*tht)
        )/12

    return [T, U, location, velocity]

def K_mat_gen(L, q_vars, equi_vals):
    subs = []
    for index in range(len(q_vars)):
        subs.append((
            q_vars[index],
            equi_vals[index]
            ))
    return sympy.Matrix(
        [[
            -L.diff(row_ind, col_ind).subs(subs) for col_ind in q_vars
        ] for row_ind in q_vars]
        )

def equi_from_jk(j, k):
    return [
        (j+k)*sympy.pi/2,
        (j-k)*sympy.pi/2,
        0
    ]

def normal_modes():
    T, U, xs, vs = return_T_U_config_velocities()
    w = sympy.symbols('omega')
    L = (T-U)
    pi = sympy.pi
    sympy.pprint(xs)
    sympy.pprint(vs)
    for vel in vs:
        sympy.pprint(L.diff(vel))
    masses = sympy.Matrix([
        [1,0,0],
        [0,1,0],
        [0,0,10]
        ])/10
    equi_curves = [
        [0,0],
        # [1,0],
        [0,1],
        # [1,1],
    ]
    for curve in equi_curves:
        gamma0 = equi_from_jk(curve[0], curve[1])
        k_vals2 = K_mat_gen(L, xs, gamma0)
        characteristic = k_vals2 - w**2*masses
        normal_modes = characteristic.eigenvects()
        print('mode analysis for ', curve)
        sympy.pprint(gamma0)
        sympy.pprint(characteristic)
        for mode in normal_modes:
            # sympy.pprint(mode)
            # sympy.pprint(mode)
            # sympy.pprint(sympy.simplify(mode[2][0][0]))
            freq_sqr = sympy.solve(mode[0], w**2)[0]
            sympy.pprint(freq_sqr)
            eig_vecs = [vi.subs(w**2, freq_sqr) for vi in mode[2][0]]
            # eig_vecs = [vi for vi in mode[2][0]]
            # sympy.pprint(eig_vecs[0].subs(w**2, freq_sqr))
            sympy.pprint(
                eig_vecs
                )

if __name__ == '__main__':
    normal_modes()


#--------#--------#--------#--------#--------#--------#--------#--------#--------#--------#
#   Householder algorithm for transforsming diagonal matrices into tridiagonal matrices   #
#                                       20/07/21                                          #
#--------#--------#--------#--------#--------#--------#--------#--------#--------#--------#
import numpy as np
import math
from QR-Algorithm import QRAlgorithm

#Householder transform function
def HH_Transform(A):
    shape = A.shape[0]
    shape_aux = shape
    I = np.identity(shape)
    HT = I
    for i in range(0,shape-2):
        #Checks the signal of the first element from the n-i dimensional matrix
        if A[i,(i+1)] != 0:
            sign = np.sign(A[i,(i+1)])
        else:
            sign = 1
        #E vector
        e = np.zeros((shape_aux-1,1))
        e[0,0] = 1
        #Obtaining w1_ vector
        w_sub = (A[(i+1):,i]).reshape(shape_aux-1,1) + sign*np.linalg.norm(A[(i+1):,i])*e
        w_sub = np.squeeze(w_sub)
        #Left-side matrix multiplication
        for j in range(i, shape):
            Hw = A[(i+1):,j]-(2*((np.dot(A[(i+1):,j],w_sub))/(np.dot(w_sub,w_sub)))*w_sub)
            A[(i+1):,j] = Hw
            if j == i: #Symmetry
                A[i,(j+1):] = Hw
        #Obtaining Ht matrix to find eigenvalues
            if i == 0:
                Ht = I[j,(i+1):]-(2*((np.dot(I[j,(i+1):],w_sub))/(np.dot(w_sub,w_sub)))*w_sub)
                HT[j,(i+1):] = Ht
            else:
                Ht = HT[j,(i+1):]-(2*((np.dot(HT[j,(i+1):],w_sub))/(np.dot(w_sub,w_sub)))*w_sub)
                HT[j,(i+1):] = Ht
        #Right-side matrix multiplication
        for j in range(i+1, shape):
            Hw = A[j,(i+1):]-(2*((np.dot(A[j,(i+1):],w_sub))/(np.dot(w_sub,w_sub)))*w_sub)
            A[j,(i+1):] = Hw
        shape_aux -= 1
    resVector = [A, HT]
    return resVector

#--------#--------#Eigenvetors & Eigenvalues test--------#--------#

A_test1 = np.array([[2.,4.,1.,1.],[4.,2.,1.,1.],[1.,1.,1.,2.],[1.,1.,2.,1.]]) #Diagonal test matrix
HH_Results = HH_Transform(A_test1)
autoValVec = QRAlgorithm(HH_Results[0], True, HH_Results[1]) #Applying QR factorization
print("Resulting Tridiagonal Matrix: \n")
print(HH_Results[0])
for i in range(0, A_test1.shape[0]):
            print("Eigenvalue " + str(i+1) + ": "+ str(autoValVec[0][i,i]))
            print("#---------#---------#")
print("Eigenvector matrix: \n")
print(str(autoValVec[1]) + '\n') #Normalized


import matplotlib.pyplot as plt
import numpy as np

def huber_loss(r, delta):
    """Huber loss function, refer to wiki https://en.wikipedia.org/wiki/Huber_loss"""
    return (abs(r) <= delta) * r ** 2 / 2 + (abs(r) > delta) * delta * (abs(r) - delta / 2)

if __name__ == '__main__':
    fig = plt.figure('Huber Loss Function', figsize=(8, 6))
    x = np.arange(-10, 10, 0.01)
    plt.plot(x, x ** 2 / 2, 'g', label='Squared Loss')
    for d in (5, 3, 1, 0.5):
        plt.plot(x, huber_loss(x, d), label=f'Huber Loss $\delta$ = {d}')

    plt.grid(True)
    plt.xlabel('Residual')
    plt.ylabel('Loss')
    plt.title('Huber Loss')
    plt.legend()

    plt.show(block=True)


import numpy as np

class LogisticRegression():
    @staticmethod
    def loss(theta, x, y, lambda_param=None):
        """Loss function for logistic regression with without regularization"""
        exponent = - y * (x.dot(theta))
        return np.sum(np.log(1+np.exp(exponent))) / x.shape[0]

    @staticmethod
    def gradient(theta, x, y, lambda_param=None):
        """
        Gradient function for logistic regression without regularization.
        Based on the above logistic_regression
        """
        exponent = y * (x.dot(theta))
        gradient_loss = - (np.transpose(x) @ (y / (1+np.exp(exponent)))) / (
            x.shape[0])

        # Reshape to handle case where x is csr_matrix
        gradient_loss.reshape(theta.shape)

        return gradient_loss

class LogisticRegressionSinglePoint():
    @staticmethod
    def loss(theta, xi, yi, lambda_param=None):
        exponent = - yi * (xi.dot(theta))
        return np.log(1 + np.exp(exponent))

    @staticmethod
    def gradient(theta, xi, yi, lambda_param=None):

        # Based on page 22 of
        # http://www.cs.rpi.edu/~magdon/courses/LFD-Slides/SlidesLect09.pdf
        exponent = yi * (xi.dot(theta))
        return - (yi*xi) / (1+np.exp(exponent))

class LogisticRegressionRegular():
    @staticmethod
    def loss(theta, x, y, lambda_param):
        regularization = (lambda_param/2) * np.sum(theta*theta)
        return LogisticRegression.loss(theta, x, y) + regularization

    @staticmethod
    def gradient(theta, x, y, lambda_param):
        regularization = lambda_param * theta
        return LogisticRegression.gradient(theta, x, y) + regularization


import math
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display, Markdown

def accuracy(truePosi, trueNega, falsePosi, falseNega): # Count of all four
	return (truePosi+trueNega)/(truePosi+trueNega+falseNega+falsePosi)

def precision(truePosi, trueNega, falsePosi, falseNega):
	if (truePosi+falsePosi) == 0:
		return 0
	preposi = truePosi/(truePosi+falsePosi)
	# prenega = trueNega/(trueNega+falseNega)
	return preposi

def recall(truePosi, trueNega, falsePosi, falseNega):
	if (truePosi+falseNega)== 0:
		return 0
	recposi = truePosi/(truePosi+falseNega)
	# recnega = trueNega/(trueNega+falsePosi)
	return recposi

def fscore(truePosi, trueNega, falsePosi, falseNega, beta: 1):
	pre = precision(truePosi, trueNega, falsePosi, falseNega)
	rec = recall(truePosi, trueNega, falsePosi, falseNega)
	if (pre*(beta**2)+rec) == 0:
		return 0
	f = (1+beta**2)*((pre*rec)/(pre*(beta**2)+rec))
	return f

def evaluate(listsofoutput, positivelabel, beta=1):
    # list is list of [predicted, actual]
    listoftptnfpfn = []
    accuarcylists = []
    precisionlists = []
    recalllists = []
    fscorelists = []
    for output in listsofoutput:
        tp, tn, fp, fn, = 0, 0, 0, 0
        for i in range(len(output)):
            if output[i][0] == positivelabel and output[i][1] == positivelabel:
                tp += 1
            elif output[i][0] != positivelabel and output[i][0] == output[i][1]:
                tn += 1
            elif output[i][0] == positivelabel and output[i][1] != positivelabel:
                fp += 1
            elif output[i][0] != positivelabel and output[i][1] == positivelabel:
                fn += 1
        tptnfpfn = [tp, tn, fp, fn]
        listoftptnfpfn.append(tptnfpfn)
        accuarcylists.append(accuracy(tp, tn, fp, fn))
        precisionlists.append(precision(tp, tn, fp, fn))
        recalllists.append(recall(tp, tn, fp, fn))
        fscorelists.append(fscore(tp, tn, fp, fn, beta))
    return accuarcylists, precisionlists, recalllists, fscorelists, listoftptnfpfn

def meanevaluation(listsofoutput, positivelabel, beta=1):
    accuarcylists, precisionlists, recalllists, fscorelists, notused = evaluate(listsofoutput, positivelabel, beta)
    return sum(accuarcylists)/len(accuarcylists), sum(precisionlists)/len(precisionlists), sum(recalllists)/len(recalllists), sum(fscorelists)/len(fscorelists)

def markdownaprf(acc,pre,rec,fsc,beta,nvalue,title):
    acc, pre, rec, fsc = round(acc,3), round(pre,3), round(rec,3), round(fsc,3)
    display(Markdown(rf"""
	Result/Stat of {nvalue} trees random forest of {title}:
    | **Accuracy** | **Precision** | **Recall** | **F-Score, Beta={beta}** |
    | :---: | :---: | :---: | :---: |
    |{acc} | {pre} | {rec} | {fsc} |
    """))

def markdownmatrix(tptnfpfn,title):
    tp, tn, fp, fn = tptnfpfn[0], tptnfpfn[1], tptnfpfn[2], tptnfpfn[3]
    display(Markdown(rf"""
    Confusion Matrix: {title}
    |  | **Predicted +** | **Predicted-** |
    | :--- | :--- | :--- |
    | **Actual +** | {tp} | {fp} |
    | **Actual -** | {fn} | {tn} |
    """))

def confusionmatrix(truePosi, trueNega, falsePosi, falseNega, title=""):
	fig = plt.figure()
	plt.title(title)
	col_labels = ['Predict:+', 'Predict:-']
	row_labels = ['Real:+', 'Real:-']
	table_vals = [[truePosi, falseNega], [falsePosi, trueNega]]
	the_table = plt.table(cellText=table_vals,
                      colWidths=[0.1] * 3,
                      rowLabels=row_labels,
                      colLabels=col_labels,
                      loc='center')
	the_table.auto_set_font_size(False)
	the_table.set_fontsize(24)
	the_table.scale(4, 4)
	plt.tick_params(axis='x', which='both', bottom=False, top=False, labelbottom=False)
	plt.tick_params(axis='y', which='both', right=False, left=False, labelleft=False)

	for pos in ['right','top','bottom','left']:
		plt.gca().spines[pos].set_visible(False)

	plt.show()
	return


# Author: Dimitrios Damopoulos
# MIT license (see LICENCE.txt in the top-level folder)

import unittest
import numpy as np
from numpy import random
from single_neuron import math_utils as math_utils

max_vector_len_to_test = 100

class SigmoidTestCase(unittest.TestCase):
    """
    sigmoid should accept vectors of arbirtary length. Expected output
    for 0 is 0.5, for infinity is 1 and for negative infinity is 0. The
    output should always be [0, 1].
    """
    def test_output_for_0(self):
        for z_len in range(1, max_vector_len_to_test):
            z = np.zeros(z_len)
            sigm = math_utils.sigmoid(z)
            self.assertAlmostEqual((sigm - 0.5).sum(), 0)

    def test_output_for_inf(self):
        for z_len in range(1, max_vector_len_to_test):
            z = np.full(z_len, np.inf)
            sigm = math_utils.sigmoid(z)
            self.assertAlmostEqual((sigm - 1).sum(), 0)

    def test_output_for_neg_inf(self):
        for z_len in range(1, max_vector_len_to_test):
            z = np.full(z_len, -np.inf)
            sigm = math_utils.sigmoid(z)
            self.assertAlmostEqual(sigm.sum(), 0)

    def test_output_between_0_1(self):
        for z_len in range(1, max_vector_len_to_test):
            z = random.rand(z_len)
            sigm = math_utils.sigmoid(z)
            self.assertTrue((sigm >= 0).all())
            self.assertTrue((sigm <= 1).all())

class RmseTestCase(unittest.TestCase):
    """
    RMSE should accept vectors of arbirtary length. The RMSE of a vector
    with a copy of itself should be zero. rmse(a, b) should be equal to
    rmse(b, a). rmse(a, 0)^2 should be equal to |a|^2 / len(a) .
    """
    def test_is_output_with_itself_zero(self):
        for y_len in range(1, max_vector_len_to_test):
            y = (500 - y_len) * random.rand(y_len)
            y_hat = np.copy(y)
            self.assertAlmostEqual(math_utils.rmse(y_hat, y), 0)

    def test_are_ab_ba_equal(self):
        for y_len in range(1, max_vector_len_to_test):
            y = (50 - y_len) * random.rand(y_len)
            y_hat = (500 - y_len) * random.rand(y_len)
            self.assertAlmostEqual(math_utils.rmse(y_hat, y),
                                   math_utils.rmse(y, y_hat))

    def test_output_with_zero(self):
        for y_len in range(1, max_vector_len_to_test):
            y = (5 - y_len) * random.rand(y_len)
            y_hat = np.zeros(y_len)
            self.assertAlmostEqual(math_utils.rmse(y_hat, y),
                                   np.sqrt(y.T.dot(y) / y.shape[0]))

class CrossEntropyTestCase(unittest.TestCase):
    """
    the cross-entropy of two distributions should be positive. The
    cross-entropy of an one-hot vector with itself is 0.
    """
    def test_is_positive(self):
        for y_len in range(1, max_vector_len_to_test):
            # the following lines contruct two random vectors with elements
            # between [0, 1] which add up to 1.
            y1 = np.zeros(y_len)
            y2 = np.zeros(y_len)
            indices = random.permutation(np.arange(y_len))

            for i in indices[:-1]:
                remaining = 1 - y1.sum()
                y1[i] = random.rand() * remaining

                remaining = 1 - y2.sum()
                y2[i] = random.rand() * remaining

            y1[indices[-1]] = 1 - y1.sum()
            y2[indices[-1]] = 1 - y2.sum()

            self.assertTrue(math_utils.cross_entropy(y1, y2) >= 0)
            self.assertTrue(math_utils.cross_entropy(y2, y1) >= 0)

    def test_is_one_hot_with_itself_0(self):
        for y_len in range(1, max_vector_len_to_test):
            # construct an one-hot vector
            y = np.zeros(y_len)
            y[random.randint(y_len)] = 1

            y_hat = np.copy(y)
            self.assertAlmostEqual(math_utils.cross_entropy(y_hat, y), 0)


import numpy as np

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

def cross_entropy_error(y, t, one_hot_encode=False):
    delta = 1e-7
    # return -np.sum(t * np.log(y + delta))
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    batch_size = y.shape[0]
    if one_hot_encode:
        return -np.sum(t * np.log(y + delta)) / batch_size
    else:
        return -np.sum(np.log(y[np.arange(batch_size), t] + delta)) / batch_size

def numerical_diff(f, x):
    h = 1e-4
    return (f(x+h) - f(x-h)) / (2*h)

def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)

    for idx in range(x.size):
        tmp_val = x[idx]

        x[idx] = tmp_val + h
        fxh1 = f(x)

        x[idx] = tmp_val - h
        fxh2 = f(x)

        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val

    return grad

def gradient_descent(f, init_x, lr=.01, step_num=100):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x


#!/usr/local/bin/python3

import numpy as np

# import scipy as sp

def minkDistance(vector1, vector2, dimenstion=2):
    distance = 0.0
    if len(vector1) == len(vector2):
        dis = sum(
            map(lambda vec1, vec2: pow(vec1 - vec2, dimenstion), vector1,
                vector2))
        distance = pow(dis, 1 / dimenstion)
    return distance

def euclDistance(vec1, vec2):
    return minkDistance(vec1, vec2, 2)

def manhaDistance(vec1, vec2) -> float:
    return minkDistance(vec1, vec2, 1)

def chebyDistance(vector1, vector2) -> float:
    distance = 0.0
    if len(vector1) == len(vector2):
        distance = max(
            map(lambda vec1, vec2: abs(vec1 - vec2), vector1, vector2))
    return distance

def _mahaDistance(vector1, vector2, cov) -> float:
    npVec1 = np.array([vector1])
    npVec2 = np.array([vector2])

    minus = npVec1 - npVec2

    invCov = np.linalg.inv(cov)

    distance = np.sqrt(np.dot(np.dot(minus, invCov), minus.T))
    return distance

def mahaDistance(vector1, vector2, vectors) -> float:
    vecs = np.array(vectors)
    vecs = vecs.transpose()
    cov = np.cov(vecs)
    return _mahaDistance(vector1, vector2, cov)

def cosineDistance(vector1, vector2) -> float:
    distance = 0.0

    if len(vector1) == len(vector2):
        x = np.dot(vector1, vector2)

        def squareSum(vector):
            _sum = sum(map(lambda vec1: pow(vec1, 2), vector))
            return _sum

        y = pow(squareSum(vector1) * squareSum(vector2), 0.5)
        distance = x / y

    return distance

def calDistances(vectors, calculator=euclDistance):
    distances = []
    if callable(calculator):
        for vec in vectors:
            vec_diss = []
            for other_vec in vectors:
                vec_diss.append(calculator(vec, other_vec))
            distances.append(vec_diss)
    return distances

def pcc(vector1, vector2):
    r = 0

    if len(vector1) == len(vector2):
        mean1 = np.mean(vector1)
        mean2 = np.mean(vector2)
        x = sum(
            map(lambda vec1, vec2: (vec1 - mean1) * (vec2 - mean2), vector1,
                vector2)) / len(vector1)

        y = pow(np.var(vector1) * np.var(vector2), 0.5)

        r = x / y

    return r

x = 0

# x = mahaDistance([3, 4], [5, 6], [[3, 4], [5, 6], [2, 2], [8, 4]])
# x = cosineDistance([1, 2, 3], [2, 4, 6])
# x = calDistances([[3, 4], [5, 6], [2, 2], [8, 4]])
x = pcc([1, 2, 3, 4], [3, 8, 7, 6])
print(x)


#!/usr/bin/python3

"""FRACTALS: LINDENMAYER SYSTEM"""

import numpy as np
import matplotlib.pyplot as plt

PRECISION = np.float32

def calc_rot_matrix(angle):
    """
    Input:

    angle
    -- integer or float number
    -- rotation angle in radians
    -- positive number gives counter-clockwise direction of rotation (turns left)
    -- negative number gives clockwise direction of rotation (turns right)

    Returns 2x2 numpy array of floats, a 2D rotation matrix.
    """

    return np.array([[np.cos(angle), -np.sin(angle)],
                     [np.sin(angle), np.cos(angle)]], dtype=PRECISION)

def generate_pattern(lvl, states, rewrite_rules):
    """
    Inputs:

    lvl
    -- integer number
    -- the number of times (iterations) rewrite rules will be applied

    states -- string, the initial state (axiom) of the system

    rewrite_rules
    -- dictionary
    -- keys (character) -> symbols
    -- values (string) -> replacement rules

    Returns string of symbols.
    """

    # In each iteration: check every character in states, replace valid symbol
    # with rewrite rule or copy character, and update states
    for _ in range(lvl + 1):
        states = ''.join([rewrite_rules.get(symbol, symbol) for symbol in states])

    # Clean states form rewrite rule flags/symbols
    drawing_rules = 'F+-'
    states = ''.join([symbol for symbol in states if symbol in drawing_rules])

    return states

def generate_points(alpha, theta, length, states):
    """
    Inputs:

    alpha
    -- integer or float number
    -- angle (in degrees) between the positive x axis
       and initial displacement vector

    theta
    -- integer or float number
    -- angle (in degrees) of a single rotation

    length
    -- integer or float number
    -- length of a displacement vector for one step

    states -- string of symbols

    Retrurns numpy array of coordinates of points on a plane.

    Notes:

    ** Initial displacement vector starting point is allways
       in the origin of the coordinate system.

    ** Only character F in states (alphabet) generates a new point.
    """

    # Convert angles from degrees to radians
    alpha = np.radians(alpha)
    theta = np.radians(theta)

    # Displacement vector, 2x1 numpy array
    vec = np.array([[np.cos(alpha)], [np.sin(alpha)]], dtype=PRECISION)
    vec_len = np.sqrt(vec[0] ** 2 + vec[1] ** 2)

    # Rescale displacement vector
    vec = vec / vec_len * length

    # Rotation matrices for positive and negative angles
    rot_left = calc_rot_matrix(theta)
    rot_right = calc_rot_matrix(-theta)

    # Container to store xy components/coordinates of points on a plane
    points = np.zeros(shape=(2, states.count('F') + 1), dtype=PRECISION)

    point_index = 1

    for st in states:
        if st == '+':
            vec = np.dot(rot_right, vec)
        elif st == '-':
            vec = np.dot(rot_left, vec)
        else:
            points[:, point_index] = points[:, point_index - 1] + vec[:, 0]
            point_index += 1

    return points

def lindemayer(lvl, length, init_angle, angle, init_state,
               title='LINDENMAYER FRACTAL', color='#0080FF', **rewrite_rules):
    """
    Inputs:

    lvl
    -- integer number
    -- the number of times (iterations) rewrite rules will be applied

    length
    -- integer or float number
    -- length of a displacement vector of each step

    init_angle
    -- integer or float number
    -- initial angle (in degrees) measured from the positive x axis

    angle
    -- integer or float number
    -- angle (in degrees) of a single rotation
    -- positive number gives counter-clockwise direction of rotation (turns left)
    -- negative number gives clockwise direction of rotation (turns right)

    init_state -- string, the initial state (axiom) of the system

    title -- string, title of the plot

    color -- string, valid matplotlib color

    rewrite_rules
    -- keyword arguments
    -- keys (character) hold flags/symbols
    -- values (string) hold rules for production/replacement

    Displays the plot of calculated sequence of points.

    This function does not return any value.
    """

    states = generate_pattern(lvl, init_state, rewrite_rules)
    points = generate_points(init_angle, angle, length, states)

    plt.ioff()

    plt.figure(num=title, facecolor='white', frameon=False, clear=True)

    plt.style.use('fivethirtyeight')
    plt.grid(False)
    plt.axis('off')
    plt.axis('equal')

    plot_options = {
        'color': color,
        'alpha': 0.5,
        'linestyle': '-',
        'linewidth': 1.3,
        'marker': '',
        'antialiased': False,
    }

    plt.plot(points[0, :], points[1, :], **plot_options)
    plt.show()

def heighway_dragon(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'FX', 'HEIGHWAY DRAGON',
               X='X+YF+', Y='-FX-Y')

def twin_dragon(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'FX+FX+', 'TWIN DRAGON',
               X='X+YF', Y='FX-Y')

def tetra_dragon(lvl, length=1, init_angle=0, angle=120):
    lindemayer(lvl, length, init_angle, angle, 'F', 'TETRA DRAGON',
               F='F+F-F')

def levy_dragon(lvl, length=1, init_angle=90, angle=45):
    lindemayer(lvl, length, init_angle, angle, 'F', 'LEVY DRAGON',
               F='+F--F+')

def koch_snowflake(lvl, length=1, init_angle=0, angle=60):
    lindemayer(lvl, length, init_angle, angle, 'F++F++F', 'KOCH SNOWFLAKE',
               F='F-F++F-F')

def koch_curve(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'F+F+F+F', 'KOCH CURVE',
               F='F+F-F-FF+F+F-F')

def sierpinski_triangle(lvl, length=1, init_angle=0, angle=120):
    lindemayer(lvl, length, init_angle, angle, 'F+F+F', 'SIERPINSKI TRIANGLE',
               F='F+F-F-F+F')

def hilbert_curve(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'X', 'HILBERT CURVE',
               X='-YF+XFX+FY-', Y='+XF-YFY-FX+')

def moor_curve(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'XFX+F+XFX', 'MOOR CURVE',
               X='-YF+XFX+FY-', Y='+XF-YFY-FX+')

def peano_curve(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'X', 'PEANO CURVE',
               X='XFYFX+F+YFXFY-F-XFYFX', Y='YFXFY-F-XFYFX+F+YFXFY')

def tiles(lvl, length=1, init_angle=0, angle=90):
    lindemayer(lvl, length, init_angle, angle, 'F+F+F+F', 'TILES',
               F='FF+F-F+F+FF')

def pentadendryt(lvl, length=2, init_angle=0, angle=72):
    lindemayer(lvl, length, init_angle, angle, 'F', 'PENTADENDRYT',
               F='F+F-F--F+F+F')

if __name__ == '__main__':
    heighway_dragon(7)
    # twin_dragon(10)
    # tetra_dragon(6)
    # levy_dragon(13)
    koch_snowflake(2)
    # koch_curve(2)
    # sierpinski_triangle(5)
    # hilbert_curve(5)
    moor_curve(4)
    # peano_curve(3)
    # tiles(2)
    # pentadendryt(4)


import numpy as np

def softmax(predictions):
    '''
    Computes probabilities from scores

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output

    Returns:
      probs, np array of the same shape as predictions -
        probability for every class, 0..1
    '''
    def softmax_row(v):
      norm = v - v.max()
      norm_exp = np.exp(norm)
      norm_exp_sum = norm_exp.sum()
      return norm_exp / norm_exp_sum

    if len(predictions.shape) == 1:
      return softmax_row(predictions)
    else:
      return np.apply_along_axis(softmax_row, 1, predictions)

def cross_entropy_loss(probs, target_index):
    '''
    Computes cross-entropy loss

    Arguments:
      probs, np array, shape is either (N) or (batch_size, N) -
        probabilities for every class
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss: single value
    '''
    if len(probs.shape) == 1:
      return -np.log(probs[target_index])
    else:
      return np.negative(np.log(probs[np.arange(probs.shape[0]), target_index])).mean()

def softmax_with_cross_entropy(predictions, target_index):
    '''
    Computes softmax and cross-entropy loss for model predictions,
    including the gradient

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss, single value - cross-entropy loss
      dprediction, np array same shape as predictions - gradient of predictions by loss value
    '''
    probs = softmax(predictions)
    loss = cross_entropy_loss(probs, target_index)
    dprediction = probs.copy()

    if len(predictions.shape) == 1:
      dprediction[target_index] -= 1
    else:
      dprediction[np.arange(dprediction.shape[0]), target_index] -= 1
      dprediction /= dprediction.shape[0]

    return loss, dprediction

def l2_regularization(W, reg_strength):
    '''
    Computes L2 regularization loss on weights and its gradient

    Arguments:
      W, np array - weights
      reg_strength - float value

    Returns:
      loss, single value - l2 regularization loss
      gradient, np.array same shape as W - gradient of weight by l2 loss
    '''
    loss = reg_strength * (W ** 2).sum()
    grad = 2 * reg_strength * W
    return loss, grad

def linear_softmax(X, W, target_index):
    '''
    Performs linear classification and returns loss and gradient over W

    Arguments:
      X, np array, shape (num_batch, num_features) - batch of images
      W, np array, shape (num_features, classes) - weights
      target_index, np array, shape (num_batch) - index of target classes

    Returns:
      loss, single value - cross-entropy loss
      gradient, np.array same shape as W - gradient of weight by loss

    '''
    predictions = X @ W
    probs = softmax(predictions)
    loss = cross_entropy_loss(probs, target_index)

    P = probs.copy()
    P[np.arange(P.shape[0]), target_index] -= 1.0
    grad = 1 / X.shape[0] * X.T @ P

    return loss, grad

class LinearSoftmaxClassifier():
    def __init__(self):
        self.W = None

    def fit(self, X, y, batch_size=100, learning_rate=1e-7, reg=1e-5,
            epochs=1):
        '''
        Trains linear classifier

        Arguments:
          X, np array (num_samples, num_features) - training data
          y, np array of int (num_samples) - labels
          batch_size, int - batch size to use
          learning_rate, float - learning rate for gradient descent
          reg, float - L2 regularization strength
          epochs, int - number of epochs
        '''

        num_train = X.shape[0]
        num_features = X.shape[1]
        num_classes = np.max(y)+1
        if self.W is None:
            self.W = 0.001 * np.random.randn(num_features, num_classes)

        loss_history = []
        for epoch in range(epochs):
            shuffled_indices = np.arange(num_train)
            np.random.shuffle(shuffled_indices)
            sections = np.arange(batch_size, num_train, batch_size)
            batches_indices = np.array_split(shuffled_indices, sections)

            for batch_indices in batches_indices:
              X_batch = X[batch_indices,]
              y_batch = y[batch_indices,]
              loss_unreg, grad_unreg = linear_softmax(X_batch, self.W, y_batch)
              loss_reg, grad_reg = l2_regularization(self.W, reg)
              loss = loss_reg + loss_unreg
              grad = grad_reg + grad_unreg
              self.W -= learning_rate * grad

            loss_history.append(loss)

            print("Epoch %i, loss: %f" % (epoch, loss))

        return loss_history

    def predict(self, X):
        '''
        Produces classifier predictions on the set

        Arguments:
          X, np array (test_samples, num_features)

        Returns:
          y_pred, np.array of int (test_samples)
        '''
        predictions = X @ self.W
        y_pred = np.argmax(predictions, axis=1)
        return y_pred


#Ref: Sreenivas Sarwar Anik

import cv2
from matplotlib import pyplot as plt
import numpy as np

#Generate a 2D sine wave image
x = np.arange(256)  # generate 1-D sine wave
y = np.sin(2 * np.pi * x / 30)  #Control the frequency
y += max(y) # offset sine wave by the max value to go out of negative range of sine

img = np.array([[y[j]*127 for j in range(256)] for i in range(256)], dtype=np.uint8) # create 2-D array of sine-wave

#img = np.rot90(frame)  #Rotate img by 90 degrees
#img = cv2.imread('images/2d_sine.png', 0) # load an image

dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)

#Shift DFT. First check the output without the shift
dft_shift = np.fft.fftshift(dft)

#Calculate magnitude spectrum from the DFT
#Added 1 as we may see 0 values and log of 0 is indeterminate
magnitude_spectrum = 20 * np.log((cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))+1)

#As the spatial frequency increases (bars closer),
#the peaks in the DFT amplitude spectrum move farther away from the origin

fig = plt.figure(figsize=(12, 12))
ax1 = fig.add_subplot(2,2,1)
ax1.imshow(img)
ax1.title.set_text('Input Image')
ax2 = fig.add_subplot(2,2,2)
ax2.imshow(magnitude_spectrum)
ax2.title.set_text('FFT of image')
plt.show()


import cv2
from matplotlib import pyplot as plt
import numpy as np

if __name__ == '__main__':

    img_path = "new_data/c2.jpg"
    img = cv2.imread(img_path, 0) # load an image

    # A saída é uma matriz complexa 2D. 1º canal real e 2º imaginário
    # Para fft in opencv, a imagem de entrada precisa ser convertida para float32
    dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)

    # Reorganiza uma transformada de Fourier X mudando a frequência zero
    # componente ao centro da matriz.
    # Caso contrário, começa no topo esquerdo do corenr da imagem (array)
    dft_shift = np.fft.fftshift(dft)

    # Magnitude da função é 20.log (abs (f))
    # Para valores que são 0, podemos acabar com valores indeterminados para log.
    # Portanto, podemos adicionar 1 ao array para evitar ver um warning.
    magnitude_spectrum = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))

    # Máscara circular HPF, o círculo central é 0, permanecendo todos uns
    # Pode ser usado para detecção de borda porque as frequências baixas no centro são bloqueadas
    # e apenas altas frequências são permitidas. As bordas são componentes de alta frequência.

    # Amplifica o ruído.
    # FIGURA 6:
    # rows, cols = img.shape
    # crow, ccol = int(rows / 2), int(cols / 2)
    # mask = np.ones((rows, cols, 2), np.uint8)
    # r = 20
    # center = [crow, ccol]
    # x, y = np.ogrid[:rows, :cols]
    # mask_area = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= r*r
    # mask[mask_area] = 0

    # Máscara LPF circular, o círculo central é 1, permanecendo todos os zeros
    # Permite apenas componentes de baixa frequência - regiões suaves
    # Pode suavizar o ruído, mas desfoca as bordas.
    # FIGURA 7:
    rows, cols = img.shape
    crow, ccol = int(rows / 2), int(cols / 2)
    mask = np.zeros((rows, cols, 2), np.uint8)
    r = 40
    center = [crow, ccol]
    x, y = np.ogrid[:rows, :cols]
    mask_area = (x - center[0]) ** 2 + (y - center[1]) ** 2 <= r*r
    mask[mask_area] = 1

    # Filtro Passa-Banda - Máscara de círculo concêntrico, apenas os pontos que vivem em um círculo concêntrico são uns
    # rows, cols = img.shape
    # crow, ccol = int(rows / 2), int(cols / 2)
    # mask = np.zeros((rows, cols, 2), np.uint8)
    # r_out = 80
    # r_in = 10
    # center = [crow, ccol]
    # x, y = np.ogrid[:rows, :cols]
    # mask_area = np.logical_and(((x - center[0]) ** 2 + (y - center[1]) ** 2 >= r_in ** 2),
    #                         ((x - center[0]) ** 2 + (y - center[1]) ** 2 <= r_out ** 2))
    # mask[mask_area] = 1

    # aplicar máscara e DFT inverso: Multiplique a imagem transformada de Fourier (valores)
    # com os valores da máscara.
    fshift = dft_shift * mask

    # Obtenha o espectro de magnitude (apenas para fins de plotagem)
    fshift_mask_mag = 20 * np.log(cv2.magnitude(fshift[:, :, 0], fshift[:, :, 1]))

    # Inverter deslocamento para mudar a origem de volta para o canto superior esquerdo.
    f_ishift = np.fft.ifftshift(fshift)

    #Inverse DFT to convert back to image domain from the frequency domain.
    #Will be complex numbers
    img_back = cv2.idft(f_ishift)

    #Magnitude spectrum of the image domain
    img_back = cv2.magnitude(img_back[:, :, 0], img_back[:, :, 1])

    fig = plt.figure(figsize=(12, 12))
    ax1 = fig.add_subplot(2,2,1)
    ax1.imshow(img, cmap='gray')
    ax1.title.set_text('Input Image')
    ax2 = fig.add_subplot(2,2,2)
    ax2.imshow(magnitude_spectrum, cmap='gray')
    ax2.title.set_text('FFT of image')
    ax3 = fig.add_subplot(2,2,3)
    ax3.imshow(fshift_mask_mag, cmap='gray')
    ax3.title.set_text('FFT + Mask')
    ax4 = fig.add_subplot(2,2,4)
    ax4.imshow(img_back, cmap='gray')
    ax4.title.set_text('After inverse FFT')
    plt.show()


from scipy.spatial.distance import euclidean,jaccard,cosine,correlation
import numpy as np
import pprint
import operator

critics = {'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
                         'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5,
                         'The Night Listener': 3.0},
           'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5,
                            'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0,
                            'You, Me and Dupree': 3.5},
           'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
                                'Superman Returns': 3.5, 'The Night Listener': 4.0},
           'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
                            'The Night Listener': 4.5, 'Superman Returns': 4.0,
                            'You, Me and Dupree': 2.5},
           'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
                            'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
                            'You, Me and Dupree': 2.0},
           'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
                             'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
           'Toby': {'Snakes on a Plane': 4.5, 'You, Me and Dupree': 1.0, 'Superman Returns': 4.0}}

pp = pprint.PrettyPrinter(indent=4)

def similarity(i,j,metric):
    first = list(critics.keys())[i]
    second = list(critics.keys())[j]
    movies = np.asanyarray([(critics[first].get(movie),critics[second].get(movie)) for movie in list(critics[first].keys()) if critics[second].get(movie)])
    return metric(movies[:,:1],movies[:,1:2])

def similarity_summary(metric=euclidean):
    keyarray = list(critics.keys())
    nmovies = len(keyarray)
    scores = {}
    for i in range(nmovies):
        for j in range(i+1,nmovies):
            score = similarity(i,j,metric)
            scores[('%s,%s')%(keyarray[i],keyarray[j])] = score
    sorted_scores = list(sorted(scores.items(), key=operator.itemgetter(1)))
    for item in sorted_scores:
        print('%30s=%f' % (item[0], item[1]))

metrices = list([euclidean,jaccard,cosine,correlation])

for metric in metrices:
    print('Summary for metric  %s'%(metric.__name__))
    similarity_summary(metric)


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

import numpy as np
from ImFEATbox.__helperCommands import rgb2grayscale

def HistogramF(I, typeflag=None, returnShape=False):
    """
     Input:     - I: A 2D image
                - typeflag: Struct of logicals to permit extracting features
                  based on desired characteristics:
                       + typeflag['global']: all features
                       + typeflag['texture']: all features
                       + typeflag['entropy']: only features based on entropy
                  default: all features are being extracted
                  For more information see README.txt

     Output:    - Out: A (1x6) vector containing 6 metrics calculated from the
                       image histogram
   """
    # ************************************************************************
    # Implemented for MRI feature extraction by the Department of Diagnostic
    # and Interventional Radiology, University Hospital of Tuebingen, Germany
    # and the Institute of Signal Processing and System Theory University of
    # Stuttgart, Germany. Last modified: November 2016
    #
    # This implementation is part of ImFEATbox, a toolbox for image feature
    # extraction and analysis. Available online at:
    # https://github.com/annikaliebgott/ImFEATbox
    #
    # Contact: annika.liebgott@iss.uni-stuttgart.de
    # ************************************************************************

    if typeflag == None:
        typeflag = dict()
        typeflag['global'] = True
        typeflag['texture'] = True
        typeflag['entropy'] = True

    if typeflag['global'] or typeflag['texture']:
        typeflag['entropy'] = True

    if returnShape:
        if typeflag['texture'] or typeflag['global']:
            return (6,1)
        else:
            return (1,1)

    # Check for color image and convert to grayscale
    if len(np.shape(I)) == 3:
        if np.shape(I)[2] == 3:
            I = rgb2grayscale(I)

    # 256 gray scale Image
    graylevels = range(0, 256)

    # Probability of occurence of gray values
    Prob = np.histogram(I, bins=len(graylevels), range=(np.min(0),np.max(I)))[0] / float(I.size)

    ## extract features

    if typeflag['texture'] or typeflag['global']:
        # Histogram Mean _ Identifier code
        Mean = np.dot(graylevels, Prob)

        # Histogram Standard Deviation
        Std = np.sqrt(np.dot(np.power((graylevels-Mean),2),Prob))

        # Histogram Skewness
        Skewness = 1/np.power(Std,3)*np.dot(np.power((graylevels-Mean),3),Prob)

        # Histogram Kurtosis
        Kurtosis = 1/np.power(Std,4)*(np.dot(np.power((graylevels-Mean),4),Prob)-3)

        # Histogram Energy
        Energy = np.sum(np.power(Prob,2))

    # Histogram Entropy
    # marginal entropies
    Entropy = -np.sum(Prob[Prob != 0] * np.log2(Prob[Prob != 0]))

    ## Return feature vector

    if typeflag['texture'] or typeflag['global']:
        Out = np.array([Mean, Std, Skewness, Kurtosis, Energy, Entropy])
    else:
        Out =  np.array([Entropy])

    return Out


#-----------------------------------
#DICE EXAMPLE
#----------------------------------
from itertools import permutations
from itertools import product
import numpy as np
import matplotlib.pyplot as plt

N_DIE=[]; ENTROPY=[]
for N_DICE in range(1,6):
	print("--------",N_DICE,"--------")
	#CREATE ALL PERMUTATIONS
	myList = list(product(range(1,7), repeat=N_DICE))

	#-------------------
	#DISTINGUISHABLE DICE: MACROSTATE --> (1,2) != (2,1)
	#-------------------
	Nmicro=len(myList)
	pi=np.ones(Nmicro)/Nmicro
	S=-sum(pi*np.log(pi))
	print("DISTINGUISHABLE",S,np.log(Nmicro),1.792*N_DICE)

	#-------------------
	#INDISTINGUISHABLE: USE SUM AS MACOSTATE  (1,2)=(2,1) --> state-3
	#-------------------

	#FIND NUMBER OF UNIQUIE STATES
	uniq=[]
	for i in range(0,len(myList)):
		S=sum(myList[i])
		if(S not in uniq): uniq.append(S)
	print("N UNIQUE SUMS",len(uniq))

	#COMPUTE PROBABLITIES FOR MAC STATES
	Nmicro=0; states=np.zeros(len(uniq))
	for i in range(0,len(myList)):
		S=sum(myList[i])
		#print(i,myList[i],S)
		Nmicro=Nmicro+1
		states[S-N_DICE]=states[S-N_DICE]+1
	pi=states/states.sum()
	S=-sum(pi*np.log(pi))
	print("STATES:",states)
	print("Pi=",np.round(pi,4) )
	print("ENTROPY",S)
	print("ENTROPY/DICE",S/N_DICE)
	N_DIE.append(N_DICE)
	ENTROPY.append(S)

plt.plot(N_DIE,ENTROPY,'bo-')
plt.show()


"""
Let T(n) be the number of tours over a 4 × n playing board such that:

    The tour starts in the top left corner.
    The tour consists of moves that are up, down, left, or right one square.
    The tour visits each square exactly once.
    The tour ends in the bottom left corner.

The diagram shows one tour over a 4 × 10 board:

T(10) is 2329. What is T(10**12) modulo 10**8?

ans: 15836928
"""

"""
Solution Method:
1. Catalogue all possible 4x2  path "slices"
2. Encode how the left side connects to the right side in an adjacency matrix
3. Raise adj. matrix to n/2-th power

The following illustrates how a 4x2 slice is encoded as a 4x2 matrix:
1 indicates a path entering or exiting the slice at the square.
2 indicates a "loop back".

The distinction between 1s and 2s stops path "islands" and premature loops from forming.

1s and 2s on the left will connect to 1s and 2s respectively on the right, with the following exceptions.
A 1 and 2 on the left can connect to each other, the remaining 2 can connect to a 1 on the right.
1s can connect on the left if the right has only 0s.

Examples:
00        _      01        ___    10      ___
11  ==  _| |_    12  ==  _|  _    10  ==  _  |
11      _   _    12      _  |_    21      _| |_
00       |_|     01       |___    21      _____

11 and 11 are unique in that these encodings refer to multiple paths:
00     20
00     20
11     11
_____    _   _    _   _    and    _   _    _____
  _       |_|      | |            _| |     ___
 | |       _       |_|            ___|     _  |
_| |_    _| |_     ___            _____    _| |_

The adjacency matrix then looks at all ways the left side (given by a column of 4 ints) can connect to the right side (again given by a column of 4 ints):

index: 0 1 2 3 4 5 6 7 8 9
col  : 1 1 0 2 1 1 1 0 0 0
       0 1 0 2 2 1 0 1 1 0
       0 0 1 1 2 2 1 0 1 0
       1 0 1 1 1 2 0 1 0 0

The 9th column is noteworthy in being all 0s. This indicates the last column of the playing board where all paths need to turn around.
"""

import numpy as np

n = 10**12
m = 10**8

adj_matrix = np.matrix([
[3,1,1,1,1,1,0,1],
[1,1,1,0,1,1,1,1],
[1,1,1,1,1,0,1,1],
[1,1,1,1,1,0,1,1],
[2,1,1,1,1,1,0,1],
[1,1,1,0,1,1,1,1],
[0,1,1,0,1,0,1,1],
[0,0,0,0,0,0,0,0]
], np.int64)

def mod_pow(base, exp, mod):
	if exp <= 0:
		raise Exception("exp nonpositive")
	elif exp == 1:
		return base
	elif exp % 2 == 0:
		tmp = mod_pow(base, exp/2, mod)
		return np.mod(np.matmul(tmp, tmp), mod)
	else:
		tmp = mod_pow(base, exp - 1, mod)
		return np.mod(np.matmul(tmp, base), mod)

def main(width, mod = 10000):
	return mod_pow(adj_matrix, width//2, mod)[0,7]

print(f"T(4) = {main(4)}")
assert main(4)  == 8

print(f"T(10) = {main(10)}")
assert main(10) == 2329

print(f"T({n}) = {main(n, m)}")


from matplotlib import pyplot as plt
import numpy as np
from skimage import io, color
from math import sqrt, exp

def distance(point1, point2):
    return sqrt((point1[0]-point2[0])**2 + (point1[1]-point2[1])**2)

def gaussianLP(lin, col, D0):
    base = np.zeros((lin, col))
    center = (lin/2, col/2)
    for x in range(col):
        for y in range(lin):
            base[y, x] = exp(((-distance((y, x), center)**2)/(2*(D0**2))))
    return base

def plotMany(figures, cmap):
    plt.figure(figsize=(10, 10))
    subplotNumber = 241
    for figure in (figures):
        plt.subplot(subplotNumber)
        plt.imshow(figure, cmap=cmap)
        subplotNumber += 1

im = io.imread('Lenna.png')

im2 = color.rgb2gray(im)
im2 = (im2 * 255).astype('uint8')

lin, col = im2.shape
# Filtro variando o sigma do filtro gaussiano
f1 = gaussianLP(lin, col, 2)
f2 = gaussianLP(lin, col, 20)
f3 = gaussianLP(lin, col, 40)
f4 = gaussianLP(lin, col, 500)

# mostra os filtros
plotMany([f1, f2, f3, f4], cmap=None)

# realiza a transformada
imft2 = np.fft.fft2(im2)
fshift2 = np.fft.fftshift(imft2)

# aplica os filtros na imagem com a transformada
blur1 = fshift2 * f1
blur2 = fshift2 * f2
blur3 = fshift2 * f3
blur4 = fshift2 * f4

# realiza a transformada inversa
blur1 = np.fft.ifft2(np.fft.ifftshift(blur1))
blur2 = np.fft.ifft2(np.fft.ifftshift(blur2))
blur3 = np.fft.ifft2(np.fft.ifftshift(blur3))
blur4 = np.fft.ifft2(np.fft.ifftshift(blur4))

plotMany([np.abs(blur1), np.abs(blur2), np.abs(
    blur3), np.abs(blur4)], cmap='gray')

# Ao aumentar o sigma no filtro gaussiano passa baixa, é possível que a imagem se torna mais nítida
# A transformada de uma gaussiana é outra gaussiana
# Após a transformada, a gaussiana continua com o mesmo tamanho


import numpy as np

def gaussElimination(A, b):
    A = A.copy()
    b = b.copy()
    n = A.shape[0]
    for j in range(0, n-1):
        for i in range(j+1, n):
            k = A[i, j]/A[j, j]
            A[i, :] = A[i, :]-A[j, :]*k
            b[i] = b[i]-b[j]*k
    x = np.zeros([n])
    for j in range(n-1, -1, -1):
        for k in range(j+1, n):
            b[j] = b[j]-x[k]*A[j, k]
        x[j] = b[j]/A[j, j]
    return x

def LU(A):
    n = A.shape[0]
    L = np.eye(n)
    U = A.copy()
    for j in range(0, n-1):
        for i in range(j+1, n):
            L[i, j] = U[i, j]/U[j, j]
            U[i, :] = U[i, :]-U[j, :]*L[i, j]
    return L, U

def jacobi(A, b, x, n=10):
    Di = np.diag(1/np.diag(A))
    L = np.tril(A, -1)
    U = np.triu(A, 1)
    for k in range(n):
        x = Di.dot(b-(L+U).dot(x))
    return x

def newton(F, DF, x0, n=10):
    x = x0
    for k in range(n):
        s = gaussElimination(DF(x), -F(x))
        x = x+s
    return x


from numpy import linalg as LA
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from collections import OrderedDict
from sklearn.cluster import KMeans

def read_data(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()

    num_points = len(lines)
    dim_points = 28 * 28
    data = np.empty((num_points, dim_points))
    labels = np.empty(num_points)

    for ind, line in enumerate(lines):
        num = line.split(',')
        labels[ind] = int(num[0])
        data[ind] = [ int(x) for x in num[1:] ]

    return (data, labels)

train_data, train_labels = read_data("sample_train.csv")

pca = PCA(n_components=2)
X_k = pca.fit_transform(train_data)
print(X_k.shape)
# then plot

kmeans = KMeans(n_clusters = 10, max_iter = 500)
kmeans.fit(X_k)
print(kmeans.labels_.shape)

colors = ['yellow', 'red', 'yellowgreen', 'magenta', 'blue', 'darkgreen', 'purple',
 'brown', 'black', 'orange', 'cyan']

for data, label in zip(X_k,train_labels):
    label = int(label)
    plt.scatter(data[0], data[1], label=label, color=colors[label])
plt.xlabel('x axis')
plt.ylabel('y axis')
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
plt.title('Ground Truth')
plt.show()

for data, label in zip(X_k,kmeans.labels_):
    label = int(label)
    plt.scatter(data[0], data[1], label=label, color=colors[label])
plt.xlabel('x axis')
plt.ylabel('y axis')
handles, labels = plt.gca().get_legend_handles_labels()
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
plt.title('K means Clustering')
plt.show()

# For each cluster find out original what is where
#
metrix = np.zeros((10, 11))
for index in range(len(kmeans.labels_)):
    klabel = kmeans.labels_[index]
    orglabl = int(train_labels[index])
    metrix[klabel][orglabl] += 1
    metrix[klabel][10] +=1
print(metrix)

kmeans_labels = []
for ki in range(10):
    maxpoints = np.max(metrix.T[ki])
    findindex = np.where(metrix.T[ki]==maxpoints)
    kmeans_labels.append(findindex[0][0])
# print(kmeans_labels)

for ki in range(10):
    metrix[ki] = metrix[ki]/ metrix[ki][10]

kmeans_labels = []
for oi in range(10):
    maxpoints = np.max(metrix.T[oi])
    findindex = np.where(metrix.T[oi]==maxpoints)
    kmeans_labels.append(findindex[0][0])
print(kmeans_labels)


import pandas as pd
from scipy.stats.mstats import gmean, hmean
import matplotlib.pyplot as plt

my_dataset = pd.read_excel('Smith_glass_post_NYT_data.xlsx', sheet_name='Supp_traces')

a_mean = my_dataset.Zr.mean()
g_mean = gmean(my_dataset['Zr'])
h_mean = hmean(my_dataset['Zr'])

print ('-------')
print ('arithmetic mean')
print ("{0:.1f} [ppm]".format(a_mean))
print ('-------')

print ('geometric mean')
print ("{0:.1f} [ppm]".format(g_mean))
print ('-------')

print ('harmonic mean')
print ("{0:.1f} [ppm]".format(h_mean))
print ('-------')

fig, ax = plt.subplots()
ax.hist(my_dataset.Zr, bins='auto', density=True, edgecolor='k', label='Measurements Hist', alpha=0.8)
ax.axvline(a_mean, color='purple', label='Arithmetic mean', linewidth=2)
ax.axvline(g_mean, color='orange',  label='Geometric mean', linewidth=2)
ax.axvline(h_mean, color='green',  label='Harmonic mean', linewidth=2)
ax.set_xlabel('Zr [ppm]')
ax.set_ylabel('Probability density')
ax.legend()

'''
Output:
-------
arithmetic mean
365.4 [ppm]
-------
geometric mean
348.6 [ppm]
-------
harmonic mean
333.8 [ppm]
-------
'''


import numpy as np

__all__ = [
    'entropy',
    'conditional_entropy',
    'mutual_information',
    'conditional_mutual_information'
]

def entropy(vector, base=None):
    """This estimator computes the entropy of the empirical probability distribution.

    Parameters
    ----------
    vector: list or np.array
        Vector of which entropy is calculated.
    base: int or float (default=np.e)
        Base of the logarithm in entropy approximation

    Returns
    --------
    vector_entropy: float
        Approximated entropy

    Examples
    --------
    >>> from bcselector.information_theory.basic_approximations import entropy
    >>> foo = [1,4,1,2,5,6,3]
    >>> entropy(foo)

    """

    assert isinstance(vector, (list)) or (isinstance(vector, np.ndarray) and len(vector.shape) == 1), "Argument 'vector' not in the right shape. Use list or numpy (n,) shape instead"
    assert len(vector) > 0, "Argument 'vector' can't be empty"

    vector = np.array(vector)

    if len(vector) == 1:
        "Entropy for one number is zero"
        return 0.0

    _, counts = np.unique(vector, return_counts=True)
    norm_counts = counts / counts.sum()
    base = np.e if base is None else base
    return -(norm_counts * np.log(norm_counts)/np.log(base)).sum()

def conditional_entropy(vector, condition, base=None):
    """This estimator computes the conditional entropy of the empirical probability distribution.

    Parameters
    ----------
    vector: list or np.array
        Vector of which entropy is calculated.
    condition: list or np.array
        Vector of condition for entropy.
    base: int or float
        Base of the logarithm in entropy approximation. If None, np.e is selected and entropy is returned in nats.

    Returns
    --------
    vector_entropy: float
        Approximated entropy.

    """
    assert isinstance(vector, (list)) or (isinstance(vector, np.ndarray) and len(vector.shape) == 1), "Argument 'vector' not in the right shape. Use list or numpy (n,) shape instead."
    assert isinstance(condition, (list)) or (isinstance(condition, np.ndarray) and len(condition.shape) == 1), "Argument 'condition' not in the right shape. Use list or numpy (n,) shape instead."
    assert len(vector) > 0, "Argument 'vector' can't be empty"
    assert len(condition) > 0, "Argument 'condition' can't be empty"

    vector = np.array(vector)
    condition = np.array(condition)

    assert vector.shape == condition.shape, "Argument 'vector' must be the same lenght as 'condition'"

    if len(vector) == 1:
        "Entropy for one number is zero"
        return 0.0

    # sort values to use np.split later
    vector_sorted = vector[condition.argsort()]
    condition_sorted = condition[condition.argsort()]

    binvalues = np.split(vector_sorted, np.unique(condition_sorted, return_index=True)[1][1:])
    _, counts = np.unique(condition_sorted, return_counts=True)
    binprobas = counts / counts.sum()
    cond_entropy = 0

    for values, proba in zip(binvalues, binprobas):
        cond_entropy += entropy(values, base=base) * proba
    return cond_entropy

def mutual_information(vector_1, vector_2, base=None):
    """This estimator computes the mutual information of two vectors with method of the empirical probability distribution.

    Parameters
    -----------
    vector_1 : list or np.array
        Vector of one variable.
    vector_2 : list or np.array
        Vector of one variable.
    base : int or float
        Base of the logarithm in entropy approximation. If None, np.e is selected and entropy is returned in nats.

    Returns
    --------
    variables_mutual_information: float
        Approximated mutual information between variables.

    """
    vector_1_entropy = entropy(vector=vector_1, base=base)
    cond_entropy = conditional_entropy(vector=vector_1, condition=vector_2, base=base)
    return vector_1_entropy - cond_entropy

def conditional_mutual_information(vector_1, vector_2, condition, base=None):
    """This estimator computes the conditional mutual information of two vectors and condition vector with method of the empirical probability distribution.

    Parameters
    -----------
    vector_1 : list or np.array
        Vector of one variable.
    vector_2: list or np.array
        Vector of one variable.
    condition: list or np.array
        Vector of condition for mutual information.
    base : int or float
        Base of the logarithm in entropy approximation. If None, np.e is selected and entropy is returned in nats.

    Returns
    --------
    variables_conditional_mutual_information : float
        Approximated conditional mutual information between variables.

    """
    assert isinstance(vector_1, (list)) or (isinstance(vector_1, np.ndarray) and len(vector_1.shape) == 1), "Argument 'condition' not in the right shape. Use list or numpy (n,) shape instead."
    assert isinstance(vector_2, (list)) or (isinstance(vector_2, np.ndarray) and len(vector_2.shape) == 1), "Argument 'condition' not in the right shape. Use list or numpy (n,) shape instead."
    assert isinstance(condition, (list)) or (isinstance(condition, np.ndarray) and len(condition.shape) == 1), "Argument 'condition' not in the right shape. Use list or numpy (n,) shape instead."
    assert len(vector_1) > 0, "Argument 'vector_1' can't be empty"
    assert len(vector_2) > 0, "Argument 'vector_2' can't be empty"
    assert len(condition) > 0, "Argument 'condition' can't be empty"

    vector_1 = np.array(vector_1)
    vector_2 = np.array(vector_2)
    condition = np.array(condition)

    assert vector_1.shape == vector_2.shape == condition.shape, "Argument 'vector_1' and 'vector_2' must be the same lenght as 'condition'"

    if len(condition) == 1:
        "Entropy for one number is zero"
        return 0.0

    vector_1_sorted = vector_1[condition.argsort()]
    vector_2_sorted = vector_2[condition.argsort()]
    condition_sorted = condition[condition.argsort()]

    binvalues_1 = np.split(vector_1_sorted, np.unique(condition_sorted, return_index=True)[1][1:])
    binvalues_2 = np.split(vector_2_sorted, np.unique(condition_sorted, return_index=True)[1][1:])
    _, counts = np.unique(condition_sorted, return_counts=True)
    binprobas = counts / counts.sum()
    cond_mutual_info = 0

    for value_1, value_2, proba in zip(binvalues_1, binvalues_2, binprobas):
        cond_mutual_info += mutual_information(value_1, value_2, base=base) * proba
    return cond_mutual_info


import dataloader
import transformer
import numpy as np
import matplotlib.pyplot as plt

'''
base analysis
'''
desc_map,price_map = dataloader.load_train()
plt.scatter(desc_map['YearBuilt'],price_map['price'],s=10)
plt.title('SalePrice_YearBuilt')
plt.xlabel('YearBuilt')
plt.ylabel('SalePrice')
plt.show()
print('SalePrice mean:',np.mean(price_map['price']),'median:',np.median(price_map['price']),'std:',np.std(price_map['price']))

'''
corrcoefs analysis
'''
corrcoefs = []
keys = []
# corrcoefs_map ={}
desc_map,price_map = dataloader.load_train()
price_map['price'] = transformer.normlize(price_map['price'])
for key in desc_map.keys():
    desc_map[key] = transformer.normlize(desc_map[key])
    corrcoefs.append(np.corrcoef(desc_map[key],price_map['price'])[0][1])
    keys.append(key)
    # corrcoefs_map[key]

zipped = zip(keys,corrcoefs)
sort_zipped = sorted(zipped,key=lambda x:(x[1],x[0]),reverse=True)
result = zip(*sort_zipped)
keys, corrcoefs = [list(x) for x in result]
plt.title('correlation')
plt.bar(keys,corrcoefs)
plt.xticks(rotation=90)
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Fri Dec 21 18:49:14 2018

@author: kennedy
"""
import pandas as pd
import numpy as np

def process_time(df):
  if 'timestamp' not in df.columns:
    df.index = pd.to_datetime(df.index)
    df['seconds'] = df.index.second
    df['minutes'] = df.index.minute
    df['hours'] = df.index.hour
    df['min'] = df['hours'].map(str)+":"+df['minutes'].map(str)
    df['years'] = df.index.year
    df['days'] = df.index.day
    df['months'] = df.index.month
    df['DayOfTheWeek'] = df.index.dayofweek
    df['WeekDay'] = (df['DayOfTheWeek'] < 5).astype(int)
    df['daylight'] = ((df['hours'] >= 8) & (df['hours'] <= 20)).astype(int)
    df['mnth'] = df['months'].map(str)+":"+df['years'].map(str)
    df['time_epoch'] = (df.index.astype(np.int64)/100000000000).astype(np.int64)

    # Cyclical variable transformations
    # wday has period of 5
    df['wday_sin'] = np.sin(2 * np.pi * df['DayOfTheWeek'] / len(df.DayOfTheWeek.unique()))
    df['wday_cos'] = np.cos(2 * np.pi * df['DayOfTheWeek'] / len(df.DayOfTheWeek.unique()))

    #days has period 31 or 30 or 28
    df['mday_sin'] = np.sin(2 * np.pi * df['days'] / df.days.max())
    df['mday_cos'] = np.cos(2 * np.pi * df['days'] / df.days.max())

    # yday has period of 365
    df['yday_sin'] = np.sin(2 * np.pi * df['DayOfTheWeek'] / 365)
    df['yday_cos'] = np.cos(2 * np.pi * df['DayOfTheWeek'] / 365)

    # month has period of 12
    df['month_sin'] = np.sin(2 * np.pi * df['months'] / 12)
    df['month_cos'] = np.cos(2 * np.pi * df['months'] / 12)

    # time has period of 24
    df['time_sin'] = np.sin(2 * np.pi * df['hours'] / 24)
    df['time_cos'] = np.cos(2 * np.pi * df['hours'] / 24)
  else:
    df['seconds'] = df['timestamp'].dt.second
    df['minutes'] = df['timestamp'].dt.minute
    df['hours'] = df['timestamp'].dt.hour
    df['min'] = df['hours'].map(str)+":"+df['minutes'].map(str)
    df['years'] = df['timestamp'].dt.year
    df['days'] = df['timestamp'].dt.day
    df['months'] = df['timestamp'].dt.month
    df['DayOfTheWeek'] = df['timestamp'].dt.dayofweek
    df['WeekDay'] = (df['DayOfTheWeek'] < 5).astype(int)
    df['daylight'] = ((df['hours'] >= 8) & (df['hours'] <= 20)).astype(int)
    df['mnth'] = df['months'].map(str)+":"+df['years'].map(str)
    df['time_epoch'] = (df['timestamp'].astype(np.int64)/100000000000).astype(np.int64)

    # Cyclical variable transformations
    # wday has period of 5
    df['wday_sin'] = np.sin(2 * np.pi * df['DayOfTheWeek'] / len(df.DayOfTheWeek.unique()))
    df['wday_cos'] = np.cos(2 * np.pi * df['DayOfTheWeek'] / len(df.DayOfTheWeek.unique()))

    #days has period 31 or 30 or 28
    df['mday_sin'] = np.sin(2 * np.pi * df['days'] / df.days.max())
    df['mday_cos'] = np.cos(2 * np.pi * df['days'] / df.days.max())

    # yday has period of 365
    df['yday_sin'] = np.sin(2 * np.pi * df['DayOfTheWeek'] / 365)
    df['yday_cos'] = np.cos(2 * np.pi * df['DayOfTheWeek'] / 365)

    # month has period of 12
    df['month_sin'] = np.sin(2 * np.pi * df['months'] / 12)
    df['month_cos'] = np.cos(2 * np.pi * df['months'] / 12)

    # time has period of 24
    df['time_sin'] = np.sin(2 * np.pi * df['hours'] / 24)
    df['time_cos'] = np.cos(2 * np.pi * df['hours'] / 24)

  return df


"""
"Gradien-free methods, such as Nelder-Mead simplex algorithm, is used to
minimize a non-linear function containing two variables. This method,
particularly, is fairly robust and works even if very little is known about
the objective function. However, in many situations, we do know more about
the objective function, and this fact allows us to devise faster and more
efficient algorithms for minimizing the function. We can do this by making
use of properties such as the gradient of the function.

The gradient of a function of more than one variable describes the rate of
change of the function in each of its component directions. This is a vector
of the partial derivatives of the function with respect to each of the
variables. From this gradient vector, we can deduce the direction in which
the function is increasing most rapidly and, conversely, the direction in which
the function is decreasing most rapidly from any given position. This gives us
the basis for gradient descent methods for minimizing a function. The algorithm
is very simple: given a starting position, x, we compute the gradient at this x
and the corresponding direction in which the gradient is most rapidly decreasing,
then make a small step in that direction. After a few iterations, this will move
from the starting position to the minimum of the function.

This module illustrates how to implement an algorithm based on the steepest
descent algorithm to minimize an objective function within a bounded region.
"""
import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D

def descend(func, x0, grad, bounds, tol=1e-8, max_iter=100):
    xn = x0
    xnm1 = np.inf
    grad_xn = grad(x0)

    for i in range(max_iter):

        if  np.linalg.norm(xn - xnm1) < tol:
            break

        direction = -grad_xn
        xnm1 = xn
        xn = xn + 0.2 * direction
        grad_xn = grad(xn)

        yield i, xn, func(xn), grad_xn

def func(x):
    return ((x[0] - 0.5)**2 + (x[1] + 0.5)**2)*np.cos(0.5*x[0]*x[1])

def grad(x):
    c1 = x[0]**2 - x[0] + x[1]**2 + 0.5
    cos_t = np.cos(0.5*x[0]*x[1])
    sin_t = np.sin(0.5*x[0]*x[1])

    return np.array([
        (2*x[0]-1)*cos_t - 0.5*x[1]*c1*sin_t,
        (2*x[1]+1)*cos_t - 0.5*x[0]*c1*sin_t])

x_r = np.linspace(-1, 1)
y_r = np.linspace(-2, 2)
x, y = np.meshgrid(x_r, y_r)
z = func([x, y])

surf_fig = plt.figure(tight_layout=True)
surf_ax = surf_fig.add_subplot(projection="3d")
surf_ax.tick_params(axis="both", which="major", labelsize=9)
surf_ax.set(xlabel="x", ylabel="y", zlabel="z")
surf_ax.set_title("Objective function")
surf_ax.plot_surface(x, y, z, alpha=0.7)

x0 = np.array([-0.8, 1.3])
surf_ax.plot([x0[0]], [x0[1]], func(x0), "r*")

cont_fig, cont_ax = plt.subplots()
cont_ax.set(xlabel="x", ylabel="y")
cont_ax.set_title("Contour plot with iterates")
cont_ax.contour(x, y, z, levels=30)

bounds = ((-1, 1), (-2, 2))
xnm1 = x0

for i, xn, fxn, grad_xn in descend(func, x0, grad, bounds):
    cont_ax.plot([xnm1[0], xn[0]], [xnm1[1], xn[1]], "k*--")
    xnm1, grad_xnm1 = xn, grad_xn

print(f"iterations={i}")
print(f"min val at {xn}")
print(f"min func value = {fxn}")

surf_ax.plot([xn[0]], [xn[1]], func(xn), "r*")

plt.show()


"""
Tests the Gaussian quadrature rules.
"""

import numpy as np
import pytest

from pysofe.quadrature.gaussian import GaussQuadSimp

# set global shortcut and tolerance
fac = np.math.factorial
eps = 1e-8

class TestGaussQuadSimp(object):
    order = 2
    quad_rule = GaussQuadSimp(order, dimension=3)

    def test_specs(self):
        assert len(self.quad_rule.points) == 4
        assert len(self.quad_rule.weights) == 4

        assert all([p.ndim == 2 for p in self.quad_rule.points])
        assert all([w.ndim == 1 for w in self.quad_rule.weights])

    def test_monomials_2D(self):
        x0, x1 = self.quad_rule.points[2]
        w = self.quad_rule.weights[2]

        for p in xrange(self.quad_rule.order + 1):
            for q in xrange(self.quad_rule.order - p + 1):
                quad = (np.power(x0, p) * np.power(x1, q) * w).sum()
                exact = (fac(p) * fac(q)) / float(fac(2 + p + q))

                assert abs(quad - exact) < eps

    def test_monomials_3D(self):
        x0, x1, x2 = self.quad_rule.points[3]
        w = self.quad_rule.weights[3]

        for p in xrange(self.quad_rule.order + 1):
            for q in xrange(self.quad_rule.order - p + 1):
                for r in xrange(self.quad_rule.order - p - q + 1):
                    quad = (np.power(x0, p) * np.power(x1, q) * np.power(x2, r) * w).sum()
                    exact = (fac(p) * fac(q) * fac(r)) / float(fac(3 + p + q + r))

                    assert abs(quad - exact) < eps


import numpy as np
import pandas as pd

def simple_feat_scale(df, column_name):
    return df[column_name]/df[column_name].max()

def min_max_scale(df, column_name):
    return (df[column_name]-df[column_name].min())/(df[column_name].max()-df[column_name].min())

def z_score_scale(df, column_name):
    return (df[column_name]-df[column_name].mean())/df[column_name].std()

def bin_column(df, column_name, n_bins = 0, group_names = None):
    bins = np.linspace(min(df[column_name]), max(df[column_name]), n_bins + 1)
    if(group_names is None):
        return pd.cut(df[column_name], bins, include_lowest = True)
    else:
        n_bins = len(group_names)
        return pd.cut(df[column_name], bins, labels = group_names, include_lowest = True)

\


import numpy as np

def get_similarity_matrix_from_distance_matrix(
    D: np.array, sigma: float = 1.0
) -> np.array:
    """
    construct the similarity matrix from the distance matrix by applying a Gaussian kernel with parameter sigma

    :param D: distance matrix
    :param sigma: parameter sigma used in the Gaussian kernel
    :return: similarity matrix S
    :rtype: np.array

    Example
    ::
        import numpy as np
        from cluster.utils import get_similarity_matrix, get_similarity_matrix_from_distance_matrix

        # some random points
        points = np.random.rand(10, 5)

        distance_matrix = get_similarity_matrix(points, distance=True)
        similarity_matrix = get_similarity_matrix_from_distance_matrix(distance_matrix)

    |

    """
    return np.exp(-(D ** 2) / (2 * sigma ** 2))

def get_distance_matrix_from_similarity_matrix(
    S: np.array, sigma: float = 1.0
) -> np.array:
    """
    construct the distance matrix from the similarity matrix by applying the inverse Gaussian kernel to the similarity
    matrix

    :param S: similarity matrix
    :param sigma: parameter sigma used in the Gaussian kernel
    :return: distance matrix D
    :rtype: np.array

    Example
    ::
        import numpy as np
        from cluster.utils import get_similarity_matrix, get_distance_matrix_from_similarity_matrix

        # some random points
        points = np.random.rand(10, 5)

        similarity_matrix = get_similarity_matrix(points)
        distance_matrix = get_distance_matrix_from_similarity_matrix(similarity_matrix)

    |

    """
    return np.sqrt(-np.log(S) * (2 * sigma ** 2))

def get_similarity_matrix(
    points: np.array,
    sigma: float = 1.0,
    norm: int = None,
    vectorized: bool = True,
    distance: bool = False,
) -> np.array:
    """
    construct the similarity matrix of points by computing distances and applying a Gaussian kernel with
    parameter sigma

    :param points: array of points of size (N x d) where N is the number of points
    :param sigma: parameter for the Gaussian kernel
    :param norm: norm to use for the distance of the points
    :param vectorized: whether to use vectorization (might use more memory initially) or filling the
        similarity matrix iteratively (uses less memory)
    :param distance: whether to return the similarity matrix or the distance matrix
    :return: similarity matrix
    :rtype: np.array

    Example
    ::
        import numpy as np
        from cluster.utils import get_similarity_matrix

        # some random points
        points = np.random.rand(10, 5)

        distance_matrix = get_similarity_matrix(points, distance=True)
        similarity_matrix = get_similarity_matrix(points)

    |

    """
    # expect points to be (N x k)
    N, k = points.shape
    if vectorized:
        M = np.expand_dims(points, 0).repeat(N, 0)
        D = np.linalg.norm((M - np.transpose(M, (1, 0, 2))), axis=2, ord=norm)
    else:
        D = np.zeros([N, N])
        for i in range(N):
            for j in range(N):
                p = points[i, :]
                q = points[j, :]
                D[i, j] = np.linalg.norm(p - q, ord=norm)

    if not distance:
        # apply Gaussian kernel to distance matrix to get similarity
        return get_similarity_matrix_from_distance_matrix(D, sigma)
    else:
        return D

def get_random_similarity_matrix(
    n: int = 10,
    d: int = 2,
    sigma: float = 1.0,
    norm: int = None,
    vectorized: bool = True,
    distance: bool = False,
):
    """
    constructs a random similarity matrix with random points of size (n x d)

    :param n: number of points to create
    :param d: number of dimension for each point to create
    :param sigma: parameter for the Gaussian kernel
    :param norm: norm to use for the distance of the points
    :param vectorized: whether to use vectorization (might use more memory initially) or filling the
        similarity matrix iteratively (uses less memory)
    :param distance: whether to return the similarity matrix or the distance matrix
    :return: similarity matrix
    :rtype: np.array

    Example
    ::

        from cluster.utils import get_random_similarity_matrix

        random_similarity_matrix = get_random_similarity_matrix(points, distance=True)

    |

    """
    points = np.random.rand(n, d)
    return get_similarity_matrix(points, sigma, norm, vectorized, distance)


"""
Tests for the measure module
"""

# imports
import molecool
import numpy as np
import pytest

def test_calculate_distance():
    r1 = np.array([0,0,0])
    r2 = np.array([0,1,0])
    expected_distance = 1

    calculated_distance = molecool.calculate_distance(r1,r2)

    assert expected_distance == calculated_distance

#Write a test for the calculate angle function
#Use points (0,0,-1) (0,0,0) (1,0,0)
#expected angle is 90 degrees

def test_calculate_angle():
    rA=np.array([0,0,-1])
    rB=np.array([0,0,0])
    rC=np.array([1,0,0])
    expectedangle=90

    calculated_angle = molecool.calculate_angle(rA,rB,rC,degrees=True)

    assert pytest.approx(expectedangle) == calculated_angle

@pytest.mark.parametrize("p1, p2,p3,expected_angle", [
    (np.array([np.sqrt(2)/2 ,np.sqrt(2)/2 , 0]) , np.array([0,0,0]), np.array([1,0,0]), 45),
    (np.array([0,0,-1]), np.array([0,1,0]), np.array([1,0,0]), 60),
])
def test_calculate_angle_many(p1,p2,p3, expected_angle):
    calculated_value = molecool.calculate_angle(p1,p2,p3, degrees=True)

    assert expected_angle == pytest.approx(calculated_value), F'{caluculated_value} {expected_angle}'


import sys
import numpy as np
import pandas as pd
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from scipy import linalg
from decimal import Decimal

# reading data from csv files
def read_params(x_path,y_path):
  x_data = pd.read_csv(x_path,header=None)
  y_data = pd.read_csv(y_path,header=None)
  x = normalize(np.asmatrix(np.array(x_data)))
  y = np.asmatrix(np.array(y_data))
  return (x,y,np.array(x_data),np.array(y_data))

# normalization function
def normalize(mat):
  mean = np.mean(mat,axis=0)
  var = np.var(mat,axis=0)
  mat-=mean
  np.divide(mat,var)

  # including the initial column of x0 feature
  z = 1 + np.zeros((mat.shape[0],1),dtype=float)
  z = np.hstack((z,mat))
  return z

# cost computation from current weights and outputs
def compute_cost(x,y,theta):
  diff = y-np.dot(x,theta)
  rv = np.dot(diff.transpose(),diff)
  return rv[0,0]/(2*len(x))

# gradient computation
def gradient(x,y,theta):
  grad = np.dot(np.dot(x.transpose(),x),theta) - np.dot(x.transpose(),y)
  return grad/len(x)

# main algorithm/
def algo(x,y,theta,alpha):
  num_iter = 0
  epsilon = 0.000000001
  old_cost = 1000
  new_cost = 0

  # contains the cost after each iteration
  cost_matrix = [[0,0,0]]

  # convergence criteria: Change in cost<epsilon
  while True:
    grad = gradient(x,y,theta)
    theta-=alpha*grad
    new_cost = compute_cost(x,y,theta)
    if(old_cost-new_cost<epsilon):
      break
    old_cost = new_cost
    cost_matrix = np.append(cost_matrix,[[new_cost,theta[0,0],theta[1,0]]],axis=0)
    num_iter+=1

  # On console the number of iterations taken and the cost
  print("Number of iterations taken to converge= " + str(num_iter))
  print("Final cost = " + str(new_cost))

  # returning the cost martix along with the theta vector
  return np.asmatrix(cost_matrix)[1:,:]

# plotting the graph
def curve_plot(x,theta,x_data,y_data):
  plt.figure()
  y_out = np.array(np.dot(x,theta))
  plt.title('Linear regression')
  plt.scatter(x_data,y_data,c='r',label='Given Data')
  plt.xlabel(' x ')
  plt.ylabel(' y ')
  new_x,new_y = zip(*sorted(zip(x_data,y_out)))
  plt.plot(new_x,new_y,'-',linewidth=2,c='g',label='Hypothesis')
  plt.legend()
  plt.savefig('linear_reg.png',dpi=200)
  plt.show()

# 2D plotting the contours. Animated the process of convergence
def contour_plot(x_data,y_data,cost_data,time_gap):
  plt.figure()
  num_points_x1 = 100
  num_points_x2 = 100
  x1 = np.linspace(-1,1,num_points_x1)
  x2 = np.linspace(-1,1,num_points_x2)
  X1,X2 = np.meshgrid(x1,x2)
  Y = np.asmatrix(np.zeros((num_points_x1,num_points_x2),dtype=float))
  for i in range(num_points_x1):
    for j in range(num_points_x2):
      Y[i,j] = compute_cost(x_data,y_data,[[X1[i][j]],[X2[i][j]]])
  cp = plt.contour(X1,X2,np.array(Y))

  plt.xlabel('theta_0')
  plt.ylabel('theta_1')
  plt.ion()
  for i in range(len(cost_data)):
    plt.scatter(cost_data[i,1],cost_data[i,2],color='r')
    plt.pause(time_gap)
  plt.ioff()
  plt.show()

# 3D animation for simulating process of convergence
def mesh_plot(x_data,y_data,cost_data,time_gap):
  fig = plt.figure()
  ax = fig.gca(projection='3d')
  num_points_x1 = 100
  num_points_x2 = 100
  x1 = np.linspace(-1,2,num_points_x1)
  x2 = np.linspace(-1,2,num_points_x2)
  X1,X2 = np.meshgrid(x1,x2)
  Y = np.asmatrix(np.zeros((num_points_x1,num_points_x2),dtype=float))
  for i in range(num_points_x1):
    for j in range(num_points_x2):
      Y[i,j] = compute_cost(x_data,y_data,[[X1[i][j]],[X2[i][j]]])
  cp = ax.plot_surface(X1,X2,np.array(Y),cmap=cm.coolwarm,linewidth=0, antialiased=False)

  plt.xlabel('theta_0')
  plt.ylabel('theta_1')
  plt.ion()
  for i in range(len(cost_data)):
    ax.scatter(cost_data[i,1],cost_data[i,2],cost_data[i,0],color='r')
    plt.pause(time_gap)
  plt.ioff()
  plt.show()

# MAIN FUNCTION
def main():
  # Taking parameters from command line
  x_path = sys.argv[1]
  y_path = sys.argv[2]
  alpha = float(sys.argv[3])
  time_gap = float(sys.argv[4])

  # Reading the dataset
  (x,y,x_data,y_data) = read_params(x_path,y_path)

  # Computing the theta vector and the cost vector
  theta = np.asmatrix(np.zeros((x.shape[1],1),dtype=float,order='F'))
  cost_matrix = algo(x,y,theta,alpha)

  # Plotting
  curve_plot(x,theta,x_data,y_data)
  contour_plot(x,y,cost_matrix,time_gap)
  mesh_plot(x,y,cost_matrix,time_gap)

if __name__ == "__main__":
	main()


""" Multi-sample tests.

Author:

    C.M. Gosmeyer

Date:

    Mar 2018

References:

    "Introduction to Statistical Problem Solving in Geography",
    J.C. McGrew, Jr., A.J. Lembo, Jr., C.B. Monroe
"""

import numpy as np

class ANOVA(object):
    """ Compares three or more independent random sample means for
    difference.

    Requirements
    ------------
    1. Three or more (k) random samples.
    2. Each population *normally* distributed.
    3. Each population has *equal* variance.
    4. Variable is measured at interval or ratio scale.

    Null Hypthothesis
    -----------------
    mean1 = mean2 = ... = meank

    Test Statistic
    --------------

        F = BetweenGroupMeanSquares / WithinGroupMeanSquares

        where

        BetweenGroupMeanSquares = BetweenGroupSumOfSquares / (NumberSamples - 1)

        where

        BetweenGroupSumOfSquares = sum( NumberObservationsInSample_i *
                                       (MeanOfSample_i - OverallMean)^2 )

        where

        OverallMean = sum( NumberObservationsInSample_i * MeanOfSample_i /
                           TotalNumberObservationsInAllSamples)

        and

        WithinGroupMeanSquares = WithinGroupSumOfSquares /
                                 (TotalNumberObservationsInAllSamples - NumberSamples)

        where

        WithinGroupSumOfSquares = sum( (NumberObservationsInSample_i - 1) *
                                      VarianceSample_i^2 )
    """
    def __init__(self, means, ns, variances):
        """
        Parameters
        ----------
        means : array
            Mean of each sample.
        ns : array
            Number of observations in each sample.
        variances : array
            Variance of each sample.
        """
        self.means = np.asarray(means)
        self.ns = np.asarray(ns)
        self.variances = np.asarray(variances)
        self.k = len(means)
        self.N = sum(ns)
        self.F = None
        self.SS_B = self.between_group_sum_of_squares()
        self.SS_W = self.within_group_sum_of_squares()
        self.MS_B = self.between_group_mean_squares()
        self.MS_W = self.within_group_mean_squares()

        self.test_statistic()
        self.test_stat = self.F

    def between_group_sum_of_squares(self):
        X_T = sum([self.ns[i]*self.means[i] for i in range(self.k)]) / float(self.N)
        SS_B = sum([self.ns[i]*(self.means[i] - X_T)**2 for i in range(self.k)])
        return SS_B

    def within_group_sum_of_squares(self):
        SS_W = sum( [(self.ns[i] - 1)*self.variances[i]**2 for i in range(self.k)] )
        return SS_W

    def between_group_mean_squares(self):
        MS_B = self.SS_B / float(self.k - 1)
        return MS_B

    def within_group_mean_squares(self):
        MS_W = self.SS_W / float(self.N - self.k)
        return MS_W

    def test_statistic(self):
        self.F = self.MS_W / self.MS_B

    def get_F(self):
        print(self.F)

class KruskalWallis(object):
    """ Compares three or more independent random sample mean ranks
    for difference.

    Requirements
    ------------
    1. Three or more (k) random samples.
    2. Each population has an underlying continuous distribution.
    3. Variable is measured at ordinal scale or downgraded from
       interval/ratio scale to ordinal.

    Null Hypthothesis
    -----------------
    The populations from which the three or more (k) samples have been
    drawn are all identical.

    Test Statistic
    --------------

        H = { [ (12 / (N * (N + 1)) * sum(NumberObservationsInSample_i * MeanRankInSampl_i) ] -
              3 * (N + 1) } /
            [ 1 - sum(T) / (N^3 - N)]

        where

        N = TotalNumberObservationsInAllSamples

        T = NumberTiedObservations^3 - NumberTiedObservations

    """
    def __init__(self, rs):
        """
        Parameters
        ----------
        rs : array
            List of ranks of each sample.
        """
        self.rs = np.asarray(rs)
        self.k = len(rs)
        self.ns = np.array([len(rs[i]) for i in range(self.k)])
        self.N = float(sum(self.ns))
        self.H = None
        self.sum_n_r = None
        self.mean_rs = self.mean_rs()
        self.T = self.tie_correction()
        self.test_statistic()

    def tie_correction(self):
        num_tied_observations = len(self.mean_rs) - len(set(self.mean_rs))
        T = num_tied_observations**3 - num_tied_observations
        return T

    def mean_rs(self):
        rs = self.rs
        mean_rs = [np.array(rs[i]).mean() for i in range(self.k)]
        return mean_rs

    def test_statistic(self):
        N = self.N

        self.sum_n_r = sum([self.ns[i]*(self.mean_rs[i])**2 for i in range(self.k)])

        self.H = ( ( (12/(N*(N + 1))) * self.sum_n_r ) - 3*(N+1) ) / \
            float(1 - (self.T/(N**3 - N)))

    def get_H(self):
        print(self.H)


# 使用expand()，我们可以扩展代数表达式； 即该方法尝试消除幂和乘法。
from sympy import expand,sin, cos, simplify, pprint
from sympy.abc import x

expr = (x + 1) ** 2

pprint(expr)

print('-----------------------')
print('-----------------------')

expr = expand(expr)
pprint(expr)

# 可以使用simplify()将表达式更改为更简单的形式。
expr = sin(x) / cos(x)

pprint(expr)

print('-----------------------')

expr = simplify(expr)
pprint(expr)