import scipy.linalg
import scipy.io
import matplotlib.pyplot as plt
import numpy as np

from lib import NonLinearFunction1, NonLinearFunction2 #non linear functions, from assignment and custom one
from lib import VectorNorm, pinv, SolveNormalEquation, GenerateRandomPoints #utility functions
from lib import NeuralNetworkPass, CalculateNeuralNetworkGradient, CalculateLoss, CalculateLossJacobian #neural network and loss function utility functions
from lib import EvaluatePerformance #performance evaluation utility for neural network
from lib import TrainNetwork #core training function of neural network

#main script

NonLinearFunction = NonLinearFunction1 #change non linear function here

trainIterations = 300 #change this to change runtime; but this will affect performance

#varying loss reg. lambda of network, then observing performance
#plot loss during training

testingPoints = GenerateRandomPoints(100, 1) #generate 100 random points, where random vector elements stay within -1 and 1
trainingPoints = GenerateRandomPoints(200, 1)

lossRegLambda = 0.00001
initialTrustLambda = 1
initalWeights = np.random.uniform(-10, 10, 13) #randomly initialize 16 weights uniformly in between -10 and 10
'Plot Grad Norm vs iteratons'

trainedWeights, loss, gradNorm = TrainNetwork(trainingPoints, trainIterations, initalWeights, lossRegLambda, initialTrustLambda, NonLinearFunction) #train network with reg lambda
performance = EvaluatePerformance(trainingPoints, trainedWeights, NonLinearFunction) #evaluate performance

plt.plot(np.arange(trainIterations), gradNorm, label = "$\lambda$: " + str(lossRegLambda)) #plot loss vs iterations
plt.title("The norm of gradients versus iteration",fontsize=20)
plt.xlabel('Iteration',fontsize=18)
plt.ylabel('Grad Norm',fontsize=18)
plt.yticks(fontsize=15)
plt.xticks(fontsize=15)
plt.grid()
plt.legend(prop={'size':15})
plt.show()

'Plot Grad Norm vs iteratons'
lossRegLambda = 0.00001
trainedWeights, loss, gradNorm = TrainNetwork(trainingPoints, trainIterations, initalWeights, lossRegLambda, initialTrustLambda, NonLinearFunction) #train network with reg lambda
performance = EvaluatePerformance(trainingPoints, trainedWeights, NonLinearFunction) #evaluate performance
plt.plot(np.arange(trainIterations), loss, label = "$\lambda$: " + str(lossRegLambda)) #plot loss vs iterations
plt.title("Training loss versus iteration",fontsize=20)
plt.xlabel('Iteration',fontsize=18)
plt.ylabel('The value of f',fontsize=18)
plt.yticks(fontsize=15)
plt.xticks(fontsize=15)
plt.grid()
plt.legend(prop={'size':15})
plt.show()

'Different Lamda & RMSE'
lossRegLambdas = [0.00001, 0.0001, 0.01, 0.1, 1, 10]
for lossRegLambda in lossRegLambdas:
    trainedWeights, loss, gradNorm = TrainNetwork(trainingPoints, trainIterations, initalWeights, lossRegLambda, initialTrustLambda, NonLinearFunction) #train network with reg lambda
    performance = EvaluatePerformance(trainingPoints, trainedWeights, NonLinearFunction) #evaluate performance
    plt.plot(np.arange(trainIterations), loss, label = "$\lambda$: " + str(lossRegLambda) + ", Final RMSE: " + str(('%.4f' %performance))) #plot loss vs iterations

plt.title("Training loss vs iteration,\n varying loss regularization lambda", fontsize=20)
plt.xlabel('Iteration',fontsize=18)
plt.ylabel('The value of f',fontsize=18)
plt.yticks(fontsize=15)
plt.xticks(fontsize=15)
plt.ylim(0,100)
plt.grid()
plt.legend(prop={'size':10})
plt.show()

#vary bounds of weights that are randomly initialized, then test performance
#plot loss during training
#everything else constant

#wider random range should prevent the loss from converging to a local minima fast; the weights are taking different "routes" to a minima

testingPoints = GenerateRandomPoints(100, 1)
trainingPoints = GenerateRandomPoints(500, 1)

bounds = [0.1, 10, 100]
lossRegLambda = 1e-5
initialTrustLambda = 1

for bound in bounds:
#    for i in range(3): #train three times with the same bound; idea is to see if the loss converges to the same value and gets "trapped" in a minima
    initalWeights = np.random.uniform(-bound, bound, 13) #randomly initialize 16 weights uniformly in between the specified bound
    trainedWeights, loss, gradNorm = TrainNetwork(trainingPoints, trainIterations, initalWeights, lossRegLambda, initialTrustLambda, NonLinearFunction)
    performance = EvaluatePerformance(trainingPoints, trainedWeights, NonLinearFunction)
    plt.plot(np.arange(trainIterations), loss, label = "initial weights random bound: " + str(bound))
plt.title("Training loss vs iteration, \n varying random bounds of initialized weights ",fontsize=20)
plt.xlabel('Iteration',fontsize=18)
plt.ylabel('The value of f',fontsize=18)
plt.yticks(fontsize=15)
plt.xticks(fontsize=15)
plt.ylim(0,300)
plt.grid()
plt.legend(prop={'size':10})
plt.show()

#vary number of test points for performance
#everything else constant

numberOfTestPoints = [50, 150, 300, 500, 1000]
performanceResults = []

trainingPoints = GenerateRandomPoints(500, 1)

initalWeights = np.random.uniform(-10, 10, 16) #randomly initialize 16 weights uniformly in between -10 and 10
lossRegLambda = 0.1
initialTrustLambda = 1

trainedWeights, loss = TrainNetwork(trainingPoints, trainIterations, initalWeights, lossRegLambda, initialTrustLambda, NonLinearFunction)

for testCount in numberOfTestPoints:
    testingPoints = GenerateRandomPoints(testCount, 1)
    performance = EvaluatePerformance(testingPoints, trainedWeights, NonLinearFunction)
    performanceResults.append(performance)

plt.plot(numberOfTestPoints, performanceResults)
plt.title("Test performance vs number of test points, loss reg. lambda of " + str(lossRegLambda))
plt.xlabel('Number of test points')
plt.ylabel('Test performance')
plt.show()


import numpy as np

def states():
    return np.arange(5)

def actions():
    return {'left', 'stay', 'right'}

def rewards(S, A):
    # a \in A = {left, stay, right}
    R = np.zeros((S.shape[0], len(A), S.shape[0]))

    # Reward staying in the rightmost state before halting
    R[-1, 1, -1] = 1

    return R

# def transitions():
#     T = np.zeros((R.shape[0], 3, R.shape[0]))  # a \in A = {left, stay, right}

#     # "If the agent chooses to stay at the location,
#     #  such an action is successful with probability 1/2"
#     T[:, 1, :] = 0.5 * np.identity(5)

#     # "If the agent is at the leftmost or rightmost grid
#     # location it ends up at its neighboring grid
#     # location with probability 1/2"
#     T[0, 2, 1] = 0.5
#     T[-1, 0, -2] = 0.5

#     # "If the agent is at any of the inner grid locations
#     # it has a probability 1/4 each of ending up at either
#     # of the neighboring locations"
#     T[1, 0, 0] = T[1, 2, 2] = 1/4
#     T[2, 0, 1] = T[2, 2, 3] = 1/4
#     T[3, 0, 2] = T[3, 2, 4] = 1/4

#     # If the agent chooses to move (either left or right) at
#     # any of the inner grid locations, such an action is successful
#     # with probability  1/3  and with probability  2/3  it fails to move
#     T[1, 1, 1] = T[2, 1, 2] = T[3, 1, 3] = T[1, 1, 1] + T[1, 0, 0] * 2/3

#     T[1, 0, 0], T[1, 2, 2] = T[1, 0, 0] * 1/3, T[1, 2, 2] * 1/3
#     T[2, 0, 1], T[2, 2, 3] = T[2, 0, 1] * 1/3, T[2, 2, 3] * 1/3
#     T[3, 0, 2], T[3, 2, 4] = T[3, 0, 2] * 1/3, T[3, 2, 4] * 1/3

#     return T

def transitions(R, A):
    # a \in A = {left, stay, right}
    # T(s, a, s')
    T = np.zeros((R.shape[0], len(A), R.shape[0]))

    # Right Move with 100% probability
    for i in range(R.shape[0] - 1):
        T[i, -1, i+1] = 1

    # Stay in place in the last move
    T[-1, 1, -1] = 1

    return T

def value_iteration_update(T, R, gamma, V):
    return np.amax(np.sum(T * (R + gamma * V), axis=2), axis=1)

if __name__ == "__main__":

    # Parameter Initialization
    gamma = 0.5
    S = states()
    A = actions()
    R = rewards(S, A)
    T = transitions(R, A)

    # Run the Value Iteration
    V = np.zeros(5)

    for k in range(5):
        V[:5 - k] = value_iteration_update(T, R, gamma, V)[:5 - k]

    print(f"V: {V}")


# 1-a
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller

def test_stationarity(timeseries):
    rolmean = pd.Series(timeseries).rolling(window=1440).mean()
    rolstd = pd.Series(timeseries).rolling(window=1440).std()
    orig = plt.plot(timeseries, color='blue',label='Original')
    mean = plt.plot(rolmean, color='red', label='Rolling Mean')
    std = plt.plot(rolstd, color='black', label = 'Rolling Std')
    plt.legend(loc='best')
    plt.title('Rolling Mean & Standard Deviation')
    plt.show(block=False)
    print("Results of Dickey-Fuller Test:")
    array = np.asarray(timeseries, dtype='float')
    np.nan_to_num(array,copy=False)
    dftest = adfuller(array, autolag='AIC')
    dfoutput = pd.Series(dftest[0:4], index=['Test Statistic','p-value','#Lags Used','Number of Observations Used'])
    for key,value in dftest[4].items():
        dfoutput['Critical Value (%s)'%key] = value
    print(dfoutput)

# Load data
df = pd.read_csv("HW03_USD_TRY_Trading.txt", sep='\t')

seriesname = 'Close'
series = df[seriesname]

test_stationarity(series)  #For entire period stationarity

size = len(series)
train = series[0:10079]  # Until May 6, there are 6 days + 23 Hours + 59 minutes = 10079 min
test = series [10079:]
testarray = np.asarray(test, dtype=float)
test_stationarity(testarray)           # For May 6 stationarity

#1-b
from math import sqrt
import statsmodels.api as sm
from statsmodels.tsa.api import SimpleExpSmoothing, Holt
from statsmodels.tsa.holtwinters import ExponentialSmoothing
from sklearn.metrics import mean_squared_error

def rmse(predictions, targets):
    return np.sqrt(((predictions - targets) ** 2))

name = 'Close'
freq = 956 # May 6, 16 saat - 4 minutes
series = df[name]
numbers = np.asarray(series,dtype='int')
result = sm.tsa.seasonal_decompose(numbers,freq=956,model='Additive')
result.plot()
plt.show() # Uncomment to reshow plot, saved as Figure 1.

#May 6 15.57 estimation

print("Estimations for 15.57")
# Function for Naive
def estimate_naive(df, seriesname):
     numbers = np.asarray ( df[seriesname] )
     return float( numbers[-2] )
naive = round(estimate_naive (df.iloc[10082:11038], seriesname),4)
print ("Naive for 15.57:", naive)
RMSE = rmse(naive, 6.01850)
print("RMSE Naive for 15.57: ", RMSE)

# Function for Simple Average
def estimate_simple_average(df,seriesname):
    avg = df[seriesname].mean()
    return avg

