from math import sqrt
from numpy import array, linalg

def det(matrix):
    return linalg.det(array(matrix))

class Vec3:
    def __init__(self, *args):
        if len(args) == 1:
            self.x = args[0][0]
            self.y = args[0][1]
            self.z = args[0][2]
        elif len(args) == 3:
            self.x = args[0]
            self.y = args[1]
            self.z = args[2]
        else:
            raise ValueError("Invalid Quantity of input: " + str(len(args)))

    def __add__(self, other):
        if type(other) == Vec3:
            return Vec3(self.x + other.x, self.y + other.y, self.z + other.z)
        elif type(other) in [list, tuple]:
            return Vec3(self.x + other[0], self.y + other[1], self.z + other[2])
        else:
            raise TypeError("Vec3 can only add with Vec3, list, tuple")

    def __radd__(self, other):
        if type(other) == Vec3:
            return Vec3(self.x + other.x, self.y + other.y, self.z + other.z)
        elif type(other) in [list, tuple]:
            return [self.x + other[0], self.y + other[1], self.z + other[2]]
        else:
            raise TypeError("Vec3 can only add with Vec3, list, tuple")

    def __sub__(self, other):
        if type(other) == Vec3:
            return Vec3(self.x - other.x, self.y - other.y, self.z - other.z)
        elif type(other) in [list, tuple]:
            return Vec3(self.x - other[0], self.y - other[1], self.z - other[2])
        else:
            raise TypeError("Vec3 can only subtract with Vec3, list, tuple")

    def __rsub__(self, other):
        if type(other) == Vec3:
            return Vec3(other.x - self.x, other.y - self.y, other.z - self.z)
        elif type(other) in [list, tuple]:
            return [other[0] - self.x, other[1] - self.y, other[2] - self.z]
        else:
            raise TypeError("Vec3 can only subtract with Vec3, list, tuple")

    def __mul__(self, other):
        if type(other) in [int, float]:
            return Vec3(self.x * other, self.y * other, self.z * other)
        if type(other) == Vec3:
            return self.x * other.x + self.y * other.y + self.z * other.z
        elif type(other) in [list, tuple]:
            return self.x * other[0] + self.y * other[1] + self.z * other[2]
        else:
            raise TypeError(f"Vec3 can only multiply with Vec3, list, tuple, int, float")

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        if type(other) in [int, float]:
            return self.__mul__(1 / other)
        else:
            raise TypeError(f"Vec3 can only divide with int, float")

    def __abs__(self):
        return sqrt(self.x * self.x + self.y * self.y + self.z * self.z)

    def __neg__(self):
        return Vec3(-self.x, -self.y, -self.z)

    def __str__(self):
        return f"Vec3({self.x}, {self.y}, {self.z})"

    def dist_sq(self):
        return self.x * self.x + self.y * self.y + self.z * self.z

    def cross(self, other):
        return Vec3(
            det([[self.y, self.z], [other.y, other.z]]),
            -det([[self.x, self.z], [other.x, other.z]]),
            det([[self.x, self.y], [other.x, other.y]]),
        )

    def to_list(self):
        return [self.x, self.y, self.z]


import numpy as np
import math

''' Misc of activation functions
    for the neural network
'''

def get_activation(name):
    return eval(name+"_activation")

def sigmoid_activation(x=None,deriv=False):
    ''' Sigmoid function (used for hidden neurons)
    '''
    if deriv == True:
        return sigmoid(x)*(1-sigmoid(x))
    return 1/(1+np.exp(-x))

def tansig_activation(x=None,deriv=False):
    ''' Tangent Sigmoid function
    '''
    return ( np.exp(x)-np.exp(-x) ) / ( np.exp(x)+np.exp(-x) )

def purelin_activation(x=None,deriv=False):
    ''' Linear (usually used in output neurons)
    '''
    if deriv == True:
        return 1
    return x

def tanh_activation(z):
    z = max(-60.0, min(60.0, 2.5 * z))
    return math.tanh(z)

def sin_activation(z):
    z = max(-60.0, min(60.0, 5.0 * z))
    return math.sin(z)

def gauss_activation(z):
    z = max(-3.4, min(3.4, z))
    return math.exp(-5.0 * z**2)

def relu_activation(z):
    return z if z > 0.0 else 0.0

def softplus_activation(z):
    z = max(-60.0, min(60.0, 5.0 * z))
    return 0.2 * math.log(1 + math.exp(z))

def identityn_activation(z):
    return z

def clamped_activation(z):
    return max(-1.0, min(1.0, z))

def inverse_activation(z):
    if z == 0:
        return 0.0

    return 1.0 / z

def logarithmic_activation(z):
    z = max(1e-7, z)
    return math.log(z)

def exponential_activation(z):
    z = max(-60.0, min(60.0, z))
    return math.exp(z)

def absolute_activation(z):
    return abs(z)

def hat_activation(z):
    return max(0.0, 1 - abs(z))

def square_activation(z):
    return z ** 2

def cube_activation(z):
    return z ** 3


import numpy as np
from scipy.optimize import linprog

"""
Wasserstein距离也被形象地称之为“推土机距离”（Earth Mover's Distance，EMD），因为它可以用一个“推土”的例子来通俗地表达它的含义。

假设在位置i=1,2,…,n处我们分布有p1,p2,…,pn那么多的土，简单起见我们设土的总数量为1，即p1+p2+⋯+pn=1，现在要将土推到位置j=1,2,…,n′上，每处的量为q1,q2,…,qn′，而从i推到j的成本为di,j，求成本最低的方案以及对应的最低成本。
"""
def wasserstein_distance(p, q, D):
    """通过线性规划求Wasserstein距离
    p.shape=[m], q.shape=[n], D.shape=[m, n]
    p.sum()=1, q.sum()=1, p∈[0,1], q∈[0,1]
    """
    A_eq = []
    for i in range(len(p)):
        A = np.zeros_like(D)
        A[i, :] = 1
        A_eq.append(A.reshape(-1))
    for i in range(len(q)):
        A = np.zeros_like(D)
        A[:, i] = 1
        A_eq.append(A.reshape(-1))
    A_eq = np.array(A_eq)
    b_eq = np.concatenate([p, q])
    D = D.reshape(-1)
    result = linprog(D, A_eq=A_eq[:-1], b_eq=b_eq[:-1])
    return result.fun


import numpy as np

def std_PCA(W,d):
    '''
    Given a n x n matrix (this is equal to A^T A for an m x n data matrix A), output the top d eigenvectors of W as numpy n x d matrix.
    '''
    [eigenValues,eigenVectors] = np.linalg.eig(W)

    #sort eigenvalues and eigenvectors in decending orders
    idx = eigenValues.argsort()[::-1]
    eigenValues = eigenValues[idx]
    eigenVectors = eigenVectors[:,idx]

    #take the first d vectors. Obtained the solution
    return eigenVectors[:,:d]


from sklearn.preprocessing import scale
import numpy as np

def preprocess(X):
    """
        R(N*M)
    :param X:
    :return:
    """
    X_average = X.mean(axis=0)
    X = X - X_average
    # sklearn does'nt make the variance to 1, cs229 suggest to do that.
    # that a difference
    # std_sigma = X.std(axis=0)
    # X = X / std_sigma

    X_scale = scale(X, axis=0)
    # assert (X == X_scale).all(), 'preprocess error'

    return X

def pca_with_svd(X, n_dimensions=2):
    X = preprocess(X)
    u, d, v_t = np.linalg.svd(X)

    result_vector = v_t[:n_dimensions, :]
    reduced_x_self = np.dot(X, result_vector.T)
    return reduced_x_self

def pca(X, n_dimensions=2, algorithm='default'):
    """

    :param n_dimensions: the dimension of subspace of original space
    :param algorithm: can use default and svd method
    :return:
    """
    X = preprocess(X)

    if algorithm == 'svd':
        return pca_with_svd(X, n_dimensions)

    dimensions = X.shape[1]
    n_samples = X.shape[0]

    Sigma = np.zeros((dimensions, dimensions))
    for index in range(n_samples):

        co_occurrence = np.dot(X[index, :][:, None], X[index, :][None, :])
        Sigma += np.cov(co_occurrence)

    Sigma = Sigma/n_samples

    # eigenvalues can be duplicated
    eigenvalues, eigenvectors = np.linalg.eig(Sigma)

    eig_vals_sorted = np.sort(eigenvalues)
    eig_vecs_sorted = eigenvectors[:, eigenvalues.argsort()]

    result_eigen_vectors = eig_vecs_sorted[:, -n_dimensions:]

    reduced_x_self = np.flip(np.dot(X, result_eigen_vectors))
    return reduced_x_self


import numpy as np

class SiLU:
    def __call__(self, x: np.ndarray) -> np.ndarray:
        return x / (1 + np.exp(-x))

    def gradient(self, x: np.ndarray) -> np.ndarray:
        return (1 + np.exp(-x) + x * np.exp(-x)) / np.power(1 + np.exp(-x), 2)


# Whitening: Normalization of Correlated Multivariate Data
# Following function is created with reference to the page 253-259 of text book "Neural Networks for Applied Sciences and Engineering" by Sandhya Samarasinghe

import numpy as np

def whitening(data_matrix):
    """
    Whitening - Normalization of Correlated Multivariate Data

    :param data_matrix: '(the number of data, the number of variables) size' data matrix
    :return: '(the number of data, the number of variables) size' whitened data matrix
    """
    # whitening the data

    matrix_mean = data_matrix.mean(axis=0)  # '(the number of data, the number of variables) size' data matrix
    num_data = data_matrix.shape[0]  # the number of data
    dim_data = data_matrix.shape[1]  # the number of variables

    matrix_cov = np.zeros((dim_data, dim_data))  # Covariance matrix (the covariance between the two variables)
    for i in range(num_data):
        matrix_cov = matrix_cov + np.dot((data_matrix[i, :].reshape([1, dim_data]) - matrix_mean[:].reshape([1, dim_data])).transpose(),
                                         (data_matrix[i, :].reshape([1, dim_data]) - matrix_mean[:].reshape([1, dim_data]))) / (num_data - 1.0)

    """The Covariance matrix can be transformed into a new matrix that corresponds to a set of new rescaled variables
    that have unit variance and are independent of one another. Therefore, covariance between two new variables is zero.
    This is accomplished by the well-known eigenvalue decomposition method or principal component analysis (PCA)."""

    eig_w, eig_v = np.linalg.eig(matrix_cov)
    """Each eigenvector (eig_v) defines the direction of a new axis for the rescaled variable called a principal component
    so that all new axes are perpendicular to each other.
    Thus, this process essentially decorrelates the original input variables."""

    # To obtain the transformed variables, the original variables are multiplied by the weights (eigenvector)
    # Prior to this, weights are normalized by dividing the corresponding standart deviation (square root of eigenvalue)
    eig_v_norm = np.zeros((dim_data, dim_data))
    for i in range(data_matrix.shape[1]):
        eig_v_norm[:, i] = eig_v[:, i]/abs(eig_w[i]) ** 0.5

    # To transform the original variables to zero mean,
    # simply subtract the corresponding mean value from the values of each of the original variables.
    matrix_whitened = np.dot((data_matrix - np.dot(np.ones((num_data, 1)).reshape([num_data, 1]), matrix_mean[:].reshape([1, dim_data]))), eig_v_norm)

    return matrix_whitened


import numpy as np
from fractions import Fraction
# import networkx as nx
# import matplotlib.pyplot as plt
#
# G=nx.barabasi_albert_graph(10,4)
# nx.draw(G, with_labels=True)
# plt.show()

def float_format(vector, decimal):
    return np.round((vector).astype(np.float), decimals=decimal)

in_pr = Fraction(1, 5)

adj_mat = np.matrix([[0, 0, 0, 0, 1],
              [0.5, 0, 0, 0, 0],
              [0.5, 0, 0, 0, 0],
              [0, 1, 0.5, 0, 0],
              [0, 0, 0.5, 1, 0]])

x = np.zeros((5, 5))
x[:] = in_pr

damping = 0.85

formula = damping * adj_mat + ((1 - damping) * x)

pr = np.matrix([in_pr, in_pr, in_pr, in_pr, in_pr])
pr = np.transpose(pr)

prev_rank = pr
for it in range(1,100):
    pr = formula * pr
    print (float_format(pr, 3))
    #checking for convergence
    if (prev_rank == pr).all():
        break
    prev_rank = pr

print ("Final:\n", float_format(pr, 3))
print ("sum", np.sum(pr))


