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

import math

from glmatrix import *

import numpy as np

print("#########################################")

#np.set_printoptions(precision=3)
np.set_printoptions(formatter={'float': '{: 8.3f}'.format})
#np.set_printoptions(suppress=True)

location_v = vec3_create([5.0, 6.0, 7.0])
location_m = gl_mat4_from_translation(location_v)

print("Location Matrix")

print("")

#print(location_m)
transform_array = np.array(location_m, np.float32)
print(transform_array)

print("")

print("#########################################")

deg = -10
rad = (deg * math.pi / 180)
q_rot = gl_quat_from_x_rotation(rad)
rotation_m = mat4_create(None)
gl_mat4_from_quat(q_rot, rotation_m)

print("Rotation Matrix - X")

print("")
transform_array = np.array(q_rot, np.float32)
print(transform_array)

transform_array = np.array(rotation_m, np.float32)
print(transform_array)

print("")

print("#########################################")

deg = -10
rad = (deg * math.pi / 180)
q_rot = gl_quat_from_y_rotation(rad)
rotation_m = mat4_create(None)
gl_mat4_from_quat(q_rot, rotation_m)

print("Rotation Matrix - Y")

print("")
transform_array = np.array(q_rot, np.float32)
print(transform_array)

transform_array = np.array(rotation_m, np.float32)
print(transform_array)

print("")

print("#########################################")

deg = -10
rad = (deg * math.pi / 180)
q_rot = gl_quat_from_z_rotation(rad)
rotation_m = mat4_create(None)
gl_mat4_from_quat(q_rot, rotation_m)

print("Rotation Matrix - Z")

print("")
transform_array = np.array(q_rot, np.float32)
print(transform_array)

transform_array = np.array(rotation_m, np.float32)
print(transform_array)

print("")

print("#########################################")

displacement_v = vec3_create([10.0, 0.0, 0])
displacement_m = gl_mat4_from_translation(displacement_v)

print("Translate Matrix")

print("")

#print(displacement_m)
transform_array = np.array(displacement_m, np.float32)
print(transform_array)

print("")

print("#########################################")

print("Translate and Rotate")

ms = matstack()

print("")

print("")

ms.loadMatrix(location_m)

mvMatrix_tmp = mat4_create(None)
ms.multMatrix(displacement_m)
ms.getMatrix(mvMatrix_tmp)
transform_array = np.array(mvMatrix_tmp, np.float32)
print(transform_array)

print("")