simpleaverage = round(estimate_simple_average(df.iloc[10082:11038], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01850)
print("RMSE simpleaverage for 15.57: ", RMSE)

# Function for Moving Average
def estimate_moving_average(df,seriesname,windowsize):
    avg = df[seriesname].rolling(windowsize).mean().iloc[-1]
    return avg

periods = 60
movingaverage = round(estimate_moving_average(df.iloc[10082:11038],seriesname, periods),4)

print("Moving average for 15.57:", movingaverage)
RMSE = rmse(movingaverage, 6.01850)
print("RMSE movingaverage for 15.57: ", RMSE)

# Function for Simple Exponential Smoothing
def estimate_ses(df, seriesname, alpha=0.2):
    numbers = np.asarray(df[seriesname])
    estimate = SimpleExpSmoothing(numbers).fit(smoothing_level=alpha,optimized=False).forecast(1)
    return estimate

alpha = 0.2
ses = round ( estimate_ses(df.iloc[10082:11038], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01850)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt
def estimate_holt(df, seriesname, alpha=0.2, slope=0.1):
    numbers = np.asarray(df[seriesname])
    model = Holt(numbers)
    fit = model.fit(alpha,slope)
    estimate = fit.forecast(1)[-1]
    return estimate

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df.iloc[10082:11038],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01850)
print("RMSE holt for 15.57: ", RMSE)
# Trend and seasonality estimation with Holt-Winters ___ I could not do it.

# Estimation for 15.58
print("Estimations for 15.58")
freq = 957 # May 6, 16 saat - 3 minutes

# for Naive

naive = round(estimate_naive (df.iloc[10083:11039], seriesname),4)
print ("Naive for 15.58:", naive)
RMSE = rmse(naive, 6.01933)
print("RMSE Naive for 15.58: ", RMSE)

#  for Simple Average

simpleaverage = round(estimate_simple_average(df.iloc[10083:11039], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01933)
print("RMSE simpleaverage for 15.58: ", RMSE)

# for Moving Average

minutes = 60
movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.58:", movingaverage)
RMSE = rmse(movingaverage, 6.01933)
print("RMSE movingaverage for 15.58: ", RMSE)

# for Simple Exponential Smoothing

alpha = 0.2
ses = round ( estimate_ses(df.iloc[10083:11039], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01933)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df.iloc[10083:11039],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01933)
print("RMSE holt for 15.57: ", RMSE)

# Smallest RMSE is moving average, so I estimated 15.59 and 16.00 with this metmod.
# Since Simple Moving Average (SMA) takes the average over some set number of time periods, It will be same for any future period. Thus, in the long run, it may not be a good indicator.
minutes = 60
movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.59:", movingaverage)

minutes = 60
movingaverave = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 16.00:", movingaverage)

minutes = 60
movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for May 7, 16.00:", movingaverage)

#1-c
df2 = df.dropna()
df2.info()
print("estimations without zeros and missing values")

seriesname = 'Close'
series = df2[seriesname]
test_stationarity(series)
size = len(series)
train = series[0:10079]  # Until May 6, there are 6 days + 23 Hours + 59 minutes = 10079 min
test = series [10079:]
testarray = np.asarray(test, dtype=float)
test_stationarity(testarray)           # For May 6 stationarity

print("Estimations for 15.57")
# Function for Naive
naive = round(estimate_naive (df2.iloc[10082:11038], seriesname),4)
print ("Naive for 15.57:", naive)
RMSE = rmse(naive, 6.01850)
print("RMSE Naive for 15.57: ", RMSE)

# Function for Simple Average
simpleaverage = round(estimate_simple_average(df2.iloc[10082:11038], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01850)
print("RMSE simpleaverage for 15.57: ", RMSE)

# Function for Moving Average

periods = 60
movingaverage = round(estimate_moving_average(df2.iloc[10082:11038],seriesname, periods),4)

print("Moving average for 15.57:", movingaverage)
RMSE = rmse(movingaverage, 6.01850)
print("RMSE movingaverage for 15.57: ", RMSE)

# Function for Simple Exponential Smoothing

alpha = 0.2
ses = round ( estimate_ses(df2.iloc[10082:11038], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01850)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df2.iloc[10082:11038],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01850)
print("RMSE holt for 15.57: ", RMSE)
# Trend and seasonality estimation with Holt-Winters ___ I could not do it.

# Estimation for 15.58
print("Estimations for 15.58")
freq = 957 # May 6, 16 saat - 3 minutes

# for Naive

naive = round(estimate_naive (df2.iloc[10083:11039], seriesname),4)
print ("Naive for 15.58:", naive)
RMSE = rmse(naive, 6.01933)
print("RMSE Naive for 15.58: ", RMSE)

#  for Simple Average

simpleaverage = round(estimate_simple_average(df2.iloc[10083:11039], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01933)
print("RMSE simpleaverage for 15.58: ", RMSE)

# for Moving Average

minutes = 60
movingaverage = round(estimate_moving_average(df2.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.58:", movingaverage)
RMSE = rmse(movingaverage, 6.01933)
print("RMSE movingaverage for 15.58: ", RMSE)

# for Simple Exponential Smoothing

alpha = 0.2
ses = round ( estimate_ses(df2.iloc[10083:11039], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01933)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df2.iloc[10083:11039],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01933)
print("RMSE holt for 15.57: ", RMSE)

# Smallest RMSE is moving average, so I estimated 15.59 and 16.00 with this metmod.
# Since Simple Moving Average (SMA) takes the average over some set number of time periods, It will be same for any future period. Thus, in the long run, it may not be a good indicator.
minutes = 60
movingaverage = round(estimate_moving_average(df2.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.59:", movingaverage)

minutes = 60
movingaverave = round(estimate_moving_average(df2.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 16.00:", movingaverage)

minutes = 60
movingaverage = round(estimate_moving_average(df2.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for May 7, 16.00:", movingaverage)

# Question 2

df3 = df.loc[df['Time'].isin(['00:57','01:57','02:57','03:57','04:57','05:57','06:57','07:57','08:57','09:57','10:57','11:57','12:57',
                                  '13:57','14:57','15:57','16:57','17:57','18:57','19:57','20:57','21:57','22:57','23:57',
                                  '00:58','01:58','02:58','03:58','04:58','05:58','06:58','07:58','08:58','09:58','10:58','11:58','12:58',
                                  '13:58','14:58','15:58','16:58','17:58','18:58','19:58','20:58','21:58','22:58','23:58',])]

print(df3)
# 2-a
seriesname = 'Close'
series = df3[seriesname]

test_stationarity(series)

# 2-b
print("Estimations for 15.57 with end times")

# Function for Naive
seriesname = 'Close'

naive = round(estimate_naive (df3.iloc[10197:10978], seriesname),4)
print ("Naive for 15.57:", naive)
RMSE = rmse(naive, 6.01850)
print("RMSE Naive for 15.57: ", RMSE)

# Function for Simple Average
simpleaverage = round(estimate_simple_average(df3.iloc[10197:10978], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01850)
print("RMSE simpleaverage for 15.57: ", RMSE)

# Function for Moving Average

periods = 60
movingaverage = round(estimate_moving_average(df3.iloc[10197:10978],seriesname, periods),4)

print("Moving average for 15.57:", movingaverage)
RMSE = rmse(movingaverage, 6.01850)
print("RMSE movingaverage for 15.57: ", RMSE)

# Function for Simple Exponential Smoothing

alpha = 0.2
ses = round ( estimate_ses(df3.iloc[10197:10978], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01850)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df3.iloc[10197:10978],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01850)
print("RMSE holt for 15.57: ", RMSE)
# Trend and seasonality estimation with Holt-Winters ___ I could not do it.

# Estimation for 15.58
print("Estimations for 15.58 with end times")

size = len(series)
train = df3[:200]
test = df3[200:]
testarray = np.asarray(test.Close)

# for Naive
test['naive'] = testarray[len(testarray)-1]
naive = round(estimate_naive (df3.iloc[10197:11037], seriesname),4)
print ("Naive for 15.58:", naive)
RMSE = rmse(naive, 6.01933)
print("RMSE Naive for 15.58: ", RMSE)

#  for Simple Average

simpleaverage = round(estimate_simple_average(df3.iloc[10197:11037], seriesname), 4)
print("Simple average estimation:", simpleaverage)
RMSE = rmse(simpleaverage, 6.01933)
print("RMSE simpleaverage for 15.58: ", RMSE)

# for Moving Average

minutes = 60
movingaverage = round(estimate_moving_average(df3.iloc[10197:11037],seriesname, minutes),4)
print("Moving average for 15.58:", movingaverage)
RMSE = rmse(movingaverage, 6.01933)
print("RMSE movingaverage for 15.58: ", RMSE)

# for Simple Exponential Smoothing

alpha = 0.2
ses = round ( estimate_ses(df3.iloc[10197:11037], seriesname, alpha)[0], 4)
print("Exponential smoothing estimation with alpha =", alpha, ": ", ses)
RMSE = rmse(ses, 6.01933)
print("RMSE simple exponential smoothing for 15.57: ", RMSE)

# Trend estimation with Holt

alpha = 0.2
slope = 0.1
holt = round(estimate_holt(df3.iloc[10197:11037],seriesname,alpha, slope),4)
print("Holt trend estimation for 15.57 with alpha =", alpha, ", and slope =", slope, ": ", holt)
RMSE = rmse(holt, 6.01933)
print("RMSE holt for 15.57: ", RMSE)

# Smallest RMSE is moving average, so I estimated 15.59 and 16.00 with this metmod.
# Since Simple Moving Average (SMA) takes the average over some set number of time periods, It will be same for any future period. Thus, in the long run, it may not be a good indicator.
minutes = 60
movingaverage = round(estimate_moving_average(df3.iloc[1197:11037],seriesname, minutes),4)
print("Moving average for 15.59:", movingaverage)

minutes = 60
movingaverave = round(estimate_moving_average(df3.iloc[10197:11037],seriesname, minutes),4)
print("Moving average for 16.00:", movingaverage)

minutes = 60
movingaverage = round(estimate_moving_average(df3.iloc[10197:11037],seriesname, minutes),4)
print("Moving average for May 7, 16.00:", movingaverage)

#2-c

df4 = df3.dropna()
df4.info()
print("estimations without missing values")

seriesname = 'Close'
series = df4[seriesname]
test_stationarity(series)

# Question 3
df = df.dropna()
df= df[df['Volume'] != 0]
seriesname = 'Close'
series = df[seriesname]

# 3-a
test_stationarity(series)

def estimate_moving_average(df,seriesname,windowsize):
    avg = df[seriesname].rolling(windowsize).mean().iloc[-1]
    return avg

periods = 60
movingaverage = round(estimate_moving_average(df.iloc[10082:11038],seriesname, periods),4)
print("Moving average for 15.57:", movingaverage)
RMSE = rmse(movingaverage, 6.01850)
print("RMSE movingaverage for 15.57: ", RMSE)

movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.58:", movingaverage)
RMSE = rmse(movingaverage, 6.01933)
print("RMSE movingaverage for 15.58: ", RMSE)

movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 15.59:", movingaverage)

movingaverave = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for 16.00:", movingaverage)

movingaverage = round(estimate_moving_average(df.iloc[10083:11039],seriesname, minutes),4)
print("Moving average for May 7, 16.00:", movingaverage)
#3-b

def linear_weight_moving_average(signal, period):
    buffer = [np.nan] * period
    for i in range(period, len(signal)):
        buffer.append(
            (signal[i - period : i] * (np.arange(period) + 1)).sum()
            / (np.arange(period) + 1).sum()
        )
    return buffer

print("linear moving average:", linear_weight_moving_average(seriesname, period=60))

#3-c
Random = df.sample()
print("Random time is", Random['Time'],"and the Close value at that time is", Random['Close'])

import numpy,import statsmodels,from statsmodels


import numpy as np

def f(x):
	return 2 * x**3 + 1

def SecantMethod(f, x0, x1, tol, max_iter):
	x = 0
	for i in range (1, max_iter):
		print("	La pasul: {}".format(i))
		f0 = f(x0)
		f1 = f(x1)
		print("f0 = {}; f1 = {}".format(f0, f1))
		xi = x1 - f1 * (x1 - x0) / (f1 - f0)
		fxi = f(xi)
		print("xi = {}; fxi = {}".format(xi, fxi))

		# lin 18
		if (abs(fxi) < np.finfo(float).eps):
			x = xi
			return x
		epsilon = abs((xi - x1) / xi)

		print("Epsilon: {}".format(epsilon))

		#stop - 25
		if (epsilon < tol):
			x = xi
			return x
		x0 = x1
		x1 = xi

	print("Maximum number of iterations reached: {}".format(i))
	return x

print(SecantMethod(f, 17.0, 6.0, 10 ** (-10), 100))


import numpy as np
from typing import Callable, List, Optional
from lab1.src.onedim.one_dim_search import dichotomy_method
from lab2.src.methods.conjugate_method import conjugate_direction_method
from lab2.src.methods.newton_step_strategy import ConstantStepStrategy

DEFAULT_EPS = 1e-6
DEFAULT_MAX_ITERS = 1000

def newton_method(f: Callable[[np.ndarray], float],
                  f_grad: Callable[[np.ndarray], np.ndarray],
                  f_hess: Callable[[np.ndarray], np.ndarray],
                  start: np.ndarray,
                  eps: float = DEFAULT_EPS,
                  max_iters: int = DEFAULT_MAX_ITERS,
                  trajectory: Optional[List] = None):
    x_prev = start
    if trajectory is not None:
        trajectory.append(start)
    iters = 1
    strategy = ConstantStepStrategy(f, 1e-10)

    while iters < max_iters:

        x_wave, _ = conjugate_direction_method(f_hess(x_prev), f_grad(x_prev), x_prev)
        # alpha = strategy.next_step(x_prev, x_wave)
        alpha, _, _ = dichotomy_method(lambda a: f(x_prev + a * x_wave), 0, 10, 1e-9)
        x_k = x_prev + alpha * x_wave
        if trajectory is not None:
            trajectory.append(x_k)
        if np.linalg.norm(x_prev - x_k) < eps:
            return x_k, iters
        x_prev = x_k

        iters += 1

    return x_prev, iters


#!/usr/bin/env python

"""
This script demonstrates the stabilization of a Markov chain.
You can change the initial probability vector to any value,
(it should sum to 1), yet the chain converges to the same P(x).

"""

import matplotlib
matplotlib.use("Agg")

import numpy as np
from pylab import *

initial = np.array([0.2, 0.7, 0.1])
results = initial

T = np.array([[0.5, 0.5, 0], [0, 0, 1], [0.9, 0.1, 0]])

for i in range(1,50):
    results = np.row_stack([results, np.dot(initial, np.linalg.matrix_power(T, i))])

np.set_printoptions(precision=3)

print results

figure()
plot(results[:,0], 'k-', lw=2, label='$s_1(t)$')
plot(results[:,1], 'r-', lw=2, label='$s_2(t)$')
plot(results[:,2], 'g-', lw=2, label='$s_3(t)$')
xlim([0, 20])
xlabel('Time Step', fontsize=20)
ylabel('Probability', fontsize=20)
legend(loc='upper right')
xticks(fontsize=14)
yticks(fontsize=14)
grid(True)
savefig('MC_Trace.pdf')


from sympy.vector import CoordSys3D
from symplyphysics import (
    symbols, Function, Eq, pretty, cos, pi
)
from symplyphysics.laws.nuclear.buckling import geometric_buckling_from_neutron_flux

# Description
## Neutron flux formula for the reactor in the shape of a slab of physical width a in the x-direction
## and infinite in the y- and z-directions.

## Law: Ф(x) = C1 * cos(Pi * x / a)
## Where:
## C1 - neutron flux power constant.
## x - distance from the center of slab.
## Pi - Pi constant.
## a - slab width.
## Ф(x) - neutron flux density.

neutron_flux_power_constant = symbols('C1', constant=True)
distance_from_center = symbols('distance_from_center')
slab_width = symbols('slab_width')
neutron_flux_function = symbols('neutron_flux_function', cls = Function)

# This constant is being used for geometric buckling calculation
# See: [geometric buckling for uniform slab](geometric_buckling_for_uniform_slab.py)
axial_constant = pi / slab_width

law = Eq(neutron_flux_function(distance_from_center),
    neutron_flux_power_constant * cos(axial_constant * distance_from_center))

# Check the solution by passing the known neutron flux to the geometric_buckling_from_neutron_flux.
# Neutron flux is a function of x coordinate in the cartesian coordinates.

# Boundary conditions:
# - vacuum boundary condition: Ф(a / 2 + d) = Ф(ae / 2) = 0
# - finite flux condition: 0 <= Ф(x) < ∞
# - interface condition: the neutron flux and the normal component of the neutron current must be continuous
# - source condition: all neutrons flowing through the bounding area of the source must come from the neutron source

# define flux function in cartesian coordinates as a function of x coordinate
cartesian_coordinates = CoordSys3D('cartesian_coordinates')
neutron_flux_function_cartesian = law.subs(distance_from_center, cartesian_coordinates.x)

solved = geometric_buckling_from_neutron_flux.apply_neutron_flux_function(neutron_flux_function_cartesian.rhs)

# check with the derived law: Bg^2 = axial_constant**2
assert solved.rhs == axial_constant**2

def print():
    return pretty(law, use_unicode=False)

# There is no calculate() method. Neutron flux is usually being used internally to pass to other laws.


#!/usr/bin/env python

"""
Show how the posterior gets updated as a set of coin tosses are generated for a biased coin.
Assume a flat prior on the bias weighting and also a Gaussian prior.
"""

import matplotlib.pyplot as pl
from scipy.stats import norm, kstest
import numpy as np

# set plot to render labels using latex
pl.rc('text', usetex=True)
pl.rc('font', family='serif')
pl.rc('font', size=14)
fig = pl.figure(figsize=(12,10), dpi=100)

# numbers of coin tosses
nt = [0, 1, 2, 5, 10, 50, 100, 500, 1000]

bias = 0.3 # biased towards tails

# bias values
H = np.linspace(0., 1., 1000)

# priors
priorflat = np.ones(len(H))
sigmah = 0.05
muh = 0.5
priorgauss = (1./np.sqrt(2.*np.pi*sigmah**2))*np.exp(-0.5*(H-muh)**2/sigmah**2)

curheads = 0. # current number of heads
for i, n in enumerate(nt):
  # generate coin tosses (making sure to include previous ones)
  if n > 0:
    rc = np.random.rand(n-nprev)
    curheads = curheads + len(np.zeros(n-nprev)[rc<bias])

  # compute likelihood
  L = H**curheads * (1.-H)**(n-curheads)

  # compute posterior
  post1 = L*priorflat
  post2 = L*priorgauss

  # normalise posterior
  post1 = post1/np.trapz(post1, H)
  post2 = post2/np.trapz(post2, H)

  # plot posterior
  pl.subplot(3,3,i+1)
  pl.plot(H, post1, 'b', label='$p(H|d,I)$ Uniform prior')
  pl.plot(H, post2, 'r', label='$p(H|d,I)$ Gaussian prior')
  pl.plot(H, priorgauss, 'k--', label='$p(H|I)$ Gaussian')
  ax = pl.gca()
  ax.set_yticklabels([])
  ax.set_yticks([])

  if i == 0:
    pl.legend(loc='lower left', fancybox=True, framealpha=0.3, prop={'size': 12})

  if i % 3 == 0:
    ax.set_ylabel('$p(H|d,I)$')

  if i > 5:
    ax.set_xlabel('$H$')
  else:
    ax.set_xticklabels([])

  ax.text(0.65, 0.8*ax.get_ylim()[1], '$n=%d$' % n, fontsize=16)

  nprev = n

pl.tight_layout()
#fig.subplots_adjust(bottom=0.12)

pl.show()
fig.savefig('../coin_toss_2.pdf')


import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors
import pandas as pd
from pandas.plotting import table
from scipy.spatial import distance_matrix
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import minimum_spanning_tree

def generate_point_set(n_list, random_seed = 42):
    """
    Generate len(n) random 2D points and return the list of points (2D arrays)
    The number of points in the sets are in n_list
    The range of the points coordinates are -1 to 1
    """
    points_list = []
    np.random.seed(random_seed)
    for n in n_list:
        points_list.append(np.random.rand(n,2))
    return points_list

def plot_point_sets(points_list, Div_mat, diversity_names):#, diversity_measure_list, diversity_measure_name_list):
    """
    Plot the points in the points list with the diversity measurement
    :param points_list: the list of points
    :param diversity_measure_name_list: The list of diversity measures
    :param diversity_measure_list: The list of names of diversity measures to show at legend
    """
    df = pd.DataFrame(Div_mat)#, columns = diversity_names)
    df.columns = diversity_names
    for cnt, points in enumerate(points_list):
        #df = pd.DataFrame(np.transpose(Div_mat[cnt,:]))#, columns = diversity_names)
        #df.info()
        #df = df.T
        #df.columns = diversity_names
        f = plt.figure()
        ax = plt.gca()
        table(ax, np.round(df.iloc[cnt,:],2), loc = 'upper right', colWidths=[0.05])
        plt.scatter(points[:,0],points[:,1])
        plt.xlabel('x1')
        plt.ylabel('x2')
        plt.title(str(cnt))
        plt.xlim(0,1)
        plt.ylim(0,1)
        f.savefig('{}.png'.format(cnt))

def get_div_mat(points_list, diversity_measures):
    #Creat the 2D matrix for diversity measure
    Div_mat = np.zeros([len(points_list), len(diversity_measures)])

    for i, points in enumerate(points_list):
        for j,div_measure in enumerate(diversity_measures):
            Div_mat[i,j] = div_measure(points)

    return Div_mat

def variance(points):
    if (len(points) < 2):
        return 0
    else:
        return np.sum(np.var(points, axis = 0))

def mean_NN_dist(points):
    if (len(points) < 2):
        return 0
    nbrs = NearestNeighbors(n_neighbors = 2).fit(points)
    distance, indices = nbrs.kneighbors(points)
    return np.sum(distance) / len(points)

def sum_NN_dist(points):
    return mean_NN_dist(points) *  len(points)

def mean_dist(points):
    if (len(points) < 2):
        return 0
    nbrs = NearestNeighbors(n_neighbors = len(points)).fit(points)
    distance, indices = nbrs.kneighbors(points)
    return np.sum(distance) / (len(points) - 1) / len(points)

def sum_dist(points):
    return mean_dist(points) * len(points)

def NNmulSum_dist(points):
    if (len(points) < 2):
        return 0
    metric = 0
    nbrs = NearestNeighbors(n_neighbors = len(points)).fit(points)
    distance, indices = nbrs.kneighbors(points)
    for i in range(len(distance)):
        point_contribution = np.sum(distance[i,:]) * distance[i,1] #The total distance times the nearest neighbor
        metric += point_contribution
    return metric

def MST2(points):   #The minimum spanning tree length of the points
    if (len(points) < 2):
        return 0
    distance_matrix_points = distance_matrix(points, points, p = 2)
    csr_mat = csr_matrix(distance_matrix_points)
    Tree = minimum_spanning_tree(csr_mat)
    return np.sum(Tree.toarray().astype(float))

def MST1(points):   #The minimum spanning tree length of the points
    if (len(points) < 2):
        return 0
    distance_matrix_points = distance_matrix(points, points, p = 1)
    csr_mat = csr_matrix(distance_matrix_points)
    Tree = minimum_spanning_tree(csr_mat)
    return np.sum(Tree.toarray().astype(float))

def MST3(points):   #The minimum spanning tree length of the points
    if (len(points) < 2):
        return 0
    distance_matrix_points = distance_matrix(points, points, p = 3)
    csr_mat = csr_matrix(distance_matrix_points)
    Tree = minimum_spanning_tree(csr_mat)
    return np.sum(Tree.toarray().astype(float))

def MST4(points):   #The minimum spanning tree length of the points
    if (len(points) < 2):
        return 0
    distance_matrix_points = distance_matrix(points, points, p = 4)
    csr_mat = csr_matrix(distance_matrix_points)
    Tree = minimum_spanning_tree(csr_mat)
    return np.sum(Tree.toarray().astype(float))

def heat_maps_for_metrics(points, diversity_measures, diversity_names, save_name = ''):
    """
    The function which plots the color map for different diversity measurements
    """
    X = np.arange(0,1,0.01)
    Y = np.arange(0,1,0.01)
    X_grid,Y_grid = np.meshgrid(X,Y)
    #Z = np.arrays(X_grid)
    grid_h, grid_w = np.shape(X_grid)
    Z = np.zeros([grid_h, grid_w])
    h,w = np.shape(points)
    point_new = np.zeros([h+1,w])
    assert w==2
    point_new[0:h,:] = points
    point_grid = [[[] for col in range(grid_w)] for row in range(grid_h)]
    for i in range(grid_h):
        for j in range(grid_w):
            point_new[h,0] = X[i]
            point_new[h,1] = Y[j]
            point_grid[i][j] = np.array(point_new)
            #print('point new at {},{} is {}'.format(i,j,point_new))

    for cnt, (div_measure, div_name) in enumerate(zip(diversity_measures, diversity_names)):
        f = plt.figure()
        ax = plt.gca()
        current_metric_value = div_measure(points)
        print("current metric value",current_metric_value)
        for i in range(grid_h):
            for j in range(grid_w):
                #print('for point {}, {}, the point is {}'.format(i,j,point_grid[i][j]))
                Z[i][j] = div_measure(point_grid[i][j])
        #print(Z)
        #plt.scatter(points[:,0], points[:,1],color = 'k',linewidths = 15,label = 'Anchor points')
        Zmax = np.max(Z)
        Zmin = np.min(Z)
        C = ax.pcolormesh(X_grid, Y_grid, Z, cmap = 'jet')
        plt.scatter(points[:,1], points[:,0],color = 'k',linewidths = 1,label = 'Anchor {}'.format(current_metric_value))
        plt.xlabel('x1')
        plt.ylabel('x2')
        plt.title('color map for {} metric'.format(div_name))
        plt.xlim(0,1)
        plt.ylim(0,1)
        fb = f.colorbar(C, ax = ax)
        line_pos = (current_metric_value - Zmin)/ (Zmax - Zmin)
        fb.ax.plot([0,1],[line_pos, line_pos], color = 'w', linewidth = 3,label = 'current value')
        plt.legend()
        f.savefig(save_name + div_name + 'heatmap')
    plt.close('all')
if __name__ == '__main__':
    points = generate_point_set([1,2,3,4,5,6,7])
    #plot_point_sets(points)
    #diversity_measures = [variance, mean_NN_dist, mean_dist, sum_NN_dist, sum_dist, NNmulSum_dist, MST]
    #diversity_names = ['variance', 'mean_NN_dist', 'mean_dist', 'sum_NN_dist', 'sum_dist','NN*sum_dist','MST']
    diversity_measures = [ MST1,MST2,MST3,MST4]
    diversity_names = ['MST1','MST2','MST3','MST4']
    #div_mat = get_div_mat(points, diversity_measures)
    #plot_point_sets(points, div_mat, diversity_names)

    for cnt, point in enumerate(points):
        heat_maps_for_metrics(point, diversity_measures, diversity_names, save_name = '{}_point_example'.format(cnt + 1))

    #points = np.array([[0.25,0.5],[0.75,0.5]])
    #heat_maps_for_metrics(points, diversity_measures, diversity_names, save_name = '2_point_example')
    #points = np.array([[0.1,0.5]])

    #points = np.array([[0.25,0.5],[0.75,0.5],[0.5,0.8]])
    #heat_maps_for_metrics(points, diversity_measures, diversity_names, save_name = '3_point_example')
    points = [[0,0],[1,1],[0,1],[1,0],[0.5,0.5]]
    print(variance(points))
    print(mean_NN_dist(points))
    print(mean_dist(points))


# coding: utf-8

# # Array Computing

# ## Terminology
#
# #### List
#
# * A sequence of values that can vary in length.
# * The values can be different data types.
# * The values can be modified (mutable).
#
#
# #### Tuple
#
# * A sequence of values with a fixed length.
# * The values can be different data types.
# * The values cannot be modified (immutable).
#
# #### Array
#
# * A sequence of values with a fixed length.
# * The values cannot be different data types.
# * The values can be modified (mutable).
#
# #### Vector:  A 1 dimensional (1D) array.
#
# #### Matrix: - A 2 dimensional (2D) array.

# Arrays are like lists but less flexible and more
# efficient for lengthy calculations (one data type,
# stored in the same location in memory).
#
# But first:
#
# ## VECTORS -- very simple arrays
#
# Vectors can have an arbitrary number of components,
# existing in an n-dimensional space.
#
#     (x1, x2, x3, ... xn)
#
# Or
#
#     (x0, x1, x2, ... x(n-1)) for Python...
#
# In Python, vectors are represented by lists or tuples:
#
# #### Lists:

# In[ ]:

x = 2
y = 3

myList = [x, y]
myList

# #### Tuples:

# In[ ]:

myTuple = (-4, 7)
myTuple

# ### Mathematical Operations on Vectors
#
# Review of vector operations: textbook sections 5.1.2 & 5.1.3
#
# In computing:
#
#     Applying a mathematical function to a vector
#     means applying it to each element in the vector.
#     (you may hear me use the phrase "element-wise,"
#     which means "performing some operation one element
#     at a time")
#
# **However, this is not true of lists and tuples**

# #### Q. What do these yield?

# In[ ]:

numList  = [0.0, 1.0, 2.0]
numTuple = (0.0, 1.0, 2.0)

# In[ ]:

2 * numList

# In[ ]:

2 * numTuple

# In[ ]:

2.0 * numList

# ### Vectors in Python programming

# Our current solution:
# * using lists for collecting function data
# * convert to NumPy arrays for doing math with them.
#
# As an example, a falling object in Earth's gravity:

# In[ ]:

def distance(t, a = 9.8):
    '''Calculate the distance given a time and acceleration.

       Input:  time in seconds <int> or <float>,
               acceleration in m/s^2 <int> or <float>
       Output: distance in m <float>
    '''
    return 0.5 * a * t**2

numPoints = 6                       # number of points
delta     = 1.0 / (numPoints - 1)   # time interval between points

# Q. What do the two lines below do?

timeList = [index * delta for index in range(numPoints)]
distList = [distance(t) for t in timeList]

# In[ ]:

print("Time List:    ", timeList)
print("Distance List:", distList)

# #### Repeat on your own: stitching results together:

# In[ ]:

timeDistList = []
for index in range(numPoints):
    timeDistList.append([timeList[index], distList[index]])

for element in timeDistList:
    print element

# Or using zip, we did this already before:

# In[ ]:

timeDistList2 = [[time, dist] for time, dist in zip(timeList, distList)]

for element in timeDistList2:
    print(element)

What zip does:
# In[ ]:

daveList = range(5)
for element in zip(timeList, distList):
    print(element)
list(zip(timeList, distList, daveList))

# When to use lists and arrays?
#
# In general, we'll use lists instead of arrays when elements have to be added (e.g., we don't know how the number of elements ahead of time, and must use methods like append and extend) or their types are heterogeneous.
#
# Otherwise we'll use arrays for numerical calculations.

# ### Basics of numpy arrays

# Characteristics of numpy arrays:
#
#   1. Elements are all the same type
#
#   2. Number of elements known when array is created
#
#   3. Numerical Python (numpy) must be imported to
#      manipulate arrays.
#
#   4. **All array elements are operated on by numpy,
#      which eliminates loops and makes programs
#      much faster.**
#
#   5. Arrays with one index are sometimes called vectors
#      (or 1D arrays). Arrays with two indices are
#      sometimes called matrices (or 2D arrays).
Some numpy functionality and standard usage:
# In[ ]:

import numpy as np

# To convert a list to an array use the array method:

# In[ ]:

myList  = [1, 2, 3]
myArray = np.array(myList)

print(type(myArray))
myArray

# Note the type!

# To create an array of length n filled with zeros (to be filled later):

# In[ ]:

get_ipython().magic('pinfo np.zeros')

# In[ ]:

myArray = np.zeros(10)
myArray

# #### To create arrays with elements of a type other than the default float, use a second argument:

# In[ ]:

myArray = np.zeros(5, dtype=int)
myArray

# We often want array elements equally spaced by some interval (delta).
#
# numpy.linspace(start, end, number of elements) does this:
#
# #### NOTE ####  HERE,  THE "end" VALUE IS NOT (end - 1)  #### NOTE ####

# In[ ]:

zArray = np.linspace(0, 5, 6)
zArray

# #### Q. What will that do?

# Array elements are accessed with square brackets, the same as lists:

# In[ ]:

zArray[3]

# Slicing can also be done on arrays:

# #### Q. What does this give us?

# In[ ]:

yArray = zArray[1:4]
yArray

# For reference below:

# In[ ]:

zArray

# Let's edit one of the values in the z array

# In[ ]:

zArray[3] = 10.0
zArray

# Now let's look at the y array again

# In[ ]:

yArray

# The variable yArray is a **reference** (or **`view`** in Numpy lingo) to three elements (a slice) from zArray: element indices 1, 2, and 3.
#
# Here is a blog post which discusses this issue nicely:
#
# http://nedbatchelder.com/text/names.html
#
# Reason this is of course memory efficiency: Why copy data if not necessary?

# In[ ]:

lList = [6, 7, 8, 9, 10, 11]
mList = lList[1:3]

print(mList)
lList[1] = 10
mList

# > Do not forget this -- check your array values frequently if you are unsure!

# ### Computing coordinates and function values
#
# Here's the distance function we did previously:

# In[ ]:

def distance(t, a = 9.8):
    '''Calculate the distance given a time and acceleration.

       Input:  time in seconds <int> or <float>,
               acceleration in m/s^2 <int> or <float>
       Output: distance in m <float>
    '''
    return 0.5 * a * t**2

numPoints = 6                       # number of points
delta     = 1.0 / (numPoints - 1)   # time interval between points

timeList = [index * delta for index in range(numPoints)]   # Create the time list
distList = [distance(t) for t in timeList]                 # Create the distance list

# We could convert timeList and distList from lists to arrays:

# In[ ]:

timeArray = np.array(timeList)
distArray = np.array(distList)

print(type(timeArray), timeArray)
print(type(distArray), distArray)

# We can do this directly by creating arrays (without converting from a list) with np.linspace
# to create timeArray and np.zeros to create distArray.
#
# (This is merely a demonstration, not superior to the above code for this simple example.)

# In[ ]:

def distance(t, a = 9.8):
    '''Calculate the distance given a time and acceleration.

       Input:  time in seconds <int> or <float>,
               acceleration in m/s^2 <int> or <float>
       Output: distance in m <float>
    '''
    return 0.5 * a * t**2

numPoints = 6                       # number of points

timeArray = np.linspace(0, 1, numPoints)   # Create the time array
distArray = np.zeros(numPoints)            # Create the distance array populated with 0's

print("Time Array:          ", type(timeArray), timeArray)
print("Dist Array Zeros:    ", type(distArray), distArray)

for index in range(numPoints):
    distArray[index] = distance(timeArray[index])   # Populate the distance array with calculated values

print("Dist Array Populated:", type(distArray), distArray)

# ### Vectorization -- one of the great powers of arrays

# The examples above are great, but they doesn't use the computation power of arrays by operating on all the elements simultaneously!
#
# Loops are slow.
# Operating on the elements simultaneously is much faster (and simpler!).
#
# "Vectorization" is replacing a loop with vector or array expressions.

# In[ ]:

def distance(t, a = 9.8):
    '''Calculate the distance given a time and acceleration.

       Input:  time(s) in seconds <int> or <float> or <np.array>,
               acceleration in m/s^2 <int> or <float>
       Output: distance in m <float>
    '''
    return 0.5 * a * t**2

numPoints = 6                              # number of points

timeArray = np.linspace(0, 1, numPoints)   # Create the time array
distArray = distance(timeArray)            # Create and populate the distance array using vectorization

print("Time Array:", type(timeArray), timeArray)
print("Dist Array:", type(distArray), distArray)

# What just happened?
#
# Let's look at what the function "distance" is doing to the values in timeArray

# In[ ]:

numPoints = 6   # Number of points
a = 9.8         # Acceleration in m/s^2

timeArray = np.linspace(0, 1, numPoints)  # The values a created like before
print("Original ", timeArray)

timeArray = timeArray**2                  # Once in the function, they are first squared
print("Squared  ", timeArray)
print(distArray)

timeArray = timeArray * 0.5               # Next they are multiplied by 0.5
print("Times 0.5", timeArray)

timeArray = timeArray * a                 # Finally, they are multiplied by a and the entire modified
print("Times a  ", timeArray)                # array is returned

# Caution: numpy has its own math functions, such as sin, cos, pi, exp, and some of these are slightly different from Python's math module.
#
# Also, the math module does not accept numpy array as arguments, i.e. it is **NOT** vectorized.
#
# Conclusiong: Use numpy built in math whenever dealing with arrays, but be aware that if you repeatedly (in a loop) calculate only 1 value at a time, the `math` library would be faster (because numpy has some overhead costs to do autmatically element-wise math).
#
# So, do this for single calculations:

# In[ ]:

import math
math.sin(0.5)

# but do this for arrays:

# In[ ]:

np.sin([0.1, 0.2, 0.3, 0.4, 0.5])

# Extras:
#
# Python names and references:
#
# http://nedbatchelder.com/text/names.html
#
# Generators:
#
# https://www.jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/
#

# In[ ]:


import random
import copy
import networkx as nx
import matplotlib.pyplot as plt
import csv
import difflib
import pandas as pd
import math
import timeit

# Karger’s Algorithm
def kargerMinCut(graph, n):
    gn = { k : str(i) for (i, k) in enumerate(graph.keys(), 0)}
    while len(graph) > n:
         v = random.choice(list(graph.keys())) # the key
         w = random.choice(graph[v]) # the list of connections

         # assigning the same label to the nodes that are being grouped
         label = gn[w]
         for key in gn.keys():
             if(gn[key] == label):
                 gn[key] = gn[v]

         contract(graph, v, w) # merge together

    mincut = len(graph[list(graph.keys())[0]]) # calculate mincut
    return (gn, mincut)

def contract(graph, v, w):
    for node in graph[w]:  # merge the nodes from w to v
         if node != v:  # we don't want to add self-loops
             graph[v].append(node)
         graph[node].remove(w)  # delete the edges to the absorbed
         if node != v:
              graph[node].append(v)
    del graph[w]  # delete the absorbed vertex 'w'

# load csv data
def load_data():
    return {int(str(line[0]).replace('[', '').replace(']', '')) : [int(str(i).replace('[', '').replace(']', '')) for i in line[1:]] for line in csv.reader(open("dataSmall.csv"))}

def DataToGraphDict(data, threshold): # compares users by comunities that they follow
    g = {}
    for i, (k1, v1) in enumerate(data.items(), 1):
        for k2, v2 in list(data.items())[i:]:
            if k1 != k2:
                sm = difflib.SequenceMatcher(None, v1, v2)
                if sm.ratio() > threshold:
                    g[k1] = g.get(k1, []) + [k2]
                    g[k2] = g.get(k2, []) + [k1]
        print("data processing: ", i, "/", len(data.keys()))
    return g

dpstart = timeit.default_timer()
g = DataToGraphDict(load_data(), 0.05)
dpend = timeit.default_timer()
print("data processing finished, took: ", dpend - dpstart, "s")

def main(g, n): # n is a number of groups we want to divide our dataset into
    res = {} # labels user id to groups, { id: group }
    cuts = []
    iterations = len(list(g.keys())) * int(math.log(len(list(g.keys())), 2)) # number of interations for great result
    for i in range(iterations):
        labels, mincut = kargerMinCut(copy.deepcopy(g), n) # run
        cuts.append(mincut)
        if min(cuts) == mincut:
            res = labels

        print(i, "/", iterations)

    print("minimum cuts: ", min(cuts))
    return res

KargerStart = timeit.default_timer()
labelsData = main(g, 12)
KargerEnd = timeit.default_timer()
print("Karger clusterization finished, took: ", KargerEnd - KargerStart, "s")

# check how many elements are in each group
ne = {}
for k, v in labelsData.items():
    ne[v] = ne.get(v, 0) + 1
print("number of entries of labels: ", ne)

# construct graph for visualization
edges = []
for k, v in g.items():
    for e in v:
        edges.append(tuple([k,e]))
G = nx.Graph()
G.add_edges_from(edges)

# color the graph
carac = pd.DataFrame({ 'ID': list(labelsData.keys()), 'groups': list(labelsData.values()) })
carac = carac.set_index('ID')
carac = carac.reindex(G.nodes())
carac['groups'] = pd.Categorical(carac['groups'])
carac['groups'].cat.codes

# draw the graph
nx.draw(G, labels = labelsData, with_labels = "True", node_color = carac['groups'].cat.codes, cmap = plt.cm.Set1, node_size = 140)
#plt.savefig("graph.png")
plt.show()


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

def plotPairs(ys1, ys2):
    """
    Display two arrays of values as a connected scatter plot
    """
    plt.figure(figsize=(3, 4))
    for i in range(len(ys1)):
        plt.plot(1, ys1[i], 'b.', ms=10)
        plt.plot(2, ys2[i], 'r.', ms=10)
        lineXs = [1, 2]
        lineYs = [ys1[i], ys2[i]]
        plt.plot(lineXs, lineYs, 'k', alpha=.5)

    pValue = pairedTTest(ys1, ys2)
    plt.title(f"p={pValue}")
    plt.axis([.5, 2.5, None, None])
    tickPositions = [1, 2]
    tickLabels = ["baseline", "drug"]
    plt.xticks(tickPositions, tickLabels)
    plt.show()

def pairedTTest(sample1, sample2):
    """
    Run the paired t-test and return the p value
    """
    mean1 = np.mean(sample1)
    mean2 = np.mean(sample2)
    stDev1 = np.std(sample1)
    stDev2 = np.std(sample2)
    stdErr1 = stDev1/np.sqrt(len(sample1))
    stdErr2 = stDev2/np.sqrt(len(sample2))
    tStatistic, pValue = scipy.stats.ttest_rel(sample1, sample2)
    # NOTE: for unpaired, two-sample t-test use ttest_ind()
    return pValue

if __name__ == "__main__":

    ys1 = [2, 5, 3, 4, 5, 7, 8]
    ys2 = [8, 6, 7, 5, 7, 5, 9]
    plotPairs(ys1, ys2)
    print("DONE")


# -*- coding:utf8 -*-

"""
@author: chenjian158978@gmail.com

@date: Tue, May 23 2017

@time: 19:05:20 GMT+8
"""

import matplotlib.pyplot as plt
import numpy as np

def calcu_cost(theta, X, Y):
    """ 计算代价函数的值

    :param theta: 斜率
    :param X: x值
    :param Y: y值
    :return: J值
    """
    m = X.shape[0]
    h = np.dot(X, theta)
    return np.dot((h - Y).T, (h - Y)) / (2 * m)

X = np.array([[0, 1, 2, 4]]).T
Y = np.array([[0, 1, 2, 4]]).T

# 从-2到4之间返回均匀间隔的数字,共101个
# theta是101*1的矩阵
theta = np.array([np.linspace(-2, 4, 101)]).T

J_list = []
for i in range(101):
    current_theta = theta[i:(i + 1)].T
    cost = calcu_cost(current_theta, X, Y)
    J_list.append(cost[0, 0])

plt.plot(theta, J_list)

plt.xlabel('theta_1')
plt.ylabel('J(theta)')
plt.title('Cost Function Example1')
plt.grid(True)
plt.savefig('cost_theta.png', dpi=200)
plt.show()


## INERTIAL OSCILLATIONS

import numpy as np
import matplotlib.pyplot as plt

## constants

f = 1e-4
q0 = (1+1j)/np.sqrt(2)
T = 10*2*np.pi/f

## functions
def forward(dt): #euler forward
    return (1-1j*f*dt)**np.arange(int(T/dt))*q0

def backward(dt):
    return (1/(1+1j*f*dt))**np.arange(int(T/dt))*q0

def leapfrog(dt):
    N = int(T/dt)
    q = np.zeros(N,dtype=complex)
    q[0] = q0
    q[1] = backward(dt)[1] #use backward for first step

    for i in range(1,N-1):
        q[i+1] = q[i-1] - 2*1j*f*q[i]*dt

    return q

def leapfrog2(dt):
    n = np.arange(int(T/dt))
    lam1 = np.sqrt(1-(f*dt)**2) - 1j*f*dt
    return lam1**n*q0

def ana(dt): #analytic
    t = np.arange(0,T/10,dt/10)
    return q0*np.exp(-1j*f*t)

## evaluate

dtlist = [100,200,500,1000]

fig,ax = plt.subplots(2,2,sharex=True,sharey=True)

for i in range(len(dtlist)):

    qf = forward(dtlist[i])
    qb = backward(dtlist[i])
    ql = leapfrog(dtlist[i])
    qa = ana(dtlist[i])

    j = int(i/2) % 2
    k = i % 2

    ax[j,k].plot(np.real(qa),np.imag(qa),'r',lw=5,label='analytic',alpha=.5)
    ax[j,k].plot(np.real(qb),np.imag(qb),'b',label='backward')
    ax[j,k].plot(np.real(qf),np.imag(qf),'g',label='forward')
    ax[j,k].plot(np.real(ql),np.imag(ql),'k',label='leapfrog')
    ax[j,k].set_title(r'$\Delta t$ = %is' % dtlist[i])
    ax[j,k].scatter(np.real(q0),np.imag(q0),50,'#FF0000',marker='v',zorder=5)
    ax[j,k].scatter(np.real(qb[-1]),np.imag(qb[-1]),50,'b',marker='v',zorder=5)
    ax[j,k].scatter(np.real(qf[-1]),np.imag(qf[-1]),50,'g',marker='v',zorder=5)

ax[0,0].set_xlim(-1.5,1.5)
ax[0,0].set_ylim(-1.5,1.5)
ax[0,0].legend(loc=10,fontsize=9)

ax[0,0].set_ylabel(r'$v$')
ax[1,0].set_ylabel(r'$v$')
ax[1,0].set_xlabel(r'$u$')
ax[1,1].set_xlabel(r'$u$')

#plt.tight_layout()
plt.show()


import numpy as np
from scipy.special import gammaln, digamma
from scipy.stats import invwishart, multivariate_normal, dirichlet

class student_t:
    # multi-variate student-t
    def __init__(self, loc, scale, df):
        self.mu = loc
        self.v = df
        self.S = scale
        self.cov = self.v / (self.v - 2) * self.S
        self.D = self.S.shape[0]
        self.L = np.linalg.cholesky(self.S)

    def logpdf(self, X):
        # KPM pg. 47
        res = X - self.mu
        Lu = np.linalg.solve(self.L, res.T)

        lp = (gammaln(self.v/2 + self.D/2) - gammaln(self.v/2) -
              self.D/2*np.log(np.pi) - self.D/2*np.log(self.v) -
              sum(np.log(np.diag(self.L))) -
              (self.v+self.D)/2 * np.log(1 + 1/self.v * np.sum(Lu.T*Lu.T, 1)))
        return lp

    def entropy(self):
        h = (- gammaln(self.v/2 + self.D/2) + gammaln(self.v/2) +
             self.D/2 * self.v*np.pi + (self.v/2 - self.D/2) *
             (digamma(self.v/2 - self.D/2) - digamma(self.v/2)) +
             np.sum(np.log(np.diag(self.L))))
        return h

    def rvs(self, n):
        # sample from mv-t
        n = int(n)
        u = np.random.chisquare(self.v, n)/self.v
        y = multivariate_normal(np.zeros(self.D), self.S).rvs(n)
        return self.mu + y/np.sqrt(u)[:,None]

class NIW:
    # normal-inverse-wishart
    def __init__(self, prior):
        # set prior
        self.m0 = prior.m0
        self.k0 = prior.k0
        self.S0 = prior.S0
        self.v0 = prior.v0
        #
        self.D = prior.S0.shape[0] # dimensions
        self.N = 0 # number of observations
        # init post. params
        self.mn = self.m0
        self.kn = self.k0
        self.Sn = self.S0
        self.vn = self.v0

    def train(self, X):
        # update given data X
        self.N = X.shape[0]
        self.xm = X.mean(0)
        self.mn = (self.k0/(self.k0 + self.N)*self.m0
                   + self.N/(self.k0 + self.N)*self.xm)
        self.kn = self.k0 + self.N
        self.vn = self.v0 + self.N
        S = np.sum([np.outer(x, x) for x in X], axis=0)

        self.Sn = (self.S0 + S + self.k0 * np.outer(self.m0, self.m0)
                   - self.kn * np.outer(self.mn, self.mn))
        # posterior MAP (of the joint)
        self.mu_map = self.mn
        self.Sig_map = self.Sn / (self.vn + self.D + 2)

    def M_step(self, X, r):
        # M-step MAP EM (conditionals)
        # r is a vector of weighted observations (column k of py_X)
        self.N = np.sum(r)
        self.xm = np.sum(r[:,np.newaxis] * X, 0)/self.N
        self.mn = (self.k0/(self.k0 + self.N)*self.m0
                   + self.N/(self.k0 + self.N)*self.xm)
        self.kn = self.k0 + self.N
        self.vn = self.v0 + self.N
        S = np.sum([r[i]*np.outer(X[i,:], X[i,:]) for i in range(len(r))],
                   axis=0)
        self.Sn = (self.S0 + S + self.k0 * np.outer(self.m0, self.m0)
                   - self.kn * np.outer(self.mn, self.mn))
        # posterior MAP (of the joint)
        self.mu_map = self.mn
        self.Sig_map = self.Sn / (self.vn + self.D + 2)

    def post_init(self, m):
        # init post with (offset) priors (e.g. to init EM)
        # center offset
        self.mn = m

    def post_logpdf(self, mu, Sig):
        # define log-pdf of posterior joint w scipy
        # KPMurphy pg. 135
        pmu = multivariate_normal(self.mn, Sig/self.kn)
        pSig = invwishart(self.vn, self.Sn)
        lpmuSig = pmu.logpdf(mu) + pSig.logpdf(Sig)
        return lpmuSig

    def theta_sample(self, n):
        # hierarchically sample Sig, mu
        Sig = [[]] * n
        mu = np.empty([n, self.D])
        for i in range(n):
            Sig[i] = invwishart(self.vn, self.Sn).rvs(1)
            mu[i, :] = multivariate_normal(self.mn, Sig[i]/self.kn).rvs(1)
        return mu, np.stack(Sig)

    def predict(self, Xt):
        # posterior-predictive given data Xt
        m = self.mn
        S = (self.kn + 1)/(self.kn*(self.vn - self.D + 1)) * self.Sn
        nu = self.vn - self.D + 1
        self.post_pred = student_t(m, S, nu) # save post. pred. object
        lp = self.post_pred.logpdf(Xt)
        return lp

    def x_sample(self, n):
        # sample observations from predictive
        n = int(n)
        # if post_pred exists already
        if hasattr(self, 'post_pred'):
            samps = self.post_pred.rvs(n)
        else:
            m = self.mn
            S = (self.kn + 1)/(self.kn*(self.vn - self.D + 1)) * self.Sn
            nu = self.vn - self.D + 1
            post_pred = student_t(m, S, nu) # save post. pred. object
            samps = post_pred.rvs(n)
        return samps

class mixture:
    # mixture model
    def __init__(self, K, base_dist, prior):
        self.K = K # number of components
        # list of conditionals
        self.base = [base_dist(prior) for _ in range(self.K)]
        if prior.alpha is None: # set alphas for the mixture
            raise Exception('define alpha(s)')
        elif np.size(prior.alpha) == 1:
            self.alpha = np.tile(prior.alpha, self.K)
        elif np.size(prior.alpha) != K:
            raise Exception('no# alphas mismatch to K')
        else:
            self.alpha = prior.alpha

    def train_supervised(self, X, Y):
        # update given supervised training data
        self.N = X.shape[0]
        Y = np.squeeze(Y)
        self.labels = np.unique(Y) # labels
        self.pi_map = np.empty(self.K) # map of mixing proportions
        self.alpha_n = np.empty(self.K) # posterior alpha
        for k in range(self.K): # update for each class
            # cluster
            self.base[k].train(X[Y==self.labels[k],:])
            # dirichlet
            nk = sum(Y==self.labels[k])
            self.pi_map[k] = ((nk + self.alpha[k] - 1)
                              /(self.N + np.sum(self.alpha) - self.K))
            # post. alpha
            self.alpha_n[k] = nk + self.alpha[k]
        # store supervised 'responsibility' (diracs)
        self.r = np.zeros([X.shape[0], self.K]) # labelled data
        for k in range(self.K):
            self.r[Y == self.labels[k], k] = 1
        # likelihood of supervised training data
        self.lpX = np.concatenate([self.base[k].predict(X[Y==self.labels[k],:])
                                   for k in range(self.K)]).sum()

    def predict(self, Xt):
        # predict label posterior (MAP) for each cluster
        lp = [self.base[k].predict(Xt) for k in range(self.K)]
        self.lpx_y = np.column_stack(lp) # likelihood of classifier
        # posterior predictive of classifier
        # log-sum-exp
        bc = self.lpx_y + np.log(self.pi_map) # px|y*py
        self.lpx = np.array([np.log(np.sum(np.exp(b_c - np.max(b_c))))
                             + np.max(b_c) for b_c in bc]) # px
        # py|x = (px|y * py) / px
        lpy_x = np.array([bc[i,:] - self.lpx[i] for i in range(bc.shape[0])])
        # unlog
        return np.exp(lpy_x)

    def EM(self, X, tol=1e-3):
        # train unsupervised via MAP EM
        self.N = X.shape[0]
        # init mixing props
        self.pi_map = self.alpha/np.sum(self.alpha)
        # init posterior each cluster (offsetting the prior)..
        m = X[np.random.choice(self.N, self.K, replace=False), :] # data as mu
        [self.base[k].post_init(m[k,:]) for k in range(self.K)]
        # init responsility
        r = self.predict(X)
        self.lml = [] # log-marg-lik
        self.ll = [] # log-joint-lik

        # EM iterations
        while (np.size(self.lml) < 5 or
               abs(sum(self.lml[-1] - self.lml[-5:-1])) > tol):
            # M-step
            for k in range(self.K): # update for each class
                # clusters
                self.base[k].M_step(X, r[:, k]) # update posterior params.
                # dirichlet
                nk = np.sum(r[:,k])
                self.pi_map[k] = ((nk + self.alpha[k] - 1)
                                  /(self.N + np.sum(self.alpha) - self.K))

            # E-step
            r = self.predict(X) # update responsibility

            # log-lik of base params
            lpth_D = np.array([self.base[k].post_logpdf(self.base[k].mu_map,
                                                        self.base[k].Sig_map)
                               for k in range(self.K)]).sum()
            # mixing props
            self.alpha_n = np.sum(r, 0) + self.alpha # posterior alpha
            lpi_D = dirichlet.logpdf(self.pi_map, self.alpha_n)

            # append lml/ll
            self.lml = np.append(self.lml, np.sum(self.lpx))
            self.ll = np.append(self.lml, np.sum(self.lpx) + lpth_D + lpi_D)
            print('log-marginal-likelihood:' + '%.4f' % self.lml[-1])

        # store the final responsibility
        self.r = r

    def semisupervisedEM(self, Xl, Yl, Xu, tol=1e-3):
        # semi-supervised training by EM
        # stack labelled + unlabelled inputs
        X = np.row_stack([Xl, Xu])
        Yl = np.squeeze(Yl)
        # init model (post) with labelled datatset
        self.train_supervised(Xl, Yl)
        self.N = X.shape[0] # ovrwrt n labelled from train_supervised
        # init responsility
        rl = self.r
        ru = self.predict(Xu) # unlabelled data
        r = np.row_stack([rl, ru])

        self.ll = [] # joint-log-likelihood

        # EM iterations
        while (np.size(self.ll) < 5 or
               abs(sum(self.ll[-1] - self.ll[-5:-1])) > tol):
            # M-step
            for k in range(self.K): # update for each class
                # clusters
                self.base[k].M_step(X, r[:, k]) # update posterior params.
                # dirichlet
                nk = np.sum(r[:,k])
                self.pi_map[k] = ((nk + self.alpha[k] - 1)
                                  /(self.N + np.sum(self.alpha) - self.K))

            # E-step
            ru = self.predict(Xu) # update resp. for unlabelled
            r = np.row_stack([rl, ru])

            # log-lik unlabelled
            ll_ul = np.sum(self.lpx)
            # log-lik of base params
            lpth_D = np.array([self.base[k].post_logpdf(self.base[k].mu_map,
                                                        self.base[k].Sig_map)
                               for k in range(self.K)]).sum()
            # mixing props
            self.alpha_n = np.sum(r, 0) + self.alpha # posterior alpha
            lpi_D = dirichlet.logpdf(self.pi_map, self.alpha_n)

            # track log-lik of the joint of the model
            self.ll = np.append(self.ll, ll_ul + self.lpX + lpth_D + lpi_D)

            print('log-joint-likelihood:' + '%.4f' % self.ll[-1])

        # store the final responsibility and likelihood
        self.r # resp. for whole semisupervised set
        # for Xu
        self.r_ul = ru
        self.lpx_ul = self.lpx


import numpy as np
from numpy.linalg import inv, norm
from math import sqrt, acos, cos, tan

def stereo_proj(p, z):
    r'''
    For a given zone axis z in Z^3 of a crystal,
    it calculates the stereoprojection of vector p.
    Noticed that if p denotes a plane of the crystal,
    it should be written in its reciprocal base.
    It always computes the projection from the south pole.
    '''
    R = norm(z)
    zhat = z/norm(z)
    Q = -np.eye(3) + 2*np.outer(zhat,zhat)
    if z[0]**2+z[1]**2>1e-6:
        z_1 = np.array([z[1],-z[0],0])
        z_1 = z_1/norm(z_1)
        z_2 = np.cross(z, z_1)
        z_2 = z_2/norm(z_2)
    else:
        z_1 = np.array([1,0,0])
        z_2 = np.array([0,1,0])

    if norm(p)>1e-6:
        if p.dot(z)>1e-6:
            phat = p/norm(p)
        else:
            phat = -(Q.dot(p))/norm(p)
        c_theta = phat.dot(z)/R
#         print c_theta*180/np.pi
#         R_ps = R*sqrt(2*(1+c_theta))
#         semi_theta = sqrt((c_theta + 1)/2)
        sq_temp = round(1.0 - c_theta**2, 6)
        sq_theta = sqrt(sq_temp)
        if sq_theta<1e-6:
            return np.array([0,0])
        else:
            return np.array([R*sqrt((1-c_theta)/(1+c_theta))*(phat.dot(z_1))/sq_theta,
                             R*sqrt((1-c_theta)/(1+c_theta))*(phat.dot(z_2))/sq_theta ])
#
#         return np.array(phat - (R_ps - R/semi_theta)*(z+phat)/R_ps)
    else:
        return np.array([0, 0])

def pole_figure(plist, z):

    if z[0]**2+z[1]**2>1e-6:
        z_1 = np.array([-z[1],z[0],0])
        z_1 = z_1/norm(z_1)
        z_2 = np.cross(z, z_1)
        z_2 = z_2/norm(z_2)
    else:
        z_1 = np.array([1,0,0])
        z_2 = np.array([0,1,0])

    plist = np.array(plist)
    if len(plist.shape)>2:
        print('The input should be triplets of numbers.')
        return None
    plist = np.array([plist]) if len(plist.shape)==1 else plist
    if plist.shape[1]!=3:
        print('The input should be triplets of numbers.')
        return None
    p_pole = []
    for p in plist:
        p_proj = stereo_proj(p, z)
#         x = p_proj.dot(z_1)
#         y = p_proj.dot(z_2)
        p_pole.append(p_proj)
    return np.array(p_pole)


import numpy as np
import matplotlib.pyplot as plt

class KMSolution(object):

    def __init__(self,dataList,knum,Niteration):
        self.dataList = dataList
        self.knum = knum
        self.Niteration = Niteration
        self.centerList = []
        self.clusterList = [-1 for i in range(len(self.dataList))]

    def initializeCenterList(self,x_range,y_range):
        for i in range(self.knum):
            xc = np.random.uniform(low=x_range[0], high=x_range[1], size=None)
            yc = np.random.uniform(low=y_range[0], high=y_range[1], size=None)
            self.centerList.append([xc,yc])

    def calDistance(self,p1,p2):
        return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2

    def initialization(self):
        x = [p[0] for p in self.dataList]
        y = [p[1] for p in self.dataList]
        x_range = [min(x),max(x)]
        y_range = [min(y),max(y)]
        self.initializeCenterList(x_range,y_range)
        return

    def updating_centering(self):
        for i in range(len(self.dataList)):
            sqd_min = 1e10
            for j in range(self.knum):
                sqd = self.calDistance(self.dataList[i], self.centerList[j])
                if sqd < sqd_min:
                    sqd_min = sqd
                    self.clusterList[i] = j
        centerCount = self.clusterList.count(set(self.clusterList))
        for j in range(self.knum):
            x_cl, y_cl = [], []
            xc, yc = 0, 0
            for i in range(len(self.dataList)):
                if self.clusterList[i] == j:
                    x_cl.append(self.dataList[i][0])
                    y_cl.append(self.dataList[i][1])
            if len(x_cl) > 0:
                xc = sum(x_cl)/len(x_cl)
                yc = sum(y_cl)/len(y_cl)
                self.centerList[j] = [xc, yc]

    def plotResult(self,para):
        x = [p[0] for p in self.dataList]
        y = [p[1] for p in self.dataList]
        xc = [p[0] for p in self.centerList]
        yc = [p[1] for p in self.centerList]
        plt.figure(figsize=(8,8))
        plt.plot(x,y,'ro',ms=2)
        plt.plot(xc,yc,'bo',ms=5)
        plt.xlim([-40,40])
        plt.ylim([-40,40])
        plt.subplots_adjust(left=0.1, right=0.95, top=0.95, bottom=0.1)
        plt.savefig(para+'.pdf')
        return
    def run(self):
        self.initialization()
        for k in range(self.Niteration):
            print(np.round(k/self.Niteration*100,1),'%',end='\r')
            self.updating_centering()

def generatingData(Size,centerNum,radius):
    theta = [2*i*np.pi/centerNum for i in range(centerNum)]
    d = 2*np.pi*radius/centerNum
    x0,y0 = [], []
    N = Size//centerNum
    for i in range(centerNum):
        for j in range(N):
            x0.append(np.random.normal(loc=radius*np.cos(theta[i]),scale=d/7,size=None))
            y0.append(np.random.normal(loc=radius*np.sin(theta[i]),scale=d/7,size=None))
    return x0, y0

if __name__ == '__main__':
    dataSize, centerNum, radius = 500, 10, 20
    x, y = generatingData(dataSize, centerNum, radius)

    dataList = [[x[i], y[i]] for i in range(len(x))]
    knum, Niteration = 15, 2000
    km_solution = KMSolution(dataList,knum,Niteration)
    km_solution.run()

    cluster = km_solution.clusterList
    centerList = km_solution.centerList
    Count = [cluster.count(i) for i in range(knum)]
    centerList = np.array([centerList[i] for i in range(knum) if Count[i]>0])
    print(centerList.shape)
    plt.figure(figsize=(8,8))
    plt.plot(x,y,'ro',ms=1)
    plt.plot(centerList[:,0],centerList[:,1],'bo',ms=2.5)
    plt.xlim([-2*radius,2*radius])
    plt.ylim([-2*radius,2*radius])
    plt.savefig('cluster_k='+str(knum)+'.pdf')


import numpy as np

from . import utils
from .estimator import Estimator

# https://github.com/Yangyangii/MachineLearningTutorial/blob/master/Numpy/RBF-Network-with-Kmeans-clustering.ipynb
# https://jakevdp.github.io/PythonDataScienceHandbook/05.11-k-means.html
class KMeans(Estimator):
  def __init__(self, k, maximum_iterations, epsilon):
    self.k = k
    self.maximum_iterations = maximum_iterations
    self.epsilon = epsilon

  def train(self, x):
    c = utils.kmeans_plus_plus(x, self.k)
    dist = np.zeros([self.k, x.shape[0]])
    eps2 = self.epsilon * self.epsilon
    it = 0

    while it < self.maximum_iterations:
      for i, centroid in enumerate(c):
        dist[i] = np.sum(np.square(np.subtract(np.broadcast_to(centroid, x.shape), x)), axis = 1)

      y = np.argmin(dist, axis = 0)

      prev_c = c
      c = np.array([np.mean(x[y == i], axis = 0) for i in range(self.k)])

      if np.all(np.square(c - prev_c) < eps2):
        break

      it += 1

    return c

  def predict(self, x, c):
    dist = np.zeros([self.k, x.shape[0]])

    for i, centroid in enumerate(c):
      dist[i] = np.sum(np.square(np.subtract(np.broadcast_to(centroid, x.shape), x)), axis = 1)

    y = np.argmin(dist, axis = 0)

    return y

  def evaluate(self, x, d, c):
    y = self.predict(x, c)

    accuracy = utils.rand_index_score(y, d)

    print(f'Accuracy: {accuracy * 100:.5f}% ({accuracy:.5f})')

    return accuracy


import numpy as np
import matplotlib.pyplot as plt

class Data:
	def __init__(self, x=[], y=[]):
		self.x = np.array(x)
		self.y = np.array(y)
		if not x or not y or len(x) is not len(y):
			raise ValueError('x and y muse be same size and non-empty')

	def plot(self, best_fit=False, title='', xlabel='', ylabel=''):
		plt.title(title)
		plt.xlabel(xlabel)
		plt.ylabel(ylabel)

		plt.scatter(self.x, self.y)
		if best_fit:
			t0, t1 = self.grad_des()
			print(type(t1))
			xs = np.array(np.arange(min(self.x), max(self.x), 0.0001))
			line = (t0 + (t1*xs)).astype(np.int)
			plt.plot(self.x, [t0 + (t1*i) for i in self.x])
			print(t0, t1)
		plt.show()

	hypothesis = lambda self, t0, t1,: t0 + (t1*self.x)

	def cost(self, t0=0, t1=1):

		m = len(self.x)
		sum_error = sum(np.power(self.hypothesis(t0, t1) - self.y, 2))
		J = (1.0/(2*m))*sum_error
		return J
	def gradient(self, t0, t1):
		m = len(self.x)
		derive_t0 = (1/m) * sum((self.hypothesis(t0, t1) - self.y))
		derive_t1 = (1/m) * sum((self.hypothesis(t0, t1) - self.y) * self.x)
		return derive_t0, derive_t1
	def grad_des(self, learning_rate=0.001):
		theta0 = 0
		theta1 = 0
		alpha = learning_rate # step per iter
		diff = 99999999999999
		while(diff > 1e-40):
			d_t0, d_t1 = self.gradient(theta0, theta1)
			temp0 = theta0 - (alpha*d_t0)
			temp1 = theta1 - (alpha*d_t1)
			diff = np.power((theta0 + theta1)/2 - (temp0 + temp1)/2, 2)

			theta0 = temp0
			theta1 = temp1
			print(diff)
		return theta0, theta1

	def model(self):
		pass
		# return t0, t1

d = Data(x=[1, 2, 3, 4, 5, ], y=[2, 4, 6, 8, 10])
d.plot(best_fit=True)


import itertools
import math
from typing import Callable, Optional, Sequence

import numpy as np

import dp_variance

def naive_k_min_variance_subset(k: int, sample: Sequence[float]) -> np.ndarray:
    canditate, var = None, math.inf
    for k_subset in itertools.combinations(sample, k):
        _k_subset = np.array(k_subset)
        current_var = np.var(_k_subset)
        if current_var < var:
            var = current_var
            canditate = _k_subset
    return canditate  # type: ignore

def naive_k_max_variance_subset(k: int, sample: Sequence[float]) -> np.ndarray:
    canditate, var = None, -math.inf
    for k_subset in itertools.combinations(sample, k):
        current_var = np.var(k_subset)
        if current_var > var:
            var = current_var
            canditate = np.array(list(k_subset))
    return canditate  # type: ignore

def test_k_min_variance_subset_indices(
    n: int = 200, seed: Optional[int] = None
) -> None:
    rng = np.random.default_rng(seed=seed)
    print(rng)
    for _ in range(n):
        k = rng.integers(low=1, high=15)
        sample = rng.random(size=15) * 100 - 50
        sample.sort()
        subset2 = naive_k_min_variance_subset(k, sample)
        subset3 = sample[dp_variance.k_min_variance_subset_indices(k, sample)]
        assert np.array_equal(subset2, subset3)

def test_k_max_variance_subset_indices(
    n: int = 200, seed: Optional[int] = None
) -> None:
    rng = np.random.default_rng(seed)
    for _ in range(n):
        k = rng.integers(low=1, high=15)
        if k == 1:
            continue
        sample = rng.random(size=15) * 100 - 50
        sample.sort()
        subset2 = naive_k_max_variance_subset(k, sample)
        subset3 = sample[dp_variance.k_max_variance_subset_indices(k, sample)]
        assert np.array_equal(subset2, subset3)

def test__mean_var_with(n: int = 100, seed: Optional[int] = None) -> None:
    _test__mean_var_with(2, 1.0, seed)
    _test__mean_var_with(3, 10.0, seed)
    _test__mean_var_with(n, 1e-100, seed)
    _test__mean_var_with(n, 1.0, seed)
    _test__mean_var_with(n, 1e100, seed)

def _test__mean_var_with(n: int, scale: float, seed: Optional[int]) -> None:
    rng = np.random.default_rng(seed)
    for _ in range(n):
        sample = (rng.random(size=n) - 0.5) * scale
        var_without_e = np.var(sample[:-1])
        mean_without_e = np.mean(sample[:-1])
        e = sample[-1]
        (mean, var) = dp_variance._mean_var_with(e, mean_without_e, var_without_e, n)
        assert np.isclose(mean, np.mean(sample), rtol=1e-9, atol=1e-18)
        assert np.isclose(var, np.var(sample), rtol=1e-9, atol=1e-18)

def test_iteration__mean_var_with(n: int = 100, seed: Optional[int] = None) -> None:
    _test_iteration__mean_var_with(1, seed)
    _test_iteration__mean_var_with(2, seed)
    _test_iteration__mean_var_with(3, seed)
    _test_iteration__mean_var_with(n, seed)

def _test_iteration__mean_var_with(n: int, seed: Optional[int]) -> None:
    rng = np.random.default_rng(seed)
    sample = rng.random(size=n) - 0.5
    online_mean = sample[0]
    online_var = 0.0
    for i in range(1, len(sample)):
        (online_mean, online_var) = dp_variance._mean_var_with(
            sample[i], online_mean, online_var, i + 1
        )
    assert np.isclose(online_mean, np.mean(sample), rtol=1e-9, atol=1e-18)
    assert np.isclose(online_var, np.var(sample), rtol=1e-9, atol=1e-18)

def test__mean_var_without(n: int = 100, seed: Optional[int] = None) -> None:
    _test__mean_var_without(2, 1.0, seed)
    _test__mean_var_without(3, 10.0, seed)
    _test__mean_var_without(n, 1e-100, seed)
    _test__mean_var_without(n, 1.0, seed)
    _test__mean_var_without(n, 1e100, seed)

def _test__mean_var_without(n: int, scale: float, seed: Optional[int]) -> None:
    rng = np.random.default_rng(seed)
    for _ in range(n):
        k = rng.integers(low=0, high=n)
        sample = (rng.random(size=n) - 0.5) * scale
        sample_var = np.var(sample)
        sample_mean = np.mean(sample)
        e = sample[k]
        (mean, var) = dp_variance._mean_var_without(
            e=e, var_with_e=sample_var, mean_with_e=sample_mean, n_with_e=n
        )
        sample_without_e = np.delete(sample, k)
        assert np.isclose(mean, np.mean(sample_without_e), rtol=1e-9, atol=1e-18)
        assert np.isclose(var, np.var(sample_without_e), rtol=1e-9, atol=1e-18)

def test_iteration__mean_var_without(n: int = 100, seed: Optional[int] = None) -> None:
    _test_iteration__mean_var_without(1, seed)
    _test_iteration__mean_var_without(2, seed)
    _test_iteration__mean_var_without(3, seed)
    _test_iteration__mean_var_without(n, seed)

def _test_iteration__mean_var_without(n: int, seed: Optional[int]) -> None:
    rng = np.random.default_rng(seed)
    sample = rng.random(size=n) - 0.5
    online_mean = np.mean(sample)
    online_var = np.var(sample)
    for i in range(len(sample) - 2):
        (online_mean, online_var) = dp_variance._mean_var_without(
            sample[i], online_mean, online_var, n - i
        )
    assert np.isclose(online_mean, np.mean(sample[-2:]), rtol=1e-9, atol=1e-18)
    assert np.isclose(online_var, np.var(sample[-2:]), rtol=1e-9, atol=1e-18)

def test_composition__mean_var_with_without(
    n: int = 100, scale: float = 1.0, seed: Optional[int] = None
) -> None:
    _test_composition__mean_var__mean_var_without(2, scale, seed)
    _test_composition__mean_var__mean_var_without(3, scale, seed)
    _test_composition__mean_var__mean_var_without(4, scale, seed)
    _test_composition__mean_var__mean_var_without(n, scale, seed)

def _test_composition__mean_var__mean_var_without(
    n: int, scale: float, seed: Optional[int]
) -> None:
    rng = np.random.default_rng(seed)
    sample = (rng.random(size=n) - 0.5) * scale
    mean = np.mean(sample)
    var = np.var(sample)
    for i in range(n - 2):
        (mean, var) = dp_variance._mean_var_without(sample[i], mean, var, n - i)
    for i in range(n - 2):
        (mean, var) = dp_variance._mean_var_with(sample[i], mean, var, i + 3)
    assert np.isclose(mean, np.mean(sample), rtol=1e-9, atol=1e-18)
    assert np.isclose(var, np.var(sample), rtol=1e-9, atol=1e-18)

def _naive_local_sensitivity(
    sample: np.ndarray, L: float, U: float, mean: float
) -> float:
    std = np.std(sample)
    dist_from_std = 0.0
    for i in range(sample.size):
        old_value = sample[i]
        sample[i] = L
        v1 = abs(std - np.std(sample))
        sample[i] = U
        v2 = abs(std - np.std(sample))
        sample[i] = mean
        v3 = abs(std - np.std(sample))
        dist_from_std = max([dist_from_std, v1, v2, v3])
        sample[i] = old_value
    local_sens = dist_from_std
    return local_sens

def test__local_sensitivity(n: int = 200, seed: Optional[int] = None) -> None:
    rng = np.random.default_rng(seed)
    L = -0.5
    U = 0.5
    mean = 0.0
    for _ in range(n):
        k = rng.integers(low=0, high=n)
        sample = rng.random(size=n) - 0.5
        sample.sort()

        wcn = dp_variance._worst_case_k_neighbor(
            k, sample, "max_var", L, U, mean, np.std
        )
        ls1 = _naive_local_sensitivity(wcn, L, U, mean)
        ls2 = dp_variance._local_sensitivity(wcn, k, L, U, mean, np.std)
        assert np.isclose(ls1, ls2)

        wcn = dp_variance._worst_case_k_neighbor(
            k, sample, "min_var", L, U, mean, np.std
        )
        ls1 = _naive_local_sensitivity(wcn, L, U, mean)
        ls2 = dp_variance._local_sensitivity(wcn, k, L, U, mean, np.std)
        assert np.isclose(ls1, ls2)

def _naive_worst_case_k_neighbor(
    k: int,
    sample: np.ndarray,
    mode: str,
    L: float,
    U: float,
    mean: float,
    dispersion: Callable[[np.ndarray], float],
) -> np.ndarray:
    """Compute k-neighbors which are good candidate for having the
    maximal local sensitivity among all k-neighbors of `sample`.
    """
    if k == 0:
        return sample
    if mode == "max_var":
        max_var_indices = dp_variance.k_max_variance_subset_indices(k, sample)
        max_var_complement = sample[
            dp_variance._complement(len(sample), max_var_indices)
        ]
        # It doesn't matter where I add the new values, as the variance
        # will stay the same.
        worst_case = np.concatenate([np.tile(mean, k), max_var_complement])
    elif mode == "min_var":
        min_var_indices = dp_variance.k_min_variance_subset_indices(k, sample)
        min_var_complement = sample[
            dp_variance._complement(len(sample), min_var_indices)
        ]
        worst_case_candidates = (
            np.concatenate([extr, min_var_complement])
            for extr in dp_variance._extreme_value_combinations(k, L, U)
        )
        worst_case = max(
            worst_case_candidates, key=lambda seq: dispersion(seq)
        )  # type: ignore
    else:
        raise ValueError("Unsupported mode: {}".format(mode))
    return worst_case  # type: ignore

def test__worst_case_k_neighbor(n: int = 100, seed: Optional[int] = None) -> None:
    seed = 42
    rng = np.random.default_rng(seed)
    L = -0.5
    U = 0.5
    mean = 0.0
    for _ in range(n):
        k = rng.integers(low=0, high=n)
        sample = rng.random(size=n) - 0.5
        sample.sort()
        wcn1 = _naive_worst_case_k_neighbor(k, sample, "max_var", L, U, mean, np.var)
        wcn2 = dp_variance._worst_case_k_neighbor(
            k, sample, "max_var", L, U, mean, np.var
        )
        assert np.array_equal(wcn1, wcn2)
        wcn3 = _naive_worst_case_k_neighbor(k, sample, "min_var", L, U, mean, np.var)
        wcn4 = dp_variance._worst_case_k_neighbor(
            k, sample, "min_var", L, U, mean, np.var
        )
        assert np.array_equal(wcn3, wcn4)


import os as _os
_on_rtd = _os.environ.get('READTHEDOCS', None) == 'True'
if not _on_rtd:
    import numpy as _np

def gaussian(x, mu, sigma):
    """
    Gaussian function of the form :math:`\\frac{1}{\\sqrt{2 \\pi}\\sigma} e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}`.

    .. versionadded:: 1.5

    Parameters
    ----------
    x : float
        Function variable :math:`x`.
    mu : float
        Mean of the Gaussian function.
    sigma : float
        Standard deviation of the Gaussian function.
    """
    return _np.exp(-(x-mu)**2/(2*sigma**2)) / (_np.sqrt(2*_np.pi) * sigma)


import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 각 요소 더하기
c = a + b
# c = np.add(a, b)
print(c)  # [5 7 9]

# 각 요소 빼기
c = a - b
# c = np.subtract(a, b)
print(c)  # [-3 -3 -3]

# 각 요소 곱하기
# c = a * b
c = np.multiply(a, b)
print(c)  # [4 10 18]

# 각 요소 나누기
# c = a / b
c = np.divide(a, b)
print(c)  # [0.25 0.4 0.5]

# Matrix 연산은 dot 메서드를 사
lst1 = [
    [1, 2],
    [3, 4]
]

lst2 = [
    [5, 6],
    [7, 8]
]
a = np.array(lst1)
b = np.array(lst2)

c = np.dot(a, b)
print(c)
# 출력:
# [[19 22]
#  [43 50]]

a = np.array([[1, 2], [3, 4]])

s = np.sum(a)
print(s)  # 10

# axis=0 이면, ax 0의 출력을 만듬. 즉 컬럼끼리 더함
# axis=1 이면, ax 1의 출력을 만듬. 득 행끼리 더함
s = np.sum(a, axis=0)
print(s)  # [4 6]

s = np.sum(a, axis=1)
print(s)  # [3 7]

s = np.prod(a)
print(s)  # 24


import numpy as np

def loessInternal( x, h, xp, yp , kernel = "Gaussian"):
    if kernel == "Gaussian":
        w = np.exp( - (x-xp)**2/(2*h**2) )/np.sqrt(2*np.pi*h**2)
    elif kernel == "TriCube":
        w =  (1 - np.abs((x - xp) / h)**3)**3

        w = np.asarray(w)
        low_values_flags = np.abs(xp - x) > h  # Where values are low
        w[low_values_flags] = 0  # All low values set to 0

    b = np.sum(w*xp)*np.sum(w*yp) - np.sum(w)*np.sum(w*xp*yp)

    b /= np.sum(w*xp)**2 - np.sum(w)*np.sum(w*xp**2)
    a = ( np.sum(w*yp) - b*np.sum(w*xp) )/np.sum(w)

    return a + b*x

def loess(x,y,h, kernel = "Gaussian"):
    """LOESS model free bandwidth reduction.

    See "Data Analysis with Open Source Tools" by P. K. Janert for fdetails.

    Watch out that x and y do not become too small,
    microseconds don't work. h is bandwidth in units of x"""
    out = []
    for k in x:
        out.append( loessInternal(k, h, x, y, kernel = kernel))
    return np.array(out)

def splitLoess(x, y, sigma, SPLIT, kernel = "Gaussian"):
    x1 = x[:SPLIT]
    y1 = y[:SPLIT]

    x2 = x[SPLIT:]
    y2 = y[SPLIT:]

    y1Loess = loess(x1, y1, sigma, kernel = kernel)
    y2Loess = loess(x2, y2, sigma, kernel = kernel)

    return np.concatenate([y1Loess, y2Loess], axis = 0)


import numpy as np

class LinearRegressor:

    def __init__(self):
        self.theta = None

    def hypothesis(self, theta, x):
        return np.dot(theta.T, x)

    def get_theta(self, x, y):
        first_term = np.linalg.pinv(np.dot(x.T, x))
        second_term = np.dot(x.T, y)
        return np.dot(first_term, second_term)

    def fit(self, x, y):
        """
        fit the parameter x and y.
        :param x: training data x
        :param y: training data y
        :return: theta
        """
        self.theta = self.get_theta(x, y)

    def predict(self, test):
        """
        predict the value of Y for the given data test
        :param test: test data shape should be same as X
        :return: prediction for the given test float value
        """

        return self.hypothesis(self.theta, test)

class LocallyWeightedRegressor:

    def hypothesis(self, theta, x):
        return np.dot(theta.T, x)

    def get_weight(self, x, test, tau):
        """
        :param x: training data
        :param test: test  point
        :param tau: hyper-parameter tau
        :return:
        """
        # Create W
        m = x.shape[0]
        w = np.eye(m)

        for i in range(m):
            w[i, i] = np.exp(-np.dot((x[i] - test), (x[i] - test).T) / (2 * tau * tau))

        return w

    def get_theta(self, x, y, w):
        """
        :param x: training data
        :param y: training outputs
        :param w:  weight matrix
        :return: theta
        """
        theta = np.dot(np.linalg.pinv(np.dot(np.dot(x.T, w), x)), np.dot(np.dot(x.T, w), y))
        return theta

    def predict(self, x, y, test, tau=1.0):
        """
        :param x: training data x
        :param y: training data labels y
        :param test: data whose output we want to predict
        :param tau: hyper-parameter
        :return: predicted value float
        """
        w = self.get_weight(x, test, tau)
        theta = self.get_theta(x, y, w)
        return self.hypothesis(theta, test)


import numpy as np
import scipy.spatial.distance as dist

class Linear(object):
	def __call__(self, x, y):
		return np.dot(x, y.T)

	def __repr__(self):
		return ("Linear Kernel")

class Poly(object):
	def __init__(self, degree=2):
		self.degree = degree

	def __call__(self, x, y):
		return (np.dot(x,y.T) ** self.degree)

	def __repr__(self):
		return ("Poly Kernel")

class RBF(object):
	def __init__(self, gamma = 0.1):
		self.gamma = gamma

	def __call__(self, x, y)
		x = np.atleast_2d(x)
		y = np.atlease_2d(y)

		return (np.exp(-self.gamma * dist.cdist(x,y) ** 2).flatten())

	def __repr__(self):
		return ("RBF kernel")


from numba import jit
import numpy as np

@jit(nopython=True, parallel=True)
def generateRandomPolynomial(X, Y, n):
    '''
    Function that generetes 2D discrete polynomial function distribution of specific order.
    Boosted with Numba: works in C and with parallel computing.

    Parameters
    ----------
    X : numpy.ndarray
        meshgrided values in X axis
    Y : numpy.ndarray
        meshgrided values in Y axis
    n : int
        order of polynomial

    Returns:
    ----------
    res : numpy.ndarray
        matrix of 2D polynomial function distribution
    '''
    a = (2 * np.random.random_sample((3*n,)) - 1) * np.eye(n*3)

    XY = []

    if n > 0:
        for i in range(n, 0, -1):
            XY.append(X**i)
            XY.append(Y**i)
            XY.append(X**(i-1)*Y**(i-1))
    elif n == 0:
        XY.append(X)
        XY.append(Y)
        XY.append(X**(0)*Y**(0))

    res = np.zeros(X.shape)
    for i in range(3*n):
        res = res + a[i, i] * XY[i]

    return res


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

x=[45, 51, 39, 41, 52, 48, 49, 46, 43, 47]
y=[171, 178, 157, 163, 183, 172, 183, 172, 175, 173]

cov_mat=np.cov(x,y)
beta_1 =cov_mat[0,1]/cov_mat[0,0]
print(beta_1)
beta_0 = np.mean(y)-beta_1*np.mean(x)
print(beta_0)

z=[beta_0+beta_1*a for a in x]

plt.plot(x,z)
plt.scatter(x,y)
plt.show()


import numpy as np

class Losser:
    def __init__(self, eps: float = 1e-8):
        self.eps = eps

    def __call__(self, y_true, y_pred):
        raise NotImplementedError

    def backward(self, model, y_true, y_pred):
        raise NotImplementedError

class BinaryCrossEntropy(Losser):
    def __init__(self):
        super().__init__()

    def __call__(self, y_true, y_pred):
        target = y_true
        output = np.clip(y_pred, self.eps, 1.0 - self.eps)
        return -1 * np.mean(target * np.log(output) + (1 - target) * np.log(1 - output))

    def backward(self, model, y_true, y_pred):
        y_pred = np.clip(y_pred, self.eps, 1 - self.eps)
        loss_grad = y_pred - y_true[:, np.newaxis]  # Cross_entropy derivative
        model.backward(model.forward_list, loss_grad)

class MeanSquaredErrorLoss(Losser):
    def __call__(self, y_true, y_pred):
        return np.mean(np.square(y_pred - y_true[:, np.newaxis]))

    def backward(self, model, y_true, y_pred):
        loss_grad = -2 * (y_true[:, np.newaxis] - y_pred)
        model.backward(model.forward_list, loss_grad)

class CrossEntropyLoss(Losser):
    def __init__(self):
        super().__init__()

    def __call__(self, y_true, y_pred):
        output = np.clip(y_pred, self.eps, 1. - self.eps)
        output /= output.sum(axis=1)[:, np.newaxis]
        return np.mean(-(y_true * np.log(output)).sum(axis=1))

    def backward(self, model, y_true, y_pred):
        loss_grad = y_pred - y_true
        model.backward(model.forward_list, loss_grad)


# Solve
# x + 2y + z = 2
# 3x + 8y + z = 12
# 4y + z = 2

import numpy as np

rows = [[1, 2, 1],
        [3, 8, 1],
        [ 0, 4,  1 ]]

A = np.array(rows)
det_A = np.linalg.det(A)

# If determinant != 0 there exist an unique solution and an inverse.
assert det_A != 0

# Find inverse
inv_A = np.linalg.inv(A)
B = np.array([[2, 12, 2]])

# By multiplying by the inverse we solve the linear system.
X = np.dot(inv_A, B.T)

#x = 2, y =1, z = -2
assert np.allclose(X, np.array([[2.],[ 1.],[-2.]]))


import numpy as np

def logistic(x, L, k, x0):
    """
    Simple logisitic function.

    L is the curve's maximum value
    k is the logistic growth rate or steepness of the curve
    x0 is the x value at the sigmoid's midpoint
    """
    y = L / (1 + np.exp(-k * (x - x0)))
    return y


import numpy as np

def calc_L(X, k, m):
    """
    Return Lm(k) as the length of the curve.

    """
    N = X.size

    n = np.floor((N-m)/k).astype(np.int64)
    norm = (N-1) / (n*k)

    sum = np.sum(np.abs(np.diff(X[m::k], n=1)))

    Lm = (sum*norm) / k

    return Lm

def calc_L_average(X, k):
    """
    Return <L(k)> as the average value over k sets of Lm(k).

    """
    calc_L_series = np.frompyfunc(lambda m: calc_L(X, k, m), 1, 1)

    L_average = np.average(calc_L_series(np.arange(1, k+1)))

    return L_average

def measure(X, k_max):
    """
    Measure the fractal dimension of the set of points (t, f(t)) forming
    the graph of a function f defined on the unit interval.

    Parameters
    ----------
    X : ndarray
        time series.

    k_max : int
        Maximum interval time that a new series.

    Returns
    -------
    D : float
        Fractal dimension.

    Examples
    --------
    >>> N = np.power(2, 15)
    >>> X = np.sin(np.linspace(0, 1000, N))
    >>> j = 11
    >>> k_max = np.floor(np.power(2, (j-1)/4)).astype(np.int64)
    >>> D = hfda.measure(X, k_max)
    >>> D
    1.0005565919808783

    """
    calc_L_average_series = np.frompyfunc(lambda k: calc_L_average(X, k), 1, 1)

    k = np.arange(1, k_max+1)
    L = calc_L_average_series(k).astype(np.float64)

    D, _ = - np.polyfit(np.log2(k), np.log2(L), 1)

    return D


import numpy as np

def rescaleNormalization(dataArray):
    min = dataArray.min()
    denom = dataArray.max() - min
    newValues = []
    for x in dataArray:
        newX = (x - min) / denom
        newValues.append(newX)
    return newValues

def rescaleMatrix(dataMatrix):
    colCount = len(dataMatrix[0])
    rowCount = len(dataMatrix)
    newMatrix = np.zeros(dataMatrix.shape)
    for i in range(0, colCount):
        min = dataMatrix[:,i].min()
        denom = dataMatrix[:,i].max() - min
        for k in range(0, rowCount):
            newX = (dataMatrix[k,i] - min) / denom
            newMatrix[k,i] = newX
    return newMatrix

def meanNormalization(dataArray):
    mean = np.mean(dataArray)
    denom = dataArray.max() - dataArray.min()
    newValues = []
    for x in dataArray:
        newX = (x - mean) / denom
        newValues.append(newX)
    return newValues


import numpy as np

def cross_entropy(Y, P):
    Y = np.float_(Y)
    P = np.float_(P)
    return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))

# Probabilities of each event happening
p = [0.8, 0.7, 0.1]

# This event series has a low value for cross entropy, so its likely to happen
y = [1, 1, 0]
result = cross_entropy(y, p)
print(result)

# This event series has a high value for cross entropy, so its unlikely to happen
y = [0, 0, 0]
result = cross_entropy(y, p)
print(result)


import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split

from perceptron.perceptron import Perceptron

def show_results(ops, weights):
    for op in ops:
        final_result = Perceptron.sigma(weights[0] + op[0]*weights[1] + op[1]*weights[2])
        print(f"{weights[0]} + {op[0]}*{weights[1]} + {op[1]}*{weights[2]} = {final_result}")

if __name__ == '__main__':
    # Binary Op
    binary_ops = np.array([
        [0, 0],
        [0, 1],
        [1, 0],
        [1, 1]
    ])

    perceptron = Perceptron(input_size=2)
    and_labels = np.array([[0], [0], [0], [1]])
    perceptron.train(binary_ops, and_labels)
    print("1st Weight vector: ", perceptron.W)

    show_results(binary_ops, perceptron.W)
    or_labels = np.array([[0], [1], [1], [1]])
    perceptron.train(binary_ops, or_labels)
    print("2nd Weight vector: ", perceptron.W)
    show_results(binary_ops, perceptron.W)

    xor_labels = np.array([[0], [1], [1], [0]])
    perceptron.train(binary_ops, xor_labels)
    print("3rd Weight vector: ", perceptron.W)
    show_results(binary_ops, perceptron.W)

    # Now try some real data
    # Load the data set
    print("Wisconsin Dataset")
    bc = datasets.load_breast_cancer()
    L = bc.data
    y = bc.target

    # Create training and test split
    X_train, X_test, y_train, y_test = train_test_split(L, y, test_size=0.3, random_state=42, stratify=y)

    perceptron = Perceptron(input_size=X_train.shape[1], epochs=100, lr=1)
    perceptron.train(X_train, y_train)

    print("Training set accuracy: ", perceptron.score(X_train, y_train))
    print("Test set accuracy: ", perceptron.score(X_test, y_test))