from scipy import stats
from tabpy.models.utils import setup_utils

def ttest(_arg1, _arg2):
    """
    T-Test is a statistical hypothesis test that is used to compare
    two sample means or a sample’s mean against a known population mean.
    For more information on the function and how to use it please refer
    to tabpy-tools.md
    """
    # one sample test with mean
    if len(_arg2) == 1:
        test_stat, p_value = stats.ttest_1samp(_arg1, _arg2)
        return p_value
    # two sample t-test where _arg1 is numeric and _arg2 is a binary factor
    elif len(set(_arg2)) == 2:
        # each sample in _arg1 needs to have a corresponding classification
        # in _arg2
        if not (len(_arg1) == len(_arg2)):
            raise ValueError
        class1, class2 = set(_arg2)
        sample1 = []
        sample2 = []
        for i in range(len(_arg1)):
            if _arg2[i] == class1:
                sample1.append(_arg1[i])
            else:
                sample2.append(_arg1[i])
        test_stat, p_value = stats.ttest_ind(sample1, sample2, equal_var=False)
        return p_value
    # arg1 is a sample and arg2 is a sample
    else:
        test_stat, p_value = stats.ttest_ind(_arg1, _arg2, equal_var=False)
        return p_value

if __name__ == "__main__":
    setup_utils.deploy_model("ttest", ttest, "Returns the p-value form a t-test")


import math

import numpy as np

from ..entities.camera import Camera

EYE_3 = np.eye(3)
EYE_4 = np.eye(4)

def create_rotation_matrix(rot_x: float, rot_y: float, rot_z: float) -> np.ndarray:
    """
    Creates rotation matrix fromEuler angles.

    Args:
        (float) rot_x: Rotation angle for x-axis
        (float) rot_y: Rotation angle for y-axis
        (float) rot_z: Rotation angle for z-axis

    Returns:
         (np.ndarray): Rotation matrix
    """
    sx, sy, sz = math.sin(rot_x), math.sin(rot_y), math.sin(rot_z)
    cx, cy, cz = math.cos(rot_x), math.cos(rot_y), math.cos(rot_z)

    cc, cs = cx * cz, cx * sz
    sc, ss = sx * cz, sx * sz

    # matrix = np.eye(3)
    matrix = EYE_3.copy()

    matrix[0, 0] = cy * cz
    matrix[0, 1] = sy * sc - cs
    matrix[0, 2] = sy * cc + ss

    matrix[1, 0] = cy * sz
    matrix[1, 1] = sy * ss + cc
    matrix[1, 2] = sy * cs - sc

    matrix[2, 0] = -sy
    matrix[2, 1] = cy * sx
    matrix[2, 2] = cy * cx

    return matrix

def create_transformation_matrix(position: np.ndarray, rotation: np.ndarray, scale: np.ndarray) -> np.ndarray:
    """
    Compose transformation matrix (translation, rotation, scale)

    Args:
        (np.ndarray) position: numpy array of shape (N, ). Order: x, y, z
        (np.ndarray) rotation: numpy array of shape (N, ). Euler angles in degrees, order: x, y, z
        (np.ndarray) scale: numpy array of shape (N, )

    Returns:
        (np.ndarray) transformation matrix, shape (n+1, n+1) where n is the number of axis (usually 3 - 3D case)
    """
    n = len(position)

    # matrix = np.eye(n + 1)
    matrix = EYE_4.copy()

    rotation_matrix = create_rotation_matrix(rotation[0], rotation[1], rotation[2])

    scale_diag = EYE_3.copy()
    scale_diag[0, 0] = scale[0]
    scale_diag[1, 1] = scale[1]
    scale_diag[2, 2] = scale[2]

    matrix[:n, :n] = np.dot(rotation_matrix, scale_diag)
    matrix[:n, n] = position[:]

    return matrix

def create_view_matrix(camera: Camera):
    n = 3
    matrix = np.eye(n + 1)

    rotation_matrix = create_rotation_matrix(-1 * camera.rot_x, -1 * camera.rot_y, -1 * camera.rot_z)

    matrix[:n, :n] = rotation_matrix
    matrix[:n, n] = np.dot(rotation_matrix, -1 * camera.position)

    return matrix


from math import log, sqrt, exp, erf
import numpy as np
invsqrt = lambda x: 1.0/sqrt(x)

def black_scholes_python ( nopt, price, strike, t, rate, vol, call, put ):
    mr = -rate
    sig_sig_two = vol * vol * 2

    for i in range(nopt):
        P = float( price [i] )
        S = strike [i]
        T = t [i]

        a = log(P / S)
        b = T * mr

        z = T * sig_sig_two
        c = 0.25 * z
        y = invsqrt(z)

        w1 = (a - b + c) * y
        w2 = (a - b - c) * y

        d1 = 0.5 + 0.5 * erf(w1)
        d2 = 0.5 + 0.5 * erf(w2)

        Se = exp(b) * S

        call [i] = P * d1 - Se * d2
        put [i] = call [i] - P + Se


from math import log

import matplotlib.pyplot as plt
import numpy as np
from numba import jit, njit, prange
from scipy.stats import pearsonr

from .signal import envelope, fftwconvolve

def rms(y):
    """
    Get RMS vlaue

    Parameters
    ----------
    y : float 1D array
        Signal

    Returns
    -------
    RMS : float
        RMS
    """

    rms = np.sqrt(np.sum(y**2)/y.size)
    return rms

def kurtosis(x):
    """
    Get kurtosis value

    Parameters
    ----------
    x : float 1D array
        Signal

    Returns
    -------
    K : float
        Kurtosis
    """

    x2 = np.abs(x - np.mean(x))**2.0
    E = np.mean(x2)
    K = np.mean(x2**2.0)/E**2.0
    return K

def standardmoment(x, k):
    """
    Get standard moment of choice

    Parameters
    ----------
    x : float 1D array
        Signal
    k : int
        Desired moment

    Returns
    SM : float
        Standard moment of choice
    """

    xk = (x - np.mean(x))**k
    x2 = (x - np.mean(x))**2
    SM = np.mean(xk)/np.mean(x2)**(float(k)/2.0)
    return SM

def absoluteMean(x):
    """
    Get absolute mean of signal

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.mean(np.abs(x))

def peakToPeak(x):
    """
    Peak-to-peak of signal

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.max(x) - np.min(x)

def squareMeanRoot(x):
    """
    Square mean root of signal

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.sum(np.sqrt(np.abs(x)))**2

def waveformLength(x):
    """
    Waveform length of signal

    Parameters
    ----------
    x : float 1D array
        Signal

    Taken from paper:
        Nayana, B. R., and P. Geethanjali.
        "Analysis of Statistical Time-Domain Features Effectiveness
        in Identification of Bearing Faults From Vibration Signal."
        IEEE Sensors Journal 17.17 (2017): 5618-5625.
    """

    return np.sum(np.abs(np.diff(x)))

def willsonAmplitude(x, epsilon=0.5):
    """
    Willson amplitude

    Parameters
    ----------
    x : float 1D array
        Signal

    Taken from paper:
        Nayana, B. R., and P. Geethanjali.
        "Analysis of Statistical Time-Domain Features Effectiveness
        in Identification of Bearing Faults From Vibration Signal."
        IEEE Sensors Journal 17.17 (2017): 5618-5625.
    """

    return np.sum(np.diff(np.abs(x)) > epsilon)

def zeroCrossing(x, epsilon=0.5):
    """
    Zero crossing og signal

    Parameters
    ----------
    x : float 1D array
        Signal
    epsilon : float, optional

    Taken from paper:
        Nayana, B. R., and P. Geethanjali.
        "Analysis of Statistical Time-Domain Features Effectiveness
        in Identification of Bearing Faults From Vibration Signal."
        IEEE Sensors Journal 17.17 (2017): 5618-5625.
    """

    temp1 = np.greater(x[:-1], 0)*np.greater(0, x[1:])
    temp2 = np.less(x[:-1], 0)*np.less(0, x[1:])
    temp3 = np.greater(np.abs(np.diff(x)), epsilon)
    return np.sum(np.logical_and(np.logical_or(temp1, temp2), temp3))

def slopeSignChange(x, epsilon=0.5):
    """
    Slope sign change

    Parameters
    ----------
    x : float 1D array
        Signal
    epsilon : float, optional

    Taken from paper:
        Nayana, B. R., and P. Geethanjali.
        "Analysis of Statistical Time-Domain Features Effectiveness
        in Identification of Bearing Faults From Vibration Signal."
        IEEE Sensors Journal 17.17 (2017): 5618-5625.
    """

    temp1 = np.greater(x[1:-1], x[:-2])*np.greater(x[1:-1], x[2:])
    temp2 = np.less(x[1:-1], x[:-2])*np.less(x[1:-1], x[2:])
    temp3 = np.greater(np.abs(np.diff(x[:-1])), epsilon)
    return np.sum(np.logical_and(np.logical_or(temp1, temp2), temp3))

def shapeFactor(x):
    """
    Shape factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return rms(x)/absoluteMean(x)

def crestFactor(x):
    """
    Crest factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.max(x)/rms(x)

def impulseFactor(x):
    """
    Impulse factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.max(x)/absoluteMean(x)

def clearanceFactor(x):
    """
    Clearance factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return np.max(x)/squareMeanRoot(x)

def skewnessFactor(x):
    """
    Skewness factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return standardmoment(x, 3)/rms(x)**3

def kurtosisFactor(x):
    """
    Kurtosis factor

    Parameters
    ----------
    x : float 1D array
        Signal
    """

    return standardmoment(x, 4)/rms(x)**4

def rootMeanSquareFrequency(f, Y):
    """
    Root mean square frequency

    Parameters
    ----------
    f : float 1D array
        Frequency of FFT
    Y : float 1D array
        Amplitude of FFT
    """

    return ( np.sum(f**2*Y)/np.sum(Y) )**0.5

def frequencyCenter(f, Y):
    """
    Frequency center of spectrum

    Parameters
    ----------
    f : float 1D array
        Frequency of FFT
    Y : float 1D array
        Amplitude of FFT
    """

    return np.sum(f*Y)/np.sum(Y)

@njit(cache=True)
def _maxdist(x_i, x_j):
    ma = 0.0
    temp = 0.0
    for k in range(0, x_i.shape[0]):
        temp = abs(x_i[k] - x_j[k])
        if temp >= ma:
            ma = temp
    return ma
    # return np.max(np.abs(x_i - x_j))

@njit(cache=True)
def _phi(U, m, r):
    N = U.size
    iterRange = N - m + 1
    iterRangeFloat = float(iterRange)
    a = 0.0
    for i in prange(0, iterRange):
        hits = 0.0
        for j in range(0, iterRange):
            if _maxdist(U[i:i+m], U[j:j+m]) <= r:
                hits += 1.0
        a += log(hits/iterRangeFloat)
    return a/iterRangeFloat

def approximateEntropy(U, N=1000, m=2, r=None):
    """
    Approximate the entropy of a signal

    Parameters
    ----------
    U : float 1D array
        Signal
    N : int, optional
    m : int, optional
    r : float, optional

    https://en.wikipedia.org/wiki/Approximate_entropy
    Default values taken from:
    Yan, Ruqiang, and Robert X. Gao.
    "Approximate entropy as a diagnostic tool for machine health monitoring."
    Mechanical Systems and Signal Processing 21.2 (2007): 824-839.
    """

    if r is None:
        r = 0.4*U.std()
    temp1 = _phi(U[:N], m+1, r)
    temp2 = _phi(U[:N], m, r)
    return abs(temp1 - temp2)

def SDofIHC(x):
    '''
    Standard deviation of inverse hyperbolic cosine

    Parameters
    ----------
    x : float 1D array
        Signal

    Taken from paper
    A Model-Based Method for Remaining Useful Life Prediction of Machinery
    Yaguo Lei et al.
    '''
    return np.std(np.log(x + np.sqrt(x**2 - 1)))

def SDofIHS(x):
    '''
    Standard deviation of inverse hyperbolic sine

    Parameters
    ----------
    x : float 1D array
        Signal

    Taken from paper
    A Model-Based Method for Remaining Useful Life Prediction of Machinery
    Yaguo Lei et al.
    '''
    return np.std(np.log(x + np.sqrt(x**2 + 1)))

def medianFrequency(Y, df):
    '''
    Median frequency of a spectrum

    Parameters
    ----------
    Y : float 1D array
        Spectrum aplitude
    df : float
        Frequency spacing between bins
    '''

    cumsum = np.cumsum(Y)
    return np.argmin(np.abs(cumsum - 0.5*cumsum[-1]))*df