mvMatrix = mat4_create(None)
ms.multMatrix(rotation_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("")

print("#########################################")

print("Rotate and Translate")

ms = matstack()

print("")

transform_array = np.array(rotation_m, np.float32)
print(transform_array)

print("")

transform_array = np.array(location_m, np.float32)
print(transform_array)

print("")

ms.loadMatrix(location_m)

mvMatrix_tmp = mat4_create(None)
ms.multMatrix(rotation_m)
ms.getMatrix(mvMatrix_tmp)
transform_array = np.array(mvMatrix_tmp, np.float32)
print(transform_array)

print("")

mvMatrix = mat4_create(None)
ms.multMatrix(displacement_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("")

print("#########################################")

print("#########################################")

print("Push / Pop version")

print("")

ms = matstack()

print("Initialise")

ms.loadMatrix(location_m)
mvMatrix = mat4_create(None)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Push")

mvMatrix = mat4_create(None)
ms.pushMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Translate")

mvMatrix = mat4_create(None)
ms.multMatrix(displacement_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Pop")

mvMatrix = mat4_create(None)
ms.popMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("#########################################")

print("Push")

mvMatrix = mat4_create(None)
ms.pushMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Translate")

mvMatrix = mat4_create(None)
ms.multMatrix(displacement_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Rotate")

mvMatrix = mat4_create(None)
ms.multMatrix(rotation_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Pop")

mvMatrix = mat4_create(None)
ms.popMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("#########################################")

print("Push")

mvMatrix = mat4_create(None)
ms.pushMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Rotate")

mvMatrix = mat4_create(None)
ms.multMatrix(rotation_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Translate")

mvMatrix = mat4_create(None)
ms.multMatrix(displacement_m)
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)

print("Pop")

mvMatrix = mat4_create(None)
ms.popMatrix()
ms.getMatrix(mvMatrix)
transform_array = np.array(mvMatrix, np.float32)
print(transform_array)


import numpy as np
# http://iamtrask.github.io/2015/07/27/python-network-part2/
# compute sigmoid nonlinearity
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output

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

# input dataset
X = np.array([  [0,1],
                [0,1],
                [1,0],
                [1,0] ])

# output dataset
y = np.array([[0,0,1,1]]).T

# seed random numbers to make calculation
# deterministic (just a good practice)
np.random.seed(1)

# initialize weights randomly with mean 0
synapse_0 = 2*np.random.random((2,1)) - 1

for iter in xrange(10000): # even just 100 is pretty good...

    # forward propagation
    layer_0 = X
    layer_1 = sigmoid(np.dot(layer_0,synapse_0))

    # how much did we miss?
    layer_1_error = layer_1 - y

    # multiply how much we missed by the
    # slope of the sigmoid at the values in l1
    layer_1_delta = layer_1_error * sigmoid_output_to_derivative(layer_1)
    synapse_0_derivative = np.dot(layer_0.T,layer_1_delta)

    # update weights
    synapse_0 -= synapse_0_derivative

print "Output After Training:"
print layer_1


# implementation of seidel method

import numpy as np
import math_utils

enable_logs = False

def solve_linear(A, b, precision):
	nrows, ncolumns =  A.shape
	assert nrows == ncolumns

	x = np.zeros(ncolumns)
	while math_utils.vec_norm_1(np.matmul(A, x) - b) > precision:
		for i in range(ncolumns):
			sum1 = sum(A[i][j] * x[j] for j in range(i))
			sum2 = sum(A[i][j] * x[j] for j in range(i + 1, ncolumns))
			x[i] = (b[i] - sum1 - sum2) / A[i][i]
	return x


import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

def model(x,t,a):
    dxdt = -a * x
    return dxdt

x0 = 100

t = np.linspace(0,100)

a = 0.1
x1 = odeint(model,x0,t,args=(a,))
a = 0.2
x2 = odeint(model,x0,t,args=(a,))
a = 0.5
x3 = odeint(model,x0,t,args=(a,))

plt.plot(t,x1,'r-',linewidth = 2,label = 'a = 0.1')
plt.plot(t,x2,'b--',linewidth = 2,label = 'a = 0.2')
plt.plot(t,x3,'g:',linewidth = 2,label = 'a = 0.5')

plt.xlabel('time')
plt.ylabel('x(t)')
plt.legend()
#plt.show()

plt.savefig('bibunn.png')


"""Plot a reaction-diffusion system to generate Turing Patterns

This system described by a PDE called the FitzHugh–Nagumo equation.
It is evaluated here using finite difference methods taken from:
https://ipython-books.github.io/124-simulating-a-partial-differential
-equation-reaction-diffusion-systems-and-turing-patterns/
"""

import numpy as np
import matplotlib.pyplot as plt
from finite_difference import laplacian, show_patterns
from gif_creator import create_gif

a = 2.8e-4
b = 5e-3
tau = .1
k = -.005

size = 100  # size of the 2D grid
dx = 2. / size  # space step

T = 10.0  # total time
dt = .001  # time step
n = int(T / dt)  # number of iterations
plot_num = 10

U = np.random.rand(size, size)
V = np.random.rand(size, size)

# U = np.ones((size, size)) + 0.01 * np.random.rand(size, size)
# V = np.ones((size, size)) + 0.01 * np.random.rand(size, size)

step_plot = n // plot_num
plot_count = 0
# Simulate the PDE with the finite difference method.
for i in range(n):
    # Compute the Laplacian of u and v.
    deltaU = laplacian(U, dx)
    deltaV = laplacian(V, dx)
    # Take the values of u and v inside the grid.
    Uc = U[1:-1, 1:-1]
    Vc = V[1:-1, 1:-1]
    # Update the variables.
    U[1:-1, 1:-1], V[1:-1, 1:-1] = \
        Uc + dt * (a * deltaU + Uc - Uc**3 - Vc + k),\
        Vc + dt * (b * deltaV + Uc - Vc) / tau
    # Neumann/zero flux conditions: derivatives at the edges
    # are null.
    for Z in (U, V):
        Z[0, :] = Z[1, :]
        Z[-1, :] = Z[-2, :]
        Z[:, 0] = Z[:, 1]
        Z[:, -1] = Z[:, -2]

    if i % step_plot == 0 and i < plot_num * step_plot:
        plot_count += 1
        plt.imshow(U, cmap=plt.cm.viridis,
              interpolation='bilinear',
              extent=[-1, 1, -1, 1])
        plt.title(f'$t={i * dt:.2f}$')
        plt.savefig(f"Images/Spatial_ODE/Turing_Evolution_Const2_{plot_count:04d}.png")
        #plt.show()

#create_gif("Images/Spatial_ODE/Turing_Evolution_Const2")

fig, ax = plt.subplots(1, 1, figsize=(8, 8))
show_patterns(U, ax=ax)
plt.title(f'$t={i * dt:.2f}$')
#plt.savefig("Images/Turing_Final_State.png")
#plt.show()


import numpy as np

def print_all():

    print("------------------------------------------------------")
    print("[START] - ELEMENT WISE OPERATIONS BETWEEN TWO MATRICES")
    print("------------------------------------------------------")

    # Create matrices
    x = np.array([[10, 20], [100, -10]])
    y = np.array([[5, 10], [-50, 10]])

    print(f"Matrices: \n-------\n  x = \n{x} \n\n y = \n{y}\n\n-------\n")

    print(f"Addition of x + y: \n-------\nnp.add(x,y):\n{np.add(x,y)}\n\n-------\n")

    print(f"Subtraction of x - y: \n-------\nnp.subtract(x,y):\n{np.subtract(x,y)}\n\n-------\n")

    print(f"Multiplication of x * y: \n-------\nnp.multiply(x,y):\n{np.multiply(x,y)}\n\n-------\n")

    print(f"Division of x / y: \n-------\nnp.divide(x,y):\n{np.divide(x,y)}\n\n-------\n")

    print("-------------------------------------------------------")
    print("[FINISH] - ELEMENT WISE OPERATIONS BETWEEN TWO MATRICES")
    print("-------------------------------------------------------")


import numpy as np
import csv

def smooth_noise_using_pca(noise_data, model_data, mean, numOfComponent=2):
    #noiseless_covmat = np.dot(noiseless - mean_noiseless, (noiseless - mean_noiseless).T)/noiseless.shape[1]
    covmat = np.dot(model_data - mean, (model_data - mean).T) / (model_data.shape[1])
    #np.cov(noiseless-mean_noiseless)
    eig_value, eig_vector = np.linalg.eig(covmat)
    #re-sort the eigenvalues & eigenvectors in descending order.
    reorder = np.argsort(eig_value)[::-1]
    eig_value = eig_value[reorder]
    eig_vector = eig_vector[:, reorder]
    # rotated using eigenvectors
    r = np.dot(eig_vector.T, (noise_data - mean))
    #r_covmat = np.cov(r)
    # filter out unselected components
    p = np.zeros(r.shape)
    p[:numOfComponent, :] = r[:numOfComponent, :]
    # undo rotation and translation to restore the data
    restored_x = np.dot(eig_vector, p) + mean
    return restored_x

# import the data from the csv.
data = {}
files_names = ["iris.csv", "dataI.csv", "dataII.csv", "dataIII.csv", "dataIV.csv", "dataV.csv"]
for name in files_names:
    with open('./homework3/hw3-data/'+name, newline='') as f:
        reader = csv.reader(f, delimiter=',', quoting=csv.QUOTE_NONNUMERIC)
        data[name] = []
        for row in reader:
            data[name].append(row)
        data[name].pop(0)

noiseless = np.array(data["iris.csv"]).T
mean_noiseless = np.mean(noiseless, axis=1, keepdims=True)

#noisy_set = np.array(data["dataI.csv"]).T
#noisy_set = np.array(data["iris.csv"]).T
#smooth_noise_using_pca(noisy_set, mean_noiseless, numOfComponent=2)

# print out the Mean square errors between noisy and noiseless data set into mse_1.csv
fobj = open('./homework3/minyuan3-numbers.csv', 'a+')
fobj.write('0N,1N,2N,3N,4N,0c,1c,2c,3c,4c\n')

for name in files_names[1:]:
    data_set = np.array(data[name]).T
    mean_noisy = np.mean(data_set, axis=1, keepdims=True)
    n = []
    c = []
    for numberOfComp in range(5):
        # calculate MSE using the mean and covariance matrix of the noiseless set
        restored_set = smooth_noise_using_pca(data_set, noiseless, mean_noiseless, numberOfComp)
        mean_square_error = np.sum((restored_set - noiseless)**2)/noiseless.shape[1]
        n.append(mean_square_error)
        # calculate MSE using the mean and covariance matrix of the noisy set
        restored_set = smooth_noise_using_pca(data_set, data_set, mean_noisy, numberOfComp)
        mean_square_error = np.sum((restored_set - noiseless)**2)/noiseless.shape[1]
        c.append(mean_square_error)
    fobj.write(','.join(np.array(n).astype(str)) + "," + ','.join(np.array(c).astype(str))+'\n')
    #fobj.write((','.join(['%.3f']*len(n))) % tuple(n) + "," + (','.join(['%.3f']*len(n))) % tuple(c) + '\n')

fobj.close()
# print out the reconstructed dataI.csv using 2 principal components.
fobj = open('./homework3/minyuan3-recon.csv', 'a+')
fobj.write('Sepal.Length, Sepal.Width, Petal.Length, Petal.Width\n')
dataI_set = np.array(data['dataI.csv']).T
mean_dataI = np.mean(dataI_set, axis=1, keepdims=True)
reconstructed_dataI = smooth_noise_using_pca(dataI_set, dataI_set, mean_dataI, 2).T
for row in reconstructed_dataI:
    fobj.write(', '.join(row.astype(str))+'\n')
fobj.close()

mean_square_error = np.sum((reconstructed_dataI.T - dataI_set)**2)/reconstructed_dataI.shape[0]
#mean_square_error_against_noiseless = np.sum((reconstructed_dataI.T - noiseless)**2)/noiseless.shape[1]


import numpy as np
from scipy.io import loadmat
import matplotlib.pyplot as pyplot
from scipy.stats import multivariate_normal

##################### LOADING DATA #####################
#Load from matlab
annots = loadmat("dataset1_G_noisy.mat")

#Training sets
trn_x = annots["trn_x"]
trn_x_class = annots["trn_x_class"]
trn_y = annots["trn_y"]
trn_y_class = annots["trn_y_class"]

#Testing sets
tst_xy = annots["tst_xy"]
tst_xy_class = annots["tst_xy_class"]
tst_xy_126 = annots["tst_xy_126"]
tst_xy_126_class = annots["tst_xy_126_class"]

#Normality tests and it is normal
pyplot.hist(trn_x[:,0])
pyplot.hist(trn_y[:,0])
pyplot.show()

##################### EXERCISE A #####################
#Both are normal so estimate priors
n = trn_x_class.size + trn_y_class.size
priorx = trn_x_class.size / n
priory = trn_y_class.size / n

#Calc means
mx = trn_x.mean(0)
my = trn_y.mean(0)

#Estimate covariance matrix and transpose to keep rank at 2
cx = np.cov(trn_x.T)
cy = np.cov(trn_y.T)

#Calc multivariate norm
mnx = multivariate_normal(mx, cx)
mny = multivariate_normal(my, cy)

#Calculate posteriori
postx = priorx * mnx.pdf(tst_xy)
posty = priory * mny.pdf(tst_xy)

#Classify predictions
p = np.where(postx - posty >= 0, 1, 2) # if its lower than 0 value is set 1 and 2 otherwise
s = tst_xy_class.squeeze() #throws error if value is larger than 1 so keeps only below 1
a = np.sum(p == s) / s.size
print("Acuracy of a: {:.8f}".format(a))

##################### EXERCISE B #####################
#Priors not required since its uniform

#Calc posteriori
postx = mnx.pdf(tst_xy_126)
posty = mny.pdf(tst_xy_126)

#Classify predictions
p = np.where(postx - posty >= 0, 1, 2)
s = tst_xy_126_class.squeeze()
a = np.sum(p == s) / s.size
print("Acuracy of b: {:.8f}".format(a))

##################### EXERCISE C #####################
##Setting new priors
priorx = 0.9
priory = 0.1

#Calc posteriori
postx = priorx * mnx.pdf(tst_xy_126)
posty = priory * mny.pdf(tst_xy_126)

#Classify predictions
p = np.where(postx - posty >= 0, 1, 2)
s = tst_xy_126_class.squeeze()
a = np.sum(p == s) / s.size
print("Acuracy of c: {:.8f}".format(a))


import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from py_expression_eval import Parser
import math
parser = Parser()

class Int_F(object):
    """
    Integrate a function f(x) from a to b
    """

    def __init__(self, f_expr):
        self.f_raw = parser.parse(f_expr)
        self. ans = None
        self.int_pts = []

    @property
    def sol(self):
        return self.ans

    def f(self, x):
        return self.f_raw.evaluate({'x': x})

    def trapeziod(self, interval, n):
        """
        order: 1
        coeffs h/2 * [1 1]
        """
        a,b = interval
        h = (b-a)/(n-1)
        x = np.linspace(a,b,n)

        self.ans = (h/2)*sum([self.f(x[i]) + self.f(x[i+1]) for i in range(0,n-1,1)])

    def simpsons(self, interval,n):
        """
        order: 2
        coeffs h/3 * [1 4 1]
        """
        if (n-1)%2 != 0:
            raise Exception('Number of points must be of the form 2k+1')

        a,b = interval
        h = (b-a)/(n-1)
        x = np.linspace(a,b,n)

        self.ans = (h/3)*sum([self.f(x[i]) + 4*self.f(x[i+1]) + self.f(x[i+2]) for i in range(0,n-2,2)])

    def simpsons_3_8(self, interval, n):
        """
        order: 3
        coeffs 3h/8 * [1 3 3 1]
        """
        if (n-1)%3 != 0:
            raise Exception('Number of points must be of the form 3k+1')

        a,b = interval
        h = (b-a)/(n-1)
        x = np.linspace(a,b,n)

        self.ans = (3*h/8)*sum([self.f(x[i]) + 3*self.f(x[i+1]) + 3*self.f(x[i+2]) + self.f(x[i+3]) for i in range(0,n-3,3)])

    def gauss_legendre(self, interval, n):
        """
        gauss-legendre method
        """
        legendre_poly = np.polynomial.legendre.Legendre([0]*n + [1])
        legendre_poly_diff = legendre_poly.deriv()
        roots = legendre_poly.roots()
        weight = [ 2/((1-x**2)*legendre_poly_diff(x)**2) for x in roots]

        a,b = interval
        self.ans = ((b-a)/2) * sum([weight[i]*self.f(((b-a)/2)*x + (b+a)/2) for i,x in enumerate(roots)])
        return [((b-a)/2)*x + (b+a)/2 for x in roots]

    def integrate(self, from_, to_, num_pts, method):
        """Wrapper method to integrate the function"""
        range = (from_,to_)
        n = num_pts
        assert (n >= 2)

        self.range_ = (from_, to_)

        if method.lower() == 'trap':
            self.int_pts = np.linspace(from_, to_, num_pts)
            self.trapeziod(range,n)
        elif method.lower() == 'simp':
            self.int_pts = np.linspace(from_, to_, num_pts)
            self.simpsons(range,n)
        elif method.lower() == 'simp_3/8':
            self.int_pts = np.linspace(from_, to_, num_pts)
            self.simpsons_3_8(range,n)
        elif method.lower() == 'gauss_legendre':
            self.int_pts =  self.gauss_legendre(range,n)
        else:
            raise Exception(f'method `{method}` not implemented')

    def table(self, num_pts=500):
        xs = np.linspace(*self.range_, num_pts)
        ys = [self.f(x) for x in xs]
        return xs, ys

    def visualize(self, num_pts=500):
        xs, ys = self.table(num_pts)

        plt.plot(xs, ys, c='b', zorder=2)
        plt.plot(xs, [0 for x in xs], c='k', zorder=1)
        for pt in self.int_pts:
            minpt = min(0, self.f(pt))
            maxpt = max(0,self.f(pt))
            plt.vlines( self.int_pts,
                        [min(0,self.f(x)) for x in self.int_pts],
                        [max(0,self.f(x)) for x in self.int_pts],
                        colors=['k' for _ in self.int_pts],
                        zorder=3)
        plt.show()


import pandas as pd
import numpy as np

from types import SimpleNamespace

class LDAClassifier:
    def __init__(self):
        self._coef = []

    def fit(self, x, y):
        mu1 = np.mean(x[y == 1], axis=0)
        mu2 = np.mean(x[y == 0], axis=0)
        mu_diff = mu1 - mu2
        s1 = np.cov(x[y == 1].T)
        s2 = np.cov(x[y == 0].T)
        s_w = s1 + s2
        self._coef = np.matmul(np.linalg.inv(s_w), mu_diff)
        return self

    def predict(self, x):
        return np.dot(x, self._coef) >= 0

    def score(self, x, y_true):
        return 1 - np.sum((self.predict(x) - y_true) ** 2) / x.shape[0]

def load_data(train_file, test_file):
    train_df = pd.read_csv(train_file)
    test_df = pd.read_csv(test_file)

    x_train = train_df.iloc[:, :-1]
    y_train = train_df.iloc[:, -1]

    x_test = test_df.iloc[:, :-1]
    y_test = test_df.iloc[:, -1]

    return {
        "x_train": x_train.values,
        "y_train": y_train.values,
        "x_test": x_test.values,
        "y_test": y_test.values
    }

def __main__():
    data = SimpleNamespace(**load_data("train.csv", "test.csv"))
    clf = LDAClassifier()
    clf.fit(data.x_train, data.y_train)
    print("TRAIN ACCURACY =", clf.score(data.x_train, data.y_train))
    # train_pred = clf.predict(data.x_train)
    # print(train_pred)
    print("TEST ACCURACY =", clf.score(data.x_test, data.y_test))
    # test_pred = clf.predict(data.x_test)
    # print(test_pred)

__main__()


import numpy as np
from numba import jit
from math import factorial, log
from sklearn.neighbors import KDTree
from scipy.signal import periodogram, welch

from .utils import _embed

all = ['perm_entropy', 'spectral_entropy', 'svd_entropy', 'app_entropy',
       'sample_entropy']

def perm_entropy(x, order=3, delay=1, normalize=False):
    """Permutation Entropy.

    Parameters
    ----------
    x : list or np.array
        One-dimensional time series of shape (n_times)
    order : int
        Order of permutation entropy
    delay : int
        Time delay
    normalize : bool
        If True, divide by log2(order!) to normalize the entropy between 0
        and 1. Otherwise, return the permutation entropy in bit.

    Returns
    -------
    pe : float
        Permutation Entropy

    Notes
    -----
    The permutation entropy is a complexity measure for time-series first
    introduced by Bandt and Pompe in 2002 [1]_.

    The permutation entropy of a signal :math:`x` is defined as:

    .. math:: H = -\\sum p(\\pi)log_2(\\pi)

    where the sum runs over all :math:`n!` permutations :math:`\\pi` of order
    :math:`n`. This is the information contained in comparing :math:`n`
    consecutive values of the time series. It is clear that
    :math:`0 ≤ H (n) ≤ log_2(n!)` where the lower bound is attained for an
    increasing or decreasing sequence of values, and the upper bound for a
    completely random system where all :math:`n!` possible permutations appear
    with the same probability.

    The embedded matrix :math:`Y` is created by:

    .. math:: y(i)=[x_i,x_{i+delay}, ...,x_{i+(order-1) * delay}]

    .. math:: Y=[y(1),y(2),...,y(N-(order-1))*delay)]^T

    References
    ----------
    .. [1] Bandt, Christoph, and Bernd Pompe. "Permutation entropy: a
           natural complexity measure for time series." Physical review letters
           88.17 (2002): 174102.

    Examples
    --------
    1. Permutation entropy with order 2

        >>> from entropy import perm_entropy
        >>> x = [4, 7, 9, 10, 6, 11, 3]
        >>> # Return a value in bit between 0 and log2(factorial(order))
        >>> print(perm_entropy(x, order=2))
            0.918
    2. Normalized permutation entropy with order 3

        >>> from entropy import perm_entropy
        >>> x = [4, 7, 9, 10, 6, 11, 3]
        >>> # Return a value comprised between 0 and 1.
        >>> print(perm_entropy(x, order=3, normalize=True))
            0.589
    """
    x = np.array(x)
    ran_order = range(order)
    hashmult = np.power(order, ran_order)
    # Embed x and sort the order of permutations
    sorted_idx = _embed(x, order=order, delay=delay).argsort(kind='quicksort')
    # Associate unique integer to each permutations
    hashval = (np.multiply(sorted_idx, hashmult)).sum(1)
    # Return the counts
    _, c = np.unique(hashval, return_counts=True)
    # Use np.true_divide for Python 2 compatibility
    p = np.true_divide(c, c.sum())
    pe = -np.multiply(p, np.log2(p)).sum()
    if normalize:
        pe /= np.log2(factorial(order))
    return pe

def spectral_entropy(x, sf, method='fft', nperseg=None, normalize=False):
    """Spectral Entropy.

    Parameters
    ----------
    x : list or np.array
        One-dimensional time series of shape (n_times)
    sf : float
        Sampling frequency
    method : str
        Spectral estimation method ::

        'fft' : Fourier Transform (via scipy.signal.periodogram)
        'welch' : Welch periodogram (via scipy.signal.welch)

    nperseg : str or int
        Length of each FFT segment for Welch method.
        If None, uses scipy default of 256 samples.
    normalize : bool
        If True, divide by log2(psd.size) to normalize the spectral entropy
        between 0 and 1. Otherwise, return the spectral entropy in bit.

    Returns
    -------
    se : float
        Spectral Entropy

    Notes
    -----
    Spectral Entropy is defined to be the Shannon Entropy of the Power
    Spectral Density (PSD) of the data:

    .. math:: H(x, sf) =  -\\sum_{f=0}^{f_s/2} PSD(f) log_2[PSD(f)]

    Where :math:`PSD` is the normalised PSD, and :math:`f_s` is the sampling
    frequency.

    References
    ----------
    .. [1] Inouye, T. et al. (1991). Quantification of EEG irregularity by
       use of the entropy of the power spectrum. Electroencephalography
       and clinical neurophysiology, 79(3), 204-210.

    Examples
    --------
    1. Spectral entropy of a pure sine using FFT

        >>> from entropy import spectral_entropy
        >>> import numpy as np
        >>> sf, f, dur = 100, 1, 4
        >>> N = sf * duration # Total number of discrete samples
        >>> t = np.arange(N) / sf # Time vector
        >>> x = np.sin(2 * np.pi * f * t)
        >>> print(np.round(spectral_entropy(x, sf, method='fft'), 2)
            0.0

    2. Spectral entropy of a random signal using Welch's method

        >>> from entropy import spectral_entropy
        >>> import numpy as np
        >>> np.random.seed(42)
        >>> x = np.random.rand(3000)
        >>> print(spectral_entropy(x, sf=100, method='welch'))
            9.939

    3. Normalized spectral entropy

        >>> print(spectral_entropy(x, sf=100, method='welch', normalize=True))
            0.995
    """
    x = np.array(x)
    # Compute and normalize power spectrum
    if method == 'fft':
        _, psd = periodogram(x, sf)
    elif method == 'welch':
        _, psd = welch(x, sf, nperseg=nperseg)
    psd_norm = np.divide(psd, psd.sum())
    se = -np.multiply(psd_norm, np.log2(psd_norm)).sum()
    if normalize:
        se /= np.log2(psd_norm.size)
    return se

def svd_entropy(x, order=3, delay=1, normalize=False):
    """Singular Value Decomposition entropy.

    Parameters
    ----------
    x : list or np.array
        One-dimensional time series of shape (n_times)
    order : int
        Order of permutation entropy
    delay : int
        Time delay
    normalize : bool
        If True, divide by log2(order!) to normalize the entropy between 0
        and 1. Otherwise, return the permutation entropy in bit.

    Returns
    -------
    svd_e : float
        SVD Entropy

    Notes
    -----
    SVD entropy is an indicator of the number of eigenvectors that are needed
    for an adequate explanation of the data set. In other words, it measures
    the dimensionality of the data.

    The SVD entropy of a signal :math:`x` is defined as:

    .. math::
        H = -\\sum_{i=1}^{M} \\overline{\\sigma}_i log_2(\\overline{\\sigma}_i)

    where :math:`M` is the number of singular values of the embedded matrix
    :math:`Y` and :math:`\\sigma_1, \\sigma_2, ..., \\sigma_M` are the
    normalized singular values of :math:`Y`.

    The embedded matrix :math:`Y` is created by:

    .. math:: y(i)=[x_i,x_{i+delay}, ...,x_{i+(order-1) * delay}]

    .. math:: Y=[y(1),y(2),...,y(N-(order-1))*delay)]^T

    Examples
    --------
    1. SVD entropy with order 2

        >>> from entropy import svd_entropy
        >>> x = [4, 7, 9, 10, 6, 11, 3]
        >>> # Return a value in bit between 0 and log2(factorial(order))
        >>> print(svd_entropy(x, order=2))
            0.762

    2. Normalized SVD entropy with order 3

        >>> from entropy import svd_entropy
        >>> x = [4, 7, 9, 10, 6, 11, 3]
        >>> # Return a value comprised between 0 and 1.
        >>> print(svd_entropy(x, order=3, normalize=True))
            0.687
    """
    x = np.array(x)
    mat = _embed(x, order=order, delay=delay)
    W = np.linalg.svd(mat, compute_uv=False)
    # Normalize the singular values
    W /= sum(W)
    svd_e = -np.multiply(W, np.log2(W)).sum()
    if normalize:
        svd_e /= np.log2(order)
    return svd_e

def _app_samp_entropy(x, order, metric='chebyshev', approximate=True):
    """Utility function for `app_entropy`` and `sample_entropy`.
    """
    _all_metrics = KDTree.valid_metrics
    if metric not in _all_metrics:
        raise ValueError('The given metric (%s) is not valid. The valid '
                         'metric names are: %s' % (metric, _all_metrics))
    phi = np.zeros(2)
    r = 0.2 * np.std(x, axis=-1, ddof=1)

    # compute phi(order, r)
    _emb_data1 = _embed(x, order, 1)
    if approximate:
        emb_data1 = _emb_data1
    else:
        emb_data1 = _emb_data1[:-1]
    count1 = KDTree(emb_data1, metric=metric).query_radius(emb_data1, r,
                                                           count_only=True
                                                           ).astype(np.float64)
    # compute phi(order + 1, r)
    emb_data2 = _embed(x, order + 1, 1)
    count2 = KDTree(emb_data2, metric=metric).query_radius(emb_data2, r,
                                                           count_only=True
                                                           ).astype(np.float64)
    if approximate:
        phi[0] = np.mean(np.log(count1 / emb_data1.shape[0]))
        phi[1] = np.mean(np.log(count2 / emb_data2.shape[0]))
    else:
        phi[0] = np.mean((count1 - 1) / (emb_data1.shape[0] - 1))
        phi[1] = np.mean((count2 - 1) / (emb_data2.shape[0] - 1))
    return phi

@jit('f8(f8[:], i4, f8)', nopython=True)
def _numba_sampen(x, mm=2, r=0.2):
    """
    Fast evaluation of the sample entropy using Numba.
    """
    n = x.size
    n1 = n - 1
    mm += 1
    mm_dbld = 2 * mm

    # Define threshold
    r *= x.std()

    # initialize the lists
    run = [0] * n
    run1 = run[:]
    r1 = [0] * (n * mm_dbld)
    a = [0] * mm
    b = a[:]
    p = a[:]

    for i in range(n1):
        nj = n1 - i

        for jj in range(nj):
            j = jj + i + 1
            if abs(x[j] - x[i]) < r:
                run[jj] = run1[jj] + 1
                m1 = mm if mm < run[jj] else run[jj]
                for m in range(m1):
                    a[m] += 1
                    if j < n1:
                        b[m] += 1
            else:
                run[jj] = 0
        for j in range(mm_dbld):
            run1[j] = run[j]
            r1[i + n * j] = run[j]
        if nj > mm_dbld - 1:
            for j in range(mm_dbld, nj):
                run1[j] = run[j]

    m = mm - 1

    while m > 0:
        b[m] = b[m - 1]
        m -= 1

    b[0] = n * n1 / 2
    a = np.array([float(aa) for aa in a])
    b = np.array([float(bb) for bb in b])
    p = np.true_divide(a, b)
    return -log(p[-1])

def app_entropy(x, order=2, metric='chebyshev'):
    """Approximate Entropy.

    Parameters
    ----------
    x : list or np.array
        One-dimensional time series of shape (n_times)
    order : int (default: 2)
        Embedding dimension.
    metric : str (default: chebyshev)
        Name of the metric function used with
        :class:`~sklearn.neighbors.KDTree`. The list of available
        metric functions is given by: ``KDTree.valid_metrics``.

    Returns
    -------
    ae : float
        Approximate Entropy.

    Notes
    -----
    Original code from the mne-features package.

    Approximate entropy is a technique used to quantify the amount of
    regularity and the unpredictability of fluctuations over time-series data.

    Smaller values indicates that the data is more regular and predictable.

    The value of :math:`r` is set to :math:`0.2 * \\text{std}(x)`.

    Code adapted from the mne-features package by Jean-Baptiste Schiratti
    and Alexandre Gramfort.

    References
    ----------
    .. [1] Richman, J. S. et al. (2000). Physiological time-series analysis
           using approximate entropy and sample entropy. American Journal of
           Physiology-Heart and Circulatory Physiology, 278(6), H2039-H2049.

    1. Approximate entropy with order 2.

        >>> from entropy import app_entropy
        >>> import numpy as np
        >>> np.random.seed(1234567)
        >>> x = np.random.rand(3000)
        >>> print(app_entropy(x, order=2))
            2.075
    """
    phi = _app_samp_entropy(x, order=order, metric=metric, approximate=True)
    return np.subtract(phi[0], phi[1])

def sample_entropy(x, order=2, metric='chebyshev'):
    """Sample Entropy.

    Parameters
    ----------
    x : list or np.array
        One-dimensional time series of shape (n_times)
    order : int (default: 2)
        Embedding dimension.
    metric : str (default: chebyshev)
        Name of the metric function used with KDTree. The list of available
        metric functions is given by: `KDTree.valid_metrics`.

    Returns
    -------
    se : float
        Sample Entropy.

    Notes
    -----
    Sample entropy is a modification of approximate entropy, used for assessing
    the complexity of physiological time-series signals. It has two advantages
    over approximate entropy: data length independence and a relatively
    trouble-free implementation. Large values indicate high complexity whereas
    smaller values characterize more self-similar and regular signals.

    Sample entropy of a signal :math:`x` is defined as:

    .. math:: H(x, m, r) = -log\\frac{C(m + 1, r)}{C(m, r)}

    where :math:`m` is the embedding dimension (= order), :math:`r` is
    the radius of the neighbourhood (default = :math:`0.2 * \\text{std}(x)`),
    :math:`C(m + 1, r)` is the number of embedded vectors of length
    :math:`m + 1` having a Chebyshev distance inferior to :math:`r` and
    :math:`C(m, r)` is the number of embedded vectors of length
    :math:`m` having a Chebyshev distance inferior to :math:`r`.

    Note that if metric == 'chebyshev' and x.size < 5000 points, then the
    sample entropy is computed using a fast custom Numba script. For other
    metric types or longer time-series, the sample entropy is computed using
    a code from the mne-features package by Jean-Baptiste Schiratti
    and Alexandre Gramfort (requires sklearn).

    References
    ----------
    .. [1] Richman, J. S. et al. (2000). Physiological time-series analysis
           using approximate entropy and sample entropy. American Journal of
           Physiology-Heart and Circulatory Physiology, 278(6), H2039-H2049.

    Examples
    --------
    1. Sample entropy with order 2.

        >>> from entropy import sample_entropy
        >>> import numpy as np
        >>> np.random.seed(1234567)
        >>> x = np.random.rand(3000)
        >>> print(sample_entropy(x, order=2))
            2.192

    2. Sample entropy with order 3 using the Euclidean distance.

        >>> from entropy import sample_entropy
        >>> import numpy as np
        >>> np.random.seed(1234567)
        >>> x = np.random.rand(3000)
        >>> print(sample_entropy(x, order=3, metric='euclidean'))
            2.725
    """
    x = np.asarray(x, dtype=np.float64)
    if metric == 'chebyshev' and x.size < 5000:
        return _numba_sampen(x, mm=order, r=0.2)
    else:
        phi = _app_samp_entropy(x, order=order, metric=metric,
                                approximate=False)
        return -np.log(np.divide(phi[1], phi[0]))


# Bench mark function 8
# Generalized Schwefel's Function No.2.26
# HW dimension: 30
# Min = -418.982887272433799807913601398 * n
# Range [-500,500]
# Reference: https://www.al-roomi.org/benchmarks/unconstrained/n-dimensions/176-generalized-schwefel-s-problem-2-26

import math
import numpy as np

name = "F8"
l_bound = -500
u_bound = 500
dim = 30
opt = -418.982887272433799807913601398 * dim

def func(X):
    sin_term = np.sin(np.absolute(X)**0.5)
    result = np.sum(-1*np.multiply(X, sin_term))
    return result

if __name__ == '__main__':
    X = np.arange(30)
    X = X * 0.3
    print(X)
    result = func(X)
    print(result)


from scipy.special import gamma, gammaln
import operator
import math
from logsumexp import logsumexp
from factorialln import factorialln

def product(l):
    return reduce(operator.mul, l)

def beta(alphas):
    """Multivariate beta function"""
    return math.exp(sum(map(gammaln, alphas)) - gammaln(sum(alphas)))

def dirichlet(probs, obs, s=1):
    """Dirichlet probability density for a given set probabilities and prior
    observation counts.  s serves as a concentration parameter between 0 and 1,
    with smaller s yielding progressively more diffuse probability density
    distributions."""
    alphas = [(a + 1) * float(s) for a in obs]
    return 1 / beta(alphas) * product([math.pow(x, a - 1) for x, a in zip(probs, alphas)])

def dirichlet_maximum_likelihood_ratio(probs, obs, s=1):
    """Ratio between the dirichlet for the specific probs and obs and the
    maximum likelihood value for the dirichlet over the given probs (this can
    be determined by partitioning the observations according to the
    probabilities)"""
    maximum_likelihood = dirichlet(probs, [float(sum(obs)) / len(obs) for o in obs], s)
    return dirichlet(probs, obs, s) / float(maximum_likelihood)

def multinomial(probs, obs):
    return math.factorial(sum(obs)) / product(map(math.factorial, obs)) * product([math.pow(p, x) for p,x in zip(probs, obs)])

def multinomialln(probs, obs):
    return factorialln(sum(obs)) - sum(map(factorialln, obs)) + sum([math.log(math.pow(p, x)) for p,x in zip(probs, obs)])

def multinomial_coefficientln(n, counts):
    return factorialln(n) - sum(map(factorialln, counts))

def multinomial_coefficient(n, counts):
    return math.exp(multinomial_coefficientln(n, counts))

def multinomial_dirichlet(probs, obs): return multinomial(probs, obs) * dirichlet(probs, obs)

# NOTE:
# I started exploring the multinomial_maximum_likelihood_ratio to see if the
# same maximim likelihood estimation approach could be applied to multinomials.
# It *can't* for the obvious reason that you can't have non-integral
# observation counts while the dirichlet distribution is defined across
# non-integral alphas!  I see no clean way to resolve this using the
# multinomial distribution and now have a better understanding of the use and
# abuse of the dirichlet distribution as a conjugate prior for multinomial
# posteriors.
def multinomial_maximum_likelihood_ratio(probs, obs):
    maximum_likelihood = multinomial(probs, [float(sum(obs)) / len(obs) for o in obs])
    return multinomial(probs, obs) / float(maximum_likelihood)

def binomial(successes, trials, prob):
    return math.factorial(trials) / (math.factorial(successes) * math.factorial(trials - successes)) * math.pow(prob, successes) * math.pow(1 - prob, trials - successes)


import math
import numpy as np

def rotate_vector(vector, angle):
    angle = math.radians(angle)
    rotated_vector = [
            vector[0]*math.cos(angle) - vector[1]*math.sin(angle),
            vector[0]*math.sin(angle) + vector[1]*math.cos(angle),
        ]
    return rotated_vector

# funcion para crear circulos con canvas de Tkinter
def create_circle(x, y, r, canvasName, color): #center coordinates, radius
    x0 = x - r
    y0 = y - r
    x1 = x + r
    y1 = y + r
    return canvasName.create_oval(x0, y0, x1, y1, outline=color)

def coords_within_circle(pos_cir, pos_obj, r):
    # euclidean distance (x1-x)² + (y1-y)² <= r²
    x1 = pos_obj[0]
    y1 = pos_obj[1]
    x = pos_cir[0]
    y = pos_cir[1]
    return (x1-x)**2 + (y1-y)**2 <= r**2

def coords_within_arc(pos_cir, pos_obj, r, anglemax, anglemin):
    inside = False
    if coords_within_circle(pos_cir, pos_obj, r):
        angle = angle_btw_2_points(pos_cir, pos_obj)
        if angle <= anglemax and angle >= anglemin:
            inside = True

    return inside

def angle_btw_2_points(pointA, pointB):
    a = np.array(pointA)
    b = np.array(pointB)
    angle = calculate_rotation_angle_from_vector_to_vector(a,b)
    if angle > 180:
        angle -= 360

    return angle

def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    all_zero = True
    i = 0
    while i < len(vector) and all_zero:
        if vector[i] != 0:
            all_zero = False

        i += 1

    if all_zero:
        return [0, 0]

    return vector / np.linalg.norm(vector)

def calculate_rotation_angle_from_vector_to_vector(a,b):
    """ return rotation angle from vector a to vector b, in degrees.
    Args:
        a : np.array vector. format (x,y)
        b : np.array vector. format (x,y)
    Returns:
        angle [float]: degrees. 0~360
    """
    unit_vector_1 = unit_vector(a)
    unit_vector_2 = unit_vector(b)
    dot_product = np.dot(unit_vector_1, unit_vector_2)
    angle = np.arccos(dot_product)
    angle = angle/ np.pi * 180
    c = np.cross(b,a)
    if c>0:
        angle +=180

    return angle


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure

def twospirals(n_points, noise = 1):
    """
     Returns the two spirals dataset.
    """
    n = np.sqrt(np.random.rand(n_points,1)) * 780 * (2*np.pi)/360
    d1x = -np.cos(n)*n + np.random.rand(n_points,1) * noise
    d1y = np.sin(n)*n + np.random.rand(n_points,1) * noise
    return (np.vstack((np.hstack((d1x,d1y)),np.hstack((-d1x,-d1y)))),
            np.hstack((np.zeros(n_points),np.ones(n_points))))

X, y_ = twospirals(2000)

#plt.plot(X[y_==0,0], X[y_==0,1], '.', color='green')
#plt.plot(X[y_==1,0], X[y_==1,1], '.', color='red')
#plt.show()

y =[]
for i in range(len(y_)):
    y.append(int(y_[i]))
y = np.eye(2)[y]


#########################
##                     ##
## Irving Gomez Mendez ##
##  February 27, 2021  ##
##                     ##
#########################

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import t, multivariate_normal
import random

random.seed(111)
mu = [0,0]
Sigma = [[2,0.7],[0.7,2]]
points = multivariate_normal.rvs(mu,Sigma,200)

# Mahalanobis distance
D_M = np.sqrt(np.diag((points-mu) @ np.linalg.inv(Sigma) @ (points-mu).T))

plt.figure(figsize=(7.5,7.5))
plt.scatter(points[:,0], points[:,1], label='Original data', c=D_M)
plt.colorbar()


import numpy as np
import phd_toolbox.constants as cst

# ORBIT EFFECT
def orbit_shift(I_L, r_w, betx, bety, qx, qy, phi_w, Brho, mu0=cst.mu0_H_m):
    '''
    This functions computes the orbit shift induced by a wire in both planes.
    Taken from https://indico.cern.ch/event/456856/contributions/1968793/attachments/1196177/1740660/BBLR_LYON_2015.pdf

    Args:
        I_L: float or array, current in the wire [A.m]
        r_w: float or array, beam-wire distance, regardless the plane [m]
        phi_w: float or array, angle between the beam and the wires, zero being horizontal [rad]
        Brho: float, magnetic rigidity
        mu0: vacuum permeability [H/m]

    Returns:
        [dx,dy]: lst of dim 2 (for x and y). Each element can be either a float or an array depending on the input.
    '''
    theta_x = mu0*I_L*np.cos(phi_w)/2/np.pi/Brho/r_w
    theta_y = mu0*I_L*np.sin(phi_w)/2/np.pi/Brho/r_w
    dx = betx*theta_x/2/np.sin(np.pi*qx)*np.cos(-np.pi*qx)
    dy = bety*theta_y/2/np.sin(np.pi*qy)*np.cos(-np.pi*qy)
    return [dx,dy]

# TUNE EFFECT
def tune_shift(I_L, r_w, betx, bety, phi_w, Brho, mu0=cst.mu0_H_m):
    '''
    This functions computes the tune shift induced by a wire in both planes.
    Taken from https://indico.cern.ch/event/456856/contributions/1968793/attachments/1196177/1740660/BBLR_LYON_2015.pdf

    Args:
        I_L: float or array, current in the wire [A.m]
        r_w: float or array, beam-wire distance, regardless the plane [m]
        phi_w: float or array, angle between the beam and the wires, zero being horizontal [rad]
        betx,bety: floats, beta functions at the wire location
        Brho: float, magnetic rigidity
        mu0: vacuum permeability [H/m]

    Returns:
        [dqx,dqy]: lst of dim 2 (for x and y). Each element can be either a float or an array depending on the input.
    '''
    dqx = -mu0*I_L*betx*np.cos(2*phi_w)/8/np.pi/np.pi/Brho/r_w/r_w
    dqy = mu0*I_L*bety*np.cos(2*phi_w)/8/np.pi/np.pi/Brho/r_w/r_w
    return [dqx,dqy]


# Exercise 5: Box-and-Whisker plot

# Generate a list of normally distributed numbers
import numpy as np
y = np.random.normal(loc=0, scale=0.1, size=100) # 100 numbers with mean of 0 and standard deviation of 0.1

# Generate boxplot
import matplotlib.pyplot as plt # import matplotlib
plt.boxplot(y) # create boxplot of y
plt.show() # print plot

# Calculate shapiro wilk p-value
from scipy.stats import shapiro
shap_w, shap_p = shapiro(y)
print(shap_p)

# Get outliers
# convert to z-scores
from scipy.stats import zscore
y_z_scores = zscore(y) # convert y into z scores

# get the number of scores with absolute value of 3 or more
total_outliers = 0
for i in range(len(y_z_scores)):
    if abs(y_z_scores[i]) >= 3:
        total_outliers += 1
print(total_outliers)

# set up some logic for the title
if shap_p > 0.05:
    title = 'Normally distributed with {} outlier(s).'.format(total_outliers)
else:
    title = 'Not normally distributed with {} outlier(s).'.format(total_outliers)
print(title)

# Generate boxplot with programmatic tile
import matplotlib.pyplot as plt # import matplotlib
plt.boxplot(y) # generate boxplot
plt.title(title) # programmatic title
plt.show() # print plot


# %%
import numpy as np

# %%
v1 = np.array([3, 2, 4])
v2 = np.array([8, 9, 7])

# %%
v1

# %%
type(v1)

# %%
v1.dtype

# %%
v3 = np.array([1.0, 2.0, 3.0])

# %%
v3.dtype

# %%
v1 + v2

# %%
lst1 = [3, 2, 4]
lst2 = [8, 9, 7]

# %%
def vector_sum(v1, v2):
    assert len(v1) == len(v2)
    result = [0] * len(v1)
    for i in range(len(v1)):
        result[i] = v1[i] + v2[i]
    return result

# %%
vector_sum(lst1, lst2)

# %%
# %%timeit
vector_sum(lst1, lst2)

# %%
# %%timeit
v1 = v2

# %%
v1 * v2

# %%
v1.dot(v2)

# %%
v1.sum()

# %%
v1.mean()

# %%
v1.std()

# %%
v1.max()

# %%
v1.min()

# %%
v1

# %%
v1.argmax()

# %%
v1.argmin()

# %%
m1 = np.array([[1, 2, 3], [4, 5, 6]])

# %%
m1

# %%
m2 = np.array([[1, 0], [0, 1], [2, 3]])

# %%
m2

# %%
m1 + m1

# %%
m2 + m2

# %%
m1 + m2

# %%
v1.shape

# %%
m1.shape

# %%
m2.shape

# %%
m1

# %%
m1.T

# %%
m2

# %%
m2.T

# %%
m1.T + m2

# %%
m1 + m2.T

# %%
m1 * m2

# %%
m1.T * m2

# %%
m1.dot(m2)

# %%
m1 @ m2

# %%
v1 @ v2

# %%


# -*- coding: utf-8 -*-
"""
The Monty Hall Problem Simulated

Created on Mon May 25 20:30:43 2020
@author: Gurpinder
"""
import random
import matplotlib.pyplot as plt
import numpy as np

def generate_doors(n):
    """
    Returns: a list of n random doors, one with a car behind it all others with Goats.
    """
    Doors = ["G" for i in range(n)]
    # choose a random place to put car behind
    Doors[random.randint(0, n-1)] = "C"
    return Doors

def play(Doors, switcher):
    """
    Plays one turn of the Monty Hall Game.
    Doors = list of Doors, one with car, all other with goats. (rhymes lol.)
    switcher = True/Fasle, tells if the player will make a switch or not.
    """

    choice = random.randint(0, len(Doors)-1) #player chooses 1 door
    #player chooses a door with no car behind
    if Doors[choice] != "C":
        #Monty keeps the car door closed, opens the rest
        closed_door = Doors.index("C")

    #if player chooses the door that contains car
    if Doors[choice] == "C":
        #Monty keeps one door closed, opens the rest
        while True:
            closed_door = random.randint(0, len(Doors)-1)
            if closed_door != choice:
                break

    if switcher: #if player wants to switch it can do so here.
            choice = closed_door #Player switches to the door that monty kept CLOSED.

    return Doors[choice] == "C" #Return if the player choice door had a car behind it

def simulation(numTrials, numDoors = 3, toprint = False):
    """
    numTrials: number of times the game will be played.
    numDoors = number of doors in the game.

    """
    results_switch =  [] #storing the results of player that makes a switch
    results_not_switch = [] #same for the player that doesnot make a switch

    for i in range(numTrials):
        #generate new door set every Trial
        Doors = generate_doors(numDoors)

        results_switch.append(play(Doors, True)) #Switcher plays the game
        results_not_switch.append(play(Doors, False)) #non switcher playes the game

    #printing the results
    if toprint:
        print("\nPrinting the simulation results:")
        print("For {} Doors, Iterations {}".format(numDoors, numTrials))
        print("The person who switched choice won {}% of times.".format((results_switch.count(True)/numTrials)*100))
        print("The person who didn't switch choice won {}% of times.".format((results_not_switch.count(True)/numTrials)*100))

    return (results_switch.count(True)/numTrials, results_not_switch.count(True)/numTrials)

def door_number_exp(numDoors, numTrials = 100, numRepitions = 20):
    """
    Does the number of doors affect the wining ratio - does it faour either player?
    Answer: Yes, the player who switches gets the benefit.

    Inputs: numDoors - list of number of doors to experiment on
            numTrials - number of times to play the game
            numRepitions - number of times each door number is played 100 times and average is plotted.
    """
    #storing the averages for each door.
    r_s = []
    r_ns = []
    for num in numDoors:
        #tempory lists to store numRepitions number of numTrials Trials.
        sc = []
        nsc = []
        for i in range(numRepitions):
            Switcher, nonSwitcher = simulation(numTrials, num)
            sc.append(Switcher)
            nsc.append(nonSwitcher)

        #append the means into the lists for plotting
        r_s.append(np.array(sc).mean())
        r_ns.append(np.array(nsc).mean())

    #plotting code
    plt.figure(figsize = (7,7))
    plt.plot(numDoors, r_s, label = "Switcher")
    plt.plot(numDoors, r_ns, label= "Non-Switcher")
    plt.title("Simulating Monty hall problem over {} doors.".format(len(numDoors)))
    plt.xlabel("Number of Doors")
    plt.ylabel("Proportion of games won")
    plt.legend(loc="best")
    plt.show()

#Running the simulation
door_number_exp(np.arange(3, 103, dtype=int))


# coding: utf-8

# ### DEMAPP01
# # Approximating functions on R

# This demo illustrates how to use CompEcon Toolbox routines to construct and operate with an approximant for a function defined on an interval of the real line.
#
# In particular, we construct an approximant for $f(x)=\exp(-x)$ on the interval $[-1,1]$.  The function used in this illustration posseses a closed-form, which will allow us to measure approximation error precisely. Of course, in practical applications, the function to be approximated will not possess a known closed-form.
#
# In order to carry out the exercise, one must first code the function to be approximated at arbitrary points.
# Let's begin:

# In[1]:

import numpy as np
import matplotlib.pyplot as plt
from compecon import BasisChebyshev, BasisSpline, demo

# ### Preliminary tasks

# Function to be approximated and derivatives

# In[2]:

def f(x): return np.exp(-x)
def d1(x): return -np.exp(-x)
def d2(x): return np.exp(-x)

# Set degree and domain of interpolation

# In[3]:

n, a, b = 10, -1, 1

# This function will be used to plot the $y=0$ line in all figures.

# In[4]:

def plotzero():
    plt.hlines(0,a,b, 'k', linestyle='--',linewidth=2)

# ### Choose an approximation scheme.

# #### Chebyshev interpolation

# In this case, let us use an order 10 Chebychev approximation scheme:

# In[5]:

F = BasisChebyshev(n, a, b, f=f)

# One may now evaluate the approximant at any point x calling the basis:

# In[6]:

x = 0
ffit = F(x)

# ... one may also evaluate the approximant's first and second derivatives at x:

# In[7]:

dfit1 = F(x, 1)
dfit2 = F(x, 2)

# ... and one may even evaluate the approximant's definite integral between the left endpoint a and x:

# In[8]:

intfit = F(x, -1)

# ###  Compare analytic and numerical computations

# In[9]:

print('Function Values, Derivatives and Definite Integral of exp(-x) at x=0')
print('%-20s %12s %12s' % (' ', 'Numerical', 'Analytic'))
print('%-20s %12.8f %12.8f' % ('Function', ffit, f(x)))
print('%-20s %12.8f %12.8f' % ('First Derivative', dfit1,  d1(x)))
print('%-20s %12.8f %12.8f' % ('Second Derivative', dfit2,  d2(x)))
print('%-20s %12.8f %12.8f' % ('Definite Integral', intfit,  np.exp(1)-1))

# ### Plots of approximation errors

# One may evaluate the accuracy of the Chebychev polynomial approximant by computing the approximation error on a highly refined grid of points:

# In[10]:

nplot = 501  # number of grid nodes
xgrid = np.linspace(a, b, nplot)    # generate refined grid for plotting

figures=[]

def approx_error(true_func, appr_func, d=0, title=''):
    demo.figure(title, 'x', 'Error')
    plotzero()
    plt.plot(xgrid, appr_func(xgrid, d) - true_func(xgrid))
    plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))
    figures.append(plt.gcf())

# #### Plot function approximation error

# In[11]:

approx_error(f,F,title='Chebychev Approximation Error - Function')

# The plot indicates that an order 10 Chebychev approximation scheme, produces approximation errors no bigger in magnitude than $6\times10^{-10}$. The approximation error exhibits the "Chebychev equioscillation property", oscilating relatively uniformly throughout the approximation domain.
#
# This commonly occurs when function being approximated is very smooth, as is the case here but should not be expected when the function is not smooth.  Further notice how the approximation error is exactly 0 at the approximation nodes --- which is true by contruction.

# #### Plot first derivative approximation error

# In[12]:

approx_error(d1,F,1, title='Chebychev Approximation Error - First Derivative')

# #### Plot second derivative approximation error

# In[13]:

approx_error(d2,F,2, title='Chebychev Approximation Error - Second Derivative')

# ### Cubic spline interpolation

# Let us repeat the approximation exercise, this time constructing a 21-function cubic spline approximant:

# In[14]:

n = 21                         # order of approximation
S = BasisSpline(n, a, b, f=f)  # define basis
yapp = S(xgrid)                # approximant values at grid nodes

# #### Plot function approximation error

# In[15]:

approx_error(f,S,title='Cubic Spline Approximation Error - Function')

# The plot indicates that an order 21 cubic spline approximation scheme produces approximation errors no bigger in magnitude than $1.2\times10^{-6}$, about four orders of magnitude worse than with Chebychev polynomials.

# #### Plot first derivative approximation error

# In[16]:

approx_error(d1,S,1, title='Cubic Spline Approximation Error - First Derivative')

# #### Plot second derivative approximation error

# In[17]:

approx_error(d2,S,2, title='Cubic Spline Approximation Error - Second Derivative')

# ### Linear spline interpolation

# Let us repeat the approximation exercise, this time constructing a 31-function linear spline approximant:

# In[18]:

n = 31
L = BasisSpline(n, a, b, k=1, f=f)

# #### Plot function approximation error

# In[19]:

approx_error(f,L,title='Linear Spline Approximation Error - Function')

# The plot indicates that an order 21 cubic spline approximation scheme produces approximation errors no bigger in magnitude than $1.2\times10^{-6}$, about four orders of magnitude worse than with Chebychev polynomials.

# In[20]:

demo.savefig(figures)


#!/usr/bin/env python
'''
  pearson correlation
'''

import argparse
import csv
import logging
import sys

import numpy as np
import scipy.stats

def correlation(values1, values2):
  logging.info('starting: %i values vs %i values', len(values1), len(values2))
  logging.debug('group 1: %s; group 2: %s', ' '.join(values1), ' '.join(values2))

  if len(values1) == 0 or len(values2) == 0:
    logging.fatal('empty groups')
    return

  num1 = [float(x) for x in values1]
  num2 = [float(x) for x in values2]

  sys.stdout.write('pearson\tp-value\tv1u\tv1sd\tv1min\tv1max\tv1n\tv2u\tv2sd\tv2min\tv2max\tv2n\n')

  if all([num1[0] == x for x in num1 + num2]):
    logging.info('all values equal')
    sys.stdout.write('0\t1\t{}\t0\t{}\t{}\t{}\t{}\t0\t{}\t{}\t{}\n'.format(num1[0], num1[0], num1[0], len(num1), num1[0], num1[0], num1[0], len(num2)))
    return

  result = scipy.stats.pearsonr(num1, num2)

  sys.stdout.write('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(result[0], result[1], np.mean(num1), np.std(num1), min(num1), max(num1), len(num1), np.mean(num2), np.std(num2), min(num2), max(num2), len(num2)))

  logging.info('done')

def read_csv(fh, col1, col2):
  logging.debug('reading stdin...')
  values1 = []
  values2 = []
  for row in fh:
    values1.append(row[col1])
    values2.append(row[col2])
  correlation(values1, values2)

if __name__ == '__main__':
  parser = argparse.ArgumentParser(description='Measure correlation')
  parser.add_argument('--values1', required=False, nargs='+', help='group 1')
  parser.add_argument('--values2', required=False, nargs='+', help='group 2')
  parser.add_argument('--col1', required=False, help='stdin table')
  parser.add_argument('--col2', required=False, help='stdin table')
  parser.add_argument('--delimiter', required=False, default='\t', help='stdin table')
  parser.add_argument('--verbose', action='store_true', help='more logging')
  args = parser.parse_args()
  if args.verbose:
    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG)
  else:
    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)

  if args.values1 is not None and args.values2 is not None:
    correlation(args.values1, args.values2)
  else:
    read_csv(csv.DictReader(sys.stdin, delimiter=args.delimiter), args.col1, args.col2)


#
# Cannon ball trajectory computed via Euler approximation
#
# assumes: i) drag force F = - B_2 * v^2
#          ii) isothermal air density model: rho(y) = rho(0) * exp(-y / y0)
#
# by Denes Molnar for PHYS 580, Fall 2020

import math
import numpy as np

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

# computes arrays of x,y for parameters v0, theta, B2/m, y0scale, dt
#
def computeTrajectory(v0 = 700., theta = 45., B2_m = 4e-5, y0scale = 1e4, dt = 0.01):
   # convert to Cartesian initconds
   x0 = 0.
   y0 = 0.
   vx = v0 * math.cos(theta / 180. * math.pi);
   vy = v0 * math.sin(theta / 180. * math.pi);

   maxr = v0 * v0 / g   # max range of shell, in vaccuum (for automatic horizontal plot range)
   maxt = maxr / vx     # flight time at maxr, in vacuum (for atomatic calculation end time)
   nsteps = math.ceil(maxt / dt)   # time steps

   # allocate storage
   # - only position is stored (needed for plotting)
   x = np.zeros(nsteps + 1)
   y = np.zeros(nsteps + 1)

   # solve using Euler method
   for i in range(nsteps):
      x[i + 1] = x[i] + vx * dt
      y[i + 1] = y[i] + vy * dt
      v = math.sqrt(vx**2 + vy**2)
      fD_over_v = B2_m * v * math.exp(-y[i] / y0scale)    # isothermal model, hardcoded
      vx = vx -      fD_over_v * vx  * dt
      vy = vy - (g + fD_over_v * vy) * dt
      if y[i + 1] <= 0.:   # stop calculation at step (i+1) if y_i is below ground
         break

   # adjust last point to match interpolated range
   xmax = (y[i + 1] * x[i] - y[i] * x[i + 1] ) / (y[i + 1] - y[i])
   x[i + 1] = xmax  # adjust  last point to keep y >= 0 in plots
   y[i + 1] = 0.

   # return appropriately truncated arrays
   return x[:(i+2)], y[:(i+2)]

# physical parameters and initial conditions (units are in [] brackets)
#
#   v0 [m/s], theta [degrees], B2/m  [1/m],  y0 [m]
#
# some reasonable choices are B2/m = 4x10^(-5), y0 = 10^4
#
v0      = float( input('initial speed [m/s]: ') )
theta   = float( input('shooting angle [degrees]: ') )
B2_m    = float( input('B2/m [1/m]: ') )
y0scale = float( input('characteristic height in atmospheric pressure [m]: ') )
g       = 9.8   # gravitational acceleration [m/s^2]

# computational parameters: dt[s], nsteps (nsteps is autodetermined)
dt = float( input('dt [s]: ') )

# compute trajectory with Euler approximation
x,y = computeTrajectory(v0, theta, B2_m, y0scale, dt)

# y vs x plot
plt.xlabel('x [m]')    # horizontal position
plt.ylabel('y [m]')    # vertical position
titleString  = 'dt=' + str(dt) + ", v0=" + str(v0) + ", B2/m=" + str(B2_m)
titleString += ", h=" + str(y0scale) + ", theta=" + str(theta)
plt.title(titleString) # plot title
plt.plot(x, y, label = "Euler")
plt.legend()             # create legends
plt.show()               # show plot

#EOF


import numpy as np
import matplotlib.pyplot as plt

# Construct xi
dx = 0.10
xi = np.arange(0, np.pi/2, dx)

# Function values
fi = np.sin(xi)
# Derivative
dfi = (fi[2:] - fi[:-2]) / (2.0*dx)

# Compare to cos. Note that derivative was not defined in end points
f_ref = np.cos(xi[1:-1])
print("Mean squared difference:")
print(np.sqrt(np.sum((dfi - f_ref)**2)))
plt.plot(xi[1:-1], dfi, label="sin'")
plt.plot(xi[1:-1], f_ref, label="cos")
plt.legend()
plt.show()


'''
Collated by Ching-Shih Tsou 鄒慶士 博士 (Ph.D.) Distinguished Prof. at the Department of Mechanical Engineering/Director at the Center of Artificial Intelligence & Data Science (機械工程系特聘教授兼人工智慧暨資料科學研究中心主任), MCUT (明志科技大學); Prof. at the Institute of Information & Decision Sciences (資訊與決策科學研究所教授), NTUB (國立臺北商業大學); the Chinese Academy of R Software (CARS) (中華R軟體學會創會理事長); the Data Science and Business Applications Association of Taiwan (DSBA) (臺灣資料科學與商業應用協會創會理事長); the Chinese Association for Quality Assessment and Evaluation (CAQAE) (中華品質評鑑協會常務監事); the Chinese Society of Quality (CSQ) (中華民國品質學會大數據品質應用委員會主任委員
Notes: This code is provided without warranty.
'''

# Take an arbitrary cosine function of the form x(t) = Acos(2π fct +φ) and proceed step by step as follows
# • Represent the signal x(t) in computer memory (discrete-time x[n]) and plot the signal in time domain
# • Represent the signal in frequency domain using FFT (X[k])
# • Extract magnitude and phase information from the FFT result
# • Reconstruct the time domain signal from the frequency domain samples

from scipy.fftpack import fft, ifft, fftshift, ifftshift
import numpy as np
import matplotlib.pyplot as plt
A = 0.5 # amplitude of the cosine wave
fc = 10 # frequency of the cosine wave in Hz
phase = 30 # desired phase shift of the cosine in degrees
fs = 32*fc # sampling frequency with oversampling factor 32
t = np.arange(start = 0,stop = 2,step = 1/fs) # 2 seconds duration
phi = phase*np.pi/180; # convert phase shift in degrees in radians 轉換為弳/弧度量
x = A*np.cos(2*np.pi*fc*t+phi) # time domain signal with phase shift

N = 256 # FFT size
X = 1/N*fftshift(fft(x, N)) # N-point complex DFT

df = fs/N # frequency resolution
sampleIndex = np.arange(start = -N//2, stop = N//2) # // for integer division (-128, 127)
f = sampleIndex*df # x-axis index converted to ordered frequencies

fig, (ax1, ax2, ax3, ax4) = plt.subplots(nrows=4, ncols=1)
ax1.plot(t,x) # plot time domain representation
ax1.set_title(r'$x(t) = 0.5 cos (2 \pi 10 t + \pi/6)$')
ax1.set_xlabel('time (t seconds)');ax1.set_ylabel('x(t)')

ax2.stem(f, abs(X), use_line_collection=True) # magnitudes vs frequencies
ax2.set_xlim(-30, 30)
ax2.set_title('Amplitude spectrum')
ax2.set_xlabel('f (Hz)');ax2.set_ylabel(r'$ \left| X(k) \right|$')

phase=np.arctan2(np.imag(X),np.real(X))*180/np.pi # phase information
ax3.plot(f,phase) # phase vs frequencies

X2 = X #store the FFT results in another array
# detect noise (very small numbers (eps)) and ignore them
threshold = max(abs(X))/10000; # tolerance threshold
X2[abs(X)<threshold]=0 # maskout values below the threshold
phase=np.arctan2(np.imag(X2),np.real(X2))*180/np.pi # phase information
ax4.stem(f,phase, use_line_collection=True) # phase vs frequencies
ax4.set_xlim(-30, 30);ax4.set_title('Phase spectrum')
ax4.set_ylabel(r"$\angle$ X[k]");ax4.set_xlabel('f(Hz)')
fig.show()

X[0:5]

np.arctan2(np.imag(X[0:5]),np.real(X[0:5]))

#### Reconstructing the time domain signal from the frequency domain samples
x_recon = N*ifft(ifftshift(X),N) # reconstructed signal
t = np.arange(start = 0,stop = len(x_recon))/fs # recompute time index

fig2, ax5 = plt.subplots()
ax5.plot(t,np.real(x_recon)) # reconstructed signal
ax5.set_title('reconstructed signal')
ax5.set_xlabel('time (t seconds)');ax5.set_ylabel('x(t)');
fig2.show()

#### Reference:
# Viswanathan, Mathuranathan, Digital Modulations using Python, December 2019.


from numpy import array, zeros, float, dot
from copy import copy
import scipy.linalg
import numpy
import time

def GaussJordan(A,b):

    n,m = A.shape
    C = zeros((n,m+1),float)
    C[:,0:n],C[:,0:n] = A, b

    for j in range(n):
        # partial pivoting.
        p = j
        # look for alternate pivot by searching for largest element in column
        for i in range(j+1,n):
            if abs(C[i,j]) > abs(C[p,j]): p = i
        if abs(C[p,j]) < 1.0e-16:
            print "matrix is singular" # its determinant is 0.
            return b
        # swap rows to get largest magnitude element on the diagonal
        C[p,:],C[j,:] = copy(C[j,:]),copy(C[p,:])

        pivot = C[j,j]
        C[j,:] = C[j,:] / pivot
        for i in range(n):
            if i == j: continue
            C[i,:] = C[i,:] - C[i,j]*C[j,:]
    I,x = C[:,0:n],C[:,n]
    return x

if __name__ == '__main__':

    import numpy as np
    data = np.zeros((100,100))
    data[np.arange(99), np.arange(99)+1] = [-1]
    data[np.arange(99)+1, np.arange(99)] = [-1]
    data[np.arange(100), np.arange(100)] = [-2]
    A = data

    data = np.zeros((100,1))
    data[np.arange(98)+1, np.arange(1)] = [1]
    data[np.arange(1), np.arange(1)] = [2]
    data[np.arange(1)+99, np.arange(1)] = [2]
    b =  data

    print "A : \n", A
    print "b : \n", b
    x = GaussJordan(A,b)
    print "x: \n", x
    print "\n\n\n"
    print "-"*40
    print "Ax : \n", dot(A,x)

    print '\n\nresidual', scipy.linalg.norm(numpy.dot(A, x) - b)/scipy.linalg.norm(A)
    tic = time.clock()
    toc = time.clock()
    print "Processing time is : %f" %(toc - tic)


#!/usr/bin/env python3

"""
    Exercise 2.13 Cuadratic Equation Ax**2+Bx+C=0
    Probability it has real roots (B**2-$AC >=0) when A, B, C are selected randomly
    in interval (-q,q) or integers in -q..q
    Solve for q = 1; 10; 100; 1000; 10000
"""

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

def simulate_AX2BXC_real(q, verbose):
    """(real, bool)
    Carry out simulation. Find A, B, C randomly in (-q,q)
    """
    A, B, C = np.random.random(3) * 2 * q - q
    discriminator = B ** 2 - 4 * A * C
    real_root = ( discriminator >= 0)
    if verbose:
        print('REAL q={} A {} B {} C {} discriminator {}  {}'.format(q,A,B,C,discriminator,real_root))
    return real_root

def simulate_AX2BXC_int(q, verbose):
    """(real, bool)
    Carry out simulation. Find A, B, C randomly in (-Q,Q)
    """
    A, B, C = np.random.randint(low=-q, high=q+1, size=3)
    discriminator = B ** 2 - 4 * A * C
    real_root = ( discriminator >= 0)
    if verbose:
        print('INT  q={} A {} B {} C {} discriminator {}  {}'.format(q,A,B,C,discriminator,real_root))
    return real_root

def main():
    # Get command-line arguments
    parser = argparse.ArgumentParser(
        description='simulate the Newspaper arrival problem')
    parser.add_argument('--q', default=1, type=int, metavar='int',
                        help='Interval q (-q,q)')
    parser.add_argument('--trials', default=10000, type=int, metavar='int',
                        help='number of trials to perform')
    parser.add_argument('--real', default=True, action='store_true',
                        help='use (-q,q) as real numbers, otherwise use -Q..Q as Integers')
    parser.add_argument('--verbose', default=False, action='store_true',
                        help='display the results of each trial')
    args = parser.parse_args()

    real_roots = 0
    intg_roots = 0
    for i in range(args.trials):
        # First, do a trial where the contestant never switches.
        real_root = simulate_AX2BXC_real(args.q, verbose=args.verbose)
        if real_root: real_roots += 1
        real_root = simulate_AX2BXC_int(args.q, verbose=args.verbose)
        if real_root: intg_roots += 1

    print('Q={0} REAL has real roots  {1:,} times out of {2:,} ({3:.2f}% of the time)'.format(
            args.q, real_roots, args.trials, (real_roots / args.trials * 100) ))
    print('Q={0} INTG has real roots  {1:,} times out of {2:,} ({3:.2f}% of the time)'.format(
            args.q, intg_roots, args.trials, (intg_roots / args.trials * 100) ))

if __name__ == '__main__':
    main()


#1. Import the NUMPY package under the name np.
import numpy as np

#2. Print the NUMPY version and the configuration.
print (np.__version__)

#3. Generate a 2x3x5 3-dimensional array with random values. Assign the array to variable "a"
# Challenge: there are at least three easy ways that use numpy to generate random arrays. How many ways can you find?
a = np.random.random ((2,3,5))

#4. Print a.
print(a)

#5. Create a 5x2x3 3-dimensional array with all values equaling 1.
#Assign the array to variable "b"
b = np.ones((5,2,3))

#6. Print b.
print(b)

#7. Do a and b have the same size? How do you prove that in Python code?
a = np.random.random ((2,3,5))
print(a.size)

b = np.ones((3,5,2))
print(b.size)
#Yes, both of them have the same size

#8. Are you able to add a and b? Why or why not?

#no  because they have different rows and columns

#9. Transpose b so that it has the same structure of a (i.e. become a 2x3x5 array). Assign the transposed array to varialbe "c".
a = np.random.random ((2,3,5))
b = np.ones((5,2,3))
c = b.transpose((1,2,0))

print(a)
print(b)
print(c)

#10. Try to add a and c. Now it should work. Assign the sum to varialbe "d". But why does it work now?

d = (np.add(a, c))
print(c)
#now it works because both matrixes have the same structure, two subgroups with 3 rows and five columns each

#11. Print a and d. Notice the difference and relation of the two array in terms of the values? Explain.
print(a)
print(d)

#The resulting matrix has the same structure than the previous one, but all the values are now increased by one

#12. Multiply a and c. Assign the result to e.
e = (np.multiply(a, c))
print(e)

#13. Does e equal to a? Why or why not?

#Yes, they are both tha same as yu are multiplying by one. It can be confirmed finding the difference by  doing:
z = (np.subtract(e, a))
print(z)

#14. Identify the max, min, and mean values in d. Assign those values to variables "d_max", "d_min", and "d_mean"

d_mean = (np.mean(d))
d_max = (np.max(d))
d_min = (np.min(d))

print(d_mean)
print(d_min)
print(d_max)

#15. Now we want to label the values in d. First create an empty array "f" with the same shape (i.e. 2x3x5) as d using `np.empty`.

f = np.empty((2,3,5))

f[d<d_mean]=25
f[d>d_mean]=75
f[d==d_min]=0
f[d==d_mean]=50
f[d==d_max]=100

#16. Populate the values in f. For each value in d, if it's larger than d_min but smaller than d_mean, assign 25 to the corresponding value in f.
# If a value in d is larger than d_mean but smaller than d_max, assign 75 to the corresponding value in f.
# If a value equals to d_mean, assign 50 to the corresponding value in f.
# Assign 0 to the corresponding value(s) in f for d_min in d.
# Assign 100 to the corresponding value(s) in f for d_max in d.
# In the end, f should have only the following values: 0, 25, 50, 75, and 100.
# Note: you don't have to use Numpy in this question.
f[d<d_mean]=25
f[d>d_mean]=75
f[d==d_min]=0
f[d==d_mean]=50
f[d==d_max]=100

#17. Print d and f. Do you have your expected f?

print(d)
print(f)

"""
For instance, if your d is:
array([[[1.85836099, 1.67064465, 1.62576044, 1.40243961, 1.88454931],
        [1.75354326, 1.69403643, 1.36729252, 1.61415071, 1.12104981],
        [1.72201435, 1.1862918 , 1.87078449, 1.7726778 , 1.88180042]],
       [[1.44747908, 1.31673383, 1.02000951, 1.52218947, 1.97066381],
        [1.79129243, 1.74983003, 1.96028037, 1.85166831, 1.65450881],
        [1.18068344, 1.9587381 , 1.00656599, 1.93402165, 1.73514584]]])
Your f should be:
array([[[ 75.,  75.,  75.,  25.,  75.],
        [ 75.,  75.,  25.,  25.,  25.],
        [ 75.,  25.,  75.,  75.,  75.]],
       [[ 25.,  25.,  25.,  25., 100.],
        [ 75.,  75.,  75.,  75.,  75.],
        [ 25.,  75.,   0.,  75.,  75.]]])
"""

"""
#18. Bonus question: instead of using numbers (i.e. 0, 25, 50, 75, and 100), how to use string values
("A", "B", "C", "D", and "E") to label the array elements? You are expecting the result to be:
array([[[ 'D',  'D',  'D',  'B',  'D'],
        [ 'D',  'D',  'B',  'B',  'B'],
        [ 'D',  'B',  'D',  'D',  'D']],
       [[ 'B',  'B',  'B',  'B',  'E'],
        [ 'D',  'D',  'D',  'D',  'D'],
        [ 'B',  'D',   'A',  'D', 'D']]])
Again, you don't need Numpy in this question.
"""


# region Starting Values
# need for multiplicating matrices in the end
import numpy as np
np.set_printoptions(suppress=True)

matrix = [[3.81, 0.25, 1.28, 2.75],
          [2.25, 1.32, 6.58, 0.49],
          [5.31, 8.28, 0.98, 1.04],
          [11.39, 2.45, 3.35, 2.28]]

rightPart = [4.21, 8.47, 2.38, 12.48]

matrixDiagonal =    [[15.2, 4.63, 2.7, 5.03],
                    [2.25, 6.58, 1.32, 0.49],
                    [5.31, 0.98, 8.28, 1.04],
                    [0, 0.119454, -0.426777, 1.48919]]

rightPartDiagonal = [16.69, 8.47, 2.38, 0.026518]

N = len(matrixDiagonal)
difference = [0] * N
X = [0] * N
rounding = 6
vectorToShow = 3
doOperations = True
epsilonValue = 0.000001 # 10^(-6)
iterations = 0

# endregion Starting Values

# copy matrix to create extended matrix
extendedMatrix = list(map(list, matrixDiagonal))

# region Prints

# Print vector
def PrintVector(vectorName,vector):
    print("\n",vectorName,"=")
    for i in vector:
        print(i, end = " \t")
    print()

# print matrix
def PrintMatrix(matrixName,matrix):
    print("\n",matrixName,"=")
    for i in matrix:
        for j in i:
            print(j, end=" \t")
            if len(str(j)) <= 5:
                print(end=" \t")
        print()

# print additional parametrs
def PrintParametrs():
    print("\n Rows =", rows)
    print(" Columns =", columns)
    print(" Exteneded Rows =", extendedRows)
    print(" Exteneded Columns =", extendedColumns)
    print(" n =", N)

# just printing
def PrintAll():
    PrintMatrix("Start Matrix",matrixDiagonal)

    PrintVector("Right Part", rightPartDiagonal)

    PrintParametrs()

    PrintMatrix("Extended Matrix",extendedMatrix)

# endregion Prints

# region Check the results
def Residual():
    multiplied = np.round(np.dot(matrixDiagonal,X),rounding)

    R = np.round(np.subtract(rightPartDiagonal,multiplied),rounding)

    print("R =\n",R)

# endregion Check the results

# add right part to main matrix
RPCounter = 0
while RPCounter < len(rightPartDiagonal):
    extendedMatrix[RPCounter].append(rightPartDiagonal[RPCounter])
    RPCounter += 1

# region Getting rows and columns

rows = len(matrixDiagonal)
columns = len(matrixDiagonal)

extendedRows = len(extendedMatrix)
extendedColumns = len(extendedMatrix)+1

# endregion Getting rows and columns

PrintAll()

while doOperations:
    tempX = X.copy()
    for c in range(0, N):
        #temporal variable to store rightPart element
        element  = rightPartDiagonal[c]

        # calculate every element in array
        for z in range(0, N):
            if(c != z):
                element  = element - (matrixDiagonal[c][z] * X[z])
        # create new value
        X[c] = round(element  / matrixDiagonal[c][c], rounding)

    # compare two vectors
    for k in range(N):
        difference[k] = abs(tempX[k] - X[k])
        if max(difference) < epsilonValue:
            doOperations = False

    # show first three iterations of vector X
    if iterations < vectorToShow:
        PrintVector("X", X)
    iterations = iterations + 1
    if doOperations:
        Residual()

print("\nFinal results:")
PrintVector("X", X)
Residual()
print("Total iterations = ", iterations)


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

import numpy as np
import numpy.matlib as npmatlib

def print_this_matrix(given_matrix):
  for i in range(len(given_matrix)):
   for j in range(len(given_matrix[i])):
    print (given_matrix[i][j]," ",end="")
   print()
  print("\n")

def print_this_string_matrix(given_matrix):
  for i in range(len(given_matrix)):
   for j in range(len(given_matrix[i])):
    print (given_matrix[i][j],end="")
   print()
  print("\n")

print("generatng matrix for given n x m - with given default values")
print(npmatlib.ones((5,6)))
print("generatng matrix for given n x m - with given default values")
print(npmatlib.zeros((4,3)))

# returns a matrix with 1 along the diagonal elements and the zeros elsewhere.
# K =  start index to start first 1
print(np.matlib.eye(n = 5, M = 8, k = 6, dtype = float))
print(np.matlib.eye(n = 5, M = 5, k = 3, dtype = float))

print("generatng identity matrix for given n x n - with given default values")
print(np.matlib.identity(5, dtype = float))

print("generatng identity matrix for given n x n - with given default values")
print(np.matlib.rand(3,3))

print("generatng matrix for given n x m - with random values")
print(np.matlib.rand(3,3))

print("generatng matrix from given array")
arr_now = np.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
print(np.matrix(arr_now))
arr_now = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11],[12,13,14,15]])
print(np.matrix(arr_now))

