import numpy as np
import math
import tarfile
import pandas as pd

small_graph = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                     [1,0,0,0,0,0,0,0,0,0,0,0],
                     [0,1,0,0,0,0,0,0,0,0,0,0],
                     [0,0,0,0,0.5,0.5,0,0,0,0,0,0],
                     [0,0,0,0.5,0,0.5,0,0,0,0,0,0],
                     [0,0,0,0,1,0,0,0,0,0,0,0],
                     [0,0,0,0,0.5,0.5,0,0,0,0,0,0],
                     [0,0,0,0,0,0.5,0.5,0,0,0,0,0],
                     [0,0,1.0/3,0,0,0,0,1.0/3,0,0,0,1.0/3],
                     [0,0,0,0,0,0,0,0.5,0.5,0,0,0],
                     [0,0,0,0,0,0,0,0.5,0,0.5,0,0],
                     [0,0,0,0,0,0,0,0.5,0.5,0,0,0]])

alpha = 0.85 #damping factor
eps = 10

# open compressed p2p_gnut archive

comp_gnut = tarfile.open('p2p_gnut.tar.gz')
uncomp_gnut = comp_gnut.extractall()
comp_gnut.close()

# load p2p_gnut graph into panda dataframe then numpy array

#df_gnut = pd.read_csv('p2p_gnut.csv', header=None)

#p2p_gnut_graph = df_gnut.iloc[:,:].values

def maxnorm(x):
    norm = 0
    for i in range(len(x)):
        if (abs(x[i]) > norm):
            norm = abs(x[i])
    return norm

def maxnormdiffer(x1, x2):
    norm = 0
    for i in range(len(x1)):
        if (abs(x1[i]-x2[i]) > norm):
            norm = abs(x1[i]-x2[i])
    return norm

def euclnorm(x):
    norm = 0
    for i in range(len(x)):
        norm += abs(x[i])**(2)
    return math.sqrt(norm)

def phi(x, M):
    x1 = []
    for i in range(len(x)):
        x1.append((M*(np.matrix(x).T)).getA1()[i]-x[i])
    return (euclnorm(x1)+eps*euclnorm(x))

def pagerank (M):
    """
    The function gets a contingency matrix of a graph and computes the pagerank score with alpha = 0.85
    It implements an iterative approach and also returns the arrays of deviations
    from the optimum on every step to evaluate the rate of convergence
    args:
        M - contingency matrix of a graph, scaled to be a probabilistic matrix (numpy nxn array)
    return (weights, conv_func, conv_norm)
        weights - pagerank scores (numpy n*1 vector)
        conv_func - numpy vector with deviations from the optimum, described in terms of target function
        conv_norm - numpy vector with deviations from the optimum, described in terms of norm (euklid?)
    """

    n = len(M)

    v = [] #personalization vector
    for i in range(n):
        v.append(1/n)

    d = []#dangling nodes column
    for i in range(n):
        iszero = 1
        for j in range(n):
            if (M[i, j] != 0): iszero = 0
        d.append(iszero)
    d = np.matrix(d).T

    M = M + d*v #matrix with fixed dangling nodes

    M = M.T

    weights = []
    conv_func = np.array([]) #if it is more convenient to you, you can use simple python list here

    n = len(M)

    e = [1/n for i in range(n)]
    e = np.matrix(e).T

    x = []#iterations to optimum
    conv_norm = []#deviations from optimum
    x.append(e)
    k = 1

    x_new = (1-1/(k+1))*M*x[k-1] + (1/(k+1))*e
    x.append(x_new)
    k+=1

    while(1):
        x_new = (1-1/(k+1))*M*x[k-1] + (1/(k+1))*e
        x.append(x_new)
        k+=1
        if (k > 10**(2)): break

    for i in range(0, k-1):
        conv_norm.append(maxnormdiffer(x[k-1].getA1(), x[i].getA1()))

    weights = x[k-1].getA1()

    return (weights, conv_norm)

print(pagerank(small_graph)[0])

#v = pagerank(small_graph)
#np.savetxt('5PagerankSmall,eps=10,iter=100.txt', v[0], delimiter=" ")
#np.savetxt('5Conv,eps=10,iter=100.txt', v[1], delimiter=" ")

#f = open('test.txt', 'r')
#print(f.read())


import math
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import scipy
from  scipy.stats import linregress as lin
from scipy.optimize import curve_fit as fit
from scipy.integrate import quad
x = [ 1.,   1.5,  2.,   2.5,  3.,   3.5,  4.,   4.5,  5.,   5.5,  6.,   6.5,  7.,   7.5, 8.,   8.5,  9.,   9.5, 10. ]
y = [3.43, 4.94, 6.45, 9.22, 6.32, 6.11, 4.63, 8.95, 7.8, 8.35, 11.45, 14.71, 11.97, 12.46, 17.42, 17.0, 15.45, 19.15, 20.86]
"""Linera regression"""
slope, intercept, r_value, p_value, std_err = lin(x,y)
"""apparently a float conversion is needed to avoid numpy errors"""
x=np.float64(x)
LinFit=x*slope+intercept
"""Cube fitting"""
def CubeFun(x,a,b,c,d):
   return a*(x**3)+b*(x**2)+c*x+d
params, params_covariance=fit(CubeFun,x,y)
CubeFit=CubeFun(x,*params)
"""integration"""
A=quad(lambda x:CubeFun(x,*params),min(x),max(x))
"""BIC calculation"""
def BIC(y, yhat, k, weight = 1):
    err = y - yhat
    sigma = np.std(np.real(err))
    n = len(y)
    B = n*np.log(sigma**2) + weight*k*np.log(n)
    return B
LineError=BIC(y,LinFit,2)
CubeError=BIC(y,CubeFit,4)
LineError=round(LineError,2)
CubeError=round(CubeError,2)

"""plotting of data"""
Fig=plt.figure()

axes = Fig.add_axes([0.1, 0.1, 0.8, 0.8]) # main axes
plt.xticks(np.arange(int(min(x)-2), int(max(x)+2), 2))
plt.yticks(np.arange(int(min(y))-3, int(max(y))+3, 3))

plt.scatter(x,y,color='black',label='Data points')
"""Plotting of linear fit"""
plt.plot(x,LinFit,'r',label='Linear fit, BIC=%s'%(LineError))
plt.plot(x,CubeFit,'g',label='Cubic fit, BIC=%s'%(CubeError))
Area=A[0]
Area=round(Area,2)
plt.text(5,19,'Area=%s'%(Area))
axes.set_xlabel('x')
axes.set_ylabel('y')
axes.set_title('Fitting of data')
plt.legend()
Fig.savefig('DataPlot.pdf')


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d

x_list = np.linspace(-2.0, 3.5, 1000)
y_list = np.linspace(-2.0, 3.5, 1000)
X, Y = np.meshgrid(x_list, y_list)

# Functions
Z1 = 2 * X + 3 * Y + 1
Z2 = X**2 + Y**2 - X * Y - 5
Z3 = (X - 5)*np.cos(Y - 5) - (Y - 5)*np.sin(X - 5)

# Create subplots for each function
fig, (f1, f2, f3) = plt.subplots(1, 3)

f1.contour(X, Y, Z1)
# Gradient of f1 at (1, 0) is [2, 3]
f1.quiver(1, 0, 2, 3)
f1.set_title('Contour Plot 1')
f1.set_xlabel('x')
f1.set_ylabel('y')

f2.contour(X, Y, Z2)
# Gradient of f2 at (1, 0) is [2, -2]
f2.quiver(1, 0, 2, -2)
f2.set_title('Contour Plot 2')
f2.set_xlabel('x')
f2.set_ylabel('y')

f3.contour(X, Y, Z3)
# Gradient of f3 at (1, 0) is about [-2.98, 3.08]
f3.quiver(1, 0, -2.98, 3.08)
f3.set_title('Contour Plot 3')
f3.set_xlabel('x')
f3.set_ylabel('y')

# Create plots for each function
fig2, mesh1 = plt.subplots(1, 1)
fig3, mesh2 = plt.subplots(1, 1)
fig4, mesh3 = plt.subplots(1, 1)
fig5, mesh4 = plt.subplots(1, 1)
fig6, mesh5 = plt.subplots(1, 1)
fig7, mesh6 = plt.subplots(1, 1)
fig8, mesh7 = plt.subplots(1, 1)
fig9, mesh8 = plt.subplots(1, 1)
fig10, mesh9 = plt.subplots(1, 1)

mesh1 = fig2.gca(projection='3d')
mesh2 = fig3.gca(projection='3d')
mesh3 = fig4.gca(projection='3d')
mesh4 = fig5.gca(projection='3d')
mesh5 = fig6.gca(projection='3d')
mesh6 = fig7.gca(projection='3d')
mesh7 = fig8.gca(projection='3d')
mesh8 = fig9.gca(projection='3d')
mesh9 = fig10.gca(projection='3d')

# Quadratic approximations
QUAD_APPROX1 = 2 * X + 3
QUAD_APPROX2 = X ** 2 + 2 * X - 4
QUAD_APPROX3 = -1.892 * X ** 2 - 2.985 * X + 2.649

# Part c)
QUAD_APPROX4 = 2 * X + 5.6
QUAD_APPROX5 = X ** 2 - 3.4 * X + 0.89
QUAD_APPROX6 = -0.826 * X ** 2 + 0.034 * X + 7.295

QUAD_APPROX7 = 2 * X + 3
QUAD_APPROX8 = X ** 2 + 6 * X + 4.75
QUAD_APPROX9 = 1.795 * X ** 2 - 3.847 * X - 5.991

mesh1.plot_surface(X, Y, QUAD_APPROX1)
mesh1.set_title('Quadratic Approximation 1')
mesh1.set_xlabel('x')
mesh1.set_ylabel('y')

mesh2.plot_surface(X, Y, QUAD_APPROX2)
mesh2.set_title('Quadratic Approximation 2')
mesh2.set_xlabel('x')
mesh2.set_ylabel('y')

mesh3.plot_surface(X, Y, QUAD_APPROX3)
mesh3.set_title('Quadratic Approximation 3')
mesh3.set_xlabel('x')
mesh3.set_ylabel('y')

mesh4.plot_surface(X, Y, QUAD_APPROX4)
mesh4.set_title('Quadratic Approximation 4')
mesh4.set_xlabel('x')
mesh4.set_ylabel('y')

mesh5.plot_surface(X, Y, QUAD_APPROX5)
mesh5.set_title('Quadratic Approximation 5')
mesh5.set_xlabel('x')
mesh5.set_ylabel('y')

mesh6.plot_surface(X, Y, QUAD_APPROX6)
mesh6.set_title('Quadratic Approximation 6')
mesh6.set_xlabel('x')
mesh6.set_ylabel('y')

mesh7.plot_surface(X, Y, QUAD_APPROX7)
mesh7.set_title('Quadratic Approximation 7')
mesh7.set_xlabel('x')
mesh7.set_ylabel('y')

mesh8.plot_surface(X, Y, QUAD_APPROX8)
mesh8.set_title('Quadratic Approximation 8')
mesh8.set_xlabel('x')
mesh8.set_ylabel('y')

mesh9.plot_surface(X, Y, QUAD_APPROX9)
mesh9.set_title('Quadratic Approximation 9')
mesh9.set_xlabel('x')
mesh9.set_ylabel('y')

plt.show()


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

initial_P1 = np.array([0, 0])  # first coordinate of the initial segment
initial_P2 = np.array([3, 3])  # second coordinate of the initial segment
nbiter = 8  # number of iterations

def tiers(A, B):
    """Add a fractal step

    Args:
        A (array): point in 2D space
        B (array): point in 2D space

    Returns:
        C, D, E: 3 new points in 2D space
    """
    step = (B - A) / 3
    C = A + step
    E = C + step
    rotation = np.array([[cos(pi / 3), -sin(pi / 3)],
                         [sin(pi / 3), cos(pi / 3)]])
    D = np.dot(rotation, (E - C)) + C
    return C, D, E

def fractal(n, A, B):
    """Calculate all the new points added by cutting our segment [A,B] n times

    Args:
        n (int): number of iterations
        A (array): point in 2D space
        B (array): point in 2D space

    Returns:
        list: List of all the points (array)
    """
    L = [A]

    def iteration(n, A, B):
        if n > 0:
            (C, D, E) = tiers(A, B)
            iteration(n - 1, A, C)
            L.append(C)
            iteration(n - 1, C, D)
            L.append(D)
            iteration(n - 1, D, E)
            L.append(E)
            iteration(n - 1, E, B)
    iteration(n, A, B)
    L.append(B)
    return L

def snowflake(n, A, B):
    """Plot the Helge von Koch snowflake

    Args:
        n (int): number of iteration
        A (array): point in 2D space
        B (array): point in 2D space
    """
    rotationC = np.array([[cos(pi / 3), -sin(-pi / 3)],
                          [sin(-pi / 3), cos(pi / 3)]])
    C = np.dot(rotationC, (B - A)) + A
    L = fractal(n, A, B) + fractal(n, B, C) + fractal(n, C, A)
    n = len(L)
    plt.axis('equal')
    plt.axis('off')
    absciss = []
    ordonate = []
    for k in range(0, n):
        absciss.append(L[k][0])
        ordonate.append(L[k][1])
    plt.plot(absciss, ordonate)
    plt.show()