def myoPulsePercentage(x, eps=5.0):
    '''
    Myo pulse percentage
    Sum of all impulses greater than a threshold eps

    Parameters
    ----------
    x : float 1D array
        Signal
    eps : float, opional
        Threshold
    '''

    return np.sum(np.greater(np.abs(x), eps))

def LOG(x):
    '''
    Exponential of the mean absolute logarithm

    Parameters
    ----------
    x : float 1D array
        Signal
    '''

    return np.exp(np.mean(np.log(np.abs(x))))

def bearingEnergy(Y, df, X, bearing):
    '''
    Energy within band of typical characteristic frequencies

    Parameters
    ----------
    Y : float 1D array
        Spectrum aplitude
    df : float
        Frequncy spacing in Hz
    X : float
        Shaft speed in Hz
    bearing : float 1D array
        Bearing characteristic frequencies in orders (i.e. per revolution)
        bearing[0] - Inner race
        bearing[1] - 2x roller spin frequency
        bearing[2] - Cage frequency
        bearing[3] - Outer race frequency
    '''

    lowerFrequency = np.min([bearing[0], bearing[1], bearing[3]])*X*0.95
    upperFrequency = np.max([bearing[0], bearing[1], bearing[3]])*X*1.05
    i1 = int(np.floor(lowerFrequency/df))
    i2 = int(np.ceil(upperFrequency/df))
    return np.sum(Y[i1:i2+1])

def snr(r, Fs, ma=0.05, mb=0.5, cb=3, mc=0.05, md=0.6, c=2, c_ech=0.05, J_min=3, toler=0.1):
    '''Estimates the Signal-to-noise ratio.

    Based on "About periodicity and signal to noise ratio - The
    strength of the autocorrelation function."
    by Nadine Martin and Corinne Mailhes

    Parameters
    ----------
    r : float 1D array
        The signal to estimate SNR of
    Fs : float
        Sampling rate
    ma : float
        Lag support start, percentage of r.size
    mb : float
        Lag support end, percentage of r.size
    cb : int
        Tolerance factor
    mc : float
        Lag support start, percentage of r.size
    md : float
        Lag support end, percentage of r.size
    c : int
        Tolerance factor
    c_ech : float
        Tolerance factor
    J_min : int
        Minimum number of detected maxima
    toler : float
        Tolerance factor applied to median

    Returns
    -------
    SNR_hat : float
        Estimated SNR ratio in dB
    flags : list, size=4
        Extra information about the calculation

        Element[i]:
        0 : boolean
            0 if r is not aperiodic (Positive)
            1 if r is random noise
        1 : boolean
            0 if card(ksi) > J_min (Positive)
            1 else
        2 : float
            Confidence value ratio
        3 : float
            Estimated fundamental frequency
    '''

    def autocorr(r):
        d = r.size
        Rr = fftwconvolve(np.flipud(r), r, 'full')
        Rr = Rr[d-1:Rr.size]/np.arange(d, 0, -1, dtype=float)
        return Rr

    flags = []
    N = r.size

    # Step 1 Compute maximum autocorrelation function
    Rr_hat = autocorr(r)
    ma = int(ma*N)
    mb = int(mb*N)
    m_max = np.argmax(Rr_hat[ma:mb])
    m_max += ma
    Rr_hat_max = Rr_hat[m_max]

    # Step 2 Set initial power, and check if Rr is associated with noise
    Px_hat_init = Rr_hat_max
    Pn_hat_init = Rr_hat[0] - Px_hat_init
    sigma_max = np.sqrt(float(N))/(np.abs(N - m_max))*Pn_hat_init
    if Rr_hat_max <= cb*sigma_max:
        flags.append(1)
        print('Negative: r[n] is random noise')
    else:
        flags.append(0)
        # print('Positive: r[n] not aperiodic')

    # Step 3 Compute the lag set ksi
    mc = int(mc*N)
    md = int(md*N)
    listnumber = 0
    ksi = np.zeros(md - mc)
    mlist = np.arange(mc, md)
    sigma = (4*N - 6*mlist)/((N - mlist)**2.0)*Pn_hat_init*Px_hat_init + N/((N - mlist)**2.0)*Pn_hat_init**2.0
    for mj in range(0, mlist.size):
        leftside = Px_hat_init - c*sigma[mj] - c_ech*Px_hat_init
        rightside = Px_hat_init + c*sigma[mj] - c_ech*Px_hat_init
        if leftside <= Rr_hat[mj] <= rightside:
            ksi[listnumber] = mlist[mj]
            listnumber += 1
    ksi = np.array(ksi[0:listnumber], dtype=int)
    J = ksi.size
    if J > J_min:
        flags.append(0)
        # print('card(ksi) is larger than Jmin')
    else:
        flags.append(1)
        print('card(ksi) is smaller than Jmin')

    if J > J_min:
        # Step 4 Evaluate the fundamental-periodicity component Cfun
        ksi_d = np.diff(ksi)
        Med_ksi_d = np.median(ksi_d)
        Cfun = 0
        for i in range(0, ksi_d.size):
            u = np.abs(ksi_d[i] - Med_ksi_d)
            if u < toler*Med_ksi_d:
                Cfun += 1
        Cfun /= ksi_d.size
        f_hat_fun = Fs/Med_ksi_d
        flags.append(Cfun)
        flags.append(f_hat_fun)
        # print('Confidence ratio is %i percentage' % (Cfun*100.0))
        # print('Estimated fundamental frequency is %f Hz' % (f_hat_fun))

        # Step 5 Get SNR
        if J > J_min:
            Px_hat = 1/J*np.sum(Rr_hat[ksi])
            Pn_hat = Rr_hat[0] - Px_hat
            SNR_hat = 10*np.log10(Px_hat/Pn_hat)
            # print('Estimated SNR is %.4f' % (SNR_hat))
    else:
        flags.append(0)
        flags.append(0)
        SNR_hat = 0.0

    return SNR_hat, flags

def maxToMinPowerDensityDrop(Y, df, X, bearing):
    '''
    Maximum to minimum power density drop

    Parameters
    ----------
    Y : float 1D array
        Spectrum aplitude
    df : float
        Frequncy spacing in Hz
    X : float
        Shaft speed in Hz
    bearing : float 1D array
        Bearing characteristic frequencies in orders (i.e. per revolution)
        bearing[0] - Inner race
        bearing[1] - 2x roller spin frequency
        bearing[2] - Cage frequency
        bearing[3] - Outer race frequency
    '''

    lowerFrequency = np.min([bearing[0], bearing[1], bearing[3]])*X*0.95
    upperFrequency = np.max([bearing[0], bearing[1], bearing[3]])*X*1.05
    i1 = int(np.floor(lowerFrequency/df))
    i2 = int(np.ceil(upperFrequency/df))
    I = int(X/df)
    if I == 0:
        I = 1
    cc = fftwconvolve(np.ones(I)/float(I), Y)
    return np.max(cc)/np.min(cc)


'''
This is an example of finding the Pearson correlation coefficient as well as Principal Component Analysis (PCA).
PCA transforms the data to align with the axes (mean of 0) without loss of information.

Inspired by the "Unsupervised Learning" course on Datacamp.com
Author: Alex Nakagawa
'''

import matplotlib.pyplot as plt
from scipy.stats import pearsonr
from sklearn.decomposition import PCA

# Assign the 0th column of grains: width
width = df[:,0]

# Assign the 1st column of grains: length
length = df[:,1]

plt.scatter(width, length)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation
correlation, pvalue = pearsonr(width, length)
print(correlation)

# Create PCA instance: model
model = PCA()
pca_features = model.fit_transform(df)

xs = pca_features[:,0]
ys = pca_features[:,1]

# Scatter plot xs vs ys
plt.scatter(xs, ys)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation after PCA
correlation, pvalue = pearsonr(xs, ys)
print(correlation)


import numpy as np
from moha.system.integral.overlap import S
def D(i,j,e,A,B,a,b):
    ''' Recursive definition of Hermite Gaussian coefficients.
        Returns a float.
        a: orbital exponent on Gaussian 'a' (e.g. alpha in the text)
        b: orbital exponent on Gaussian 'b' (e.g. beta in the text)
        i,j: orbital angular momentum number on Gaussian 'a' and 'b'
        t: number nodes in Hermite (depends on type of integral,
           e.g. always zero for overlap integrals)
        Qx: distance between origins of Gaussian 'a' and 'b'
    '''
    if e < 1:
        raise ValueError('e should be integer larger than zero')
    elif e == 1:
        return 2*a*S(i+1,j,A,B,a,b)-i*S(i-1,j,A,B,a,b)
    elif e == 2:
        return 4*a**2*S(i+2,j,A,B,a,b) -\
               2*a*(2*i+1)*S(i,j,A,B,a,b) +\
               i*(i-1)*S(i-2,j,A,B,a,b)
    else:
        return 2*a*D(i+1,j,e-1,A,B,a,b) - i*D(i-1,j,e-1,A,B,a,b)

def Dxyz(a,lmn1,A,b,lmn2,B,es):
    ''' Evaluates overlap integral between two Gaussians
        Returns a float.
        a:    orbital exponent on Gaussian 'a' (e.g. alpha in the text)
        b:    orbital exponent on Gaussian 'b' (e.g. beta in the text)
        lmn1: int tuple containing orbital angular momentum (e.g. (1,0,0))
              for Gaussian 'a'
        lmn2: int tuple containing orbital angular momentum for Gaussian 'b'
        A:    list containing origin of Gaussian 'a', e.g. [1.0, 2.0, 0.0]
        B:    list containing origin of Gaussian 'b'
    '''
    i,k,m = lmn1 # shell angular momentum on Gaussian 'a'
    j,l,n = lmn2 # shell angular momentum on Gaussian 'b'
    e,g,f = es # shell angular momentum on Gaussian 'b'
    Dx = D(i,j,e,A[0],B[0],a,b) # X
    Dy = D(k,l,g,A[1],B[1],a,b) # Y
    Dz = D(m,n,f,A[2],B[2],a,b) # Z
    return np.power(np.pi/(a+b),1.5)*Dx*Dy*Dz

def differential(a,b,es):
    '''Evaluates overlap between two contracted Gaussians
       Returns float.
       Arguments:
       a: contracted Gaussian 'a', BasisFunction object
       b: contracted Gaussian 'b', BasisFunction object
    '''
    d = 0.0
    for ia, ca in enumerate(a.coefs):
        for ib, cb in enumerate(b.coefs):
            d += a.norm[ia]*b.norm[ib]*ca*cb*\
                     Dxyz(a.exps[ia],a.shell,a.origin,
                     b.exps[ib],b.shell,b.origin,es)
    return d


import numpy as np

from sklearn.datasets import make_classification, make_moons

def create_lineal_data(slope=1, bias=0, spread=0.25, data_size=50):
    """
    Helper function to create lineal data.

    :param slope: slope of the lineal function.
    :param bias: bias of the lineal function.
    :param spread: spread of the normal distribution.
    :param data_size: number of samples to generate.

    :return x, y: data and labels
    """
    x = np.linspace(0, 1, data_size)
    y = x * slope + bias + np.random.normal(scale=spread, size=x.shape)

    return x, y

def sin_function(x):
    """
    Returns the sin function complete on interval [0, 1] by scaling the input
    by a 2*pi factor.

    :param x: input data vector.

    :returns: sin function of the data vector on interval [0, 1]
    """

    return np.sin(2 * np.pi * x)

def create_sinusoidal_data(spread=0.25, data_size=50):
    """
    Creates a sinusoidal dataset.

    :param spread: spread of the normal distribution.
    :param data_size: number of samples to generate.

    :return x, y: data and labels
    """
    x = np.linspace(0, 1, data_size)
    y = sin_function(x) + np.random.normal(scale=spread, size=x.shape)

    return x, y

def create_classification_data(data_kind, negative_label=-1, data_size=100):
    """
    Creates a classification toy dataset using the scikit learn helper
    functions.

    :param data_kind: string in {'lineal', 'nonlineal'}
    :negative_label: integer used for the negative label.
    :param data_size: number of samples.

    :return x, y: data and labels
    """

    assert data_kind in {'lineal', 'nonlineal'},\
            "The data kind parameter should be either 'lineal' or 'nonlineal'"

    if data_kind == 'lineal':
        X, y = make_classification(n_samples=data_size, n_features=2,
                                   n_redundant=0, n_informative=2,
                                   n_clusters_per_class=1, class_sep=1.5,
                                   random_state=5)
    else:
        X, y = make_moons(noise=0.1, random_state=5)

    y[y == 0] = negative_label

    return X, y