arr_now = np.array([[0,1,2,3],[4,5,6,7],[8,9,10,11],[12,13,14,15]])
print("as matrix from given array -- \n", np.asmatrix(arr_now))

matrx_now = np.matlib.ones((5,6))
print("as array from given matrix -- \n", np.asarray(matrx_now))

# inner product
arr_normal = np.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
print("arr_normal =   \n",arr_normal)
print("inner product  \n",np.inner(arr_normal,arr_normal))

arr_2d_1 = [[ 0,  1,  2,  3],
            [ 4,  5,  6,  7],
            [ 8,  9, 10, 11],
            [12, 13, 14, 15]]
arr_2d_2 = [[ 0,  1,  2,  3],
            [ 4,  5,  6,  7],
            [ 8,  9, 10, 11],
            [12, 13, 14, 15]]
print("arr_2d_1 =   \n",arr_2d_1)
print("arr_2d_2 =   \n",arr_2d_2)
print("np.matmul(arr_2d_1,arr_2d_2) =   \n",np.matmul(arr_2d_1,arr_2d_2))

# solving linear equation
aaa=   2.0
bbb= - 9.0
ccc=  55.0
print(( 9 * aaa) + (    bbb) - (10 * ccc)) # -541
print((-8 * aaa) - (5 * bbb) - ( 0 * ccc)) #  029
print(( 9 * aaa) + (3 * bbb) + ( 1 * ccc)) #  046