snowflake(nbiter, initial_P1, initial_P2)


from math import e, pi, sqrt, cos, sin
import numpy as np

i_vector = np.identity(2)

not_vector = np.array([[0, 1],
                       [1, 0]], dtype=np.complex)

y_vector = np.array([[0, -1j],
                     [1j, 0]], dtype=np.complex)

h_vector = (1/sqrt(2)) * np.array([[1, 1],
                                   [1, -1]], dtype=np.complex)

#  Identity gate
def I(state):
    """Applies the Identity gate.

    state -- state of a single qubit
    """
    return i_vector @ state

#  Pauli-X / NOT gate / Half turn
def X(state):
    """Applies the NOT gate.

    state -- state of a single qubit
    """
    return not_vector @ state

#  Hadamard gate / Half turn
def H(state):
    """Applies the Hadamard gate.

    state -- state of a single qubit
    """
    return h_vector @ state

#  Pauli-Y / Y gate / Half turn
def Y(state):
    """Applies the Y gate.

    state -- state of a single qubit
    """
    return y_vector @ state

#  Pauli-Z / Z gate / Half turn
def Z(state):
    """Applies the Z gate.

    state -- state of a single qubit
    """
    z_vector = np.array([[1, 0],
                         [0, -1]], dtype=np.complex)
    return z_vector @ state

#  Phase shift / RZ gate
def RZ(phi, state):
    """Applies the RZ gate.

    state -- state of a single qubit
    """
    rz_vector = np.array([[1, 0],
                          [0, e ** (phi * 1j)]], dtype=np.complex)
    return rz_vector @ state

#  S gate / sqrt(Z) / Quarter turn
def S(state):
    """Applies the S gate.

    state -- state of a single qubit
    """
    return RZ(pi/2, state)

#  T gate / sqrt(S) / Eighth turn
def T(state):
    """Applies the T gate.

    state -- state of a single qubit
    """
    return RZ(pi/4, state)

# Controlled NOT / CNOT / CX gate
def CNOT(joint_state):
    """Applies the CNOT gate.

    joint_state -- joint state of 2 qubits
    """
    cnot_vector = np.array([[1, 0, 0, 0],
                            [0, 1, 0, 0],
                            [0, 0, 0, 1],
                            [0, 0, 1, 0]], dtype=np.complex)
    return cnot_vector @ joint_state

#  Controlled Phase shift / CPHASE gate
def CPHASE(phi, joint_state):
    """Applies the CPHASE gate.

    joint_state -- joint state of 2 qubits
    """
    cr_vector = np.array([[1, 0, 0, 0],
                          [0, 1, 0, 0],
                          [0, 0, 1, 0],
                          [0, 0, 0, e ** (phi * 1j)]], dtype=np.complex)
    return cr_vector @ joint_state

# Controlled Z / CZ gate
def CZ(joint_state):
    """Applies the CZ gate.

    joint_state -- joint state of 2 qubits
    """
    cz_vector = np.array([[1, 0, 0, 0],
                          [0, 1, 0, 0],
                          [0, 0, 1, 0],
                          [0, 0, 0, -1]], dtype=np.complex)
    return cz_vector @ joint_state

# Controlled U / CU gate
def CU(U, joint_state):
    """Applies the CU gate.

    U           -- Pauli matrices
    joint_state -- joint state of 2 qubits
    """
    cu_vector = np.identity(4, dtype=np.complex)
    cu_vector[2:4, 2:4] = U
    return cu_vector @ joint_state

# Controlled X / CX gate / same as CNOT
def CX(joint_state):
    """Applies the CX gate.

    joint_state -- joint state of 2 qubits
    """
    return CU(not_vector, joint_state)

# Controlled Y / CY gate
def CY(joint_state):
    """Applies the CY gate.

    joint_state -- joint state of 2 qubits
    """
    return CU(y_vector, joint_state)

# SWAP gate
def SWAP(joint_state):
    """Applies the SWAP gate.

    joint_state -- joint state of 2 qubits
    """
    swap_vector = np.array([[1, 0, 0, 0],
                            [0, 0, 1, 0],
                            [0, 1, 0, 0],
                            [0, 0, 0, 1]], dtype=np.complex)
    return swap_vector @ joint_state

# Hadamard gate extended with Identity
# K will leave the second qubit untouched and apply the Hadamard transform to the first qubit
def K(joint_state):
    """Applies the K / HI gate.

    joint_state -- joint state of 2 qubits
    """
    return np.kron(h_vector, i_vector) @ joint_state

#  Hadamard transform - Hadamard gate (H) applied in parallel on 2 qubits
def H2(joint_state):
    """Applies the Hadamard transform.

    joint_state -- joint state of 2 qubits
    """
    return np.kron(h_vector, h_vector) @ joint_state

# Toffoli / CCNOT gate
def CCNOT(joint_state):
    """Applies the CCNOT gate.

    joint_state -- joint state of 3 qubits
    """
    ccnot_vector = np.array([[1, 0, 0, 0, 0, 0, 0, 0],
                             [0, 1, 0, 0, 0, 0, 0, 0],
                             [0, 0, 1, 0, 0, 0, 0, 0],
                             [0, 0, 0, 1, 0, 0, 0, 0],
                             [0, 0, 0, 0, 1, 0, 0, 0],
                             [0, 0, 0, 0, 0, 1, 0, 0],
                             [0, 0, 0, 0, 0, 0, 0, 1],
                             [0, 0, 0, 0, 0, 0, 1, 0]], dtype=np.complex)
    return ccnot_vector @ joint_state

# Fredkin / CSWAP gate
def CSWAP(joint_state):
    """Applies the CSWAP gate.

    joint_state -- joint state of 3 qubits
    """
    cswap_vector = np.array([[1, 0, 0, 0, 0, 0, 0, 0],
                             [0, 1, 0, 0, 0, 0, 0, 0],
                             [0, 0, 1, 0, 0, 0, 0, 0],
                             [0, 0, 0, 1, 0, 0, 0, 0],
                             [0, 0, 0, 0, 1, 0, 0, 0],
                             [0, 0, 0, 0, 0, 0, 1, 0],
                             [0, 0, 0, 0, 0, 1, 0, 0],
                             [0, 0, 0, 0, 0, 0, 0, 1]], dtype=np.complex)
    return cswap_vector @ joint_state

# Universal quantum gates for single qubit
def U3(_theta, _phi, _lambda, state):
    """Applies the U3 gate.

    state   -- state of a single qubit
    """
    u3_vector = np.array([[cos(_theta/2), -((e ** (_lambda * 1j) * (sin(_theta/2))))],
                         [((e ** (_phi * 1j) * (sin(_theta/2)))), ((e ** ((_lambda * 1j) + (_phi * 1j)) * (cos(_theta/2))))]], dtype=np.complex)
    return u3_vector @ state

def U2(_phi, _lambda, state):
    return U3(pi/2, _phi, _lambda, state)

def U1(_lambda, state):
    return U3(0, 0, _lambda, state)


import numpy as np
import cv2
import math

def discreteFourierTransform(image):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

    dft = cv2.dft(np.float32(gray), flags = cv2.DFT_COMPLEX_OUTPUT)
    dft_shift = np.fft.fftshift(dft)

    magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))

    return magnitude_spectrum

def butterworthLowPassMask(width,height,dinit = 20,n = 1):
    width = width / 2
    height = height / 2

    hw = np.arange(-width, width)
    hh = np.arange(-height, height)

    x, y = np.meshgrid(hh, hw)
    mg = np.sqrt(x**2 + y**2)
    return 1 / (1 + (mg/dinit)**(2*n))

def butterworthHighPassMask(width,height,dinit = 20,n = 1):
    width = width / 2
    height = height / 2

    hw = np.arange(-width, width)
    hh = np.arange(-height, height)

    x, y = np.meshgrid(hh, hw)
    mg = np.sqrt(x**2 + y**2)
    return 1 / (1 + (dinit/mg)**(2*n))

def butterworthFilter(image, d=20, n=1, filter_type="low"):
    gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    width, height = gray.shape

    mask = ""
    if filter_type == "low":
        mask = butterworthLowPassMask(width, height, d, n)
    elif filter_type == "high":
        mask = butterworthHighPassMask(width, height, d, n)

    fft = np.fft.fftshift(np.fft.fft2(np.float32(gray)))

    img_pass = mask * fft
    result = np.abs(np.fft.ifft2(np.fft.ifftshift(img_pass)))

    return result

def notchFilter(image):
    pass

if __name__ == "__main__":
    img = cv2.imread("../sample/hw4_radiograph_1.jpg")
    cv2.imwrite("../output/output.jpg", butterworthFilter(img, 20, 1, "high"))


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

def single_spiral(grid_size, noise = 0):
	step = 1
	radius = float(grid_size)
	theta = 0
	result = []
	for i in range(int(grid_size / step)):
		theta_rad = theta * math.pi / 180
		actual_noise = noise * math.sqrt(radius / grid_size)
		x = radius * math.cos(theta_rad) + random.uniform(-actual_noise, actual_noise)
		y = radius * math.sin(theta_rad) + random.uniform(-actual_noise, actual_noise)
		result.append((x, y))
		theta += 4
		radius -= step
	return result

def double_spiral(grid_size, noise = 0):
	original = single_spiral(grid_size, noise)
	result = []
	rotation = math.pi
	sin = math.sin(rotation)
	cos = math.cos(rotation)
	for i in range(len(original)):
		(x, y) = original[i]
		xn = x * cos - y * sin
		yn = x * sin + y * cos
		result.append((xn, yn))
	return [single_spiral(grid_size, noise), result]


import numpy as np

from .Scoring import Scoring

class SimpleLinearRegression(Scoring):

    def __init__(self):
        super().__init__()

    def fit(self, X, Y):
        self.m, self.n = X.shape

        ones = np.ones(shape=self.m).reshape(-1, 1)
        X = np.concatenate((ones, X), 1)

        inv_mat = np.linalg.inv(X.transpose().dot(X))

        coefficient = inv_mat.dot(X.transpose()).dot(Y)

        self.intercept_ = coefficient[0]
        self.coef_ = coefficient[1:]

        return self

    def __str__(self):
        res = ""
        res += f"Slope: {list(self.coef_)}, Intercept: {self.intercept_}\n"
        res_p = f"Equation {list(self.coef_)}X + {self.intercept_}"
        if self.intercept_ < 0:
            res_p = f"Equation: Y = {list(self.coef_)}X - {np.abs(self.intercept_)}" # noqa
        res += res_p
        return res


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

K_list = [1, 2, 3, 4, 5, 100] #和を取る一様乱数の数
n_iter = 1000000 #乱数の発生個数
#step = 1/1000
#x = np.arange(-2, 2+step, step)

#def norm_dist(x:np.ndarray, mu:float, sigma:float) -> np.ndarray:
#  return np.exp(-np.square(x-mu)/(2*sigma))/(np.sqrt(2*np.pi)*sigma)

#a=np.random.rand(K, n_iter)-0.5
#plt.hist(np.sum(a, axis=0), bins=1000, normed=True)
#plt.plot(x, norm_dist(x, 0, 1))
#plt.show()