# not yet run through python environment

import numpy as np

import random
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
np.random.seed(123)

# install matplotlib
# this line for ipython mac only
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

class LinearRegression:

    def __init__(self):
        pass

    def train_gradient_descent(self, X, y, learning_rate=0.01, n_iters=100):
        """
        Trains a linear regression model using gradient descent
        """
        # Step 0: Initialize the parameters
        n_samples, n_features = X.shape
        self.weights = np.zeros(shape=(n_features,1))

        # ? why bias initial as zero
        # ? and it serves as a matrix
        # ? why not append bias as part of input
        self.bias = 0
        costs = []

        # termination condition is decide by n_iters, hyperparamter
        for i in range(n_iters):
            # Step 1: Compute a linear combination of the input features and weights
            y_predict = np.dot(X, self.weights) + self.bias

            # ? why divided by n_samples for all the following
            # from Angrew NG
            # it caclulates all samples 's cnotribution following then average them out

            # Step 2: Compute cost over training set
            cost = (1 / n_samples) * np.sum((y_predict - y)**2)
            costs.append(cost)

            # print out cost every 100 iterations
            if i % 100 == 0:
                print(f"Cost at iteration {i}: {cost}")

            # Step 3: Compute the gradients
            # there is the from  derived from stochastic gradient decent (SGD) to batch processing
            # ? why 2 in numerator
            dJ_dw = (2 / n_samples) * np.dot(X.T, (y_predict - y))
            dJ_db = (2 / n_samples) * np.sum((y_predict - y))

            # Step 4: Update the parameters
            self.weights = self.weights - learning_rate * dJ_dw
            self.bias = self.bias - learning_rate * dJ_db

        return self.weights, self.bias, costs

    def train_normal_equation(self, X, y):
        """
        Trains a linear regression model using the normal equation
        """
        # normal linear regression close form
        self.weights = np.dot(np.dot(np.linalg.inv(np.dot(X.T, X)), X.T), y)
        self.bias = 0

        return self.weights, self.bias

    def predict(self, X):
        return np.dot(X, self.weights) + self.bias

# data set
# We will use a simple training set
# random data points
X = 2 * np.random.rand(500, 1)
y = 5 + 3 * X + np.random.randn(500, 1)

fig = plt.figure(figsize=(8,6))
plt.scatter(X, y)
plt.title("Dataset")
plt.xlabel("First feature")
plt.ylabel("Second feature")
plt.show()

# Split the data into a training and test set

X_train, X_test, y_train, y_test = train_test_split(X, y)

print(f'Shape X_train: {X_train.shape}')
print(f'Shape y_train: {y_train.shape}')
print(f'Shape X_test: {X_test.shape}')
print(f'Shape y_test: {y_test.shape}')

#########################################
#  if going with gradient decent
#########################################
# traiing
regressor  = LinearRegression()
w_trained, b_trained, costs = regressor.train_gradient_descent(X_train, y_train, learning_rate=0.005, n_iters=600)

fig = plt.figure(figsize=(8,6))
plt.plot(np.arange(600), costs)
plt.title("Development of cost during training")
plt.xlabel("Number of iterations")
plt.ylabel("Cost")
plt.show()

# test
n_samples, _ = X_train.shape
n_samples_test, _ = X_test.shape

y_p_train = regressor.predict(X_train)
y_p_test = regressor.predict(X_test)

# this can be replaced by 'from sklearn.metrics import mean_squared_error'
error_train =  (1 / n_samples) * np.sum((y_p_train - y_train) ** 2)
error_test =  (1 / n_samples_test) * np.sum((y_p_test - y_test) ** 2)

print(f"Error on training set: {np.round(error_train, 4)}")
print(f"Error on test set: {np.round(error_test)}")

#########################################
#  if going with normal equation
#########################################
# traninig
# To compute the parameters using the normal equation, we add a bias value of 1 to each input example
'''
X_b_train = np.c_[np.ones((n_samples)), X_train]
X_b_test = np.c_[np.ones((n_samples_test)), X_test]

reg_normal = LinearRegression()
w_trained = reg_normal.train_normal_equation(X_b_train, y_train)

# testing
y_p_train = reg_normal.predict(X_b_train)
y_p_test = reg_normal.predict(X_b_test)

error_train =  (1 / n_samples) * np.sum((y_p_train - y_train) ** 2)
error_test =  (1 / n_samples_test) * np.sum((y_p_test - y_test) ** 2)

print(f"Error on training set: {np.round(error_train, 4)}")
print(f"Error on test set: {np.round(error_test, 4)}")
'''

# Plot the test predictions (visualization)
fig = plt.figure(figsize=(8,6))
plt.title("Dataset in blue, predictions for test set in orange")
plt.scatter(X_train, y_train)
plt.scatter(X_test, y_p_test)
plt.xlabel("First feature")
plt.ylabel("Second feature")
plt.show()


import numpy as np
from sklearn import datasets
from sklearn.model_selection import cross_val_predict
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

P=np.pi

# --------------------------------------------------------------
# Creat samples
N=15
bins=np.random.uniform(0.2,2,N)
bins=np.sort(bins)
t=np.sin(2*P*bins)+ np.random.normal(0.1,1)
print(t,bins)
plt.plot(bins,t, 'ro',linewidth=0.7)

# --------------------------------------------------------------
# first Fi
bins=bins[:, np.newaxis]
z = np.ones((N,1))
Fi=np.append( z,bins, axis=1)
lr = LinearRegression()
lr.fit(Fi, t)

plt.plot(bins, lr.predict(Fi), '--',linewidth=0.7)
Fi1=Fi
new=bins

# -----------------------------------------------------------------
# creat fi for different degrees (2,N)

for i in range (2,N):
    new=np.power(bins,i)
    Fi1 = np.append(Fi1, new, axis=1)
    lr = LinearRegression()
    lr.fit(Fi1, t)
    P=lr.predict(Fi1)
    plt.plot(bins,P,'-',linewidth=0.7 )

plt.show()
y = t
predicted = cross_val_predict(lr,bins, y, cv=10)
fig, ax = plt.subplots()
ax.scatter(y, predicted, edgecolors=(0, 0, 0))
ax.plot([y.min(), y.max()], [y.min(), y.max()], 'k--', lw=4)
ax.set_xlabel('Measured')
ax.set_ylabel('Predicted')
plt.show()

boston = datasets.load_boston()
lr = LinearRegression()

y = boston.target

predicted = cross_val_predict(lr, boston.data, y, cv=10)

fig, ax = plt.subplots()
ax.scatter(y, predicted, edgecolors=(0, 0, 0))
ax.plot([y.min(), y.max()], [y.min(), y.max()], 'k--', lw=4)
ax.set_xlabel('Measured')
ax.set_ylabel('Predicted')
plt.show()

#Importing libraries. The same will be used throughout the article.
import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
#matplotlib inline
from matplotlib.pylab import rcParams
rcParams['figure.figsize'] = 12, 10

#Define input array with angles from 60deg to 300deg converted to radians
x = np.array([i*np.pi/180 for i in range(60,300,4)])
np.random.seed(10)  #Setting seed for reproducability
y = np.sin(x) + np.random.normal(0,0.15,len(x))
data = pd.DataFrame(np.column_stack([x,y]),columns=['x','y'])
plt.plot(data['x'],data['y'],'.')

for i in range(2,16):  #power of 1 is already there
    colname = 'x_%d'%i      #new var will be x_power
    data[colname] = data['x']**i
print(data.head())

# Import Linear Regression model from scikit-learn.
from sklearn.linear_model import LinearRegression

def linear_regression(data, power, models_to_plot):
    # initialize predictors:
    predictors = ['x']
    if power >= 2:
        predictors.extend(['x_%d' % i for i in range(2, power + 1)])

    # Fit the model
    linreg = LinearRegression(normalize=True)
    linreg.fit(data[predictors], data['y'])
    y_pred = linreg.predict(data[predictors])

    # Check if a plot is to be made for the entered power
    if power in models_to_plot:
        plt.subplot(models_to_plot[power])
        plt.tight_layout()
        plt.plot(data['x'], y_pred)
        plt.plot(data['x'], data['y'], '.')
        plt.title('Plot for power: %d' % power)
        # plt.show()

    # Return the result in pre-defined format
    rss = sum((y_pred - data['y']) ** 2)
    ret = [rss]
    ret.extend([linreg.intercept_])
    ret.extend(linreg.coef_)
    return ret

#Initialize a dataframe to store the results:
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['model_pow_%d'%i for i in range(1,16)]
coef_matrix_simple = pd.DataFrame(index=ind, columns=col)

#Define the powers for which a plot is required:
models_to_plot = {1:231,3:232,6:233,9:234,12:235,15:236}

#Iterate through all powers and assimilate results
for i in range(1,16):
    coef_matrix_simple.iloc[i-1,0:i+2] = linear_regression(data, power=i, models_to_plot=models_to_plot)
plt.show()

#Set the display format to be scientific for ease of analysis
pd.options.display.float_format = '{:,.2g}'.format
print(coef_matrix_simple)

from sklearn.linear_model import Ridge

def ridge_regression(data, predictors, alpha, models_to_plot={}):
    # Fit the model
    ridgereg = Ridge(alpha=alpha, normalize=True)
    ridgereg.fit(data[predictors], data['y'])
    y_pred = ridgereg.predict(data[predictors])

    # Check if a plot is to be made for the entered alpha
    if alpha in models_to_plot:
        plt.subplot(models_to_plot[alpha])
        plt.tight_layout()
        plt.plot(data['x'], y_pred)
        plt.plot(data['x'], data['y'], '.')
        plt.title('Plot for alpha: %.3g' % alpha)

    # Return the result in pre-defined format
    rss = sum((y_pred - data['y']) ** 2)
    ret = [rss]
    ret.extend([ridgereg.intercept_])
    ret.extend(ridgereg.coef_)
    return ret

#Initialize predictors to be set of 15 powers of x
predictors=['x']
predictors.extend(['x_%d'%i for i in range(2,16)])

#Set the different values of alpha to be tested
alpha_ridge = [1e-15, 1e-10, 1e-8, 1e-4, 1e-3,1e-2, 1, 5, 10, 20]

#Initialize the dataframe for storing coefficients.
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['alpha_%.2g'%alpha_ridge[i] for i in range(0,10)]
coef_matrix_ridge = pd.DataFrame(index=ind, columns=col)

models_to_plot = {1e-15:231, 1e-10:232, 1e-4:233, 1e-3:234, 1e-2:235, 5:236}
for i in range(10):
    coef_matrix_ridge.iloc[i,] = ridge_regression(data, predictors, alpha_ridge[i], models_to_plot)
plt.show()

#Set the display format to be scientific for ease of analysis
pd.options.display.float_format = '{:,.2g}'.format
print(coef_matrix_ridge)

coef_matrix_ridge.apply(lambda x: sum(x.values==0),axis=1)

from sklearn.linear_model import Lasso

def lasso_regression(data, predictors, alpha, models_to_plot={}):
    # Fit the model
    lassoreg = Lasso(alpha=alpha, normalize=True, max_iter=1e5)
    lassoreg.fit(data[predictors], data['y'])
    y_pred = lassoreg.predict(data[predictors])

    # Check if a plot is to be made for the entered alpha
    if alpha in models_to_plot:
        plt.subplot(models_to_plot[alpha])
        plt.tight_layout()
        plt.plot(data['x'], y_pred)
        plt.plot(data['x'], data['y'], '.')
        plt.title('Plot for alpha: %.3g' % alpha)

    # Return the result in pre-defined format
    rss = sum((y_pred - data['y']) ** 2)
    ret = [rss]
    ret.extend([lassoreg.intercept_])
    ret.extend(lassoreg.coef_)
    return ret

#Initialize predictors to all 15 powers of x
predictors=['x']
predictors.extend(['x_%d'%i for i in range(2,16)])

#Define the alpha values to test
alpha_lasso = [1e-15, 1e-10, 1e-8, 1e-5,1e-4, 1e-3,1e-2, 1, 5, 10]

#Initialize the dataframe to store coefficients
col = ['rss','intercept'] + ['coef_x_%d'%i for i in range(1,16)]
ind = ['alpha_%.2g'%alpha_lasso[i] for i in range(0,10)]
coef_matrix_lasso = pd.DataFrame(index=ind, columns=col)