eq1      = np.array([[9,1,-10],[-8,-5,0],[9,3,1]])
eq_total = np.array([-541,29,46])
print("np.linalg.solve(eq1, eq_total) =   \n",np.linalg.solve(eq1, eq_total))

arr_extreme = np.array([[ 4,   5,  6,   7],
                        [ 3,  -1, -2,  -3],
                        [ 8,   9,  10,  11],
                        [12,   13, 14,  15]])
print("np.amax(arr_extreme)                    --->",np.amax(arr_extreme))
print("np.amax(arr_extreme, axis=0)--vertical  --->",np.amax(arr_extreme, axis=0))
print("np.amax(arr_extreme, axis=1)--horizontal--->",np.amax(arr_extreme, axis=1))
print("np.amax(arr_extreme, axis=1)--horizontal--->",np.amax(arr_extreme, axis=1))

# flatten : return a copy of the array collapsed into one dimension
arr_extreme.flatten()
print("just index of sorted element")
print("np.argmax(arr_extreme, axis=1)--horizontal--->",np.argmax(arr_extreme, axis=1))

print("numbers sorting using numpy--->")
arr_tosort = np.array([[ 4.,   5.,  6,  -7],
                       [ 3.,  -1., -2,  -3],
                       [ 8.,   9.,  10, -11],
                       [12.,   13.,-14,  15]],dtype='float64')