nrow_fig, ncol_fig = 3, 2 # グラフの横の数と縦の数を求める
fig = plt.figure(figsize=(8,8)) # グラフの描画処理
for i, K in enumerate(K_list):
    ax = plt.subplot2grid((nrow_fig, ncol_fig), (i//ncol_fig, i%ncol_fig))
    # K*n_iterの一様乱数[-0.5, 0.5)の発生
    sum_uniform = np.sum(np.random.rand(K, n_iter)-0.5, axis=0) #行方向に和を取る(n_iter個の一様乱数の和になっている)
    ax.hist(sum_uniform, bins=1000, normed=True) #ヒストグラムの描画
    ax.legend(f"K={K}") #サブプロットにタイトルを表示

plt.tight_layout() #サブプロット間の間隔調整
plt.show()


import numpy as np

def sphere(x):
    return np.sum(np.power(x, 2))

def griewank(x):
    D = x.shape[0]
    return np.sum(np.power(x, 2) / 4000) - \
        np.prod(np.cos(x / np.sqrt(np.linspace(1, D, D)))) + 1

def rastrigin(x):
    return np.sum(np.power(x, 2) - 10 * np.cos(2 * np.pi * x) + 10)

def schwefel(x):
    D = x.shape[0]
    return 418.9829 * D - np.sum(x * np.sin(np.power(np.abs(x), 0.5)))

def rosenbrock(x):
    return np.sum(
        100 * np.power(x[:-1] **  2 - x[1:], 2) + \
        (x[:-1] - 1) ** 2)

def ackley(x):
    return -20 * np.exp(-0.2 * np.sqrt(np.mean(np.power(x, 2)))) - \
        np.exp(np.mean(np.cos(2 * np.pi * x))) + 20 + np.exp(1)

# Weierstrass function
def get_weierstrass_const(D, a=0.5, b=3, kmax=20):
    return np.sum([a ** k * np.cos(2 * np.pi * b ** k * 0.5) for k in range(kmax)])

weierstrass_const = {
    25: get_weierstrass_const(25),
    50: get_weierstrass_const(50),
}
def weierstrass(x, a=0.5, b=3, kmax=20):
    D = x.shape[0]
    return np.sum(np.stack([a ** k * np.cos(2 * np.pi * b ** k * (x + 0.5))
                            for k in range(kmax)])) - D * weierstrass_const[D]


# --------------
import pandas as pd
import scipy.stats as stats
import math
import numpy as np
import warnings

warnings.filterwarnings('ignore')
#Sample_Size
sample_size=2000

#Z_Critical Score
z_critical = stats.norm.ppf(q = 0.95)

# path        [File location variable]
data = pd.read_csv(path)

data_sample = data.sample(n=sample_size,random_state =0)
sample_mean = data_sample['installment'].mean()
sample_std = data_sample['installment'].std()
margin_of_error = z_critical*(sample_std/math.sqrt(sample_size))
confidence_interval =(sample_mean - margin_of_error,
sample_mean + margin_of_error)
true_mean = data['installment'].mean()
print("True mean :{}".format(true_mean))

import matplotlib.pyplot as plt
import numpy as np

#Different sample sizes to take
sample_size=np.array([20,50,100])

fig ,axes = plt.subplots(nrows = 3 , ncols = 1)
for i in range(len(sample_size)):
    m = []
    for j in range(1000):
        data['installment'].sample(n=sample_size[i])
        m.append(data['installment'].mean())
    mean_series = pd.Series(m)
    axes[i].hist(mean_series)

#Importing header files
from statsmodels.stats.weightstats import ztest
import statsmodels

data['int.rate'] = data['int.rate'].str.replace('%','')
data['int.rate'] = data['int.rate'].astype(float)/100
x1 = data[data['purpose']=='small_business']['int.rate']
z_statistic,p_value= statsmodels.stats.weightstats.ztest(x1,value=data['int.rate'].mean(),alternative='larger')
if p_value < 0.05:    # alpha value is 0.05 or 5%
   print(" we are rejecting null hypothesis")
else:
  print("we are failed to reject  null hypothesis")

#Importing header files

from statsmodels.stats.weightstats import ztest

x1 = data[data['paid.back.loan']=='No']['installment']
x2 = data[data['paid.back.loan']=='Yes']['installment']
z_statistic,p_value = statsmodels.stats.weightstats.ztest(x1,x2)
if p_value < 0.05:    # alpha value is 0.05 or 5%
   print(" we are rejecting null hypothesis")
else:
  print("we are failed to reject  null hypothesis")

#Importing header files
from scipy.stats import chi2_contingency

#Critical value
critical_value = stats.chi2.ppf(q = 0.95, # Find the critical value for 95% confidence*
                      df = 6)   # Df = number of variable categories(in purpose) - 1

yes = data[data['paid.back.loan']=='Yes']['purpose'].value_counts()
no = data[data['paid.back.loan']=='No']['purpose'].value_counts()
observed = pd.concat([yes.transpose(),no.transpose()], 1,keys=['Yes','No'])

chi2, p, dof, ex = chi2_contingency(observed, correction=False)

print("Critical value")
print(critical_value)
print("Chi Statistic")
print(chi2)

# --------------

import numpy,import scipy,from scipy,import statsmodels,from statsmodels


# Newton interpolating polynomial

from divdiff import divdiff
import numpy as np

def newton_interp(x, y, X):
    '''
    Newton interpolation method
    Input:
      - x : precomputed points x axis values
      - y : precomputed points y axis values
      - X : query points x axis
    Output:
      - Y : query points y axis (interpolation results)
    '''
    n = len(x)
    Y = np.zeros(len(X))
    for k in range(n):
        L = np.ones(len(X))
        for i in range(k):
            L *= (X - x[i])
        a = divdiff(x[0:k+1], y[0:k+1])
        Y += a*L
    return Y


import torch
import numpy as np

def xyz2sphere(xyz, normalize=True):
    """
    Convert XYZ to Spherical Coordinate

    reference: https://en.wikipedia.org/wiki/Spherical_coordinate_system

    :param xyz: [N, 3] / [N, G, 3]
    :return: (rho, theta, phi) [N, 3] / [N, G, 3]
    """
    rho = torch.sqrt(torch.sum(torch.pow(xyz, 2), dim=-1, keepdim=True))
    rho = torch.clamp(rho, min=0)  # range: [0, inf]
    theta = torch.acos(xyz[..., 2, None] / rho)  # range: [0, pi]
    phi = torch.atan2(xyz[..., 1, None], xyz[..., 0, None])  # range: [-pi, pi]
    # check nan
    idx = rho == 0
    theta[idx] = 0

    if normalize:
        theta = theta / np.pi  # [0, 1]  TODO Use range of [-1, 1] instead
        phi = phi / (2 * np.pi) + .5  # [0, 1]
    out = torch.cat([rho, theta, phi], dim=-1)
    return out

def xyz2sphere_np(xyz, normalize=True):
    """
    Convert XYZ to Spherical Coordinate

    reference: https://en.wikipedia.org/wiki/Spherical_coordinate_system

    :param xyz: [N, 3] / [N, G, 3]
    :return: (rho, theta, phi) [N, 3] / [N, G, 3]
    """
    rho = np.sqrt(np.sum(np.power(xyz, 2), axis=-1))[..., np.newaxis]
    rho = np.clip(rho, 0, None)  # range: [0, inf]
    theta = np.arccos(xyz[..., 2, None] / rho)  # range: [0, pi]
    phi = np.arctan2(xyz[..., 1, None], xyz[..., 0, None])  # range: [-pi, pi]
    # check nan
    idx = rho == 0
    theta[idx] = 0

    if normalize:
        theta = theta / np.pi  # [0, 1]  TODO Use range of [-1, 1] instead
        phi = phi / (2 * np.pi) + .5  # [0, 1]
    out = np.concatenate([rho, theta, phi], axis=-1).astype(np.float32)
    return out

def xyz2sphere_aug(xyz, normalize=True):
    """
    Convert XYZ to Spherical Coordinate (Augmentation by YZ & XZ direction)

    reference: https://en.wikipedia.org/wiki/Spherical_coordinate_system

    :param xyz: [B, N, 3] / [B, N, G, 3]
    :return: (rho, theta, phi) [B, N, 3] / [B, N, G, 3]
    """
    rho = torch.sqrt(torch.sum(torch.pow(xyz, 2), dim=-1, keepdim=True))
    rho = torch.clamp(rho, 0, 1)  # range: [0, 1]

    # XY direction
    theta_xy = torch.acos(xyz[..., 2, None] / rho)  # range: [0, pi]
    phi_xy = torch.atan2(xyz[..., 1, None], xyz[..., 0, None])  # range: [-pi, pi]
    # XZ direction
    theta_xz = torch.acos(xyz[..., 1, None] / rho)  # range: [0, pi]
    phi_xz = torch.atan2(xyz[..., 2, None], xyz[..., 0, None])  # range: [-pi, pi]
    # YZ direction
    theta_yz = torch.acos(xyz[..., 0, None] / rho)  # range: [0, pi]
    phi_yz = torch.atan2(xyz[..., 2, None], xyz[..., 1, None])  # range: [-pi, pi]

    # check nan
    idx = rho == 0
    theta_xy[idx] = 0
    theta_xz[idx] = 0
    theta_yz[idx] = 0

    theta = torch.cat([theta_xy, theta_xz, theta_yz], dim=-1)
    phi = torch.cat([phi_xy, phi_xz, phi_yz], dim=-1)

    if normalize:
        theta = theta / np.pi
        phi = phi / (2 * np.pi) + .5
    out = torch.cat([rho, theta, phi], dim=-1)
    return out

def normal2sphere(xyz, normalize=False):
    """
    Convert Normal Vector to Spherical Coordinate

    reference: https://en.wikipedia.org/wiki/Spherical_coordinate_system

    :param xyz: [B, N, 3] / [B, N, G, 3]
    :return: (theta, phi) [B, N, 2] / [B, N, G, 2]
    """
    theta = torch.acos(xyz[..., 2, None])  # range: [0, pi]
    phi = torch.atan2(xyz[..., 1, None], xyz[..., 0, None])  # range: [-pi, pi]

    if normalize:
        theta = theta / np.pi
        phi = phi / (2 * np.pi) + .5
    out = torch.cat([theta, phi], dim=-1)
    return out

def sphere2normal(sphere):
    x = torch.cos(sphere[..., 1, None]) * torch.sin(sphere[..., 0, None])
    y = torch.sin(sphere[..., 1, None]) * torch.sin(sphere[..., 0, None])
    z = torch.cos(sphere[..., 0, None])
    return torch.cat([x, y, z], dim=-1)

def xyz2cylind(xyz, normalize=True):
    """
    Convert XYZ to Cylindrical Coordinate

    reference: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system

    :param normalize: Normalize phi & z
    :param xyz: [B, N, 3] / [B, N, G, 3]
    :return: (rho, phi, z) [B, N, 3]
    """
    rho = torch.sqrt(torch.sum(torch.pow(xyz[..., :2], 2), dim=-1, keepdim=True))
    rho = torch.clamp(rho, 0, 1)  # range: [0, 1]
    phi = torch.atan2(xyz[..., 1, None], xyz[..., 0, None])  # range: [-pi, pi]
    z = xyz[..., 2, None]
    z = torch.clamp(z, -1, 1)  # range: [-1, 1]

    if normalize:
        phi = phi / (2 * np.pi) + .5
        z = (z + 1.) / 2.
    out = torch.cat([rho, phi, z], dim=-1)
    return out


from math import log
import numpy as np
import aux

def _stochastic_block_model(n, p, q):
    """
    Generates a SBM observation with inner and inter cluster probability.
    """

    z_sbm = np.hstack(
        (np.ones(int(n / 2)), -np.ones(int(n / 2)))).reshape(n, 1)
    R1_vec = aux.rounding_with_prob(
        np.random.random_sample(int(n / 2) * int(n / 2)), p)
    R3_vec = aux.rounding_with_prob(
        np.random.random_sample(int(n / 2) * int(n / 2)), p)
    R1_pre = R1_vec.reshape((int(n / 2), int(n / 2)))
    R3_pre = R3_vec.reshape((int(n / 2), int(n / 2)))
    for i in range(int(n / 2)):
        for j in range(int(n / 2)):
            if i > j:
                R1_pre[i, j] = R1_pre[j, i]
                R3_pre[i, j] = R3_pre[j, i]
            elif i == j:
                R1_pre[i, i] = 1
                R3_pre[i, i] = 1
    R2_vec = aux.rounding_with_prob(
        np.random.random_sample(int(n / 2) * int(n / 2)), q)
    R2_pre = R2_vec.reshape((int(n / 2), int(n / 2)))
    Y = np.vstack((np.hstack((R1_pre, R2_pre)), np.hstack((R2_pre.T, R3_pre))))
    return Y, z_sbm

def sbm_logarithm(n, a, b):
    """
    Returns an observation with logarithmic regime.
    """

    p = a * log(n) / n
    q = b * log(n) / n
    Y, z_sbm = _stochastic_block_model(n, p, q)
    print('Adjacency matrix for SBM (logarithmic regime) is generated!')
    return Y, z_sbm

def sbm_linear(n, a, b):
    """
    Returns an observation with linear regime.
    """

    p = a / n
    q = b / n
    Y, z_sbm = _stochastic_block_model(n, p, q)
    print('Adjacency matrix for SBM (linear regime) is generated!')
    return Y, z_sbm

if __name__ == "__main__":
    print('Test starts for module sbm_generator...')
    Y_logarithm, z_logarithm = sbm_logarithm(10, 3, 9)
    Y_linear, z_linear = sbm_linear(10, 3, 9)
    print(Y_logarithm)
    print(z_logarithm)
    print(Y_linear)
    print(z_linear)


import numpy as np

from dslr.stats import sigmoid_

class LogisticRegression:
    """
    Logistic Regression Class, fitting data and predicting the values
    """

    def __init__(self, learning_rate=0.001, n_iters=1000):
        self.learning_rate = learning_rate
        self.n_iters = n_iters
        self.weights = None
        self.bias = None

    def fit(self, x_train, y_test):  # pylint: disable=missing-function-docstring, invalid-name
        """
        train x data
        :param x_train:    the path containing the dataset as csv
        :param y_test:    the path containing the dataset as csv
        """
        # init parameters
        n_samples, n_features = x_train.shape
        self.weights = np.zeros(n_features)
        self.bias = 0

        for _ in range(self.n_iters):
            # approximate
            y_predicted = self.approximation(x_train)

            weights_derivative = (1 / n_samples) * np.dot(x_train.T, (y_predicted - y_test))
            bias_derivative = (1 / n_samples) * np.sum(y_predicted - y_test)

            # gradient descent
            self.weights -= self.learning_rate * weights_derivative
            self.bias -= self.learning_rate * bias_derivative

    def predict(self, to_predict):  # pylint: disable=invalid-name
        y_predicted = self.approximation(to_predict)
        return [1 if i > 0.5 else 0 for i in y_predicted]

    def approximation(self, to_approx):  # pylint: disable=invalid-name
        linear_model = np.dot(to_approx, self.weights) + self.bias
        return sigmoid_(linear_model)


import numpy as np
import cvxpy as cp
import itertools
from scipy.sparse.csgraph import connected_components
from scipy.sparse import csr_matrix
import scipy

def distance(x1, y1, x2, y2):
    return np.sqrt((x1-x2)**2 + (y1-y2)**2)

def pathloss(d, eta):
    delta = 1.0e-1
    return (d+delta)**(-eta)

def genLocation(size, len_area):
    x = np.random.uniform(0.0, len_area, size)
    y = np.random.uniform(0.0, len_area, size)
    return x, y

'''Signal-to-Noise power Ratio in True Domain'''
def calcSNR(ptx, eta, d, awgn_mw, bw):
    prx = ptx * pathloss(d, eta)
    return prx / (awgn_mw*bw)

''' Channel Capacity '''
def calcCapacity(ptx, eta, d, awgn_mw, bw):
    snr = calcSNR(ptx, eta, d, awgn_mw, bw)
    return bw * np.log2(1.0+snr)

'''distance matrix'''
def genDistanceMatrix(x, y):
    return distance(x, y, x[:, np.newaxis], y[:, np.newaxis])

'''compute whether the topology is strongly connected'''
## a: adjacency matrix WITHOUT self loop
def DFS(a):
    n = len(a) #number of nodes
    seen = np.zeros(n, dtype=bool) #seen vector
    seen[0] = True
    tmp = np.where(a[0]==1)[0]
    seen[tmp] = True
    td = tmp.tolist()

    while len(td) > 0:
        indx = td[len(td)-1] #stack structure for DFS
        td.pop(len(td)-1)
        t = np.where(a[indx]==1)[0]
        for w in t:
            if seen[w] == False:
                seen[w] = True
                td.append(w)

    return len(np.where(seen[:] == True)[0]) == len(seen)

'''Gen Adjacency matrix with self loop (based on communication distance)'''
def genConnmat(dmat, d_vec):
    connmat = np.array([(dmat[j] <= d_vec[j]) for j in range(len(dmat))]) * 1
    np.fill_diagonal(connmat, 1)
    return connmat

'''Gen Adjacency matrix with self loop (based on capacity)'''
def genConnmatRate(cmat, r_vec):
    connmat = np.array([(cmat[j] >= r_vec[j]) for j in range(len(cmat))]) * 1
    np.fill_diagonal(connmat, 1)
    return connmat

def convertConnmatToWeightMat(connmat):
    n_node = len(connmat)
    w = np.array([connmat[i] / connmat[i].sum() for i in range(n_node)])
    return w

def optimizeTopologyDirectedMod(x, y, xi_target, eta, ptx_mw, awgn_mw, bw):
    dmat = genDistanceMatrix(x, y)

    n = len(x)
    indx_opt = []
    t_min = np.inf

    p = itertools.product([i for i in range(n-1)], repeat=n)
    indx_kouho = np.array([np.argsort(dmat[i])[1:][::-1] for i in range(len(dmat))])
    ## "kouho" means "candidate"
    for v in p:
        d_vec = np.array([dmat[j, indx_kouho[j, v[j]]] for j in range(n)])
        connmat = genConnmat(dmat, d_vec)
        weight = convertConnmatToWeightMat(connmat)
        eig= np.linalg.eig(weight)
        a = np.sort(np.abs(eig[0]))[::-1]

        if (a[1] <= xi_target):
            adjmat = np.copy(connmat)
            np.fill_diagonal(adjmat, 0)

            if (DFS(adjmat) == True):
                rate = calcCapacity(ptx_mw, eta, d_vec, awgn_mw, bw)
                t = (1.0 / rate ).sum() #total communication time
                if t < t_min:
                    t_min = t
                    indx_opt = [indx_kouho[j, v[j]] for j in range(n)]

    d_min = np.array([dmat[j, indx_opt[j]] for j in range(len(indx_opt))])
    connmat_opt = genConnmat(dmat, d_min)

    return d_min, connmat_opt

#assuming R_1=R_2=...=R{ij}
def optimizeTopologyUndirectedMod(x, y, xi_target, eta, ptx_mw, awgn_mw, bw, approx=False):
    dmat = genDistanceMatrix(x, y)
    cmat = calcCapacity(ptx_mw, eta, dmat, awgn_mw, bw)
    n = len(x)

    t_min = np.inf

    r_kouho = np.sort(np.triu(cmat).flatten())
    r_kouho = r_kouho[r_kouho>0.0]

    if approx==True: #quantized solution (for Appendix)
        r_kouho = np.linspace(np.min(r_kouho), np.max(r_kouho), 2000)

    for r in r_kouho:
        r_vec = np.full(n, r)
        connmat = genConnmatRate(cmat, r_vec)

        adjmat = np.copy(connmat)
        np.fill_diagonal(adjmat, 0)

        graph = csr_matrix(adjmat)
        n_con = connected_components(graph, directed=False, return_labels=False)
        if (n_con==1): ## strongly connected case
            weight = convertConnmatToWeightMat(connmat)

            eig = scipy.linalg.eigvalsh(weight, check_finite=False, eigvals=(n-2, n-2))[0]
            if (eig <= xi_target):
                t = (1.0 / r_vec ).sum() #total communication time
                if t < t_min:
                    t_min = t
                    r_min = np.copy(r_vec)
        else:
            break

    connmat_opt = genConnmatRate(cmat, r_min)

    d_min = np.array([np.max(connmat_opt[i]*dmat[i]) for i in range(n)])

    return d_min, connmat_opt

def stochasticOptimizedTopology(x, y, xi_target, eta, pout, ptx_mw, awgn_mw, bw):
    dmat = genDistanceMatrix(x, y)
    n = len(x)

    '''brute force search'''
    indx_opt = []
    p = itertools.product([i for i in range(n-1)], repeat=n)
    indx_kouho = np.array([np.argsort(dmat[i])[1:][::-1] for i in range(len(dmat))])

    t_min = np.inf
    r_opt = np.zeros(n)
    for v in p:
        d_vec = np.array([dmat[j, indx_kouho[j, v[j]]] for j in range(n)])
        connmat = genConnmat(dmat, d_vec)
        weight = convertConnmatToWeightMat(connmat)
        eig= np.linalg.eig(weight)
        a = np.sort(np.abs(eig[0]))[::-1]

        adjmat = np.array([(dmat[j] <= d_vec[j]) for j in range(n)]) * 1
        for j in range(len(adjmat)):
            adjmat[j][j] = 0

        if (DFS(adjmat) == True) and (a[1] <= xi_target):
            rvec, status = rateAlloc(d_vec, dmat, eta, pout, ptx_mw, awgn_mw, bw)
            t = (1.0/rvec).sum()
            if t < t_min:
                t_min = t
                indx_opt = [indx_kouho[j, v[j]] for j in range(n)]
                r_opt = np.copy(rvec)

    d_min = np.array([dmat[j, indx_opt[j]] for j in range(len(indx_opt))])
    connmat_opt = genConnmat(dmat, d_min)

    # return d_min, connmat_opt, t_min, r_opt, status
    return d_min, connmat_opt

def rateAlloc(d_com, dmat, eta, pout, ptx_mw, awgn_mw, bw):
    d_tmp = []
    n = len(dmat)

    for i in range(len(dmat)):
        a = dmat[i, np.where(dmat[i]<=d_com[i])][0]
        d_tmp.append(a[a>0])

    b = np.zeros(n)
    for i in range(n):
        tmp = calcSNR(ptx_mw, eta, d_tmp[i], awgn_mw, bw)
        b[i] = (1.0/tmp).sum()

    r = cp.Variable(n, pos=True)
    obj = cp.Minimize(cp.sum(cp.inv_pos(r)))

    constraints = [-r <= 0, cp.log(1.0 - pout) - cp.sum(b) + cp.sum(cp.exp(r*cp.log(2.0) + cp.log(b))) <= 0]
    #note: all SNRs should be larger than 1.0

    prob = cp.Problem(obj, constraints)
    result = prob.solve(solver='SCS', verbose=False)

    return (r.value) * bw, prob.status

import numpy,import scipy,from scipy,import cvxpy


# -*- coding:utf-8 -*-
"""
Project   : numpy
File Name : 5_13_to_5_14.py
Keywords :
Abstract  : The Fibonacci numbers are based on a recurrence relation.
            It is difficult to express this relation
            directly with NumPy code.
            However, we can express this relation
            in a matrix form or use the golden ratio formula.
            This will introduce the matrix and rint functions.
            The matrix function creates matrices
            and the rint function rounds numbers to
            the closest integer, but the result is not integer.
Author    : Focus
Date      : 8/21/2021 1:27 PM
"""

import numpy as np

"""
1. Computing.
"""
F = np.matrix([[1, 1], [1, 0]])
print("F: ", F)

"""
2. The eighth Fibonacci number(ignore 0)
"""
print("The 8th Fibonacci: ", (F ** 7)[0, 0])

"""
3. Binet's formula.
The golden ratio formula,
better known as Binet's formula,
allows us to calculate Fibonacci numbers
with a rounding step at the end.
Calculate the first eight Fibonacci numbers:
"""

n = np.arange(1, 9)
sqrt5 = np.sqrt(5)
phi = (1 + sqrt5) / 2
fibonacci = np.rint((phi ** n - (-1 / phi) ** n) / sqrt5)
print("Fibonacci: ", fibonacci)


import numpy as np
import matplotlib.pyplot as plt

phi = -0.8
times = list(range(16))
y1 = [phi**k / (1 - phi**2) for k in times]
y2 = [np.cos(np.pi * k) for k in times]
y3 = [a * b for a, b in zip(y1, y2)]

num_rows, num_cols = 3, 1
fig, axes = plt.subplots(num_rows, num_cols, figsize=(10, 8))
plt.subplots_adjust(hspace=0.25)

# Autocovariance when phi = -0.8
ax = axes[0]
ax.plot(times, y1, 'bo-', alpha=0.6, label=r'$\gamma(k)$')
ax.legend(loc='upper right')
ax.set_xlim(0, 15)
ax.set_yticks((-2, 0, 2))
ax.hlines(0, 0, 15, linestyle='--', alpha=0.5)

# Cycles at frequence pi
ax = axes[1]
ax.plot(times, y2, 'bo-', alpha=0.6, label=r'$\cos(\pi k)$')
ax.legend(loc='upper right')
ax.set_xlim(0, 15)
ax.set_yticks((-1, 0, 1))
ax.hlines(0, 0, 15, linestyle='--', alpha=0.5)

# Product
ax = axes[2]
ax.stem(times, y3, label=r'$\gamma(k) \cos(\pi k)$')
ax.legend(loc='upper right')
ax.set_xlim((0, 15))
ax.set_ylim(-3, 3)
ax.set_yticks((-1, 0, 1, 2, 3))
ax.hlines(0, 0, 15, linestyle='--', alpha=0.5)

plt.show()


import math
import scipy.stats
import matplotlib.pyplot as plt

global god
god = 365

global step
step = 0.01

class Calculation():

    def d1(self, S, K, V, T):
        return (math.log(S / float(K)) + (V**2 / 2) * T) / (V * math.sqrt(T))

    def d2(self, S, K, V, T):
        return self.d1(S, K, V, T) - (V * math.sqrt(T))

    def theo(self, S, K, V, T, dT):
        if dT == 'C':
            return S * scipy.stats.norm.cdf(self.d1(S, K, V, T)) - K * scipy.stats.norm.cdf(self.d2(S, K, V, T))
        else:
            return K * scipy.stats.norm.cdf(-self.d2(S, K, V, T)) - S * scipy.stats.norm.cdf(-self.d1(S, K, V, T))

    def delta(self, S, K, V, T, dT):
        if dT == 'C':
            delta = scipy.stats.norm.cdf(self.d1(S, K, V, T))
        elif dT == 'P':
            delta = scipy.stats.norm.cdf(self.d1(S, K, V, T)) - 1
        else:
            delta = 1
        return delta

    def vega(self, S, K, V, T):
        vega = (S * math.sqrt(T) * scipy.stats.norm.pdf(self.d1(S, K, V, T))) / 100
        return vega

    def theta(self, S, K, V, T):
        theta = -((S * V * scipy.stats.norm.pdf(self.d1(S, K, V, T))) / (2 * math.sqrt(T))) / god
        return theta

    def gamma(self, S, K, V, T):
        gamma = scipy.stats.norm.pdf(self.d1(S, K, V, T))/(S * V * math.sqrt(T))
        return gamma

    def deltaFull(self, fPrice, params, exp):
        i = 0
        n = len(params)
        deltaFull = 0
        while i < n:
            param = params[i]
            if exp:
                param['exp'] = exp
            deltaSingle = self.delta(fPrice, param['strike'], param['vola'], param['exp'], param['dType']) * param['quant']
            deltaFull = deltaFull + deltaSingle
            i+=1
        return deltaFull

    def vegaFull(self, fPrice, params, exp):
        i = 0
        n = len(params)
        vegaFull = 0
        while i < n:
            param = params[i]
            if exp:
                param['exp'] = exp
            if param['dType'] != 'F':
                vegaSingle = self.vega(fPrice, param['strike'], param['vola'], param['exp']) * param['quant']
                vegaFull = vegaFull + vegaSingle
            i+=1
        return vegaFull

    def thetaFull(self, fPrice, params, exp):
        i = 0
        n = len(params)
        thetaFull = 0
        while i < n:
            param = params[i]
            if exp:
                param['exp'] = exp
            if param['dType'] != 'F':
                thetaSingle = self.theta(fPrice, param['strike'], param['vola'], param['exp']) * param['quant']
                thetaFull = thetaFull + thetaSingle
            i+=1
        return thetaFull

    def gammaFull(self, fPrice, params, exp):
        i = 0
        n = len(params)
        gammaFull = 0
        while i < n:
            param = params[i]
            if exp:
                param['exp'] = exp
            if param['dType'] != 'F':
                gammaSingle = self.gamma(fPrice, param['strike'], param['vola'], param['exp']) * param['quant']
                gammaFull = gammaFull + gammaSingle
            i+=1
        return gammaFull

    def p_l(self, fPrice, params, exp):
        i = 0
        n = len(params)
        plF = 0
        while i < n:
            param = params[i]
            if exp:
                param['exp'] = exp
            if param['dType'] == 'F':
                plS = (fPrice - param['price']) * param['quant']
            else:
                theo = round(self.theo(fPrice, param['strike'], param['vola'], param['exp'], param['dType']), 3)
                plS = (theo - param['price']) * param['quant']
            #print(param['dType'], plS)
            plF = plF + plS
            i+=1
        return plF

    def plotPL(self, bePriceS, bePriceF, params, exp):
        bePrice = []
        i = bePriceS
        while i <= bePriceF:
            bePrice.append(i)
            i+=step
        P_L = []
        for s in bePrice:
           P_L.append(self.p_l(s, params, exp))
        plt.plot(bePrice, P_L, label=int(exp*god))

    def plotDelta(self, bePriceS, bePriceF, params, exp):
        bePrice = []
        i = bePriceS
        while i <= bePriceF:
            bePrice.append(i)
            i+=step
        D = []
        for s in bePrice:
           D.append(self.deltaFull(s, params, exp))
        plt.plot(bePrice, D, label=int(exp*god))

    def plotTheta(self, bePriceS, bePriceF, params, exp):
        bePrice = []
        i = bePriceS
        while i <= bePriceF:
            bePrice.append(i)
            i+=step
        T = []
        for s in bePrice:
           T.append(self.thetaFull(s, params, exp))
        plt.plot(bePrice, T, label=int(exp*god))

    def plotVega(self, bePriceS, bePriceF, params, exp):
        bePrice = []
        i = bePriceS
        while i <= bePriceF:
            bePrice.append(i)
            i+=step
        V = []
        for s in bePrice:
           V.append(self.vegaFull(s, params, exp))
        plt.plot(bePrice, V, label=int(exp*god))

    def plotGamma(self, bePriceS, bePriceF, params, exp):
        bePrice = []
        i = bePriceS
        while i <= bePriceF:
            bePrice.append(i)
            i+=step
        V = []
        for s in bePrice:
           V.append(self.gammaFull(s, params, exp))
        plt.plot(bePrice, V, label=int(exp*god))

    def searchDelta(self, fPrice, deep, acc, paramD, params):
        """if paramD['dType'] == 'C':
            if paramD['quant'] < 0:
                direct = 'U'
            else:
                direct = 'D'
        else:
            if paramD['quant'] < 0:
                direct = 'D'
            else:
                direct = 'U'
        if direct == 'U':
            start = fPrice
            finish = fPrice + deep
        else:
            start = fPrice - deep
            finish = fPrice"""
        matrix = []
        start = fPrice - deep
        finish = fPrice + deep
        while start <= finish:
            deltaD = round((self.delta(start, paramD['strike'], paramD['vola'], paramD['exp'], paramD['dType'])) * paramD['quant'], 4)
            deltaF = round(self.deltaFull(start, params, 0), 3)
            #print(start, '|', deltaD, '|', deltaF)
            if deltaD == -deltaF:
                if paramD['dType'] != 'F':
                    theo = round(self.theo(start, paramD['strike'], paramD['vola'], paramD['exp'], paramD['dType']), 3)
                else:
                    theo = 0
                matrix.append([round(start, 2), paramD['dType'], paramD['strike'], paramD['quant'], deltaD, deltaF , theo])
                #break
            start = start + acc
        i = 0
        n = len(matrix)
        prices = []
        while i < n:
            if prices:
                if matrix[i-1][4] != matrix[i][4]:
                    prices.append(matrix[i])
            else:
                prices.append(matrix[i])
            i = i + 1
        for j in prices:
            print(j[0], '|', j[1], '|', j[2], '|', j[3], '|', j[4], '|', j[5], '|', j[6])

calc = Calculation()

deep = 2

acc = 0.0001

exp = 15 / god

fPrice = 55

volaShift = 0/100

exp2 = 12
exp3 = 9

params = []

dType = 'F'
quant = 4
price = 55.36
strike = 0
vola = 0
params.append({'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp})

dType = 'C'
quant = 10
price = 1.31
strike = 56
vola = 27.25/100 + volaShift
params.append({'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp})

dType = 'P'
quant = 8
price = 1.24
strike = 56
vola = 27.25/100 + volaShift
params.append({'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp})

dType = 'P'
quant = 5
price = 0.82
strike = 55
vola = 27.7/100 + volaShift
params.append({'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp})

bePriceS = fPrice - deep
bePriceF = fPrice + deep

calc.plotPL(bePriceS, bePriceF, params, exp)
calc.plotPL(bePriceS, bePriceF, params, exp2/god)
calc.plotPL(bePriceS, bePriceF, params, exp3/god)
plt.ylabel("P/L")
plt.xlabel("Price")
plt.title("Option")
plt.grid(True)
plt.legend()
plt.show()

calc.plotDelta(bePriceS, bePriceF, params, exp)
calc.plotDelta(bePriceS, bePriceF, params, exp2/god)
calc.plotDelta(bePriceS, bePriceF, params, exp3/god)
plt.xlabel("Price")
plt.title("Option")
plt.grid(True)
plt.legend()
plt.ylabel("Delta")
plt.show()

calc.plotTheta(bePriceS, bePriceF, params, exp)
calc.plotTheta(bePriceS, bePriceF, params, exp2/god)
calc.plotTheta(bePriceS, bePriceF, params, exp3/god)
plt.xlabel("Price")
plt.title("Option")
plt.grid(True)
plt.legend()
plt.ylabel("Theta")
plt.show()

calc.plotVega(bePriceS, bePriceF, params, exp)
calc.plotVega(bePriceS, bePriceF, params, exp2/god)
calc.plotVega(bePriceS, bePriceF, params, exp3/god)
plt.xlabel("Price")
plt.title("Option")
plt.grid(True)
plt.legend()
plt.ylabel("Vega")
plt.show()

calc.plotGamma(bePriceS, bePriceF, params, exp)
calc.plotGamma(bePriceS, bePriceF, params, exp2/god)
calc.plotGamma(bePriceS, bePriceF, params, exp3/god)
plt.xlabel("Price")
plt.title("Option")
plt.grid(True)
plt.legend()
plt.ylabel("Gamma")
plt.show()

print('D:\t', round(calc.deltaFull(fPrice, params, exp), 2))

print('V:\t', round(calc.vegaFull(fPrice, params, exp), 2))

print('T:\t', round(calc.thetaFull(fPrice, params, exp), 2))

print('G:\t', round(calc.gammaFull(fPrice, params, exp), 2))

print('P/L:\t', calc.p_l(fPrice, params, exp))

#Donee

dType = 'F'
strike = 0
price = 0
quant = -1
vola = 0
paramD_1 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

dType = 'F'
strike = 0
price = 0
quant = 1
vola = 0
paramD1 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

dType = 'F'
strike = 0
price = 0
quant = -2
vola = 0
paramD_2 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

dType = 'F'
strike = 0
price = 0
quant = 2
vola = 0
paramD2 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

dType = 'F'
strike = 0
price = 0
quant = -3
vola = 0
paramD_3 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

dType = 'F'
strike = 0
price = 0
quant = 3
vola = 0
paramD3 = {'dType': dType, 'price': price, 'quant': quant, 'strike': strike, 'vola': vola, 'exp': exp}

calc.searchDelta(fPrice, deep, acc, paramD1, params)
calc.searchDelta(fPrice, deep, acc, paramD_1, params)
calc.searchDelta(fPrice, deep, acc, paramD2, params)
calc.searchDelta(fPrice, deep, acc, paramD_2, params)
calc.searchDelta(fPrice, deep, acc, paramD3, params)
calc.searchDelta(fPrice, deep, acc, paramD_3, params)

S = 54
K = 53
V = 0.35
T = 3
dT = "P"

#print('One: ', round(calc.theo(S, K, V, T/god, dT), 2))


import numpy as np

def PCA(data):
    '''
    PCA	Principal Component Analysis

    Input:
      data      - Data numpy array. Each row vector of fea is a data point.
    Output:
      eigvector - Each column is an embedding function, for a new
                  data point (row vector) x,  y = x*eigvector
                  will be the embedding result of x.
      eigvalue  - The sorted eigvalue of PCA eigen-problem.
    '''

    # YOUR CODE HERE
    # Hint: you may need to normalize the data before applying PCA
    # begin answer
    N, P = data.shape
    mu = np.mean(data, axis=0)
    X = data - mu             # mu(X) = 0
    S = np.matmul(X.T, X)/N   # S.shape = (P, P)
    ex, ev = np.linalg.eig(S)
    tmp = zip(ex, range(len(ex)))
    stmp = sorted(tmp, key=lambda x:x[0], reverse=True)
    eigvalue = np.array([x[0] for x in stmp])
    eigvector = np.vstack([ev[:,x[1]] for x in stmp]).T
    return eigvector, eigvalue
    # end answer


# #### To caclulate a Gröbner Basis

x,y,z = QQ["x,y,z"].gens()
f1, f2 = y-2*x, 1+2*x*y
I = ideal(f1,f2)
B = I.groebner_basis()
print(B)

I2 = ideal(x - 2*y, 1+2*x*y)
B2 = I2.groebner_basis()
print(B2)

I3 = ideal(1+x*z, 2+y**2*x**2)
B3 = I3.groebner_basis()
print(B3)

[0,-1,0]
[0,0,3]
[0,0,0]

[-1,0,0]
[0,0,0]
[0,5,0]

# #### To use verbose Buchberger's

from sage.rings.polynomial.toy_buchberger import *

I3 = ideal(1+x, 1+y)
B3 = I3.groebner_basis()
print(B3)

set_verbose(1)
buchberger(I3)

f1 = 1 + x
f2 = 1+ y
f3 = 1+x**2*y+x
I = ideal(f1,f2,f3)
buchberger(I)


import numpy as np

def problem(x):

    transl = 1/ np.sqrt(2)
    part1 = (x[0] - transl)**2 + (x[1] - transl)**2
    part2 = (x[0] + transl)**2 + (x[1] + transl)**2
    f1 = 1 - np.exp(-part1)
    f2 = 1 - np.exp(-part2)
    return [f1, f2]

def bounds():

    return np.array([[-np.pi, -5.,-5.],[np.pi,5.,5.]])


"""File for non sklearn metrics that are to be used for reference for tests."""
from typing import Optional, Union

import numpy as np
from sklearn.metrics._regression import _check_reg_targets
from sklearn.utils import assert_all_finite, check_consistent_length, column_or_1d

def symmetric_mean_absolute_percentage_error(
    y_true: np.ndarray,
    y_pred: np.ndarray,
    sample_weight: Optional[np.ndarray] = None,
    multioutput: str = "uniform_average",
):
    r"""Symmetric mean absolute percentage error regression loss (SMAPE_):

    .. math:: \text{SMAPE} = \frac{2}{n}\sum_1^n\frac{max(|   y_i - \hat{y_i} |}{| y_i | + | \hat{y_i} |, \epsilon)}

    Where :math:`y` is a tensor of target values, and :math:`\hat{y}` is a tensor of predictions.

    Args:
        y_true: array-like of shape (n_samples,) or (n_samples, n_outputs)
            Ground truth (correct) target values.
        y_pred: array-like of shape (n_samples,) or (n_samples, n_outputs)
            Estimated target values.
        sample_weight: array-like of shape (n_samples,), default=None
            Sample weights.
        multioutput: {'raw_values', 'uniform_average'} or array-like
            Defines aggregating of multiple output values.
            Array-like value defines weights used to average errors.
            If input is list then the shape must be (n_outputs,).

                - 'raw_values': Returns a full set of errors in case of multioutput input.
                - 'uniform_average': Errors of all outputs are averaged with uniform weight.

    Returns:
        loss: float or ndarray of floats in the range [0, 1]
            If multioutput is 'raw_values', then symmetric mean absolute percentage error
            is returned for each output separately.
            If multioutput is 'uniform_average' or an ndarray of weights, then the
            weighted average of all output errors is returned.
            MAPE output is non-negative floating point. The best value is 0.0.
            But note the fact that bad predictions can lead to arbitarily large
            MAPE values, especially if some y_true values are very close to zero.
            Note that we return a large value instead of `inf` when y_true is zero.

    """
    _, y_true, y_pred, multioutput = _check_reg_targets(y_true, y_pred, multioutput)
    check_consistent_length(y_true, y_pred, sample_weight)
    epsilon = np.finfo(np.float64).eps
    smape = 2 * np.abs(y_pred - y_true) / np.maximum(np.abs(y_true) + np.abs(y_pred), epsilon)
    output_errors = np.average(smape, weights=sample_weight, axis=0)
    if isinstance(multioutput, str):
        if multioutput == "raw_values":
            return output_errors
        # pass None as weights to np.average: uniform mean
        multioutput = None

    return np.average(output_errors, weights=multioutput)

# sklearn reference function from
# https://github.com/samronsin/scikit-learn/blob/calibration-loss/sklearn/metrics/_classification.py.
# TODO: when the PR into sklearn is accepted, update this to use the official function.
def calibration_error(
    y_true: np.ndarray,
    y_prob: np.ndarray,
    sample_weight: Optional[np.ndarray] = None,
    norm: str = "l2",
    n_bins: int = 10,
    strategy: str = "uniform",
    pos_label: Optional[Union[int, str]] = None,
    reduce_bias: bool = True,
) -> float:
    """Compute calibration error of a binary classifier. Across all items in a set of N predictions, the
    calibration error measures the aggregated difference between (1) the average predicted probabilities assigned
    to the positive class, and (2) the frequencies of the positive class in the actual outcome. The calibration
    error is only appropriate for binary categorical outcomes. Which label is considered to be the positive label
    is controlled via the parameter pos_label, which defaults to 1.

    Args:
        y_true: array-like of shape (n_samples,)
            True targets of a binary classification task.
        y_prob: array-like of (n_samples,)
            Probabilities of the positive class.
        sample_weight: array-like of shape (n_samples,)
        norm: {'l1', 'l2', 'max'}
            Norm method. The l1-norm is the Expected Calibration Error (ECE),
            and the max-norm corresponds to Maximum Calibration Error (MCE).
        n_bins: int, default=10
        The number of bins to compute error on.
        strategy: {'uniform', 'quantile'}
            Strategy used to define the widths of the bins.
            uniform
                All bins have identical widths.
            quantile
                All bins have the same number of points.
        pos_label: int or str, default=None
            Label of the positive class. If None, the maximum label is used as positive class.
        reduce_bias: bool, default=True
            Add debiasing term as in Verified Uncertainty Calibration, A. Kumar.
            Only effective for the l2-norm.

    Returns:
        score: float with calibration error
    """
    y_true = column_or_1d(y_true)
    y_prob = column_or_1d(y_prob)
    assert_all_finite(y_true)
    assert_all_finite(y_prob)
    check_consistent_length(y_true, y_prob, sample_weight)
    if any(y_prob < 0) or any(y_prob > 1):
        raise ValueError("y_prob has values outside of [0, 1] range")

    labels = np.unique(y_true)
    if len(labels) > 2:
        raise ValueError(f"Only binary classification is supported. Provided labels {labels}.")

    if pos_label is None:
        pos_label = y_true.max()
    if pos_label not in labels:
        raise ValueError(f"pos_label={pos_label} is not a valid label: {labels}")
    y_true = np.array(y_true == pos_label, int)

    norm_options = ("l1", "l2", "max")
    if norm not in norm_options:
        raise ValueError(f"norm has to be one of {norm_options}, got: {norm}.")

    remapping = np.argsort(y_prob)
    y_true = y_true[remapping]
    y_prob = y_prob[remapping]
    if sample_weight is not None:
        sample_weight = sample_weight[remapping]
    else:
        sample_weight = np.ones(y_true.shape[0])

    n_bins = int(n_bins)
    if strategy == "quantile":
        quantiles = np.percentile(y_prob, np.arange(0, 1, 1.0 / n_bins) * 100)
    elif strategy == "uniform":
        quantiles = np.arange(0, 1, 1.0 / n_bins)
    else:
        raise ValueError(
            f"Invalid entry to 'strategy' input. \
                The strategy must be either quantile' or 'uniform'. Got {strategy} instead."
        )

    threshold_indices = np.searchsorted(y_prob, quantiles).tolist()
    threshold_indices.append(y_true.shape[0])
    avg_pred_true = np.zeros(n_bins)
    bin_centroid = np.zeros(n_bins)
    delta_count = np.zeros(n_bins)
    debias = np.zeros(n_bins)

    loss = 0.0
    count = float(sample_weight.sum())
    for i, i_start in enumerate(threshold_indices[:-1]):
        i_end = threshold_indices[i + 1]
        # ignore empty bins
        if i_end == i_start:
            continue
        delta_count[i] = float(sample_weight[i_start:i_end].sum())
        avg_pred_true[i] = np.dot(y_true[i_start:i_end], sample_weight[i_start:i_end]) / delta_count[i]
        bin_centroid[i] = np.dot(y_prob[i_start:i_end], sample_weight[i_start:i_end]) / delta_count[i]
        if norm == "l2" and reduce_bias:
            # NOTE: I think there's a mistake in the original implementation.
            # delta_debias = (
            #     avg_pred_true[i] * (avg_pred_true[i] - 1) * delta_count[i]
            # )
            # delta_debias /= (count * delta_count[i] - 1)
            delta_debias = avg_pred_true[i] * (avg_pred_true[i] - 1) * delta_count[i]
            delta_debias /= count * (delta_count[i] - 1)
            debias[i] = delta_debias

    if norm == "max":
        loss = np.max(np.abs(avg_pred_true - bin_centroid))
    elif norm == "l1":
        delta_loss = np.abs(avg_pred_true - bin_centroid) * delta_count
        loss = np.sum(delta_loss) / count
    elif norm == "l2":
        delta_loss = (avg_pred_true - bin_centroid) ** 2 * delta_count
        loss = np.sum(delta_loss) / count
        if reduce_bias:
            # convert nans to zero
            loss += np.sum(np.nan_to_num(debias))
        loss = np.sqrt(max(loss, 0.0))
    return loss


# -*- coding: utf-8 -*-
"""2. numpy linearRegression-with-CostFn.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/1V1eAdA5KWorgeq9P6vR8x2IME3lLEQ1q
"""

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

# from: https://ml-cheatsheet.readthedocs.io/en/latest/linear_regression.html#cost-function
#
# We need a cost fn and its derivative...

dataset = pandas.read_csv("data/Advertising.csv").as_matrix()

def cost_function(X, y, weight, bias):
    n = len(X)
    total_error = 0.0
    for i in range(n):
        total_error += (y[i] - (weight * X[i] + bias)) ** 2
    return total_error / n

def update_weights(X, y, weight, bias, alpha):
    weight_deriv = 0
    bias_deriv = 0
    n = len(X)

    for i in range(n):
        # Calculate partial derivatives
        # -2x(y - (mx + b))
        weight_deriv += -2 * X[i] * (y[i] - (weight * X[i] + bias))

        # -2(y - (mx + b))
        bias_deriv += -2 * (y[i] - (weight * X[i] + bias))

    # We subtract because the derivatives point in direction of steepest ascent
    weight -= (weight_deriv / n) * alpha
    bias -= (bias_deriv / n) * alpha

    return weight, bias

def train(X, y, weight, bias, alpha, iters):
    cost_history = []

    for i in range(iters):
        weight, bias = update_weights(X, y, weight, bias, alpha)

        # Calculate cost for auditing purposes
        cost = cost_function(X, y, weight, bias)
        # cost_history.append(cost)

        # Log Progress
        if i % 10 == 0:
            print("iter: " + str(i) + " weight: " + str(weight) + " bias: " + str(bias) + " cost: " + str(cost))

    return weight, bias  # , cost_history

# work out
y = dataset[:, 4].reshape(200, 1)
X = dataset[:, 1].reshape(200, 1)

m = 0
c = 0

alpha = 0.1
iters = 100

# normalise the data
y = y / np.linalg.norm(y, ord=np.inf, axis=0, keepdims=True)
X = X / np.linalg.norm(X, ord=np.inf, axis=0, keepdims=True)

weight, bias = train(X, y, m, c, alpha, iters)

# a picture is worth a thousand words...
_ = plt.plot(X, y, 'o', [0, 1], [bias, weight + bias], '-')
plt.show()


# Copyright (c) AIRBUS and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import numpy as np
import math

def f_sum(args):
	return 0.5 * (args[0] + args[1])

def f_aminus(args):
	return 0.5 * (abs(args[0] - args[1]))

def f_mult(args):
	return args[0] * args[1]

def f_exp(args):
	return (np.exp(args[0]) - 1.0) / (np.exp(1.0) - 1.0)

def f_abs(args):
	return abs(args[0])

def f_sqrt(args):
	return np.sqrt(abs(args[0]))

def f_sqrtxy(args):
	return np.sqrt(args[0] * args[0] + args[1] * args[1]) / np.sqrt(2.0)

def f_squared(args):
	return args[0] * args[0]

def f_pow(args):
	return pow(abs(args[0]), abs(args[1]))

def f_one(args):
	return 1.0

def f_zero(args):
	return 0.0

def f_inv(args):
	if args[0] != 0.0:
		return args[0] / abs(args[0])
	else:
		return 0.0

def f_gt(args):
	return float(args[0] > args[1])

def f_acos(args):
	return math.acos(args[0]) / np.pi

def f_asin(args):
	return 2.0 * math.asin(args[0])/ np.pi

def f_atan(args):
	return 4.0 * math.atan(args[0]) / np.pi

def f_min(args):
	return np.min(args)

def f_max(args):
	return np.max(args)

def f_round(args):
	return round(args[0])

def f_floor(args):
	return math.floor(args[0])

def f_ceil(args):
	return math.ceil(args[0])


import numpy as np
from scipy.signal import welch
from scipy.integrate import simps
from matplotlib import pyplot as plt
from scipy import signal
import numpy.fft as fft
from sklearn.preprocessing import StandardScaler

class features:
    def __init__(self, X, Y):
        self.X = X.copy()
        self.Y = Y.copy()

        self.features = []
        self.labels = Y.copy()

        self.num_channels = self.X.shape[1]
        self.num_trials = self.X.shape[0]
        self.size_trials = self.X.shape[2]

    def getDatos(self):
        return self.features, self.labels

    def calc_hjorth(self, x):
        first_deriv = np.diff(x)
        second_deriv= np.diff(x, 2)
        var_zero = np.mean(x**2)
        var_d1 = np.mean(first_deriv**2)
        var_d2 = np.mean(second_deriv**2)
        activity = var_zero
        mobility = np.sqrt(var_d1/var_zero)
        complexity = np.sqrt(var_d2/var_d1) / mobility

        return activity, mobility, complexity

    def _embed(self, x, order=3, delay=1):
        """Time-delay embedding.
        Parameters
        ----------
        x : 1d-array
            Time series, of shape (n_times)
        order : int
            Embedding dimension (order).
        delay : int
            Delay.
        Returns
        -------
        embedded : ndarray
            Embedded time-series, of shape (n_times - (order - 1) * delay, order)
        """
        N = len(x)
        if order * delay > N:
            raise ValueError("Error: order * delay should be lower than x.size")
        if delay < 1:
            raise ValueError("Delay has to be at least 1.")
        if order < 2:
            raise ValueError("Order has to be at least 2.")
        Y = np.zeros((order, N - (order - 1) * delay))
        for i in range(order):
            Y[i] = x[(i * delay):(i * delay + Y.shape[1])]
        return Y.T

    def perm_entropy(self, x, order=3, delay=1, normalize=False):
        '''Permutation Entropy.
        Parameters
        ----------
        x : list or np.array
            One-dimensional time series of shape (n_times)
        order : int
            Order of permutation entropy. Default is 3.
        delay : int
            Time delay (lag). Default is 1.
        normalize : bool
            If True, divide by log2(order!) to normalize the entropy between 0
            and 1. Otherwise, return the permutation entropy in bit.
        Returns
        -------
        pe : float
            Permutation Entropy.
        Notes
        -----
        The permutation entropy is a complexity measure for time-series first
        introduced by Bandt and Pompe in 2002.
        The permutation entropy of a signal :math:`x` is defined as:
        .. math:: H = -\\sum p(\\pi)\\log_2(\\pi)
        where the sum runs over all :math:`n!` permutations :math:`\\pi` of order
        :math:`n`. This is the information contained in comparing :math:`n`
        consecutive values of the time series. It is clear that
        :math:`0 ≤ H (n) ≤ \\log_2(n!)` where the lower bound is attained for an
        increasing or decreasing sequence of values, and the upper bound for a
        completely random system where all :math:`n!` possible permutations appear
        with the same probability.'''

        from math import factorial, log
        x = np.array(x)
        ran_order = range(order)
        hashmult = np.power(order, ran_order)
        # Embed x and sort the order of permutations
        sorted_idx = self._embed(x, order=order, delay=delay).argsort(kind='quicksort')
        # Associate unique integer to each permutations
        hashval = (np.multiply(sorted_idx, hashmult)).sum(1)
        # Return the counts
        _, c = np.unique(hashval, return_counts=True)
        # Use np.true_divide for Python 2 compatibility
        p = np.true_divide(c, c.sum())
        pe = -np.multiply(p, np.log2(p)).sum()
        if normalize:
            pe /= np.log2(factorial(order))
        return pe

    def permutation_entropy(self):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                pe = self.perm_entropy(segmento)
                feature.extend([pe])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def calc_higuchi(self, a, k_max = 5):
        L = []
        x = []
        N = a.size

        for k in range(1,k_max):
            Lk = 0
            for m in range(0,k):
                #we pregenerate all idxs
                idxs = np.arange(1,int(np.floor((N-m)/k)),dtype=np.int32)

                Lmk = np.sum(np.abs(a[m+idxs*k] - a[m+k*(idxs-1)]))
                Lmk = (Lmk*(N - 1)/(((N - m)/ k)* k)) / k
                Lk += Lmk

        L.append(np.log(Lk/(m+1)))
        x.append([np.log(1.0/ k), 1])

        (p, r1, r2, s)=np.linalg.lstsq(x, L)
        return p[0]

    def hjorth(self):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                activity, mobility, complexity = self.calc_hjorth(segmento)

                feature.extend([activity, mobility, complexity])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def statistics_features(self, mean_b, std_b, max_b, min_b, kurtosis_b, wave_b):
        from scipy.stats import kurtosis

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                f = []
                if mean_b == 1:
                    a = np.mean(segmento)
                    f.append(a)
                if std_b == 1:
                    a = np.std(segmento)
                    f.append(a)
                if max_b == 1:
                    a = max(segmento)
                    f.append(a)
                if min_b == 1:
                    a = min(segmento)
                    f.append(a)
                if kurtosis_b == 1:
                    a = kurtosis(segmento)
                    f.append(a)
                if wave_b == 1:
                    a = self.wave_length(segmento)
                    f.append(a)

                f = np.asarray(f)

                feature.extend(f)

            features.append(feature)

        self.features = np.asarray(features).copy()

    def butter_bandpass(self, lowcut, highcut, fm = 250, order=5):
        from scipy.signal import butter, welch, filtfilt, lfilter
        from scipy import signal

        nyq = 0.5 * fm
        low = lowcut / nyq
        high = highcut / nyq
        b, a = butter(order, [low, high], btype='band')

        return b, a

    def hjorth_freq(self, fm = 250):
        from scipy.signal import butter, welch, filtfilt, lfilter
        from scipy import signal

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]

                b1, a1 = self.butter_bandpass(1, 4, fm, 5)
                b2, a2 = self.butter_bandpass(4, 7, fm, 5)
                b3, a3 = self.butter_bandpass(8, 12, fm, 5)
                b4, a4 = self.butter_bandpass(13, 30, fm, 5)
                b5, a5 = self.butter_bandpass(31, 45, fm, 5)

                channel = np.asarray(np.concatenate((segmento, segmento)))
                tamano = channel.shape[0]

                y1 = lfilter(b1, a1, channel)
                y1 = y1[0:tamano]

                y2 = lfilter(b2, a2, channel)
                y2 = y2[0:tamano]

                y3 = lfilter(b3, a3, channel)
                y3 = y3[0:tamano]

                y4 = lfilter(b4, a4, channel)
                y4 = y4[0:tamano]

                y5 = lfilter(b5, a5, channel)
                y5 = y5[0:tamano]

                activity1, mobility1, complexity1 = self.calc_hjorth(y1)
                activity2, mobility2, complexity2 = self.calc_hjorth(y2)
                activity3, mobility3, complexity3 = self.calc_hjorth(y3)
                activity4, mobility4, complexity4 = self.calc_hjorth(y4)
                activity5, mobility5, complexity5 = self.calc_hjorth(y5)

                f = [activity1, mobility1, complexity1, activity2, mobility2, complexity2,
                                activity3, mobility3, complexity3, activity4, mobility4, complexity4,
                                activity5, mobility5, complexity5]
                f = (f-np.min(f))/(np.max(f)-np.min(f)) #Normalizamos las features
                feature.extend(f)

            features.append(feature)

        self.features = np.asarray(features).copy()

    def higuchi(self, k=5):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                activity = self.calc_higuchi(segmento, k)
                feature.extend([activity])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def CPT5(sef, x):
        den = len(x)*np.exp(np.std(x))
        return sum(np.exp(x))/den

    def SSC(self, x):
        x = np.array(x)
        x = np.append(x[-1], x)
        x = np.append(x,x[1])
        xn = x[1:len(x)-1]
        xn_i2 = x[2:len(x)]    # xn+1
        xn_i1 = x[0:len(x)-2]  # xn-1
        ans = np.heaviside((xn-xn_i1)*(xn-xn_i2),0)
        return sum(ans[1:])

    def wave_length(self, x):
        x = np.array(x)
        x = np.append(x[-1], x)
        x = np.append(x,x[1])
        xn = x[1:len(x)-1]
        xn_i2 = x[2:len(x)]    # xn+1
        return sum(abs(xn_i2-xn))

    def SRAV(self, x):
        SRA = sum(np.sqrt(abs(x)))
        return np.power(SRA/len(x),2)

    def mean_abs(self, x):
        return sum(abs(x))/len(x)

    def statics(self,):
        from scipy.stats import kurtosis

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                segmento = StandardScaler().fit_transform(segmento.reshape(-1,1)).reshape(1,-1)[0]
                p1 =  self.CPT5(segmento)
                p2 = self.SSC(segmento)
                p3 = self.wave_length(segmento)
                p4 = self.SRAV(segmento)
                p5 = self.mean_abs(segmento)

                feature.extend([p1,p2,p3,p4,p5])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def statics_hjorth(self,):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                p1 = np.mean(segmento)
                p2 = np.std(segmento)
                p3 = max(segmento)
                p4 = min(segmento)
                p5 = sum(segmento)
                activity, mobility, complexity = self.calc_hjorth(segmento)

                feature.extend([p1, p2, p3, p4, p5,  activity, mobility, complexity])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def calculate_entropy(self, list_values):
        counter_values = Counter(list_values).most_common()
        probabilities = [elem[1]/len(list_values) for elem in counter_values]
        entropy=scipy.stats.entropy(probabilities)
        return entropy

    def calculate_statistics(self, list_values):
        n5 = np.nanpercentile(list_values, 5)
        n25 = np.nanpercentile(list_values, 25)
        n75 = np.nanpercentile(list_values, 75)
        n95 = np.nanpercentile(list_values, 95)
        median = np.nanpercentile(list_values, 50)
        mean = np.nanmean(list_values)
        std = np.nanstd(list_values)
        var = np.nanvar(list_values)
        rms = np.nanmean(np.sqrt(list_values**2))
        # New features
        kur = kurtosis(list_values)
        MeanAbs = mean_abs(list_values)
        norm_ent = norm_entropy(list_values)
        skewness = skew(list_values)
        CPT_5 = CPT5(list_values)
        SSC_1 = SSC(list_values)
        WL = wave_length(list_values)
        SRAV_1 = SRAV(list_values)
        return [n5, n25, n75, n95, median, mean, std, var, rms, kur, MeanAbs, norm_ent, skewness, CPT_5, SSC_1, WL, SRAV_1]

    def calculate_crossings(self, list_values):
        zero_crossing_indices = np.nonzero(np.diff(np.array(list_values) > 0))[0]
        no_zero_crossings = len(zero_crossing_indices)
        mean_crossing_indices = np.nonzero(np.diff(np.array(list_values) > np.nanmean(list_values)))[0]
        no_mean_crossings = len(mean_crossing_indices)
        return [no_zero_crossings, no_mean_crossings]

    def get_features(self, list_values):
        '''entropy = self.calculate_entropy(list_values)
        crossings = self.calculate_crossings(list_values)'''
        statistics = self.calculate_statistics(list_values)
        return statistics
        #return [entropy] + crossings + statistics

    def wavelet(self, level):
        import pywt

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]

                data_std = StandardScaler().fit_transform(segmento.reshape(-1,1)).reshape(1,-1)[0]

                # WPD tree
                wptree = pywt.WaveletPacket(data=data_std, wavelet='db5', mode='symmetric', maxlevel=level)
                levels = wptree.get_level(level, order = "freq")

                #Feature extraction for each node
                for node in levels:
                    data_wp = node.data
                    '''activity, mobility, complexity = self.calc_hjorth(data_wp)
                    feature.extend([activity, mobility, complexity])'''
                    # Features group
                    feature.extend(self.get_features(data_wp))

            features.append(feature)

        self.features = np.asarray(features).copy()

    def bandpower(self, data, band, fm, window_sec=None, relative=False):
        """Compute the average power of the signal x in a specific frequency band.

        Parameters
        ----------
        data : 1d-array
            Input signal in the time-domain.
        sf : float
            Sampling frequency of the data.
        band : list
            Lower and upper frequencies of the band of interest.
        window_sec : float
            Length of each window in seconds.
            If None, window_sec = (1 / min(band)) * 2
        relative : boolean
            If True, return the relative power (= divided by the total power of the signal).
            If False (default), return the absolute power.

        Return
        ------
        bp : float
            Absolute or relative band power.
        """

        band = np.asarray(band)
        low, high = band

        # Define window length
        if window_sec is not None:
            nperseg = window_sec * fm
        else:
            nperseg = (2 / low) * fm

        # Compute the modified periodogram (Welch)
        freqs, psd = welch(data, fm, nperseg=nperseg)

        # Frequency resolution
        freq_res = freqs[1] - freqs[0]

        # Find closest indices of band in frequency vector
        idx_band = np.logical_and(freqs >= low, freqs <= high)

        # Integral approximation of the spectrum using Simpson's rule.
        bp = simps(psd[idx_band], dx=freq_res)

        if relative:
            bp /= simps(psd, dx=freq_res)

        return bp

    def BP(self, banda1, banda2, fm):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                b = self.bandpower(segmento,[banda1,banda2],fm)
                feature.extend([b])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def psd(self, fm):
        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                freqs, psd = welch(segmento, fm, nperseg=125)

                idx_band = np.logical_and(freqs >= 1, freqs <= 25)
                feature.extend(psd[idx_band])

            features.append(feature)

        self.features = np.asarray(features).copy()

    def bands_alpha_beta(self, fm):
        from scipy.fft import fft, ifft

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]

                fft_wave  = np.fft.fft(segmento)
                fft_fre = np.fft.fftfreq(n=segmento.size, d=1/fm)
                #Cojo los valores de 8-12 Hz, 13-24 Hz, 25-30 Hz

                idx_band1 = np.logical_and(fft_fre >= 8, fft_fre <= 12)
                idx_band2 = np.logical_and(fft_fre >= 13, fft_fre <= 24)
                idx_band3 = np.logical_and(fft_fre >= 25, fft_fre <= 30)

                alpha = sum(fft_wave.real[idx_band1])
                beta1 = sum(fft_wave.real[idx_band2])
                beta2 = sum(fft_wave.real[idx_band3])

                feature.extend([alpha, beta1, beta2])

                '''plt.subplot(211)
                plt.plot(fft_fre, fft_wave.real, label="Real part")
                plt.xlim(-50,50)
                plt.ylim(-600,600)
                plt.legend(loc=1)
                plt.title("FFT in Frequency Domain")

                plt.subplot(212)
                plt.plot(fft_fre, fft_wave.imag,label="Imaginary part")
                plt.legend(loc=1)
                plt.xlim(-50,50)
                plt.ylim(-600,600)
                plt.xlabel("frequency (Hz)")

                plt.show()'''

            features.append(feature)

        self.features = np.asarray(features).copy()

    def calculate_entropy(self,list_values):
        from collections import Counter
        import scipy

        counter_values = Counter(list_values).most_common()
        probabilities = [elem[1]/len(list_values) for elem in counter_values]
        entropy=scipy.stats.entropy(probabilities)
        return entropy

    def calculate_statistics(self,list_values):
        n5 = np.nanpercentile(list_values, 5)
        n25 = np.nanpercentile(list_values, 25)
        n75 = np.nanpercentile(list_values, 75)
        n95 = np.nanpercentile(list_values, 95)
        median = np.nanpercentile(list_values, 50)
        mean = np.nanmean(list_values)
        std = np.nanstd(list_values)
        var = np.nanvar(list_values)
        rms = np.nanmean(np.sqrt(list_values**2))
        return [n5, n25, n75, n95, median, mean, std, var, rms]

    def get_features(self,list_values):
        #entropy = self.calculate_entropy(list_values)
        statistics = self.calculate_statistics(list_values)
        return statistics #[entropy] + statistics

    def entropy_statics(self):
        from sklearn import preprocessing
        import pywt

        features = []
        for trial in self.X:
            feature = []
            for ch in range(trial.shape[0]):
                segmento = trial[ch,:]
                #[cfs, _] = pywt.cwt(segmento, scales, waveletname, 1)
                #power = (abs(cfs)) ** 2

                feature += self.get_features(segmento)
                #print(np.asarray(feature).shape)

                '''power = (abs(cfs)) ** 2

                period = 1. / frequencies
                levels = [0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8]
                f, ax = plt.subplots(figsize=(15, 10))
                ax.contourf(time, np.log2(period), np.log2(power), np.log2(levels),
                            extend='both')

                ax.set_title('%s Wavelet Power Spectrum (%s)' % ('Nino1+2', waveletname))
                ax.set_ylabel('Period (seconds)')
                Yticks = 2 ** np.arange(np.ceil(np.log2(period.min())),
                                        np.ceil(np.log2(period.max())))
                ax.set_yticks(np.log2(Yticks))
                ax.set_yticklabels(Yticks)
                ax.invert_yaxis()
                ylim = ax.get_ylim()
                ax.set_ylim(ylim[0], -1)

                plt.show()'''

            features.append(feature)

        self.features = np.asarray(features).copy()

    def create_cwt_images(self, n_scales, rescale_size, wavelet_name = "morl"):
        import pywt

        n_samples = self.X.shape[0]
        n_signals = self.X.shape[1]

        X_cwt = np.ndarray(shape=(n_samples, rescale_size, rescale_size, n_signals), dtype = 'float32')

        scales = np.arange(1, n_scales + 1)
        for sample in range(n_samples):
            for ch in range(self.X[sample].shape[0]):
                segmento = self.X[sample,ch,:]
                coeffs, _ = pywt.cwt(segmento, scales, wavelet_name)
                rescale_coeff = np.resize(coeffs,(rescale_size, rescale_size))
                X_cwt[sample,:,:,ch] = rescale_coeff

        self.features = X_cwt.copy()

    def create_time_images(self, fm):
        import math
        import pywt

        n_samples = self.X.shape[0]
        n_signals = self.X.shape[1]

        rescale_size = int(math.sqrt(fm))
        X_cwt = np.ndarray(shape=(n_samples, rescale_size, rescale_size, n_signals), dtype = 'float32')

        for sample in range(n_samples):
            for ch in range(self.X[sample].shape[0]):
                segmento = self.X[sample,ch,:]
                segmento = np.resize(segmento,(rescale_size, rescale_size))
                X_cwt[sample,:,:,ch] = segmento

        self.features = X_cwt.copy()

    def create_STFT(self):
        import math
        import pywt

        n = 16

        features = []
        for sample in range(len(self.X)):
            features_segment = []
            for ch in range(self.X[sample].shape[0]):
                segmento = self.X[sample,ch,:]
                s = self.stft(segmento, n, Nfft=n*2)
                t, f = self.stftbins(segmento, n, Nfft=n*2, d=1 / 250)
                assert (len(t) == s.shape[0])
                assert (len(f) == s.shape[1])

                '''plt.imshow(np.abs(s), aspect="auto", extent=[f[0], f[-1], t[-1], t[0]])
                plt.xlabel('frequency (Hertz)')
                plt.ylabel('time (seconds (start of chunk))')
                plt.title('STFT with chirp example')
                plt.grid()
                plt.show()'''

                s = np.abs(s)
                s = np.asarray(s)
                feature = np.reshape(s,(s.shape[0]*s.shape[1]))
                features_segment.extend(feature)

            features.append(features_segment)

        self.features = np.asarray(features)
            #self.features[sample,:,:,ch] = f

    def stft(self, x, Nwin, Nfft=None):
        """
        Short-time Fourier transform: convert a 1D vector to a 2D array
        The short-time Fourier transform (STFT) breaks a long vector into disjoint
        chunks (no overlap) and runs an FFT (Fast Fourier Transform) on each chunk.
        The resulting 2D array can
        Parameters
        ----------
        x : array_like
            Input signal (expected to be real)
        Nwin : int
            Length of each window (chunk of the signal). Should be ≪ `len(x)`.
        Nfft : int, optional
            Zero-pad each chunk to this length before FFT. Should be ≥ `Nwin`,
            (usually with small prime factors, for fastest FFT). Default: `Nwin`.
        Returns
        -------
        out : complex ndarray
            `len(x) // Nwin` by `Nfft` complex array representing the STFT of `x`.

        See also
        --------
        istft : inverse function (convert a STFT array back to a data vector)
        stftbins : time and frequency bins corresponding to `out`
        """
        Nfft = Nfft or Nwin
        Nwindows = x.size // Nwin
        # reshape into array `Nwin` wide, and as tall as possible. This is
        # optimized for C-order (row-major) layouts.
        arr = np.reshape(x[:Nwindows * Nwin], (-1, Nwin))
        stft = fft.rfft(arr, Nfft)
        return stft

    def stftbins(self, x, Nwin, Nfft=None, d=1.0):
        """
        Time and frequency bins corresponding to short-time Fourier transform.
        Call this with the same arguments as `stft`, plus one extra argument: `d`
        sample spacing, to get the time and frequency axes that the output of
        `stft` correspond to.
        Parameters
        ----------
        x : array_like
            same as `stft`
        Nwin : int
            same as `stft`
        Nfft : int, optional
            same as `stft`
        d : float, optional
            Sample spacing of `x` (or 1 / sample frequency), units of seconds.
            Default: 1.0.
        Returns
        -------
        t : ndarray
            Array of length `len(x) // Nwin`, in units of seconds, corresponding to
            the first dimension (height) of the output of `stft`.
        f : ndarray
            Array of length `Nfft`, in units of Hertz, corresponding to the second
            dimension (width) of the output of `stft`.
        """
        Nfft = Nfft or Nwin
        Nwindows = x.size // Nwin
        t = np.arange(Nwindows) * (Nwin * d)
        f = fft.rfftfreq(Nfft, d)
        return t, f


import numpy as np
import newton_raphson as nr

# Creates a gravitational force function
def gravitational_force(k, z0):
    return lambda z: -k * (z - z0) / (((z-z0)**2).sum()**(3/2))

# Creates the jacobian matix correspounding to a gravitational force
def gravitional_jacob(k, z0):
    def res(z):
        v = ((z[0] - z0[0])**2 + (z[1] - z[1])**2 )**(3/2)
        x = z[0]
        y = z[1]
        x0 = z0[0]
        y0 = z0[1]
        resx_x = -k * (v - (x - x0) *(3/2) * ((x-x0)**2 + (y - y0)**2)**(1/2) * 2 * (x - x0))/ (v ** 2)
        resx_y = -k * ( - (x - x0) *(3/2) * ((x-x0)**2 + (y - y0)**2)**(1/2) * 2 * (y - y0 ))/ (v ** 2)
        resy_x = -k * ( - (y - y0) *(3/2) * ((x-x0)**2 + (y - y0)**2)**(1/2) * 2 * (x - x0 ))/ (v ** 2)
        resy_y = -k * (v - (y - y0) *(3/2) * ((x-x0)**2 + (y - y0)**2)**(1/2) * 2 * (y - y0))/ (v ** 2)
        return np.matrix([[resx_x,resx_y],[resy_x, resy_y]])
    return res;

# Creates a centrifigual force function
def centrifigual_force(k, z0):
    return lambda z : k * (z - z0)

# Creates the jacobian matix correspounding to a centrifigual force
def centrifigual_jacob(k, z0):
    return lambda z : np.matrix([[k,0],[0,k]])

# Creates an elastic force function
def elastic_force(k, z0):
    return lambda z: - k * (z - z0)

# Creates the jacobian matix correspounding to an elastic force
def elastic_jacob(k, z0):
    return lambda z : np.matrix([[-k,0],[0,-k]])

# Apply two gravitational forces to z
def forces1(z):
    return gravitational_force(1,np.array([0,0]))(z) + gravitational_force(0.01, np.array([1,0]))(z)

# Apply two gravitational forces and a centrifigual force to z
def forces2(z):
    return forces1(z) + centrifigual_force(1, np.array([0.01/1.01, 0]))(z)

# Produces the jacobian matix of two gravitational forces aplied to z
def jacobians1(z):
    return gravitional_jacob(1,np.array([0,0]))(z) + gravitional_jacob(0.01, np.array([1,0]))(z)

# Produces the jacobian matix of two gravitational forces and a centrifigual force aplied to z
def jacobians2(z):
    return jacobians1(z) + centrifigual_jacob(1, np.array([0.01/1.01, 0]))(z)

# Finds an equilibrium point for forces1 and forces2
def solve_two_grav():
    print("First case : Two gravitational forces with respective coefficients 1 (resp. 0.01) originating from [0,0] (resp. [1,0])")
    Eq1 = nr.Newton_Raphson(forces1, jacobians1, np.array([1.5,0]), 1000, 1e-10);
    print("Equilibrium points =", Eq1)
    print("f(Eq) =", forces1(Eq1))

    print("Second case : previous forces and a centrifugal force centered on the barycenter of the two masses, with coefficient 1")
    Eq2 = nr.Newton_Raphson(forces2, jacobians2, np.array([1.5,0]), 1000, 1e-10);
    print("Equilibrium points =", Eq2)
    print("f(Eq) =", forces2(Eq2))

# Tests forces using an example
def test_forces():
    U = np.array([1.5,0])
    print("U =", U)
    print("f(U) =", forces2(U))
    print("df(U) =\n", jacobians2(U))

# Finds an equilibrium point for forces2 depending on U0
def find_lagragian_points(U0):
    # U0 = (0.5,  0)  -> U = ( 0.85927766, 0 ) = L1
    # U0 = (1.5,  0)  -> U = ( 1.15775715, 0 ) = L2
    # U0 = (-1.5, 0)  -> U = (-0.99754112, 0 ) = L3

    U = nr.Newton_Raphson(forces2, jacobians2, U0, 1000, 1e-10)
    print("U =", U)
    print("f(U) =", forces2(U))
    print("df(U) =\n", jacobians2(U))

if __name__ == "__main__":
    test_forces()
    solve_two_grav()


import numpy as np

class mdp:
    def __init__(self, grid_map, state_trans_prob=1, discount_factor=0.95):
        # self.
        self.state_space = grid_map.state_space    # width and height
        self.action_space = grid_map.action_space  # up, down, left, right

        self.state_trans_prob = state_trans_prob
        self.gamma = discount_factor  # the discount_factor to calculate the action value

        self.policy_matrix = np.ones((grid_map.state_space[0], grid_map.state_space[1], len(self.action_space))) / len(self.action_space)  # policy matrix, include the probability of each action, the init probabilities are equal

        self.grid_map = grid_map

    def policy_iteration(self, policy_value):
        ## please complete this function by 1. calculating the action value 2. finding the action index with maximum value

        # (1) The policy iteration under current policy value
        # (2) In each state index, the action value will be calculated depend on current policy value and action reward (the formula in the lecture)
        # (3) In each state index, find the action with maximum value calculated by previous line (2)., check whether it is equal to the old action
        # (4) If all the actions are stable, the policy iteration is done.

        interate_done = True

        for i in range(self.state_space[0]):
            for j in range (self.state_space[1]):

                old_action_index = np.argmax(self.policy_matrix[i, j])
                action_value_list = []
                state_index = (i, j)

                for action_index, action in enumerate(self.action_space):
                    next_state, reward, state_prob, done = self.grid_map.step(state_index, action_index)

                    # calculate the action value
                    # action_value = ?
                    action_value_list.append(action_value)

                # find the action index with maximum action value
                # action_index = ?

                self.policy_matrix[i, j, :] = 0
                self.policy_matrix[i, j, action_index] = 1

                # iteration done

                if action_index != old_action_index:
                    interate_done = False

        return interate_done

    def value_iteration(self, threshold=0.01):

        ## please complete this function by 1. calculating the policy value 2. completing the judgement condition indicator delta
        # (1) The value iteration under current policy matrix
        # (2) in each state index, the policy value will be calculated (cumulative reward and value)
        # (3) This iteration will be done if all the values are stable (Compare the maximum difference value with the threshold to judge)

        policy_value = np.zeros((self.state_space[0], self.state_space[1]))

        while True:
            delta = 0

            for i in range (self.state_space[0]):
                for j in range (self.state_space[1]):

                    old_value = policy_value[i, j]

                    state_index = (i, j)
                    temp_action_value = 0

                    for action_index, action_prob in enumerate(self.policy_matrix[i, j]):
                        next_state_index, reward, state_prob, done = self.grid_map.step( state_index, action_index)

                        ##  calculate the temp_action_value (policy value)
                        # temp_action_value ?

                    policy_value[i, j] = temp_action_value

                    ## complete the judgement condition indicator  (Compare the maximum difference value with the threshold to judge)
                    # delta = ?

            if delta < threshold:
                break

        return policy_value