#Define the models to plot
models_to_plot = {1e-10:231, 1e-5:232,1e-4:233, 1e-3:234, 1e-2:235, 1:236}

#Iterate over the 10 alpha values:
for i in range(10):
    coef_matrix_lasso.iloc[i,] = lasso_regression(data, predictors, alpha_lasso[i], models_to_plot)
plt.show()
coef_matrix_lasso.apply(lambda x: sum(x.values==0),axis=1)


import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

T = 5
Fs = 400.0
n = T * Fs

f1 = 10
f2 = 25
f3 = 50
f4 = 100

t1 = np.linspace(0, T, n)
x1 = np.cos(2 * np.pi * f1 * t1)
t2 = np.linspace(T, 2 * T, n)
x2 = np.cos(2 * np.pi * f2 * t2)
t3 = np.linspace(2 * T, 3 * T, n)
x3 = np.cos(2 * np.pi * f3 * t3)
t4 = np.linspace(3 * T, 4 * T, n)
x4 = np.cos(2 * np.pi * f4 * t4)

x = np.hstack((x1, x2, x3, x4))
t = np.hstack((t1, t2, t3, t4))

TW = 1000e-3
NW = int(TW * Fs)
print(NW)

nperseg = NW

noverlap = 1
nfft = []

window = ('gaussian', 30)

print(NW)

nffts = [400, 512, 1024, 2048]

print(t.shape, x.shape)

plt.figure()
plt.plot(t, x)
plt.xlabel('Time/s')
plt.ylabel('Amplitude')
plt.title('Time domain signal')
plt.figure()

cnt = 1
for nfft in nffts:

    # (x, fs=1.0, window=('tukey', 0.25), nperseg=None, noverlap=None, nfft=None, detrend='constant', return_onesided=True, scaling='density', axis=-1, mode='psd')
    # [F, T, S] = signal.spectral.spectrogram(x, np.hamming(1024), nperseg=1024, noverlap=0, detrend=False, return_onesided=True, mode='magnitude')
    [F, T, S] = signal.spectral.spectrogram(x, fs=Fs, window=window, nperseg=nperseg, noverlap=noverlap,
                                            nfft=nfft, detrend=False, return_onesided=True,
                                            scaling='density', mode='psd')

    print('T: ', T.shape, 'F:', F.shape, 'S:', S.shape)

    plt.subplot(2, 2, cnt)
    map = plt.pcolormesh(T, F, S, cmap='jet')
    plt.ylabel('Frequency/Hz')
    plt.xlabel('Time/s')
    plt.axis('tight')
    plt.title('STFT->' + 'nfft:' + str(nffts[cnt - 1]))
    cb = plt.colorbar(cax=None, ax=None, shrink=0.5)
    cnt = cnt + 1
plt.show()


""" Class defintion for fermat factorization """

from sympy import integer_nthroot
from RSAExploits.exploits.exploit import Exploit
from RSAExploits.util import compute_priv_rsa_exp

class Fermat(Exploit):
    """ Class providing a run interface to a fermat facorization function"""

    def run(self, rsadata_list, info_dict = None):
        """ Factors moduli using fermat factorization

            This algorithm is best suited for finding prime factors when
            the difference between them is small

            Args:
                rsadata_list: A list of RSA_Data objects with moduli to factor
                info_dict: User can specify a bound on the runtime using the
                           key "Fermat" and specifying a number

            Side Effects:
                If a modulus is factored, the corresponding RSA_Data object's
                private key will be computed and initialized

            Return:
                True if at least one modulus was factored
        """

        print ("Fermat: Running Attack...")
        success = False
        limit = None
        if info_dict != None and "Fermat" in info_dict:
            limit = info_dict["Fermat"]

        for rsadata in rsadata_list:
            if limit != None:
                roots = Fermat.fermat_factor(rsadata.get_n(), limit)
            else:
                roots = Fermat.fermat_factor(rsadata.get_n())

            if roots != None:
                rsadata.set_d(compute_priv_rsa_exp(rsadata.get_e(), roots))
                success = True

        if success:
            print("Fermat: Success, roots found, assigned in rsadata.")
        else:
            print("Fermat: Failure, roots not found.")
        return success

    @staticmethod
    def fermat_factor(n, limit = 1000000):

        """ Attempt to factor n using fermat factorization

            Args:
                n: The modulus to factor
                limit: Limits the runtime of this function

            Assumptions:
                n is the product of 2 primes p and q

            Return:
                The two prime factors p and q
        """

        a, exact = integer_nthroot(n, 2)
        _max = a + limit
        while a < _max:
            b2 = a*a - n
            if b2 >= 0:
                b, exact = integer_nthroot(b2, 2)
                if b != None:
                    if b*b == b2:
                        break
            a += 1
        if a < _max:
            p = a+b
            q = a-b
            return p, q
        else:
            return None


""" Script to visualize the Lennard-jones 12-6 potential

Usage:
#the script must be in pythonpath or working directory
from lennardjones import visualize_lj
visualize_lj(e=[3,4],s=[4,6],x=[3,10,0.05],ylim=[-6.5,4.5])

Requirement:
numpy
matplotlib

Limitations:
Even if there is only one epsilon or sigma, they must be in a list, ex: e=[3] and not e=3
"""

import numpy as np
import matplotlib.pyplot as plt

def visualize_lj(e, s, x, ylim):
    """Function to visualize the Lennardjones-potential

    Parameters:
    ----------
    e: list of the epsilon values ex: [e1,e2]
    s: list of sigma values ex: [s1,s2]
    x: list for the range of x values and step ex: [x_0,x_final,step]
    ylim: list for the y limits of the plotted axis ex:[lower_y,upper_y]

    Returns:
    --------
    Matplolib graph of the potential
    """
    x = np.arange(x[0], x[1], x[2])
    # lennardjones-potential

    def lennardjones(x, e, c):
        """Function to compute the Lennard-Jones 12-6 potential"""
        return 4 * e * ((c / x)**12 - (c / x)**6)
    # Vectorize the function with numpy so it will return an array
    vlj = np.vectorize(lennardjones)
    # For loops for each epsilon and sigma values
    for i in e:
        for j in s:
            # calculate lennard jones
            e_lj = vlj(x, i, j)
            # plot with the sigma and epsilon as label
            plt.plot(x, e_lj, label=(
                r"$\epsilon =$ %.2f $\sigma =$ %.2f" % (i, j)), linewidth=1)

    plt.ylabel(r"$v^{LJ}$ (potential)")
    plt.xlabel(r"r (distance)")
    plt.ylim(ylim[0], ylim[1])
    plt.legend()
    plt.show()


"""ЛР 2.2, Ларькин Владимир, М8О-303Б-18"""

import numpy as np
import fire  # CLI
import matplotlib.pyplot as plt

from sympy import sympify, lambdify, diff, Expr
from numpy.linalg import det

from typing import Iterable, Callable

from utilities import str2fun

def get_q(x, phi):
    """Максимум модуля суммы по строке"""
    return np.abs(jacobian(x, phi).sum(axis=1)).max()

def get_a(x: np.ndarray, jacobi: np.ndarray, f: Iterable[Callable]) -> np.ndarray:
    """Возвращает тензор ixjxi, состоящий из i якобианов, в каждом из которых столбец i заменён на столбец из f_j(x)"""

    f_col = np.array([f_i(*x) for f_i in f])
    a = np.stack([jacobi] * jacobi.shape[1])
    for i in range(a.shape[0]):
        a[i, :, i] = f_col
    return a

def jacobian(x: np.ndarray, f: Iterable[Expr]) -> np.ndarray:
    """Матрица Якоби для списка функций f, вычисленная в точке x"""

    # получаем список переменных
    var_str = get_variables(f)
    var_list = var_str.split(",")

    # матрица ixj производных df_j/dx_i
    partials = [[lambdify(var_str, diff(fun, var)) for var in var_list] for fun in f]

    # вычисляем производные в точке и возвращаем
    return np.array([[fun(*x) for fun in row] for row in partials])

def get_variables(functions: Iterable[Expr]) -> str:
    """Возвращает строку вида "x1,x2,…", содержащую все переменные из функций в списке functions"""

    var_sets = [fun.free_symbols for fun in functions]
    var_set = set()
    for vs in var_sets:
        var_set |= vs

    var_list = sorted(map(str, var_set))
    return ",".join(var_list)

def iteration_method(init_dict, count_it=False):
    intervals, eps = init_dict['intervals'], init_dict['eps']

    phi = [sympify(fun) for fun in init_dict["phi"]]
    var_str = get_variables(phi)

    x_prev = np.array([inter[1] - inter[0] for inter in intervals]) / 2
    q = get_q(x_prev, phi)

    phi = [lambdify(var_str, fun) for fun in phi]

    c = 0
    while True:
        c += 1
        x = np.array([fun(*x_prev) for fun in phi])

        finish_iter = max([abs(i - j) for i, j in zip(x, x_prev)]) * q / (1 - q)
        if finish_iter <= eps:
            break

        x_prev = x

    return x if not count_it else (x, c)

def newton_method(init_dict, count_it=False):
    intervals, eps = init_dict['intervals'], init_dict['eps']

    f_expr = [sympify(fun) for fun in init_dict["f"]]
    var_str = get_variables(f_expr)

    x_prev = np.array([inter[1] - inter[0] for inter in intervals]) / 2

    f_call = [lambdify(var_str, fun) for fun in f_expr]

    c = 0
    while True:
        c += 1

        jacobi = jacobian(x_prev, f_expr)
        a = get_a(x_prev, jacobi, f_call)

        x = x_prev - np.array([det(a[i]) / det(jacobi) for i in range(a.shape[0])])

        finish_iter = max([abs(i - j) for i, j in zip(x, x_prev)])
        if finish_iter <= eps:
            break

        x_prev = x

    return x if not count_it else (x, c)

def main(eps=0.01, plot=False, test=False):
    """Решение системы нелинейных уравнений методами простой итерации и Ньютона

    :param eps: точность вычисления
    :param plot: флаг, отвечающий за вывод графика решения
    :param test: флаг, запускающий тестирование
    """

    init_dict = {
        "f": ["x1 - cos(x2) - 1", "x2 - log(x1 + 1, 10) - 3"],
        "phi": ["cos(x2) + 1", "log(x1 + 1, 10) + 3"],
        "intervals": [(-0.5, 0.5), (2.5, 3.5)],
        "eps": eps
    }

    print("eps =", init_dict["eps"], end="\n\n")

    # вывод всех функций f(x) и phi(x)
    print("Функции f(x):", *[f"\tf{i+1}(x) = {e}" for i, e in enumerate(init_dict["f"])], sep="\n", end="\n\n")
    print("Функции phi(x):", *[f"\tphi{i+1}(x) = {e}" for i, e in enumerate(init_dict["phi"])], sep="\n")

    # получение и вывод решений
    ans, it = newton_method(init_dict, count_it=True)
    print(f"\nРешение методом Ньютона:\t\t\t{ans} за {it} ит.")
    ans, it = iteration_method(init_dict, count_it=True)
    print(f"Решение методом простой итерации:\t{ans} за {it} ит.")

    if plot:
        y = np.arange(-10, 10, .001)
        phi1 = str2fun(init_dict["phi"][0])
        x = phi1(y)
        plt.plot(x, y, label=f"f1(x) = {init_dict['f'][0]}")

        x = np.arange(-0.999, 10, .001)
        phi2 = str2fun(init_dict["phi"][1])
        y = phi2(x)
        plt.plot(x, y, label=f"f2(x) = {init_dict['f'][1]}")

        plt.plot(*ans, "ro", label="Решение системы")

        plt.legend()
        plt.axis("equal")
        plt.grid(True)
        plt.xlim(init_dict["intervals"][0][0] - 2, init_dict["intervals"][0][1] + 2)
        plt.ylim(init_dict["intervals"][1][0] - 2, init_dict["intervals"][1][1] + 2)
        plt.xlabel("x1")
        plt.ylabel("x2")

        plt.savefig("plot.jpg", dpi=300)
        plt.show()

    if test:
        powers = np.arange(0, 11, .5)  # порядки эпсилонов (eps = 10 ** -p)
        mi = []
        mn = []
        for p in powers:
            init_dict["eps"] = 10 ** -p
            mi.append(iteration_method(init_dict, count_it=True)[1])
            mn.append(newton_method(init_dict, count_it=True)[1])

        plt.plot(powers, mi, label="Метод простых итераций")
        plt.plot(powers, mn, label="Метод Ньютона")
        plt.legend()
        plt.title("Зависимость числа итераций от точности")
        plt.xlabel("Порядок точности ($\\epsilon = 10^{-порядок}}$)")
        plt.ylabel("Число итераций")
        plt.xticks(powers[::2])
        plt.yticks(range(0, max(mi[-1], mn[-1]), 1))
        plt.grid(True)
        plt.savefig("benchmark.jpg", dpi=300)
        plt.show()