print_this_matrix(arr_tosort)

print("np.sort() does not change original array --->\n")
print("np.sort(arr_tosort)                          --->\n",np.sort(arr_tosort))
print("np.sort(arr_tosort,axis = 0)   vertically    --->\n",np.sort(arr_tosort,axis = 0))
print("np.sort(arr_tosort,axis = 1)   horizontally  --->\n",np.sort(arr_tosort,axis = 1))
print("sort on a row --->\n",arr_tosort[arr_tosort[:,1].argsort()])

print_this_matrix(arr_tosort)
print("array_name.sort() changes original array --->")
arr_tosort.sort()
print("after arr_tosort.sort() --->")
print_this_matrix(arr_tosort)
print("\n")

print("string sorting using numpy--->")
input_string_array = np.array(["year","month","eye","i","stream","key","house"])
print("input string array")
print_this_string_matrix(input_string_array)
input_string_array_sorted_index = list(map(lambda x: len(x), input_string_array))
output_string_array = input_string_array[np.argsort(input_string_array_sorted_index)]
print("output string array sorted ---> ")
print_this_string_matrix(output_string_array)
print("\n")

arr_where = np.array([[ 4.,   5.,  6,  -7],
                      [ 3.,  -1., -2,  -3],
                      [ 8.,   9.,  10, -11],
                      [12.,   13.,-14,  15]],dtype='float64')
print_this_matrix(arr_where)
arr_idx_greater_than = np.where(arr_where > 5.5)
print("indices of elements greater than 5.5     --->",arr_idx_greater_than)
print("all actual elements greater than 5.5     --->",arr_where[arr_idx_greater_than])
print("shape of resultant array is always 1d    --->",arr_where[arr_idx_greater_than].shape)
print("\n")

print("numpy extract elements out of an array with a condition")
arr_extract = np.array([[ 4.,   5.,  6,  -7],
                        [ 3.,  -1., -2,  -3],
                        [ 8.,   9.,  10, -11],
                        [12.,   13.,-14,  15]],dtype='float64')
print_this_matrix(arr_where)
condition = np.mod(arr_extract,2.0) == 0
print("condition = np.mod(arr_extract,2.0) == 0  ----> ",np.mod(arr_extract,2.0) == 0)
print("np.extract(condition, arr_extract)        ----> ",np.extract(condition, arr_extract))
print("\n")

print("save array output to a .npy file")
arr_saveme = np.array([[ 4.,   5.,  6,  -7],
                       [ 3.,  -1., -2,  -3],
                       [ 8.,   9.,  10, -11],
                       [12.,   13.,-14,  15]],dtype='float64')
np.save('__arr_saveme',arr_saveme)
print("load array output from a .npy file \n",np.load('__arr_saveme.npy'))
print("\n")


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