if __name__ == "__main__":
    fire.Fire(main)


'''
 Tema 2 - Calcul Numeric
 Badea Adrian Catalin, grupa 334, anul III, Informatica
 Facultatea de Matematica si Informatica

'''

import numpy as np

'''
Exercitiul 1. Sa se verifice daca sistemul dat admite soluttie unica
si in caz afirmativ sa se determine solutia folosind
Metoda Gauss cu pivotare totala.

Sa se verifice daca sistemul admite solutie unica si in caz afirmativ
sa se determine solutia folosind metoda Gauss cu pivotare totala.

Rezolvare.

Vom folosi metoda prezentata in curs.
Initial vom verifica ca matricea este patratica si ca vectorul b se potriveste in lungime.

La fiecare pas (1 .. n - 1) al algoritmului, alegem ca pivot elementul curent cu valoarea
absoluta cea mai mare dintr-o anumita submatrice

'''

EPS = 0.000001

def gaussPivotareTotala(a, b):

    """ (Optional) Verifica daca vatricea este patratica + compatibilitatea cu vectorul b. """

    assert np.asarray(a).shape[0] == np.asarray(a).shape[1], 'matricea introdusa nu este patratica!'
    assert np.asarray(a).shape[0] == np.asarray(b).shape[0], 'matricea introdusa si vectorul b nu se potrivesc!'

    v = a

    # α este lungimea matricei initiale
    α = len(v)
    x = np.zeros(len(a))

    for i in range(0, α):
       v[i].append(b[i])

    # Lungimea matricei extinse
    β = len(v[i])

    # Vectorul de pozitie al solutiilor, ne va ajuta in reconstructia solutiilor
    pos = []
    for i in range(0, α):
        pos.append(i)

    for k in range(α):

        # Gasim elementul (pivotul), elementul maxim in valoare absoluta din submatricea urmatoare
        # Acesta are indicii psol, msol la final

        maxim = -1
        for p in range(k, α):
            for m in range(k, α):
                if abs(v[p][m]) > maxim:

                   maxim = abs(v[p][m])
                   psol = p;
                   msol = m

        assert maxim > EPS  , 'Sistem incompatibil sau compatibil nedeterminat'

        # Daca p-ul gasit este diferit de k, schimbam liniile
        if psol != k:
           v[psol], v[k] = v[k], v[psol]

        # Daca m-ul gasit este diferit de k, schimba coloanele

        if msol != k :

           pos[msol], pos[k] = pos[k], pos[msol]
           for i in range(0, α):
               v[i][msol], v[i][k] = v[i][k], v[i][msol]

        for l in range(k + 1, α):
            rap = a[l][k] / a[k][k]

            # Scadem din linia l linia k inmultita cu raportul
            for i in range(0, β):
                v[l][i] -= rap * v[k][i]

    assert v[α - 1][α] != 0, 'Sistem compatibil sau compatibil nedeterminat'

    # Reconstruim solutia folosind metoda substitutiei ascendente
    for i in range(α - 1, -1, -1):
        for j in range(β - 2, β - (α - i + 1), -1) :
            v[i][β - 1] -= v[i][j] * x[j]

        sol = v[i][β - 1] / v[i][β - (α - i + 1)]

        x[i] = sol

    # Permutam solutia in ordinea care trebuie (au fost permutate coloane)

    y = x

    for i in range(0, α):
        for j in range(0, α):
            if (pos[i] < pos[j]):
                pos[i], pos[j] = pos[j], pos[i]
                y[i], y[j] = y[j], y[i]

    return y

if __name__ == "__main__":

    a = [[0, -1, -9, -6],
         [5, 1, 8, 3],
         [2, 3, 6, -8],
         [3, 8, -9, 7]]

    b = [-85, 77, 0, 38]

    # Apelam functia cu parametri corespunzatori

    print(gaussPivotareTotala(a, b))

'''

Exercitiul 2. Verificati daca matricea B este inversabila si in caz afirmativ aplicati
Metoda Gauss pentru determinarea inversei.

Rezolvare. Pentru verificarea invserabilitatii, putem folosi functia determinantOfMatrix care calculeaza recursiv, naiv,
dupa prima linie, determinantul matricei.
Altfel, am putea folosi functia de calcul al determinantului din numpy

Daca determinantul este diferit de 0, atunci cautam inversa
folosing metoda Gauss Jordan cu esalonarea matricei.

'''

# Functia care calculeaza submatricea fara liniile p, q si o pune in temp

def getSubMatrix(mat, temp, p, q, n):

    i = 0
    j = 0
    for row in range(n):
        for col in range(n):

            if (row != p and col != q) :

                temp[i][j] = mat[row][col]
                j += 1
                if (j == n - 1):
                    j = 0
                    i += 1

# Functia care calculeaza determinantul matricei recursiv
def determinantOfMatrix(mat, n):
    det = 0

    if (n == 1):
        return mat[0][0]

    # Matricea temporara
    temp = [[0 for x in range(n)]
               for y in range(n)]

    # Semn initial - plus (pentru primul determinant din calcul)
    sign = 1

    for f in range(n):

        getSubMatrix(mat, temp, 0, f, n)

        # Adunam la determinantul curent determinantele submatricelor factor (dupa prima linie)

        det += (sign * mat[0][f] *
              determinantOfMatrix(temp, n - 1))

        sign = -sign

        # Dupa fiecare adunare schimbam semnul

    return det

# Functie care returneaza daca matricea este inversabila daca determinantul este diferit de 0
def isInvertible(mat, n):

    if (abs(determinantOfMatrix(mat, n)) < EPS):
        return False
    else:
        return True

def inverseGaussJordan(mat, n):

    a = np.zeros((n,2*n), np.float32)

    # Augmentare gauss-jordan
    for i in range(n):
        for j in range(n):
            a[i][j] = mat[i][j]

    for i in range(n):
        a[i][i + n] = 1

    # Aplicam eliminarea Gauss Jordan

    for i in range(n):

        # Daca a[i][i] este diferit de 0, il folosim ca pivot
        # Altfel, alegem ca pivot un element de pe coloana i si interschibam cu linia i

        if abs(a[i][i]) < EPS:

            for j in range(i + 1, n):
                if abs(a[j][i]) > EPS:
                    break

            # Interschimbam liniile i si j
            a[[i, j]] = a[[j, i]]

        # Scadem linia i din fiecare celelalte.
        for j in range(n):
            if i != j:

                ratio = a[j][i] / a[i][i]

                for k in range(2*n):

                    a[j][k] = a[j][k] - ratio * a[i][k]

    for i in range(n):

        divisor = a[i][i]

        for j in range(2*n):

            a[i][j] = a[i][j] / divisor

    # Reconstruim solutia in b

    b = np.zeros((n, n), np.float32)

    for i in range(n):
        for j in range(n):
            b[i][j] = a[i][j + n]

    return b

if __name__ == "__main__":

    a = [[0, 3, 1, -1],
         [3, 8, -10, 5],
         [-5, -8, 0, 0],
         [4, 5, -4, 7]]

    if isInvertible(a, 4):
        print(inverseGaussJordan(a, 4))
    else:
        print ("Matricea nu este inversabila")

'''
Exercitiul 3.
Sa se verifice daca sistemul admite solutie unica si in caz afirmativ
sa se determine solutia folosind factorizarea LU cu pivotare partiala.

Rezolvare. Vom transforma matricea initiala intr-o pereche de doua matrice, L si U
pentru care L este inferior triunghiulara U este superior triunghiulara, si L * U = matricea initiala
Descompunerea lor este unica.

Apoi, A * X = B echivalent cu L * U * X = B
Notam U * X cu Y, de aici rezulta ca L * Y = B,

determinam Y folosind substitutie ascendenta
revenim la notatie si determinam X folosind substitutie descendenta

'''

def LUFactor(a):

    n = a.shape[0]

    # l este matricea identitate

    l = np.zeros((n, n), np.float64)
    for i in range(n):
        l[i][i] = 1

    w = np.arange(0,n)

    for k in range(0, n - 1):

        p = np.argmax(abs(a[k:n, k])) + k

        # Indicele p este linia argumentului maxim

        assert abs(a[p][k]) > EPS, 'Nu admite factorizare LU'

        # Daca p este diferit de k, le interschimbam in matrice

        if p != k:
            a[[k,p], :] = a[[p,k], :]
            w[p], w[k] = w[k], w[p]

            if k > 0 :
                # Interschimbam subliniile situate sub diagonala principala intre ele

                for i in range(0, k):
                    l[k][i], l[p][i] = l[p][i], l[k][i]

        for i in range(k + 1, n):

            l[i,k] = a[i,k] / a[k,k]

            for j in range(0,n):

                a[i,j] -= l[i,k]* a[k,j]

    return (l, a, w)

# Metoda substitutiei ascendente

def sub_asc(a, b):

    n = a.shape[0]

    # Initializam tabloul solutiilor
    x_num = np.zeros(n)

    # Prima solutie

    x_num[0] = b[0] / a[0][0]

    # Reconstruim pe rand solutiile de jos in sus

    for i in range(1, n):

        # Gasim suma pe linie pentru toate coloanele diferite de cea pe care urmeaza sa o calculam
        for j in range(i) :
            x_num[i] -= a[i][j] * x_num[j]

        x_num[i] = (b[i] + x_num[i]) / a[i][i]

    return x_num

def sub_desc(a, b):

    n = a.shape[0]

    # Initializam tabloul solutiilor
    x_num = np.zeros(n)

    # Prima solutie (in partea de jos)

    x_num[n - 1] = b[n - 1] / a[n - 1][n - 1]

    # Reconstruim pe rand solutiile de sus in jos

    for i in range(n - 2, -1, -1):

        # Gasim suma pe linie pentru toate coloanele diferite de cea pe care urmeaza sa o calculam
        for j in range(i + 1, n) :
            x_num[i] -= a[i][j] * x_num[j]

        x_num[i] = (b[i] + x_num[i]) / a[i][i]

    return x_num

if __name__ == "__main__":

    a = [[0, -1, 9, 1],
         [8, 0, 5, 7],
         [-2, 9, 5, 0],
         [1, 0, 2, -3]]

    b = [38, 71, 43, -5]
    b = np.array(b, np.float64)

    # l, u sunt matricele mentionate in rezolvare, pentru care l * u = a

    l, u, w = LUFactor(np.asarray(a, np.float64))

    bp = np.zeros(len(b))
    for i in range(len(b)):
        bp[i] = b[w[i]]

    y = sub_asc(l, bp)
    print(sub_desc(u, y))

'''
Exercitiul 4.
Sa se verifice daca matricea C admite facrotizare Choleskuy si in caz afirmativ
Sa se determine aceasta factorizare.

Rezolvare.
Daca matricea este pozitiv definita si este simetrica,
aceasta admite factorizare Cholesky

Pentru o matrice A, cautam o matrice L inferior triunghiulara,
astfel incat L * L(transpus) = A
'''

def Cholesky(a):

     α = a[0][0]

     # Verificam ca A este pozitiv definita

     assert α  > 0, 'A nu este pozitiv definit'

     λ = a.shape[0]

     # Verificam proprietatea de simetricitate a elementelor

     symmetric = True
     for i in range(λ):
         for j in range(λ):
             if (a[i][j] != a[j][i]) :
                 symmetric = False

     assert symmetric == True, 'Matricea nu este simetrica'

     l = np.zeros((λ, λ), np.float64)

     l[0][0] = np.sqrt(α)

     for i in range(1, λ):
         l[i][0] = a[i][0] / l[0][0]

     for k in range(1, λ):

        # Calculam suma patratelor in Σ

        Σ = 0
        for j in range(0, k):
            Σ += l[k][j] * l[k][j]

        α = a[k][k] - Σ

        # Verificam ca la fiecare moment α este mereu pozitiv calculat

        assert α  > 0, 'A nu este pozitiv definit'

        # Elementul de pe diagonala principala este mereu dat de α corespunzator.

        l[k][k] = np.sqrt(α)

        for i in range(k + 1, λ):

            Σ = 0
            for j in range(0, k):

               Σ += l[i][j] * l[k][j]

            l[i][k] = (1.0 / l[k][k]) * (a[i][k] - Σ)

     return l

if __name__ == "__main__":

    a = [[49, -28, 35, -28],
         [-28, 65, -6, 2],
         [35, -6, 45, 8],
         [-28, 2, 8, 165]]

    cholesky = Cholesky(np.array(a, np.float64))

    # Pentru verificarea rezultatelor (ar trebui sa fie exact aceeasi matricea ca cea initiala)
    print(np.matmul(cholesky, np.transpose(cholesky)))


from typing import Callable, Tuple

import numpy as np

def posterior_factory(y: np.ndarray, sigma_y: float, sigma_theta: float) -> Tuple[Callable]:
    """The banana distribution is a distribution that exhibits a characteristic
    banana-shaped ridge that resembles the posterior that can emerge from
    models that are not identifiable. The distribution is the posterior of the
    following generative model.

        y ~ Normal(theta[0] + theta[1]**2, sigma_sq_y)
        theta[i] ~ Normal(0, sigma_sq_theta)

    Args:
        y: Observations of the banana model.
        sigma_y: Standard deviation of the observations.
        sigma_theta: Standard deviation of prior over linear coefficients.

    Returns:
        log_posterior: Function to compute the log-posterior.
        metric: Function to compute the Fisher information metric.
        euclidean_auxiliaries: Function to compute the log-posterior and its
            gradient.
        riemannian_auxiliaries: Function to compute the log-posterior, the
            gradient of the log-posterior, the Fisher information metric, and the
            derivatives of the Fisher information metric.

    """
    sigma_sq_y = np.square(sigma_y)
    sigma_sq_theta = np.square(sigma_theta)

    def log_posterior(theta: np.ndarray) -> float:
        """The banana-shaped distribution posterior.

        Args:
            theta: Linear coefficients.

        Returns:
            out: The log-posterior of the banana-shaped distribution.

        """
        p = theta[0] + np.square(theta[1])
        ll = -0.5 / sigma_sq_y * np.square(y - p).sum()
        lp = -0.5 / sigma_sq_theta * np.square(theta).sum()
        return ll + lp

    def grad_log_posterior(theta: np.ndarray) -> np.ndarray:
        """Gradient of the banana-shaped distribution with respect to the linear
        coefficients.

        Args:
            theta: Linear coefficients.

        Returns:
            out: The gradient of the log-posterior of the banana-shaped
                distribution with respect to the linear coefficients.

        """
        p = theta[0] + np.square(theta[1])
        d = np.sum(y - p)
        ga = d / sigma_sq_y - theta[0] / sigma_sq_theta
        gb = 2.0*d / sigma_sq_y * theta[1] - theta[1] / sigma_sq_theta
        return np.hstack((ga, gb))

    def metric(theta: np.ndarray) -> np.ndarray:
        """The Fisher information is the negative expected outer product of the
        gradient of the posterior.

        Args:
            theta: Linear coefficients.

        Returns:
            G: The Fisher information metric of the banana-shaped distribution.

        """
        n = y.size
        s = 2.0*n*theta[1] / sigma_sq_y
        G = np.array([[n / sigma_sq_y + 1.0 / sigma_sq_theta, s],
                      [s, 4.0*n*np.square(theta[1]) / sigma_sq_y + 1.0 / sigma_sq_theta]])
        return G

    def grad_metric(theta: np.ndarray) -> np.ndarray:
        """The gradient of the Fisher information metric with respect to the linear
        coefficients.

        Args:
            theta: Linear coefficients.

        Returns:
            dG: The gradient of the Fisher information metric with respect to the
                linear coefficients.

        """
        n = y.size
        dG = np.array([
            [[0.0, 0.0], [0.0, 2.0*n / sigma_sq_y]],
            [[0.0, 2.0*n / sigma_sq_y], [0.0, 8.0*n*theta[1] / sigma_sq_y]]
        ])
        return dG

    def euclidean_auxiliaries(theta: np.ndarray) -> Tuple[np.ndarray]:
        """Function to compute the log-posterior and the gradient of the
        log-posterior.

        Args:
            theta: Linear coefficients.

        Returns:
            lp: The log-posterior of the banana-shaped distribution.
            glp: The gradient of the log-posterior of the banana-shaped
                distribution with respect to the linear coefficients.

        """
        lp = log_posterior(theta)
        glp = grad_log_posterior(theta)
        return lp, glp

    def riemannnian_auxiliaries(theta: np.ndarray) -> Tuple[np.ndarray]:
        """Function to compute the log-posterior, the gradient of the log-posterior,
        the Fisher information metric and the derivatives of the Fisher
        information metric.

        Args:
            theta: Linear coefficients.

        Returns:
            lp: The log-posterior of the banana-shaped distribution.
            glp: The gradient of the log-posterior of the banana-shaped
                distribution with respect to the linear coefficients.
            G: The Fisher information metric of the banana-shaped distribution.
            dG: The gradient of the Fisher information metric with respect to the
                linear coefficients.

        """
        lp = log_posterior(theta)
        glp = grad_log_posterior(theta)
        G = metric(theta)
        dG = grad_metric(theta)
        return lp, glp, G, dG

    def log_posterior_and_metric(theta: np.ndarray) -> Tuple[np.ndarray]:
        lp = log_posterior(theta)
        G = metric(theta)
        return lp, G

    return log_posterior, metric, log_posterior_and_metric, euclidean_auxiliaries, riemannnian_auxiliaries

def generate_data(t: float, sigma_y: float, sigma_theta: float, num_obs: int) -> np.ndarray:
    """Generate data from the banana-shaped posterior distribution.

    Args:
        t: Free-parameter determining the thetas.
        sigma_y: Noise standard deviation.
        sigma_theta: Prior standard deviation over the thetas.
        num_obs: Number of observations to generate.

    Returns:
        theta: Linear coefficients of the banana-shaped distribution.
        y: Observations from the unidentifiable model.

    """
    theta = np.array([t, np.sqrt(1.0 - t)])
    y = theta[0] + np.square(theta[1]) + sigma_y * np.random.normal(size=(num_obs, ))
    return theta, y


"""
montecarloSimulation.py

Created by Luca Camerani at 31/08/2020, University of Milano-Bicocca.
(l.camerani@campus.unimib.it)
All rights reserved.

This file is part of the EcoFin-Library (https://github.com/LucaCamerani/EcoFin-Library),
and is released under the "BSD Open Source License".
"""

import math
from collections import namedtuple

import numpy as np
from scipy.stats import norm, t

from EcoFin.utils.utils import listInterpreter

class Process():
    def __init__(self, T, dt, t=0, n=None):
        self.T = T
        self.t = t
        self.dt = dt
        self.n = n
        self.steps = self.getStepsN()
        if n is None: self.n = 1

    def getStepsN(self):
        return math.trunc((self.T - self.t) / self.dt) + 1

    def getTimeVector(self):
        return np.arange(self.t, self.T + 1, self.dt)

    def uniformStochasticProcess(self, lineCorr=True):
        output = []
        for i in range(0, self.n):
            output.append(np.random.rand(self.steps))

        if lineCorr: output = listInterpreter(output)
        return output

    def gaussianProcess(self, mean=None, covMatrix=None, lineCorr=True):
        output = []
        for i in range(0, self.n):
            if not mean: meanN = np.full(self.steps, 0)
            if not covMatrix: covMatrixN = np.eye(self.steps)

            output.append(np.random.multivariate_normal(meanN, covMatrixN))

        if lineCorr:
            output = listInterpreter(output)

        return output

    def standardBrownianMotion(self, lineCorr=True):
        output = {'process': [], 'increments': []}
        for i in range(0, self.n):
            x0 = np.asarray(0)
            r = norm.rvs(size=x0.shape + (self.steps - 1,), scale=np.sqrt(self.dt))
            r = np.insert(r, 0, 0)

            out = np.empty(r.shape)

            np.cumsum(r, axis=-1, out=out)
            out += np.expand_dims(x0, axis=-1)

            output['process'].append(out)
            output['increments'].append(r)

        if lineCorr:
            output['process'] = listInterpreter(output['process'])
            output['increments'] = listInterpreter(output['increments'])
        return namedtuple('Output', ['process', 'increments'])(**{
            "process": output['process'],
            "increments": output['increments']
        })

    def tStudentBrownianMotion(self, lineCorr=True, df=1):
        output = {'process': [], 'increments': []}
        for i in range(0, self.n):
            x0 = np.asarray(0)
            r = t.rvs(size=x0.shape + (self.steps - 1,), scale=np.sqrt(self.dt), df=df)
            r = np.insert(r, 0, 0)

            out = np.empty(r.shape)

            np.cumsum(r, axis=-1, out=out)
            out += np.expand_dims(x0, axis=-1)

            output['process'].append(out)
            output['increments'].append(r)

        if lineCorr:
            output['process'] = listInterpreter(output['process'])
            output['increments'] = listInterpreter(output['increments'])

        return namedtuple('Output', ['process', 'increments'])(**{
            "process": output['process'],
            "increments": output['increments']
        })


"""
The simplest model: y = alpha + beta * x + error
the error is uncontrolled factor affected the result. we ignore it in our model
so given out ALPHA and BETA, we can compute the yi value
The predict error given by:
prediected_error = y - yi
So we just need to minimize the predict_error

# some useful numpy function
np.std(array)                       # standard deviation
np.var(array)                       # variance
np.correlate(v1, v2)
correlation(x, y) = covariance(x, y) / (std(x) * std(y)) when std(x)*std(y)!=0
covariance(x, y) = (x - avg(x)) * (y - avg{y}) / (n - 1)
"""

import numpy as np

def predict(alpha, beta, x_i):
    return beta * x_i + alpha

def error(alpha, beta, x_i, y_i):
    return y_i - predict(alpha, beta, x_i)

def sum_of_squared_errors(alpha, beta, x, y):
    return sum(error(alpha, beta, x_i, y_i) ** 2
               for x_i, y_i in zip(x, y))

# the three function do the things can be imployed as the following one:
def numpy_sum_of_squared_errors(alpha, beta, x, y):
    error = np.array(y) - (beta * np.array(x) + alpha)
    squared_error = np.square(error)
    return np.sum(squared_error)

# least squares fit need some calculus
def least_squares_fit(x, y):
    beta = numpy_correlation(x, y) * np.std(y) / np.std(x)
    alpha = np.mean(y) - beta * np.mean(x)
    return np.double(alpha), np.double(beta)

def total_sum_of_squares(y):
    return sum((np.array(y) - np.average(y))**2)

def r_squared(alpha, beta, x, y):
    return 1.0 - (sum_of_squared_errors(alpha, beta, x, y) /
                  total_sum_of_squares(y))

# some helpful function:
def numpy_covariance(x, y):
    x_ = np.array(x) - np.average(x)
    y_ = np.array(y) - np.average(y)
    return np.dot(x_, y_) / (len(x) -1)

def numpy_correlation(x, y):
    return numpy_covariance(x, y) / (np.std(x) * np.std(y))

# NOTE: Given the beta as
# beta = correlation(x, y) * std(y) / std(x)
#      = covariance(x, y) / std(x) / std(y) * std(y) / std(x)
#      = covariance(x, y) / (std(x) * std(x))
# very strange!

# reference to the multipleRegression.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
from scipy import sparse
from scipy.sparse import linalg
import matplotlib.pyplot as plt
np.set_printoptions(threshold=np.nan)
np.set_printoptions(precision=5)

#<startTeX>
def nine_point_poisson(m,img_q=False):
    # define RHS function
    def f(x,y): return x**2 + y**2
    def d2f(x,y): return 4

    # define boundary conditions
    bd = np.ones((m+2,m+2))
    bd[1:m+1,1:m+1] = np.zeros((m,m)) #must be zero on interior for below

    x = np.linspace(0,1,m+2)
    h = 1/(m+1)

    # construct A
    T = -20*sparse.eye(m,m,k=0) + 4*sparse.eye(m,m,k=1) + 4*sparse.eye(m,m,k=-1)
    S = 4*sparse.eye(m,m,k=0) + sparse.eye(m,m,k=1) + sparse.eye(m,m,k=-1)
    I = sparse.eye(m)
    II = sparse.eye(m,k=1)+sparse.eye(m,k=-1)
    A = sparse.kron(I,T)+sparse.kron(II,S)
    A /= h**2

    # build RHS
    F = f(x[1:m+1,None],x[None,1:m+1])

    # subtract stencil evaluated at known boundary points
    F -= 4*(bd[0:m,1:m+1] + bd[2:m+2,1:m+1] + bd[1:m+1,0:m] + bd[1:m+1,2:m+2])/h**2
    F -= (bd[0:m,0:m] + bd[0:m,2:m+2] + bd[2:m+2,0:m] + bd[2:m+2,2:m+2])/h**2

    # add dominant error term
    F += (h**2/12)*d2f(x[1:m+1,None],x[None,1:m+1])

    # evaluate f along grid, account for known boundary points and error term, and flatten
    F_ = np.reshape(F,(-1,))

    # solve system
    U_ = sparse.linalg.spsolve(A.tocsr(),F_)

    # construct 2D solution
    U = bd
    U[1:m+1,1:m+1] = U_.reshape(m,m)

    # plot output
    if(img_q):
        plt.figure()
        plt.pcolormesh(x,x,U.T)
        plt.axis('image')
        plt.colorbar()
        plt.savefig('img/2/'+str(m)+'.pdf')

    return U