import numpy as np
import matplotlib.pyplot as plt

#%% simulate Brownian Motion
T = 4
N = 1001

t = np.linspace(0,T,N)
k = t[1]

num_trajectories = 1

dWt = np.random.randn(num_trajectories,N)*np.sqrt(k)
dWt[:,0] = 0

Wt = np.cumsum(dWt,axis=1)

plt.figure()
plt.plot(t,Wt.T,color='k',alpha=0.01)
plt.show()

#%% check statistics
mean = np.mean(Wt,axis=0)
var = np.var(Wt,axis=0)

plt.figure()
plt.plot(t,mean)
plt.show()

plt.figure()
plt.plot(t,var)
plt.show()

plt.figure()
plt.hist(Wt[:,-1])
plt.show()

#%% euler-maruyama method
def euler_maruyama(mu,sigma,x0,t,Wt):
    N = len(t)
    x = np.zeros(N)
    x[0] = x0
    for i in range(N-1):
        dt = t[i+1] - t[i]
        dWt = Wt[i+1] - Wt[i]
        x[i+1] = x[i] + mu(t,x[i])*dt + sigma(t,x[i])*dWt
    return x

#%% Geometric Brownian Motion
u = 1
s = 1

def mu(t,x):
    return u*x

def sigma(t,x):
    return s*x