#<endTeX>
def compare_mesh():
    E = 10
    M = 2**E-1
    fine_solution = nine_point_poisson(M,False)

    global h,error_inf,error_2
    h, error_inf, error_2 = [], [], []

    for e in [2,3,4,5,6,7,8,9]:
        m = 2**e-1
        coarse_solution = nine_point_poisson(m,False)
        diff = np.abs(coarse_solution - fine_solution[::2**(E-e),::2**(E-e)])
        h = np.append(h,1/(m+1))
        error_inf = np.append(error_inf,np.max(diff))
        error_2 = np.append(error_2,h[-1]*np.linalg.norm(diff,'fro'))

def save_figures():
    linear_fit_inf = np.poly1d(np.polyfit(np.log2(h), np.log2(error_inf), 1))
    linear_fit_2 = np.poly1d(np.polyfit(np.log2(h), np.log2(error_2), 1))
    np.savetxt('img/2/fit_inf.txt',[linear_fit_inf[1]],fmt="%.6s")
    np.savetxt('img/2/fit_2.txt',[linear_fit_2[1]],fmt="%.6s")
    print(linear_fit_inf[1], linear_fit_2[1])

    plt.figure()
    plt.scatter(np.log2(h),np.log2(error_inf))
    plt.plot(np.log2(h), linear_fit_inf(np.log2(h)), label = 'slope = '+str(round(linear_fit_inf[1],3)))
    plt.legend()
    plt.savefig('img/2/error_inf.pdf')

    plt.figure()
    plt.scatter(np.log2(h),np.log2(error_2))
    plt.plot(np.log2(h), linear_fit_2(np.log2(h)), label = 'slope = '+str(round(linear_fit_2[1],3)))
    plt.legend()
    plt.savefig('img/2/error_2.pdf')

    table = np.array([h,error_2, error_inf],dtype='<U32').T
    np.savetxt('9pp_error.txt',table,delimiter=' & ', newline=' \\\\ \hline \n',fmt='%15s')


# Modified from a stackoverflow post by Hyperboreus:
# http://stackoverflow.com/questions/6964392/speed-comparison-with-project-euler-c-vs-python-vs-erlang-vs-haskell
from __future__ import print_function, division, absolute_import
import math
from numba import jit
from numba.utils import benchmark

def py_factorCount(n):
    square = math.sqrt(n)
    isquare = int (square)
    count = -1 if isquare == square else 0
    for candidate in range(1, isquare + 1):
        if not n % candidate:
            count += 2
    return count

def py_euler():
    triangle = 1
    index = 1
    while py_factorCount(triangle) < 1001:
        index += 1
        triangle += index
    return triangle

@jit("intp(intp)", nopython=True)
def factorCount(n):
    square = math.sqrt(n)
    isquare = int (square)
    count = -1 if isquare == square else 0
    for candidate in range(1, isquare + 1):
        if not n % candidate:
            count += 2
    return count

@jit("intp()", nopython=True)
def euler():
    triangle = 1
    index = 1
    while factorCount(triangle) < 1001:
        index += 1
        triangle += index
    return triangle

answer = 842161320

def numba_main():
    result = euler()
    assert result == answer

def python_main():
    result = py_euler()
    assert result == answer

if __name__ == '__main__':
    print(benchmark(python_main))
    print(benchmark(numba_main))


from turtle import color
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal
np.set_printoptions(threshold=np.inf)
plt.rcParams['figure.figsize'] = [9,9]
from numpy import linalg as LA

N_features = 4                  # Number of features
N_Samples = 10000               # Number of Samples
N_labels = 2                    # Number of classes

# Mean vectors
mean_matrix = np.ones(shape=[N_labels, N_features])
mean_matrix [0, :] = [-1,-1,-1,-1]

# Covariance matrices
covariance_matrix = np.ones(shape=[N_labels, N_features, N_features])
covariance_matrix [0, :, :] = [[2, -0.5, 0.3, 0],[-0.5, 1, -0.5, 0], [0.3, -0.5, 1, 0], [0, 0, 0, 2]]
covariance_matrix [1, :, :] = [[1, 0.3, -0.2, 0], [0.3, 2, 0.3, 0], [-0.2, 0.3, 1, 0], [0, 0, 0, 3]]

#Seed to obtain same results for random numbers
np.random.seed(10)

# Class Priors and assigning labels
priors = [0.7, 0.3]
label = (np.random.rand(N_Samples) >= priors[1]).astype(int)

# Generate gaussian distribution for 10000 samples using mean and covariance matrices for each label
X = np.zeros(shape = [N_Samples, N_features])
for i in range(N_Samples):
        if (label[i] == 0):
                X[i, :] = np.random.multivariate_normal(mean_matrix[0, :], covariance_matrix[0, :,:])
        elif (label[i] == 1):
                X[i, :] = np.random.multivariate_normal(mean_matrix[1, :], covariance_matrix[1, :,:])

# Compute between-class and within-class scatter matrices
Sb = (mean_matrix[0, :] - mean_matrix[1, :]) * np.transpose (mean_matrix[0, :] - mean_matrix[1, :])
Sw = covariance_matrix[0, :, :] + covariance_matrix[1, :, :]

# Eigenvalues and eigen vectors of inverse(Sw.Sb)
V, W = LA.eig(LA.inv(Sw) * Sb)

# Eigen vector with maximizing optimization objective
W_LDA = W[np.argmax(V)]
X0 = X[np.where(label == 0)]
X1 = X[np.where(label == 1)]

# Data projection using wLDA
Y0 = np.zeros(len(X0))
Y1 = np.zeros(len(X1))
Y0 = np.dot(np.transpose(W_LDA), np.transpose(X0))
Y1 = np.dot(np.transpose(W_LDA), np.transpose(X1))

# Ranging threshold from minimum to maximum
Y = np.concatenate([Y0, Y1])
sort_Y = np.sort(Y)
tau_sweep = []

# Calculate mid-points which will be used as threshold values
for i in range(0,9999):
        tau_sweep.append((sort_Y[i] + sort_Y[i+1])/2.0)

# Array initialization for results
decision = []
TP = [None] * len(tau_sweep)
FP = [None] * len(tau_sweep)
minPerror = [None] * len(tau_sweep)

# Classify for each threshold and compute error and evaluation metrics
for (index, tau) in enumerate(tau_sweep):
        decision = (Y >= tau)
        TP[index] = (np.size(np.where((decision == 1) & (label == 1))))/np.size(np.where(label == 1))
        FP[index] = (np.size(np.where((decision == 1) & (label == 0))))/np.size(np.where(label == 0))
        minPerror[index] = (priors[0] * FP[index]) + (priors[1] * (1 - TP[index]))

# Theoretical classification based on class priors
gamma_ideal = priors[0] / priors[1]
ideal_decision = (Y >= gamma_ideal)
TP_ideal = (np.size(np.where((ideal_decision == 1) & (label == 1))))/np.size(np.where(label == 1))
FP_ideal = (np.size(np.where((ideal_decision == 1) & (label == 0))))/np.size(np.where(label == 0))
minPerror_ideal = (priors[0] * FP_ideal) + (priors[1] * (1 - TP_ideal))
print("Tau Ideal - %f and corresponding minimum error %f" %(gamma_ideal, minPerror_ideal))

# Plot ROC curve
plt.plot(FP, TP, color = 'red')
plt.xlabel('False Positive')
plt.ylabel('True Positive')
plt.title('ROC Curve')
plt.plot(FP[np.argmin(minPerror)], TP[np.argmin(minPerror)],'o',color = 'black')
plt.show()

print("Tau Practical - %f and corresponding minimum error %f"%(tau_sweep[np.argmin(minPerror)], np.min(minPerror)))


from sympy import ( symbols, lambdify, diff, exp, solve, pprint )
import matplotlib.pyplot as plt
import numpy as np

g_xlim = [ -5, 40 ]
g_ylim = [ -5, 70 ]

def plot_fun( fun, name, col ):
	x_vals = np.linspace( g_xlim[0], g_xlim[1], 1000, endpoint=True )
	y_vals = fun( x_vals )
	plt.plot( x_vals, y_vals, label = name, color = col )

# The equation:

initial_investment = 4000
compounding_rate = 3.5 / 100

# gives the balance after t years of an initial investment of $ dollars which pays % compounded continuously.

t = symbols( 't' )
A = initial_investment * exp( compounding_rate * t )

dA_dt = diff( A, t )
pprint( dA_dt )

# A'( 6 ) =
fv_12 = dA_dt.subs( { t: 6 } )
round( fv_12 )

lam_g = lambdify( t, A, np )
plot_fun( lam_g, 'A', 'G' )

lam_dg = lambdify( t, dA_dt, np )
plot_fun( lam_dg, 'dA/dt', 'B' )

delta = A.subs( { t: 7 } ) - A.subs( { t: 6 } )
round( delta, 2 )

plt.legend()
plt.show()


# ！/usr/bin/python
# -*- coding: utf-8 -*-
# Created by PJZero on 17-5-5

"""
画出正态分布的图。
"""
import numpy as np
import matplotlib.pyplot as plt

def cal_f(x, u=0, sigma=1):
    """
    计算正态分布的概率密度值
    :param x:
    :param u:
    :param sigma:
    :return:
    """
    return 1 / np.sqrt(2 * np.pi * np.power(sigma, 2)) \
           * np.power(np.e, - np.power(x - u, 2) / (2 * np.power(sigma, 2)))

u = 0
sigma = 1
x = np.arange(-4 * sigma, 4 * sigma, 0.1)
y = cal_f(x, u, sigma)
# y = 1 / np.sqrt(2 * np.pi * np.power(sigma, 2)) \
#     * np.power(np.e, - np.power(x - u, 2) / (2 * np.power(sigma, 2)))

plt.figure()
plt.plot(x, y)
plt.show()


import tensorflow as tf
import numpy as np

xy = np.loadtxt('train.txt', unpack=True, dtype='float32')
x_data = np.transpose(xy[0:3])
y_data = np.transpose(xy[3:])

# tf Graph Input
X = tf.placeholder("float", [None, 3])  # x1, x1 and 1 (for bias)
Y = tf.placeholder("float", [None, 3])  # A, B , C => 3 classes

# Set model weights
W = tf.Variable(tf.zeros([3, 3]))

# Construct model
hypothesis = tf.nn.softmax(tf.matmul(X, W))  # Softmax

# Minimize error using cross entropy
learning_rate = 0.001

# cross entropy
cost = tf.reduce_mean(-tf.reduce_sum(Y * tf.log(hypothesis), reduction_indices=1))

# Gradient Descent
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

# Initialize the variables
init = tf.initialize_all_variables()

# Launch the graph
with tf.Session() as sess:
    sess.run(init)

    for step in xrange(2001):
        sess.run(optimizer, feed_dict={X: x_data, Y: y_data})
        if step % 200 == 0:
            print step, sess.run(cost, feed_dict={X: x_data, Y: y_data}), sess.run(W)

    print '-' * 60
    print '# Test'
    print '-' * 60

    a = sess.run(hypothesis, feed_dict={X: [[1, 11, 7]]})
    print a, sess.run(tf.arg_max(a, 1))

    b = sess.run(hypothesis, feed_dict={X: [[1, 3, 4]]})
    print a, sess.run(tf.arg_max(b, 1))

    c = sess.run(hypothesis, feed_dict={X: [[1, 1, 0]]})
    print a, sess.run(tf.arg_max(c, 1))

    all = sess.run(hypothesis, feed_dict={X: [[1, 11, 7], [1, 3, 4], [1, 1, 0]]})
    print a, sess.run(tf.arg_max(all, 1))

    print '-' * 60