def x_true_(x0,t,Wt):
    return x0*np.exp((u-s**2/2)*t+s*Wt)

#%% Solve over generated BM trajectories
x0 = 1
x_gbm = euler_maruyama(mu,sigma,x0,t,Wt[0])
x_true = x_true_(x0,t,Wt[0])

plt.figure()
plt.plot(t,x_gbm)
plt.plot(t,x_true)

#%% OU Process
theta = 10
u = 1
s = 0.5

def mu(t,x):
    return theta*(u-x)

def sigma(t,x):
    return s

#%% Solve over generated BM trajectories
x0 = 0
plt.figure()
for wt in Wt:
    x_ou = euler_maruyama(mu,sigma,x0,t,wt)
    plt.plot(t,x_ou,color='k',alpha=.01)
plt.show()


import os
import numpy as np
import matplotlib.pyplot as plt
import scipy.optimize as op
from ex2_utils import plotData, plotDecisionBoundary, sigmoid, predict

## Machine Learning Online Class - Exercise 2: Logistic Regression

def costFunction(initial_theta, X, y):
    m = y.size
    hx = sigmoid(X @ initial_theta)
    J = (- y @ np.log(hx) - (1 - y) @ np.log(1 - hx))/m
    grad = X.T @ (hx - y) / m
    return (J, grad)

# Load Data
#  The first two columns contains the exam scores and the third column
#  contains the label.
scriptdir = os.path.dirname(os.path.realpath(__file__))
data = np.loadtxt(scriptdir + '//ex2data1.txt', delimiter=',')
X = data[:,:-1]
y = data[:,-1]

# ==================== Part 1: Plotting ====================
#  We start the exercise by first plotting the data to understand the
#  the problem we are working with.

print('Plotting data with + indicating (y = 1) examples and o indicating (y = 0) examples.')

plotData(X, y)
# Labels and Legend
plt.legend(['Admitted', 'Not Admitted'])
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.show()
input('\nProgram paused. Press enter to continue.\n')

# ============ Part 2: Compute Cost and Gradient ============
#  Setup the data matrix appropriately, and add ones for the intercept term
m, n = X.shape
# Add intercept term to x and X_test
X = np.c_[np.ones(m), X]

# Initialize fitting parameters
initial_theta = np.zeros(n + 1)

# Compute and display initial cost and gradient
cost, grad = costFunction(initial_theta, X, y)

print(f'Cost at initial theta (zeros): {cost}')
print('Expected cost (approx): 0.693')
print('Gradient at initial theta (zeros):')
print(grad)
print('Expected gradients (approx):\n -0.1000\n -12.0092\n -11.2628\n')

# Compute and display cost and gradient with non-zero theta
test_theta = [-24, 0.2, 0.2]
cost, grad = costFunction(test_theta, X, y)

print(f'Cost at test theta: {cost}')
print(f'Expected cost (approx): 0.218')
print(f'Gradient at test theta:')
print(grad)
print('Expected gradients (approx):\n 0.043\n 2.566\n 2.647\n')

input('Program paused. Press enter to continue.')

# ============= Part 3: Optimizing using fminunc  =============
Result = op.minimize(fun = costFunction, x0 = initial_theta, args = (X, y), method = 'TNC', jac = True)
cost = Result.fun
theta = Result.x
# Print theta to screen
print(f'Cost at theta found by minimize: {cost}')
print('Expected cost (approx): 0.203')
print(f'theta: {theta}')
print('Expected theta (approx):')
print(' -25.161\n 0.206\n 0.201\n')

# Plot Boundary
plotDecisionBoundary(theta, X, y)
# Labels and Legend

plt.legend(['Admitted', 'Not Admitted', 'Decision Boundary'])
plt.xlabel('Exam 1 score')
plt.ylabel('Exam 2 score')
plt.show()

input('Program paused. Press enter to continue.\n')

# ============== Part 4: Predict and Accuracies ==============
#  Predict probability for a student with score 45 on exam 1
#  and score 85 on exam 2

prob = sigmoid([1, 45, 85] @ theta)
print(f'For a student with scores 45 and 85, we predict an admission probability of {prob}')
print('Expected value: 0.775 +/- 0.002\n')

# Compute accuracy on our training set
p = predict(theta, X)

print(f'Train Accuracy: {np.mean((p == y)) * 100}')
print('Expected accuracy (approx): 89.0\n')


# coding: utf-8

# ## Creating, Adding, Split and Removing arrays
# Numpy, short for Numerical Python, is the fundamental package required for hight performance scientific computing and its best library to learn and apply on data science career.
# ### Here are the main steps we will go through
# * How to create array in numpy?
# * How to add two arrays together?
# * How to delete array in numpy?
# * Inserts values into arr given index
# * Split array in given index
#
# This is just little illustration.
#

# #### How to create array in numpy?
# arange([start,] stop[, step,][, dtype]) :
#
# arange() will create arrays with regularly incrementing values. Check the docstring for complete information on the various ways it can be used. A few examples will be given here:

# In[8]:

import numpy as np
# we can use the first argument [start]
arr = np.arange(5)
arr

# In[17]:

# we can pass [start] stop[step]
arr2 = np.arange(1, 10)
print("array containing [start(1) - end(10)]: ",arr2)

#apply step
arr3 = np.arange(1, 10, 2)
print("array containing [start(1) - end(10) - step(2)]: ", arr3)

# In[20]:

# we can print shape of the array and as well as dtype
shp = np.arange(1,10)
print("Shape of array: ",shp.shape )
# dtype
dty = np.arange(1,20)
print("Dtype: ", dty.shape)

# ##### creating 2D array
# np.array
#
# NumPy’s array class is called ndarray. It is also known by the alias array. Note that numpy.array is not the same as the Standard Python Library class array.array, which only handles one-dimensional arrays and offers less functionality. Check the docstring for complete information on the various ways it can be used.

# In[43]:

# we can create 2-dimention array
d_2 = np.array([[1,2,3],[4,5,6]])
d_2
print("2D shape: ", d_2.shape)
# we can use random function
rnd = np.random.random(9).reshape(3,3)
rnd
print("random array: ", rnd.shape)

# #### How to add two arrays together?
# numpy.concatenate((a1, a2, ...), axis=0)
#
# Join a sequence of arrays along an existing axis. Check the docstring for complete information on the various ways it can be used.

# In[57]:

array_1 = np.array([[1, 2], [3, 4]])
array_2 = np.array([[5, 6], [7, 8]])
array_1

# In[58]:

array_2

# In[54]:

# we can add array_2 as rows to the end of array_1
# axis 0 = rows
np.concatenate((array_1, array_2), axis=0)

# In[55]:

# we can add array_2 as columns to end of array_1
# axis 1 = columns
np.concatenate((array_1, array_2), axis=1)

# #### How to delete array in numpy?
# numpy.delete(arr, obj, axis=None)
#
# Return a new array with sub-arrays along an axis deleted. Check the docstring for complete information on the various ways it can be used.

# In[78]:

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

# In[77]:

# column 2: [3 and 6]
# we can delete columm on index 2 of array
del_arry = np.delete(del_arry, 2, axis=1)
del_arry

# In[79]:

# row 1: [4, 5, 6]
# we can delete row on index 1 of the array
del_arry = np.delete(del_arry, 1, axis=0)
del_arry

# #### Inserts values into arr given index?
# numpy.insert(arr, obj, values, axis=None)
#
# Insert values along the given axis before the given indices. Check the docstring for complete information on the various ways it can be used.

# In[94]:

insert_array = np.array([[1,2,3],[4,5,6]])
# we can insert values into array index 6 - at the end
insert_array = np.insert(insert_array, 6, 10)
# we can also insert at the begining
insert_array = np.insert(insert_array, 0, 100)
insert_array

# In[106]:

# we can fill up the whole column given value
insert_2 = np.arange(0,9).reshape(3,3)
print("original array:")
print(insert_2)

# we can insert 0s in second column
insert_2 = np.insert(insert_2, 1, 0, axis=1)
print("\nafter inserting 0's on the first column:")
print(insert_2)

# In[119]:

# we can also insert list as well
list_array = np.arange(0,9).reshape(3,3)
list_array = np.insert(list_array, [1], [[10],[10],[10]], axis=1)
list_array = np.insert(list_array, [1], 10, axis=0)
list_array

# #### Split array in given index?
# numpy.split(ary, indices_or_sections, axis=0)
#
# Split an array into multiple sub-arrays. Check the docstring for complete information on the various ways it can be used.

# In[142]:

or_array = np.array([[1,2,3],[4,5,6]])
print("Orignal array:\n ",or_array)
#splits arr into 3 sub-arrays
split_array = np.split(or_array, 2)
print("\nwe have our array splitted into two arrays")
split_array

# In[149]:

copy_array = np.arange(16.0).reshape(4, 4)
#splits arr horizontally on the 5th index
print("copy array:\n",copy_array)

# we splits our array into horizontal on the given index
h_split = np.hsplit(copy_array, 2)
h_split

# In[154]:

# we can also split array into vertical on the given index
h_split = np.vsplit(copy_array, 2)
h_split