import numpy as np

class LogisticRegression:
    """
    Classification using logistic regression
    """

    def __init__(self,table,reg=False,lamda=0,degree = 1):
        """Initializes Class for Logistic Regression

        Parameters
        ----------
        table : ndarray(n-rows,m-features + 1)
            Numerical training data, last column as training labels
        reg : Boolean
            Set True to enable regularization, false by default
        degree: int
            Degree of polynomial to fit to the data. Default is 1.

        """
        # Regularization Parameters
        self.reg=reg
        self.lamda=lamda
        # training data
        self.degree=degree
        self.table = table
        # num of rows in the training data
        self.num_training = np.shape(table)[0]
        # map the features according to the degree of the fit
        self.X = self.map_features()
        self.num_features = np.shape(self.X)[1]
        # extract last column from training data table
        self.y = table[:,-1]
        # craete an array of parameters initialzing them to 1
        self.theta = np.zeros(self.num_features)

    def map_features(self):
        """
        Generates polynomial features based on the degree
        """
        X = self.table[:,0]
        Y = self.table[:,1]
        # First column is ones for calculation of intercept
        features = np.ones(self.num_training)

        for i in range(1,self.degree+1):
                for j in range(0,i+1):
                    col = np.power(X,i-j)*np.power(Y,j)
                    features = np.column_stack((features,col))
        return features

    @staticmethod
    def sigmoid(val):
        """Computes sigmoid function of input value

        Parameters
        ----------
        val : float
              Value of which sigmoid is to calculate
        Returns
        -------
        val : float
            Sigmoid value of the parameter

        """
        return float(1) / (1 + np.exp(-val))

    def compute_cost(self):
        """Computes cost based on the current values of the parameters

        Returns
        -------
        cost : float
            Cost of the selection of current set of parameters

        """
        hypothesis = LogisticRegression.sigmoid(np.dot(self.X, self.theta))
        #new ndarray to prevent intercept from theta array to be changed
        theta=np.delete(self.theta,0)
        #regularization term
        reg = (self.lamda/2*self.num_training)*np.sum(np.power(theta,2))
        cost = -(np.sum(self.y * np.log(hypothesis) + (1 - self.y) * (np.log(1 - hypothesis)))) / self.num_training
        #if regularization is true, add regularization term and return cost
        if self.reg:
            return cost + reg
        return cost

    def gradient_descent(self, num_iters=1000, alpha=0.01):
        """Runs the gradient descent algorithm

        Parameters
        ----------
        num_iters : int
            The maximum number of iterations allowed to run before the algorithm terminates
        alpha : float
            The learning rate for the algorithm

        Returns
        -------
        self.theta: ndarray(self.features)
            Array of parameters after running the algorithm
        """
        old_cost=0
        for i in range(0, num_iters):
            hypothesis = LogisticRegression.sigmoid(np.dot(self.X, self.theta))
            loss = hypothesis - self.y
            cost = self.compute_cost()
            #if the cost is equal for two iterations, break from the loop
            if cost==old_cost:
                break
            old_cost = cost
            #print "Iteration: %d Cost: %f" % (i, cost)
            gradient = np.dot(self.X.T, loss) / self.num_training
            #regularization term
            reg = (self.lamda/self.num_training)*self.theta
            if self.reg:
                self.theta = self.theta - alpha * (gradient+reg)
            else:
                self.theta = self.theta - alpha * gradient
        return self.theta

    def predict(self, data, prob=False):
        """Computes the logistic probability of being a positive example

        Parameters
        ----------
        data : ndarray (n-rows,n-features)
            Test data to score using the current weights
        prob : Boolean
            If set to true, probability will be returned, else binary classification
        Returns
        -------
        0 or 1: int
            0 if probablity is less than 0.5, else 1
        """
        data = np.column_stack((np.ones(data.shape[0]), data))

        hypothesis = LogisticRegression.sigmoid(np.dot(data, self.theta))
        if not prob:
            return np.where(hypothesis >= .5, 1, 0)
        return hypothesis

    def accuracy(self):
        """Calculates percentage of correct predictions by the model on training data

        Returns
        -------
        accuracy : float
            Percentage of correct predictions on the features of training data
        """
        #delete extra ones column that was added
        x = np.delete(self.X, 0, 1)
        predicted = self.predict(x)
        match = float(np.sum(self.y == predicted))
        return (match / self.num_training) * 100

    def plot_data(self):
        """Plot the training data in X array
        """
        from matplotlib import pyplot as plt
        plt.scatter(self.X[:, 1], self.X[:, 2], s=40, c=self.y, cmap=plt.cm.Spectral)
        plt.show()

    def plot_fit(self):
        """Plot the training data in X array along with decision boundary
        """
        from matplotlib import pyplot as plt
        x1 = np.linspace(self.table.min(), self.table.max(), 100)
        #reverse self.theta as it requires coeffs from highest degree to constant term
        x2 = np.polyval(np.poly1d(self.theta[::-1]),x1)
        plt.plot(x1, x2, color='r', label='decision boundary');
        plt.scatter(self.X[:, 1], self.X[:, 2], s=40, c=self.y, cmap=plt.cm.Spectral)
        plt.legend()
        plt.show()


"""
  Name     : c14_07_mvn_multiVariateNormalGraph.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#
# input area
n   = 60                      # number of intervals
x   = np.linspace(-3, 3, n)   # x dimention
y   = np.linspace(-3, 4, n)   # y dimention
x,y = np.meshgrid(x, y)       # grid
#
# Mean vector and covariance matrix
mu = np.array([0., 1.])
cov= np.array([[ 1. , -0.5], [-0.5,  1.5]])
#
# combine x and y into a single 3-dimensional array
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x
pos[:, :, 1] = y
#
def multiNormal(pos, mu, cov):
    n = mu.shape[0]
    Sigma_det = np.linalg.det(cov)
    Sigma_inv = np.linalg.inv(cov)
    n2 = np.sqrt((2*np.pi)**n * Sigma_det)
    fac = np.einsum('...k,kl,...l->...', pos-mu, Sigma_inv, pos-mu)
    return np.exp(-fac/2)/n2
#
z    = multiNormal(pos, mu, cov)
fig  = plt.figure()
ax   = fig.gca(projection='3d')
ax.plot_surface(x, y, z, rstride=3, cstride=3,linewidth=1, antialiased=True,cmap=cm.viridis)
cset = ax.contourf(x, y, z, zdir='z', offset=-0.15, cmap=cm.viridis)
ax.set_zlim(-0.15,0.2)
ax.set_zticks(np.linspace(0,0.2,5))
ax.view_init(27, -21)
plt.title("Bivariate normal distribtuion")
plt.ylabel("y values ")
plt.xlabel("x values")
plt.show()


"""
conceptualQ3.py

Plot the classification error, Gini index, and cross-entropy for a two-class
problem as p_m1 goes from 0 to 1
"""
import math
import numpy as np
import matplotlib.pyplot as plt
def classificationError(p_m1):
    """
    This is defined as E 1 - max_k{p_mk}
    """
    p_m2 = 1 - p_m1
    E = 1 - max(p_m1, p_m2)  # You could do E=min(p_m1, p_m2) instead
    return E

def giniIndex(p_m1):
    """
    G = sum_k { p_mk(1-p_mk }
    """
    G = p_m1*(1-p_m1)*2
    return G

def crossEntropy(p_m1):
    """
    D = - sum_k { p_mk*log(p_mk) }
    """
    p_m2 = 1 - p_m1
    D = - p_m1*math.log(p_m1) - p_m2*math.log(p_m2)
    return D

classificationErrors = []
ginis = []
crossEntropies = []
p_m1s = np.linspace(0.001,0.999)
for p_m1 in p_m1s:
    print(p_m1)
    classificationErrors.append(classificationError(p_m1))
    ginis.append(giniIndex(p_m1))
    crossEntropies.append(crossEntropy(p_m1))

plt.scatter(p_m1s, classificationErrors)
plt.scatter(p_m1s, ginis)
plt.scatter(p_m1s, crossEntropies)
plt.show()


from __future__ import division
import scipy as sp
import scipy.linalg as la

def hqr(A):
	"""Finds the QR decomposition of A using Householder reflectors.
	input: 	A, mxn array with m>=n
	output: Q, orthogonal mxm array
	        R, upper triangular mxn array
	        s.t QR = A
	"""
	R = A.copy()
	m,n = R.shape
	Q = sp.eye(m,m)
	for k in sp.arange(n-1):
		v = R[k:m,k].copy()
		v[0] += sp.sign(v[0])*la.norm(v)
		v = v/la.norm(v)
		v = v.reshape(m-k,1)
		P = sp.eye(m,m)
		P[k:m,k:m] -= 2*sp.dot(v,v.T)
		Q = sp.dot(P,Q)
		R = sp.dot(P,R)
	return Q.T,R

def hess(A):
	"""Computes the upper Hessenberg form of A using Householder reflectors.
	input:  A, mxn array
	output: Q, orthogonal mxm array
			H, upper Hessenberg
			s.t. QHQ' = A
	"""
	H = A.copy()
	m,n = H.shape
	Q = sp.eye(m,m)
	for k in sp.arange(n-2):
		v = H[k+1:m,k].copy()
		v[0] += sp.sign(v[0])*la.norm(v)
		v = v/la.norm(v)
		v = v.reshape(m-k-1,1)
		P = sp.eye(m,m)
		P[k+1:m,k+1:m] -= 2*sp.dot(v,v.T)
		Q = sp.dot(P,Q)
		H = sp.dot(P,sp.dot(H,P.T))
	return Q.T,H

def gqr(A):
	"""Finds the QR decomposition of A using Givens rotations.
	input: 	A, mxn array with m>=n
	output: Q, orthogonal mxm array
	        R, upper triangular mxn array
	        s.t QR = A
	"""
	def rotate(i,k,B):
	# create the Givens rotation matrix G to zero out the i,k entry of B
		c,s,r = solve(B[k,k],B[i,k])
		r = sp.sqrt(B[k,k]**2 + B[i,k]**2)
		c = B[k,k]/r
		s = -B[i,k]/r
		G = sp.eye(m)
		G[i,i] = c
		G[k,k] = c
		G[k,i] = -s
		G[i,k] = s
		return G

	B = A.copy()
	m,n = B.shape
	G = sp.eye(m)
	#cycle through each nonzero subdiagonal element of B, and rotate it to zero
	for k in sp.arange(n-1):
		for i in sp.arange(k+1,m):
			if B[i,k] is not 0:
				H = rotate(i,k,B)
				B = sp.dot(H,B)
				G = sp.dot(H,G)
	return G.T, B


import matplotlib.pylab as plt
import numpy as np
from matplotlib import cm
import scipy.optimize

def steepest_descent(x, f, grad_f, hessian_f):
    return -grad_f(x)

def newton(x, f, grad_f, hessian_f):
    A = hessian_f(x)
    return -np.linalg.solve(A, grad_f(x))

def backtracking(f, grad_f, x, p):
    alpha = 1.0
    c = 1e-4
    rho = 0.5
    f_x = f(x)
    c_grad_f_x_p = c*grad_f(x).dot(p)
    while f(x + alpha*p) > f_x + alpha * c_grad_f_x_p:
        alpha = rho * alpha
    return alpha

def scipy_line_search(f, grad_f, x, p):
    return scipy.optimize.line_search(f, grad_f, x, p)[0]

def minimise(f, grad_f, hessian_f, x0, tol=1e-5, max_iter=1000, search_direction=steepest_descent, line_search=backtracking):
    x = np.copy(x0)
    alpha = 1.0
    p = np.zeros_like(x)
    p[0] = 1.
    i = 0
    array_x = np.empty((max_iter+1, len(x)), dtype=float)
    array_alpha = np.empty(max_iter, dtype=float)
    array_x[0, :] = x
    while np.linalg.norm(alpha * p) > tol and i < max_iter:
        p = search_direction(x, f, grad_f, hessian_f)
        alpha = line_search(f, grad_f, x, p)
        x += alpha * p
        array_x[i+1, :] = x
        array_alpha[i] = alpha
        i += 1
    return array_x[:i,:], array_alpha[:i]

def rosenbrock(x, y):
    return (1 - x)**2 + (y - x**2)**2

def rosenbrock_2d(x):
    return (1 - x[0])**2 + (x[1] - x[0]**2)**2

def grad_rosenbrock_2d(x):
    return np.array([
        4*x[0] * (x[0]**2 - x[1]) + 2*x[0] - 2,
        -2*x[0]**2 + 2*x[1]
    ])

def hessian_rosenbrock_2d(x):
    return np.array([
        [12 * x[0]**2 - 4*x[1] + 2, -4*x[0]],
        [-4*x[0], 2],
    ])

# verify grad and hessian using finite differences
h = 1e-5
x0 = np.array([0., 0.])
x0_x = np.array([h, 0.])
x0_y = np.array([0., h])
fd_grad_rosenbrock = np.array([
    (rosenbrock_2d(x0_x) - rosenbrock_2d(x0)) / h,
    (rosenbrock_2d(x0_y) - rosenbrock_2d(x0)) / h,
])
np.testing.assert_almost_equal(fd_grad_rosenbrock, grad_rosenbrock_2d(x0), decimal=4)
fd_hessian_rosenbrock = np.vstack((
    (grad_rosenbrock_2d(x0_x) - grad_rosenbrock_2d(x0)) / h,
    (grad_rosenbrock_2d(x0_y) - grad_rosenbrock_2d(x0)) / h,
))
np.testing.assert_almost_equal(fd_hessian_rosenbrock, hessian_rosenbrock_2d(x0),
                               decimal=4)

# plot rosenbrock surface
x = np.linspace(-1.5, 2.0, 100)
y = np.linspace(0.2, 3.0, 100)
X, Y = np.meshgrid(x, y)
Z = np.vectorize(rosenbrock)(X, Y)

fig = plt.figure()
ax = fig.gca(projection='3d')
fig2 = plt.figure()
ax2 = fig2.add_subplot(1,1,1)

surf = ax.plot_surface(X, Y, Z, alpha=0.5, cmap=cm.coolwarm)

# pick two initial starting points
x01 = np.array([1.2, 1.2])
x02 = np.array([-1.2, 1])

# run all methods and plot results
for x0 in [x01, x02]:
    for p_method in [steepest_descent, newton]:
        for ls_method in [backtracking, scipy_line_search]:
            label = '{}-{}'.format(p_method.__name__, ls_method.__name__)
            pos, alpha = \
                minimise(rosenbrock_2d, grad_rosenbrock_2d, hessian_rosenbrock_2d, x0,
                         search_direction=p_method, line_search=ls_method)
            print('method', label, 'finished in', len(alpha), 'iterations')
            xs = pos[:, 0]
            ys = pos[:, 1]
            ax.plot(xs, ys, rosenbrock(xs, ys), label=label, linewidth=2)
            ax2.plot(alpha, label=label)
ax2.legend()
plt.show()


"""Helper functions for linear algebra."""
import numpy as np

def modified_gram_schmidt(v):
    """Orthonormalise vectors using the modified Gram--Schmidt process.

    Parameters
    ----------
    v : (n, m) ndarray
        horizontally stacked vectors of dimension n. It is assumed that m <= n.

    Returns
    -------
    (n, m) ndarray
        orthonormalised vectors such that span(w) = span(v)

    """
    w = np.empty(v.shape)
    for k in range(v.shape[1]):
        w[:, k] = v[:, k]
        for j in range(k):
            w[:, k] -= np.dot(w[:, j], v[:, k]) * w[:, j]
        w[:, k] /= np.linalg.norm(w[:, k])
    return w

def projector_onto_kernel(a):
    """Compute the orthogonal projection matrix P onto the kernel of A.

    This function uses an SVD-based method described in Golub.

    If all rows of A are linearly independent,
    you can probably use a simpler/faster method.

    Parameters
    ----------
    a : (n, m) ndarray
        input matrix A. It is assumed that n <= m

    Returns
    -------
    (m, m) ndarray
        orthogonal projector onto the kernel of A

    """
    r = np.linalg.matrix_rank(a)
    _, _, v = np.linalg.svd(a)
    v_tilde = v.T[:, r:]
    return v_tilde @ v_tilde.T

def online_variance(iterator, item_shape):
    """Compute the elementwise variance in one pass using an online algo.

    See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance

    Parameters
    ----------
    iterator : iterator
        an iterator yielding ndarrays of the same shape

    item_shape : tuple of ints
        a shape specification as a tuple

    Returns
    -------
    ndarray
        elementwise variance of the items in the iterator

    """
    n = 0
    mean = np.zeros(item_shape)
    m2 = np.zeros(item_shape)
    for item in iterator:
        n += 1
        delta = item - mean
        mean += delta/n
        delta2 = item - mean
        m2 += delta*delta2

    if n < 2:
        return float('nan')
    else:
        return m2 / n

def log_rel_error(testee, reference):
    """Compute the log relative error of a vector to some reference.

    Be careful if reference is near zero!

    Parameters
    ----------
    testee : (n,) ndarray
        an inexact result

    reference : (n,) ndarray
        an exact reference

    Returns
    -------
    float
        log relative error, i.e. log10(||testee - reference|| / ||reference||)

    """
    res = np.log10(np.linalg.norm(testee - reference))
    res -= np.log10(np.linalg.norm(reference))
    return res


"""
In this script, I write useful method for Skill Set Challenge Hudson & Thames
"""

import numpy as np

def compute_log_returns(prices):
    """
    Compute log returns for each ticker.

    Parameters
    ----------
    prices : DataFrame
        Prices for each ticker and date

    Returns
    -------
    log_returns : DataFrame
        Log returns for each ticker and date
    """

    log_returns = np.log(prices/prices.shift(1))[1:]

    return log_returns

def convert_to_distance_matrix(correlation_matrix):
    """
    Compute pair-wise distances using correlation coefficents

    References:
    ----------
        - Hierarchical Structure in Financial Markets
        (https://www.researchgate.net/publication/46462363_Hierarchical_Structure_in_Financial_Markets)

    Parameters
    ----------
    correlation_matrix : DataFrame
        correlation for every pairs of stocks.

    Returns
    -------
    distance_matrix : DataFrame
        distance matrix for every pairs of stocks.
    """

    distance_matrix = np.sqrt(2*(1-correlation_matrix))

    return distance_matrix

def get_condensed_distance_matrix(matrix):
    """
    Basically, This returns upper triangular part of square matrix(without diagonals)
    So, we can use this method to get condensed distance matrix.

    Parameters
    ----------
    matrix: numpy.ndarray
        It should be square matrix

    Returns
    -------
    condensed_distance: numpy.ndarray
        condensed distance matrix which 1-d array. If matrix has NxN shape, The lenght
        of return should be N*(N-1)/2

    """
    x_indices, y_indices = np.triu_indices_from(matrix, k=1)
    condensed_distance = matrix[x_indices.tolist(), y_indices.tolist()]
    return condensed_distance


import numpy as np
from matplotlib import pyplot as plt

def softplus(x, beta=1):
    return (1 / beta) * np.log(1 + np.exp(beta * x))

def save_relu_and_softplus_plot(figure_path: str):
    x = np.linspace(-4, 4, 100)

    # the function, which is y = e^x here
    y_relu = np.maximum(0, x)
    y_softplus = np.log(1 + np.exp(x))
    y_softplus_beta_2 = softplus(x, beta=2)
    y_softplus_beta_5 = softplus(x, beta=5)

    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.spines["left"].set_position("center")
    ax.spines["bottom"].set_position("zero")
    ax.spines["right"].set_color("none")
    ax.spines["top"].set_color("none")
    ax.xaxis.set_ticks_position("bottom")
    ax.yaxis.set_ticks_position("left")

    # plot the function
    plt.plot(x, y_relu, "r", label="ReLU(x)")
    plt.plot(x, y_softplus, "b", label=r"Softplus(x), $\beta=1$")
    plt.plot(x, y_softplus_beta_2, "orange", label=r"Softplus(x), $\beta=2$")
    plt.plot(x, y_softplus_beta_5, "g", label=r"Softplus(x), $\beta=5$")
    plt.legend(loc="upper left")

    # show the plot
    plt.savefig(
        figure_path,
        transparent=True,
    )
    plt.show()

if __name__ == "__main__":
    fig_path = "/home/steffi/dev/master_thesis/images/relu_softplus_plot.png"
    save_relu_and_softplus_plot(fig_path)


# -*- coding: utf-8 -*-
"""
Created on Fri Apr 13 23:04:11 2018

@author: Marcus
"""

import numpy as np
import time

def my_ifft(x):
    N = len(x)
    y = np.zeros(N, dtype=np.complex)
    if (N is 1):
        return x
    else:
        Wn = np.exp(1j * 2 * np.pi / N)
        W = 1
        x_even = x[0::2]
        x_odd = x[1::2]
        y_even = my_ifft(x_even)
        y_odd = my_ifft(x_odd)
        for m in range(N//2):
            y[m] = y_even[m] + (W * y_odd[m])
            y[m + N//2] = y_even[m] - (W * y_odd[m])
            W *= Wn
        return y

def my_ifft_adjusted(x):
    return my_ifft(x) / len(x)

length = 2**14
samples = np.random.normal(0, 1, length) + 1j*np.random.normal(0, 1, length)

a0 = time.clock()
my_result = my_ifft_adjusted(samples)
a1 = time.clock()
da = a1 - a0

b0 = time.clock()
np_result = np.fft.ifft(samples)
b1 = time.clock()
db = b1 - b0

print("My IFFT took:", da, "seconds")
print("NP IFFT took:", db, "seconds")
print("NP IFFT is", da/db, "times faster")
print(np.allclose(my_result, np_result))


import numpy
import scipy.spatial as spatial
import pylab

# Calcul le cout du clustering courant
def compute_cost(X, C, assign):
    n = len(X)
    d = len(X[0])
    acc = 0.
    for i in range(n):
        acc += numpy.linalg.norm(X[i] - C[assign[i]]) ** 2
    return acc

# Assigne à chaque point de X le n° du point de C dont il est le plus proche
def ppcentroide(X, C):
    mat_dist = spatial.distance.cdist(X, C, "euclidean")
    return numpy.argmin(mat_dist, axis=1)

# Algorithme kmeans
def kmeans(X, k, n_iter):
    n = len(X)
    d = len(X[0])

    cost = numpy.inf

    A = [i for i in range(n)]
    B = []
    for i in range(k):
        B.append(A.pop(numpy.random.randint(0,n-i)))
    C = [X[i] for i in B]

    for iter in range(n_iter):
        assign = ppcentroide(X, C)
        for i_k in range(k):
            cluster = [X[i] for i in range(n) if assign[i] == i_k]
            C[i_k] = numpy.mean(cluster, axis = 0)
        cost = compute_cost(X, C, assign)
        print(iter, cost)
    assign = ppcentroide(X, C)
    return C, assign, cost

# Zone d'interface

# Zone de tests

###
def read_data(file_name, X):
    f = open(file_name, 'r')
    data = f.readlines()
    for line in data:
        X.append((map(float, filter(lambda x: x <> '\n', line.split("\t")))))
    f.close()

def write_cluster(file_name, assign, n_cluster, n_iter, s):
    n = len(assign)
    f = open(file_name, 'w')
    f.write("n_cluster = " + str(n_cluster) + "\n")
    f.write("n_iter = " + str(n_iter) + "\n")
    f.write("n_data = " + str(n) + "\n")
    f.write("seed = " + str(s) + "\n")
    for i in range(n):
        f.write(str(assign[i]) + "\n")
    f.close()
###

filename = 'cinq_classes1'

X = []
read_data('./data/' + filename + '/' + filename + '.txt',X)

s = 2574927556214
n = len(X)
k = 5
n_iter = 30

numpy.random.seed(s)
C, assign, cost = kmeans(X, k, n_iter)

pylab.figure()
colors = "rbgkmc"
for i_k in range(k):
    pylab.plot([X[i][0] for i in range(n) if assign[i] == i_k],
               [X[i][1] for i in range(n) if assign[i] == i_k],
               colors[i_k] + ".")
    pylab.plot(C[i_k][0], C[i_k][1], colors[i_k] + "o")
pylab.show()

write_cluster('./data/' + filename + '/kmeans.txt', assign, k, n_iter, s)


import scipy.stats
import numpy as np

import chi_squared_goodness_of_fit

def chi_squared_homogeneity(*args, statistic: str = "likelihood_ratio"):
    """Chi-squared test for homogeneity.

    Checks whether all data groups follows the same data distribution.

    Assumptions:
        None.

    Test statistic:
        The same statistics from the Chi-squared test for goodness of
        fit. Check `chi_squared_goodness_of_fit.py` documentation for
        more specific info.

    Null distribution: Y ~ Chi^2((group_num - 1) * (bins_per_group - 1))
    Where:
        Y can be either G or X^{2} test statistic (again, check Chi-
        squared goodness of fit documentation for more info.);
        Chi^2 is the Chi^2 distribution.

    H0: all groups follows the same data distribution.
    HA: At least one group does not follow the same data distribution
        as the remaining groups.
    """
    assert len(set(map(len, args))) == 1

    args = np.asfarray(args)

    num_groups, bins_per_group = args.shape

    group_size = np.sum(args, axis=1)
    null_dist_pmf = np.sum(args, axis=0) / np.sum(args)

    expected_counts = np.outer(group_size, null_dist_pmf)

    ddof = (
        num_groups + bins_per_group - 1
    )  # df = (num_groups - 1) * (bins_per_group - 1)

    return chi_squared_goodness_of_fit.chi_squared_goodness_of_fit(
        data_counts=args.ravel(),
        expected_counts=expected_counts.ravel(),
        ddof=ddof,
        statistic=statistic,
    )

def _test():
    n = 500
    p = 0.7
    group_num = 4
    x = np.arange(n * p - 5, n * p + 6)

    expected_counts = n * scipy.stats.binom(n, p).pmf(x)
    group_counts = [
        np.ceil(np.maximum(0.0, expected_counts + np.random.randint(-20, 20)))
        for _ in range(group_num)
    ]
    group_counts[0][1] *= 4
    group_counts[1][2] *= 1.2

    print(expected_counts)

    res = chi_squared_homogeneity(*group_counts, statistic="pearson_chi_square")
    res_scipy = scipy.stats.chi2_contingency(
        np.asfarray(group_counts), correction=False
    )[:2]
    print(res, res_scipy)
    assert np.allclose(res, res_scipy)

    res = chi_squared_homogeneity(*group_counts, statistic="likelihood_ratio")
    print(res)
    assert np.allclose(
        res,
        scipy.stats.chi2_contingency(
            np.asfarray(group_counts), correction=False, lambda_="log-likelihood"
        )[:2],
    )

if __name__ == "__main__":
    _test()


import numpy as np

def compute_assign_prob_TS(alpha_1, beta_1, alpha_2, beta_2, num_sims):
    """
    Return assign prob for action 1 and action 2
    """
    action_1_count = 0
    action_2_count = 0
    for i in range(num_sims):
        draw_1 = np.random.beta(alpha_1, beta_1)
        draw_2 = np.random.beta(alpha_2, beta_2)
        if draw_1 > draw_2:
            action_1_count +=1
        else:
            action_2_count +=1

    return action_1_count/num_sims, action_2_count/num_sims #arm 1 prob, arm2 prob

def compute_assign_prob_TS_2(alphas: list, betas: list, num_sims: int, versions: list):
    """
    Return assign probability for all versions
    """
    draw = np.random.beta(alphas, betas, (num_sims, len(alphas)))
    arms = list(np.argmax(draw, axis=1))
    return dict({'probability_{}'.format(versions[i]): arms.count(i) / num_sims for i in range(len(alphas))})


from abc import ABC
from math import pi

import numpy as np
import statsmodels.stats.weightstats as ws
import statsmodels.tsa.stattools as ts
from scipy.stats import ttest_ind

class HypothesisTest(ABC):
    """
    Abstract base class for hypothesis tests.
    """
    alpha = 0.05

    def run(self, name: str, **kwargs):
        """Run test

        Args:
            name (str): Name of test to be run

        Returns:
            (None):
        """
        self.name = name

        test = getattr(self, name)

        self.stat, self.pval = test(**kwargs)
        self.accept_h0 = (self.pval > self.alpha)

class TwoSampleTest(HypothesisTest):
    def __init__(self, control: np.array, treatment: np.array):
        self.control = control
        self.treatment = treatment

    def ind_t(self, **kwargs):
        """Calculate the T-test for the means of two independent samples of scores.
        The null-hypothesis (H0) is: mean(control) <= mean(treatment)
        The alternative hypothesis (H1) is: mean(control) > mean(treatment)
        This test provides the probability to see the sample's test statistic or
        something that is even less in line with the null hypothesis (H0)
        under the H0: P(T<=0|H0), where T is the Welch's_t-test statistic [4, 2].

        Necessary Assumptions [1]:
            * 1. The means of the two populations being compared should follow
                normal distributions. Under weak assumptions, this follows in large
                samples from the central limit theorem, even when the distribution
                of observations in each group is non-normal.[18]

            * 2. If using Student's original definition of the t-test, the two
                populations being compared should have the same variance
                (testable using F-test, Levene's test, Bartlett's test, or the
                Brown–Forsythe test; or assessable graphically using a Q–Q plot).
                If the sample sizes in the two groups being compared are equal,
                Student's original t-test is highly robust to the presence of unequal
                variances. Welch's t-test is insensitive to equality of the
                variances regardless of whether the sample sizes are similar.

            * 3. The data used to carry out the test should either be sampled
                independently from the two populations being compared or be fully
                paired. This is in general not testable from the data, but if the
                data are known to be dependent (e.g. paired by test design), a
                dependent test has to be applied. For partially paired data, the
                classical independent t-tests may give invalid results as the test
                statistic might not follow a t distribution, while the dependent
                t-test is sub-optimal as it discards the unpaired data

            Most two-sample t-tests are robust to all but large deviations from the assumptions

        Args:
            control (Iterable): Iterable containing average to be tested
            treatment (Iterable): Iterable containing average to be tested
            pval_threshold (float, optional): p-value threshold. Defaults to 0.05.

        Returns:
            (float, float): Test statistic and p-value

        Example:
            >>> np.random.seed(1) # seed the random number generator
            >>> control = 5 * np.random.randn(100) + 50
            >>> treatment = 5 * np.random.randn(100) + 51
            >>> test = TwoSampleTest(control, treatment)
            >>> test.run("ind_t")
            >>> test.name
            'ind_t'
            >>> test.stat
            -2.2620139704259556
            >>> test.pval
            0.024782819014639627
            >>> test.accept_h0
            False

        References:
            [1] https://en.wikipedia.org/wiki/Student%27s_t-test
            [2] https://stats.stackexchange.com/questions/491294/can-you-multiply-p-values-if-you-perform-the-same-test-multiple-times
            [3] https://stackoverflow.com/questions/15984221/how-to-perform-two-sample-one-tailed-t-test-with-numpy-scipy
            [4] https://en.wikipedia.org/wiki/Welch%27s_t-test
            [5] https://machinelearningmastery.com/how-to-code-the-students-t-test-from-scratch-in-python/
        """
        return ttest_ind(self.control, self.treatment, **kwargs)

    def ind_z(self, **kwargs):
        """Do a independent z-test to verify if the difference of a mean between treatment and control
        is statistically significant. The need for independent test is because the variance of control
        and treatment are not necessarily equal.

        Necessary Assumptions [3]:
            * Nuisance parameters should be known, or estimated with high accuracy
            (an example of a nuisance parameter would be the standard deviation in a one-sample location test).
            Z-tests focus on a single parameter, and treat all other unknown parameters as being fixed at their
            true values. In practice, due to Slutsky's theorem, "plugging in" consistent estimates of
            nuisance parameters can be justified. However if the sample size is not large enough for these
            estimates to be reasonably accurate, the Z-test may not perform well.

            * The test statistic should follow a normal distribution. Generally, one appeals to the central limit
            theorem to justify assuming that a test statistic varies normally. There is a great deal of
            statistical research on the question of when a test statistic varies approximately normally.
            If the variation of the test statistic is strongly non-normal, a Z-test should not be used.

        Args:
            control (Iterable): Iterable containing average to be tested
            treatment (Iterable): Iterable containing average to be tested
            pval_threshold (float, optional): p-value threshold. Defaults to 0.05.

        Returns:
            (float, float): Test statistic and p-value

        Example:
            >>> np.random.seed(1) # seed the random number generator
            >>> control = 5 * np.random.randn(100) + 50
            >>> treatment = 5 * np.random.randn(100) + 51
            >>> test = TwoSampleTest(control, treatment)
            >>> test.run("ind_z")
            >>> test.name
            'ind_z'
            >>> test.stat
            -2.2620139704259556
            >>> test.pval
            0.02369654016419
            >>> test.accept_h0
            False

        References:
            [1] https://stats.stackexchange.com/questions/124096/two-samples-z-test-in-python
            [2] https://stackoverflow.com/questions/65235896/statsmodel-z-test-not-working-as-intended-statsmodels-stats-weightstats-compare
            [3] https://en.wikipedia.org/wiki/Z-test
            [4] https://stats.stackexchange.com/questions/491294/can-you-multiply-p-values-if-you-perform-the-same-test-multiple-times
        """
        col1 = ws.DescrStatsW(self.control)
        col2 = ws.DescrStatsW(self.treatment)

        cm_obj = ws.CompareMeans(col1, col2)

        return cm_obj.ztest_ind(**kwargs)

class SingleSampleTest(HypothesisTest):
    def __init__(self, data: np.array):
        self.data = data

    def stationarity(self, **kwargs):
        """[summary]

        Returns:
            [type]: [description]

        Example:
            >>> x = np.arange(365)
            >>> data = np.sin(pi*x/60)
            >>> test = SingleSampleTest(data)
            >>> test.run("stationarity")
            >>> test.name
            'stationarity'
            >>> test.stat
            -7612364038365.818
            >>> test.pval
            0.0
            >>> test.accept_h0
            False
        """

        stat, pval, _, _, critical_vals, _ = ts.adfuller(self.data, **kwargs)

        return stat, pval

import numpy,from scipy,import statsmodels


"""
Compute PCA on a dataset of multivariate time series represented as 3-dimensional tensor
and reduces the size along the third dimension:
    [N, T, V] --> [N, T, D]
with D <= V.
The input dataset must be a 3-dimensional tensor with shapes
- N: number of observations
- T: number of time steps in the time series
- V: number of variables in the time series

"""

import numpy as np
import numpy.linalg as linalg

class tensorPCA:

    def __init__(self, n_components):
        self.n_components=n_components
        self.first_eigs = None

    def fit(self, X): # X has shape [N,T,V]

        if len(X.shape) is not 3:
            raise RuntimeError('Input must be a 3d tensor')

        Xt = np.swapaxes(X,1,2)  # [N,T,V] --> [N,V,T]
        Xm = np.expand_dims(np.mean(X, axis=0), axis=0) # mean sample
        Xmt = np.swapaxes(Xm,1,2)

        C = np.tensordot(X-Xm,Xt-Xmt,axes=([1,0],[2,0])) / (X.shape[0]-1) # covariance of 0-mode slices

        # sort eigenvalues of covariance matrix
        eigenValues, eigenVectors = linalg.eig(C)
        idx = eigenValues.argsort()[::-1]
        eigenVectors = eigenVectors[:,idx]

        self.first_eigs = eigenVectors[:,:self.n_components]

    def transform(self, X):
        return np.einsum('klj,ji->kli',X,self.first_eigs)

    def fit_transform(self, X):
        self.fit(X)
        return self.transform(X)


from math import sqrt, exp, log, pi
from scipy.stats import norm
import datetime
import numpy as np

#  Option parameters
sigma = 0.15
S = 586.08    # current stock price
K = 585.0   # strike price
r = 0.01   # FED Fund's Rate  .25
t = (datetime.date(2014,10,18) - datetime.date(2014,9,8)).days / 365. / 365.0   # in years
c0 = 1.50   # call price

def d(sigma, S, K, r, t):
    d1 = 1 / (sigma * sqrt(t)) * (log(S/K) + (r + sigma**2/2) * t)
    d2 = d1 - sigma * sqrt(t)
    return d1, d2

def call_price(sigma, S, K, r, t, d1, d2):
    C = norm.cdf(d1) * S - norm.cdf(d2) * K * exp(-r * t)
    return C

# print(d(0.15, S, K, r, t))
# d1, d2 = (d(0.15, S, K, r, t))
# print(call_price(sigma, S, K, r, t, d1, d2))

#  Tolerances
tol = 1e-3
epsilon = 1

count = 0
max_iteration = 1000

vol = 0.50  # initial guess to get things started

while epsilon > tol:
    count += 1
    # print(count)
    if count >= max_iteration:
        print('Breaking on count')
        break;

    orig_vol = vol

    d1, d2 = d(vol, S, K, r, t)
    function_value = call_price(vol, S, K, r, t, d1, d2) - c0

    vega = S * norm.pdf(d1) * sqrt(t)   #  vega == the derivative
    vol = -function_value / vega + vol

    epsilon = abs((vol - orig_vol) / orig_vol)  # epsilon ==

print('sigma = ', vol)
print(count, 'iterations')

# PUTS
def f(x):
    return np.exp(x) - 5 * x

def fddx(x):
    return np.exp(x) - 5

# def find_vol(target_value, call_put, S, K, T, r):
#     MAX_ITERATIONS = 250
#     PRECISION = 1.0e-3

#     sigma = 0.5
#     for i in range(0, MAX_ITERATIONS):
#         price = bs_price(call_put, S, K, T, r, sigma)
#         vega = bs_vega(call_put, S, K, T, r, sigma)

#         price = price
#         diff = target_value - price  # our root

#         print(i, sigma, diff)

#         if (abs(diff) < PRECISION):
#             return sigma
#         sigma = sigma + diff/vega # f(x) / f'(x)

#     # value wasn't found, return best guess so far
#     return sigma

# n = norm.pdf
# N = norm.cdf

# def bs_price(cp_flag,S,K,T,r,v,q=0.0):
#     d1 = (log(S/K)+(r+v*v/2.)*T)/(v*sqrt(T))
#     d2 = d1-v*sqrt(T)
#     if cp_flag == 'c':
#         price = S*exp(-q*T)*N(d1)-K*exp(-r*T)*N(d2)
#     else:
#         price = K*exp(-r*T)*N(-d2)-S*exp(-q*T)*N(-d1)
#     return price

# def bs_vega(cp_flag,S,K,T,r,v,q=0.0):
#     d1 = (log(S/K)+(r+v*v/2.)*T)/(v*sqrt(T))
#     return S * sqrt(T)*n(d1)

# V_market = 17.5
# K = 585
# t = (datetime.date(2014,10,18) - datetime.date(2014,9,8)).days / 365.
# S = 586.08
# r = 0.0002
# c0 = 'c' # call option

# implied_vol = find_vol(V_market, c0, S, K, t, r)

# print('Implied vol: %.2f%%' % (implied_vol * 100))

# print('Market price = %.2f' % V_market)
# print('Model price = %.2f' % bs_price(c0, S, K, t, r, implied_vol))


#--------------------------------
#SIMPLE UNIVARIABLE REGRESSION EXAMPLE
#--------------------------------

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from   scipy.optimize import curve_fit

#------------------------
#GENERATE TOY DATA
#------------------------

#GROUND TRUTH FUNCTION
def G(x):
	GT=2
	if(GT==1):
		out=400000.0*np.exp(-((x-0.0)/2.0)**2.0)
		out=out+150000.0*np.exp(-((x-15.0)/1.0)**2.0)
		out=out+100000
	if(GT==2):
		out=400000.0*np.exp(-((x+2.0)/4.0)**4.0)
		out=out+250000.0*np.exp(-((x-5.0)/1.0)**4.0)
		out=out+150000.0*np.exp(-((x-15.0)/1.0)**4.0)
		out=out+100000
	return out

#NOISY DATA
N=100; xmin=-10; xmax=10
x = np.linspace(xmin,xmax,N)
y = G(x)  #PRISTINE DATA
noise=np.random.normal(loc=0.0, scale=0.05*(max(y)-min(y)),size=len(x))
# noise=0.15*(max(y)-min(y))*np.random.uniform(-1,1,size=len(x))
yn = y+ noise

#GROUND TRUTH DATA
xe = np.linspace(xmin,2*xmax,int(2*N))
ye = G(xe)

#NORMALIZE
xe=(xe-np.mean(x))/np.std(x)
ye=(ye-np.mean(y))/np.std(y)
yn=(yn-np.mean(y))/np.std(y)

x=(x-np.mean(x))/np.std(x)
y=(y-np.mean(y))/np.std(y)

#------------------------
#FIT MODEL
#------------------------

##FITTING MODEL
def model(x,p1,p2,p3,p4,p5,p6,p7):
    return p1*np.exp(-((x-p2)/p3)**2.0)+p4*np.exp(-((x-p5)/p6)**2.0)+p7
po=[0.1,0.1,0.1,0.1,0.1,0.1,0.1]
popt, pcov = curve_fit(model, x, yn,po) #,[0.1,0.1,0.1])

#------------------------
#PLOT
#------------------------

fig, ax = plt.subplots()
ax.plot(x, yn, 'o', label='Data')
ax.plot(xe, ye, '-', label='Ground-Truth')
ax.plot(xe, model(xe, *popt), 'r-', label="Model")

ax.legend()
FS=18   #FONT SIZE
plt.xlabel('Distance (miles)', fontsize=FS)
plt.ylabel('House Price ($) ', fontsize=FS)

plt.show()


import math

import numpy as np
import pandas as pd

from .time_series import TimeSeries

def affine(
    start,
    end,
    freq,
    start_y,
    end_y,
):
    index = pd.date_range(start=start, end=end, freq=freq)
    return TimeSeries(index=index, y=np.linspace(start_y, end_y, len(index)))

def constant(
    start,
    end,
    freq,
    value,
):
    return affine(start, end, freq, value, value)

def cosine(
    start,
    end,
    freq,
    amp=1,
    period=1,
):
    index = pd.date_range(start=start, end=end, freq=freq)
    return TimeSeries(
        index=index,
        y=amp * np.cos(np.linspace(0, math.tau * period, num=len(index))),
    )

def sine(
    start,
    end,
    freq,
    period=1,
):
    index = pd.date_range(start=start, end=end, freq=freq)
    return TimeSeries(
        index=index,
        y=np.sin(np.linspace(0, math.tau * period, num=len(index))),
    )


import numpy as np
import abc

class BaseFilter(abc.ABC):

    @abc.abstractmethod
    def predict_and_update(self, new_observation):
        pass

    def filter_list(self, array):
        """Filters a list of values using the current filter"""

        filtered_values = [self.predict_and_update(
            observation) for observation in array]

        return filtered_values

class KalmanFilter(BaseFilter):
    def __init__(self, A=1, B=1,C=1, Q=4, R=0.008) -> None:
        """

        First Order Kalman Filter Implementation
        Parameters:
        previous_mean: previous mean state
        previous_var : previous variance state
        A: state vector
        B: control vector
        C: control vector
        Q: measurement noise
        R: process noise """

        self.previous_mean = None
        self.previous_var = None
        self.A = A
        self.B = B
        self.C = C
        self.Q = Q
        self.R = R

        np.random.seed(3) # to ensure constant results for evaluation

    def predict_and_update(self, new_observation):
        """
        Prediction and update in Kalman filter

        Parameters:
        new_observation: current observation

        Returns:
        new_mean: mean state prediction
        new_var: variance state prediction
        """

        if self.previous_mean is None:
            self.previous_mean = ( 1/ self.C) * new_observation
            self.previous_var = (1/ self.C) * self.Q * (1/self.C)
            return new_observation
        else:
            x_mean = self.A * self.previous_mean + self.B * np.random.normal(0, self.R, 1)
            P_mean = (self.A * self.previous_var * self.A )+ self.R

            #kalman gain
            K = P_mean * self.C * (1 / ((self.C * P_mean * self.C) + self.Q))

            new_mean = x_mean + K * (new_observation - (self.C * x_mean))
            new_var = P_mean - (K * self.C * P_mean)

            self.previous_mean = new_mean
            self.previous_var = new_var

            return new_mean[0]

class BasicFilter(BaseFilter):

    def __init__(self) -> None:
        self.previous_observation = None

    def predict_and_update(self, new_observation):
        if self.previous_observation is None:
            self.previous_observation = new_observation
            return new_observation

        predicted_observation = new_observation * \
            0.1 + self.previous_observation * 0.9
        self.previous_observation = predicted_observation
        return predicted_observation

class MovingMeanFilter(BaseFilter):
    def __init__(self) -> None:
        self.n = 10
        self.buffer = []

    def predict_and_update(self, new_observation):
        self.buffer.append(new_observation)

        prediction = np.mean(self.buffer)

        if len(self.buffer) >= self.n:
            self.buffer.pop(0)

        return prediction

class MovingMedianFilter(BaseFilter):
    def __init__(self) -> None:
        self.n = 10
        self.buffer = []

    def predict_and_update(self, new_observation):
        self.buffer.append(new_observation)

        prediction = np.median(self.buffer)

        if len(self.buffer) >= self.n:
            self.buffer.pop(0)

        return prediction


# Assignment on Binomial Distribution

import numpy as np
from scipy.stats import binom

'''
1.For a Binomial Distribution parameter n=5 and p=0.3. Find the probabilities of getting
a)At least 3 successes
b)At most 3 successes
c)Exactly 3 failures
'''
print("Assignment 1")
print("At least 3 successes:",1-binom.cdf(k=2,n=5,p=0.3))
print("At most 3 successes:",binom.cdf(k=3,n=5,p=0.3))
print("Excatly 3 failures",binom.pmf(k=5-3,n=5,p=0.3))
print("\n")

'''
2.If on an average one vessel in every ten is wrecked, find the probability that out of five vessels expected to arrive, four at least will arrive safely
'''
print("Assignment 2")

print("Probabilty of atleast 4/5 arrive safely",
        binom.pmf(k=4,n=5,p=9/10)+binom.pmf(k=5,n=5,p=9/10))
print("\n")

'''
3.Five coins are tossed 3,200 times.
a)Find the Frequencies of the distribution of heads and tabulate the results
b)Calculate the mean number of success and standard deviations
'''

print("Assignment 3")
print("Frequency Distribution of Heads")
for k in np.arange(5+1):
    print("Frequency of {} Heads is {}"\
    .format(k,3_200*binom.pmf(k=k,n=5,p=1/2)))
print("Mean of Success",binom.mean(n=5,p=1/2))
print("Standard Deviation of Success",binom.std(n=5,p=1/2))


from statistics import mean
import numpy as np
import random
import matplotlib.pyplot as plt

def create_dataset(hm, variance, step = 2, correlation = False):
    val = 1
    ys = []

    for i in range(hm):
        y = val + random.randrange(-variance, variance)
        ys.append(y)

        if correlation and correlation == 'pos':
            val += step
        elif correlation and correlation == 'neg':
            val -= step
    xs = [i for i in range(len(ys))]

    return np.array(xs, dtype = np.float64), np.array(ys, dtype = np.float64)

def best_fit_slope_and_intercept(xs, ys):
    m = (   (   (mean(xs) * mean(ys)) - mean(xs*ys)   ) /
        (       (mean(xs)*mean(xs)) - mean(xs**2)     )     )
    b = mean(ys) - m * mean(xs)
    return m, b

def squared_error(ys_orig, ys_line):
    return sum( (ys_line - ys_orig) ** 2 )

def coefficient_of_determination(ys_orig, ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]
    squared_error_regr = squared_error(ys_orig, ys_line)
    squared_error_y_mean = squared_error(ys_orig, y_mean_line)
    return 1 - (squared_error_regr / squared_error_y_mean)

plt.style.use('ggplot') #set plot style

#set example data
    #xs = np.array([1,2,3,4,5,6], dtype = np.float64)
    #ys = np.array([5,4,6,5,6,7], dtype = np.float64)
xs, ys = create_dataset(40, 20, 2, correlation = 'pos')

#calcucale reg line and slope
m, b = best_fit_slope_and_intercept(xs, ys)
regression_line = [(m * x) + b for x in xs]

#predict
predict_x = 8
predict_y = (m * predict_x) + b

#coef
r_squared = coefficient_of_determination(ys, regression_line)

print("m = ", m)
print("Coef:", r_squared)
#draw plot
plt.scatter(xs, ys) #display x and y data
plt.scatter(predict_x, predict_y, s = 50, color = 'g') #display the prediction
plt.plot(xs, regression_line) #display reg line
plt.show()


import numpy as np
import sys

class Solve:
	def __init__(self, coefficient_matrix, rhs_vector):
		'''
			Do the initialization of coef. matrix, rhs-vector,
			solution-matrix and transformation matrix
		'''
		self.coefficient_matrix = coefficient_matrix
		self.rhs_vector = rhs_vector
		self.degree = rhs_vector.shape[0]
		self.solution_matrix = np.zeros((self.degree,1))
		self.transformation_matrix = np.eye(self.degree)

	def solve(self):
		'''
			Solve the linear system here by elimination
		'''

		for j in range(self.degree):
			pivot = self.coefficient_matrix[j][j]

			# In case pivot is equal to 0, exchange the rows
			if pivot == 0:
				for k in range(j+1, self.degree):
					if self.coefficient_matrix[k][j] != 0:
						transformation_matrix_t = np.eye(self.degree)
						status = True
						temp_1 = np.copy(transformation_matrix_t[k])
						temp_2 = np.copy(transformation_matrix_t[j])
						transformation_matrix_t[j] = temp_1
						transformation_matrix_t[k] = temp_2
						self.coefficient_matrix = np.matmul(transformation_matrix_t, self.coefficient_matrix)
						self.transformation_matrix = np.matmul(transformation_matrix_t, self.transformation_matrix)
						break

			pivot = self.coefficient_matrix[j][j]

			'''
				If we can't get pivot non-zero number by exchanging the rows, the system
				does not have unique finite solution
			'''
			if pivot == 0:
				print("The system of equations does not have unique and finite solution.")
				sys.exit()

			# Transforms coefficient matrix to upper triangular matrix by doing Gaussian elimination
			# Transformation matrix stores the net transformation the coefficient matrix goes through
			for i in range(j+1,self.degree):
				if self.coefficient_matrix[i][j] == 0:
					continue
				transformation_matrix_t = np.eye(self.degree)
				transformation_matrix_t[i][j] = - self.coefficient_matrix[i][j]/pivot
				self.transformation_matrix = np.matmul(transformation_matrix_t,self.transformation_matrix)
				self.coefficient_matrix = np.matmul(transformation_matrix_t, self.coefficient_matrix)

		# Transforming rhs_vector with transformation_matrix
		self.rhs_vector = np.matmul(self.transformation_matrix, self.rhs_vector)

		# Solving for the last variable by doing pivot division
		self.solution_matrix[-1][0] = self.rhs_vector[-1][0]/self.coefficient_matrix[-1][-1]

		# Solving for the rest of the variables
		for i in range(2,self.solution_matrix.shape[0]+1):
			temp = np.copy(self.coefficient_matrix[-i])
			temp[-i] = 0
			temp_product = np.dot(temp, self.solution_matrix[:,0])
			self.solution_matrix[-i][0] = (self.rhs_vector[-i][0]-temp_product)/self.coefficient_matrix[-i][-i]

def main():
	solve = Solve(np.array([[4,2,0],[2,1,0],[5,3,9]]), np.array([[1],[3],[7]]))
	solve.solve()
	print(solve.solution_matrix)

if __name__ == "__main__":
	main()


import numpy as np

def getEulerAngles(R):
    """
    Obtain the Euler angles corresponding to the rotation matrix R
    IMPORTANT: This function assumes all the angles are within the range -90 to 90 degrees!!
    """
    rx = -np.arcsin(R[1, 2])
    ry = np.arctan2(R[0, 2], R[2, 2])
    rz = np.arctan2(R[1, 0], R[1, 1])

    return rz, rx, ry

def vectorToYawElevation(vector, unit='deg'):
    """ Convert a vector to two angle representation by building the rotation matrix where the input vector is z axis.
        The input vector become the z axis, x axis is kept in the XZ plan of the coordinate system, on the left of the
        z axis. The y axis is deduced from the two firsts.
        The output angles are:
            - the first angle is the euler's yaw, called "yaw" or "phi" (rotation around y);
            - the second is the inverse euler's pitch, called "elevation" or "theta" (rotation around x). """
    vector = np.resize(vector, (1, 3))[0]

    R = [0.0, 0.0, 0.0]
    R[2] = vector / np.linalg.norm(vector)
    R[0] = np.cross([0.0, 1.0, 0.0], R[2])
    R[1] = np.cross(R[2], R[0])

    roll, pitch, yaw = getEulerAngles(np.array(R).T)
    if unit == 'deg':
        yaw = yaw / np.pi*180
        elevation = -pitch / np.pi*180
    elif unit == 'rad':
        elevation = -pitch
    else:
        raise Exception('Unknow angle unit ' + unit)

    return [yaw, elevation]

def yawElevationToVector(yawElevation, unit='deg'):
    """ Transforms an angular representation of gaze (phi, theta) into a vectorial one (x,y,z).
        WARNING: angles outer than [-90, 90] will give wrong vectors """
    # TODO: generalize with matrix rotation
    yawElevation = map(float, yawElevation)
    if unit == 'deg':
        yaw, elevation = yawElevation[0]/180*np.pi, yawElevation[1]/180*np.pi
        if np.abs(yaw) >= 90 or np.abs(elevation) >= 90:
            raise Exception('Can not handle angles outer [-90, 90]')
    elif unit == 'rad':
        yaw, elevation = yawElevation
        if np.abs(yaw) >= np.pi/2 or np.abs(elevation) >= np.pi/2:
            raise Exception('Can not handle angles outer [-90, 90]')
    else:
        raise Exception('Unknow angle unit ' + unit)

    y = np.sin(elevation)

    x = np.sqrt((np.cos(elevation)**2 * np.tan(yaw)**2) / (1 + np.tan(yaw)**2))
    if yaw < 0:  # The signed of x is taken from the sign of the phi
        x = -x

    if x**2 + y**2 >= 1.0:
        z = 0.0
    else:
        z = np.sqrt(1 - x**2 - y**2)
    if np.isnan(z):
        print '[yawElevationToVector]', np.array(yawElevation).flatten()*180/np.pi, [x, y, z]

    return np.resize([x, y, z], (3, 1))

def angleBetweenVectors_deg(x, y):
    """ Angle in degree between x and y vectors """
    x = np.resize(x.copy(), (3, 1))
    y = np.resize(y.copy(), (3, 1))
    angle = np.arccos(np.dot(x.T, y) / (np.linalg.norm(x) * np.linalg.norm(y))) / np.pi * 180
    return np.linalg.norm(angle)


import numpy as np
from math import exp

def barnes(x):

    a1 = 75.196
    a3 = 0.12694
    a5 = 1.0345e-5
    a7 = 0.030234
    a9 = 3.5256e-5
    a11 = 0.25645
    a13 = 1.3514e-5
    a15 = -5.2375e-6
    a17 = 7.0e-10
    a19 = -1.6638e-6
    a21 = 0.0005
    a2 = -3.8112
    a4 = -2.0567e-3
    a6 = -6.8306
    a8 = -1.28134e-3
    a10 = -2.266e-7
    a12 = -3.4604e-3
    a14 = -28.106
    a16 = -6.3e-8
    a18 = 3.4054e-4
    a20 = -2.8673

    x1 = x[0]
    x2 = x[1]
    y1 = x1*x2
    y2 = y1*x1
    y3 = x2**2
    y4 = x1**2

    # --- function value ---

    f = a1 + a2*x1 + a3*y4 + a4*y4*x1 + a5*y4**2 + \
        a6*x2 + a7*y1 + a8*x1*y1 + a9*y1*y4 + a10*y2*y4 + \
        a11*y3 + a12*x2*y3 + a13*y3**2 + a14/(x2+1) + \
        a15*y3*y4 + a16*y1*y4*x2 + a17*y1*y3*y4 + a18*x1*y3 + \
        a19*y1*y3 + a20*exp(a21*y1)

    # --- constraints ---

    c = np.zeros(3)
    c[0] = 1 - y1/700.0
    c[1] = y4/25.0**2 - x2/5.0
    c[2] = (x1/500.0- 0.11) - (x2/50.0-1)**2

    # --- derivatives of f ---

    dy1 = x2
    dy2 = y1 + x1*dy1
    dy4 = 2*x1
    dfdx1 = a2 + a3*dy4 + a4*y4 + a4*x1*dy4 + a5*2*y4*dy4 + \
        a7*dy1 + a8*y1 + a8*x1*dy1 + a9*y1*dy4 + a9*y4*dy1 + a10*y2*dy4 + a10*y4*dy2 + \
        a15*y3*dy4 + a16*x2*y1*dy4 + a16*x2*y4*dy1 + a17*y3*y1*dy4 + a17*y3*y4*dy1 + a18*y3 + \
        a19*y3*dy1 + a20*exp(a21*y1)*a21*dy1

    dy1 = x1
    dy2 = x1*dy1
    dy3 = 2*x2
    dfdx2 = a6 + a7*dy1 + a8*x1*dy1 + a9*y4*dy1 + a10*y4*dy2 + \
        a11*dy3 + a12*x2*dy3 + a12*y3 + a13*2*y3*dy3 + a14*-1/(x2+1)**2 + \
        a15*y4*dy3 + a16*y4*y1 + a16*y4*x2*dy1 + a17*y4*y1*dy3 + a17*y4*y3*dy1 + a18*x1*dy3 + \
        a19*y3*dy1 + a19*y1*dy3 + a20*exp(a21*y1)*a21*dy1

    dfdx = np.array([dfdx1, dfdx2])

    # --- derivatives of c ---

    dcdx = np.zeros((3, 2))
    dcdx[0, 0] = -x2/700.0
    dcdx[0, 1] = -x1/700.0
    dcdx[1, 0] = 2*x1/25**2
    dcdx[1, 1] = -1.0/5
    dcdx[2, 0] = 1.0/500
    dcdx[2, 1] = -2*(x2/50.0-1)/50.0

    dcdx = np.transpose(dcdx)  # matlab format

    return f/30.0, c, dfdx/30.0, dcdx

if __name__ == '__main__':

    from opt import fmincon

    # --- name of function to optimize ----
    function = 'example.barnes'
    providegradients = True

    # -------- starting point and bounds --------------
    x0 = np.array([10.0, 10.0])
    ub = np.array([65.0, 70.0])
    lb = np.array([0.0, 0.0])

    # ---- set options ----
    options = {'Algorithm': 'active-set', 'AlwaysHonorConstraints': 'bounds',
        'display': 'iter-detailed', 'MaxIter': 1000, 'MaxFunEvals': 10000,
        'TolCon': 1e-6, 'TolFun': 1e-6, 'Diagnostics': 'on'}

    # --- load fmincon and run ----
    xopt, fopt, exitflag, output = fmincon(function, x0, lb, ub, options,
        providegradients=providegradients)

    # --- print results
    print xopt
    print fopt
    print exitflag
    # print output


# -*- coding: utf-8 -*-
"""
1D Wasserstein barycenter demo

@author: rflamary
"""

import numpy as np
import matplotlib.pylab as pl
import ot
from mpl_toolkits.mplot3d import Axes3D #necessary for 3d plot even if not used
from matplotlib.collections import PolyCollection

#%% parameters

n=100 # nb bins

# bin positions
x=np.arange(n,dtype=np.float64)

# Gaussian distributions
a1=ot.datasets.get_1D_gauss(n,m=20,s=5) # m= mean, s= std
a2=ot.datasets.get_1D_gauss(n,m=60,s=8)

# creating matrix A containing all distributions
A=np.vstack((a1,a2)).T
nbd=A.shape[1]

# loss matrix + normalization
M=ot.utils.dist0(n)
M/=M.max()

#%% plot the distributions

pl.figure(1)
for i in range(nbd):
    pl.plot(x,A[:,i])
pl.title('Distributions')

#%% barycenter computation

alpha=0.2 # 0<=alpha<=1
weights=np.array([1-alpha,alpha])

# l2bary
bary_l2=A.dot(weights)

# wasserstein
reg=1e-3
bary_wass=ot.bregman.barycenter(A,M,reg,weights)

pl.figure(2)
pl.clf()
pl.subplot(2,1,1)
for i in range(nbd):
    pl.plot(x,A[:,i])
pl.title('Distributions')

pl.subplot(2,1,2)
pl.plot(x,bary_l2,'r',label='l2')
pl.plot(x,bary_wass,'g',label='Wasserstein')
pl.legend()
pl.title('Barycenters')

#%% barycenter interpolation

nbalpha=11
alphalist=np.linspace(0,1,nbalpha)

B_l2=np.zeros((n,nbalpha))

B_wass=np.copy(B_l2)

for i in range(0,nbalpha):
    alpha=alphalist[i]
    weights=np.array([1-alpha,alpha])
    B_l2[:,i]=A.dot(weights)
    B_wass[:,i]=ot.bregman.barycenter(A,M,reg,weights)

#%% plot interpolation

pl.figure(3,(10,5))

#pl.subplot(1,2,1)
cmap=pl.cm.get_cmap('viridis')
verts = []
zs = alphalist
for i,z in enumerate(zs):
    ys = B_l2[:,i]
    verts.append(list(zip(x, ys)))

ax = pl.gcf().gca(projection='3d')

poly = PolyCollection(verts,facecolors=[cmap(a) for a in alphalist])
poly.set_alpha(0.7)
ax.add_collection3d(poly, zs=zs, zdir='y')

ax.set_xlabel('x')
ax.set_xlim3d(0, n)
ax.set_ylabel('$\\alpha$')
ax.set_ylim3d(0,1)
ax.set_zlabel('')
ax.set_zlim3d(0, B_l2.max()*1.01)
pl.title('Barycenter interpolation with l2')

pl.show()

pl.figure(4,(10,5))

#pl.subplot(1,2,1)
cmap=pl.cm.get_cmap('viridis')
verts = []
zs = alphalist
for i,z in enumerate(zs):
    ys = B_wass[:,i]
    verts.append(list(zip(x, ys)))

ax = pl.gcf().gca(projection='3d')

poly = PolyCollection(verts,facecolors=[cmap(a) for a in alphalist])
poly.set_alpha(0.7)
ax.add_collection3d(poly, zs=zs, zdir='y')

ax.set_xlabel('x')
ax.set_xlim3d(0, n)
ax.set_ylabel('$\\alpha$')
ax.set_ylim3d(0,1)
ax.set_zlabel('')
ax.set_zlim3d(0, B_l2.max()*1.01)
pl.title('Barycenter interpolation with Wasserstein')

pl.show()


# coding: utf-8

import numpy as np

# y - activation of the unit given input z
# dy_dz - derivative of the activation y of the unit with respect to the input z

class Sigmoid(object):

    @staticmethod
    def y(z):
        return 1 / (1 + np.exp(-z))

    @staticmethod
    def dy_dz(y):
        return y * (1. - y)

class Softmax(object):

    @staticmethod
    def y(z):
        # Subtract maximum for numerical stability. (does not affect outputs)
        y = np.exp(z - z.max(axis=1)[:,np.newaxis])
        y /= y.sum(axis=1)[:,np.newaxis]
        return y

class Linear(object):

    @staticmethod
    def y(z):
        return z

    @staticmethod
    def dy_dz(y):
        return 1.

class RectifiedLinear(object):

    @staticmethod
    def y(z):
        return np.maximum(z, 0.)

    @staticmethod
    def dy_dz(y):
        return y > 0.

class Tanh(object):

    @staticmethod
    def y(z):
        return np.tanh(z)

    @staticmethod
    def dy_dz(y):
        return 1. - y**2


""" Basic operations on numpy arrays (addition, etc.) are elementwise

This works on arrays of the same size. Nevertheless, It’s also possible to do operations on arrays of different sizes if NumPy can transform these arrays so that they all have the same size: this conversion is called broadcasting. """

import numpy as np

a = np.tile(np.arange(0, 40, 10), (3,1))
""" array([[ 0, 10, 20, 30],
       [ 0, 10, 20, 30],
       [ 0, 10, 20, 30]]) """

a=a.T
""" array([[ 0,  0,  0],
       [10, 10, 10],
       [20, 20, 20],
       [30, 30, 30]]) """

c = np.tile(np.arange(0, 40, 10), (3,2))
""" array([[ 0, 10, 20, 30,  0, 10, 20, 30],
       [ 0, 10, 20, 30,  0, 10, 20, 30],
       [ 0, 10, 20, 30,  0, 10, 20, 30]]) """

b = np.array([0, 1, 2])
# array([0, 1, 2])

a + b # b is replicated as many required for a element wise addition
""" array([[ 0,  1,  2],
       [10, 11, 12],
       [20, 21, 22],
       [30, 31, 32]]) """

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

one_dim_array = np.arange(0, 40, 10)
# array([ 0, 10, 20, 30])
one_dim_array.shape
# (4,)

# convert one_dim_array to two_dim_array

two_dim_array = one_dim_array[:, np.newaxis]  # adds new axis -> 2D array
""" array([[ 0],
       [10],
       [20],
       [30]]) """

two_dim_array.shape
# (4, 1)


#!/usr/bin/env python

"""
A convergence test of the multigrid solver.  We solve Laplace's equation.  Here, we
solve

u_xx = sin(x)
u = 0 on the boundary [0,1]

The analytic solution is u(x) = -sin(x) + x sin(1)

we run at a variety of resolutions and compare to the analytic solution.

"""
#from io import *
import numpy as np
import multigrid
import matplotlib.pyplot as plt

def true(x):
    # the analytic solution
    return -np.sin(x) + x*np.sin(1.0)

def error(myg, r):
    # L2 norm of elements in r, multiplied by dx to normalize
    return np.sqrt(myg.dx*np.sum((r[myg.ilo:myg.ihi+1]**2)))

def f(x):
    # the righthand side
    return np.sin(x)

def mgsolve(nx):

    # create the multigrid object
    a = multigrid.CellCenterMG1d(nx, xl_BC_type="dirichlet", xr_BC_type="dirichlet",
                                 verbose=0)

    # initialize the solution to 0
    a.init_zeros()

    # initialize the RHS using the function f
    a.init_RHS(f(a.x))

    # solve to a relative tolerance of 1.e-11
    a.solve(rtol=1.e-11)

    # get the solution
    v = a.get_solution()

    # compute the error from the analytic solution
    return error(a.soln_grid, v - true(a.x))

N = [16, 32, 64, 128, 256, 512]
err = []

for nx in N:
    err.append(mgsolve(nx))

N = np.array(N, dtype=np.float64)
err = np.array(err)

plt.scatter(N, err, color="r")
plt.plot(N, err[len(N)-1]*(N[len(N)-1]/N)**2, color="k", label=r"$\mathcal{O}(\Delta x^2)$")

print(N, err[0]*(N[0]/N)**2)

ax = plt.gca()
ax.set_xscale('log')
ax.set_yscale('log')

plt.ylim(1.e-8, 1.e-3)

plt.xlabel("N")
plt.ylabel("L2 norm of absolute error")

plt.legend(frameon=False)

plt.savefig("mg-converge.png")


'''
Part 1 - basic

Please solve the following exercises. Good Luck!
'''

'''
1. Import the numpy package under the name `np`
'''

import numpy as np

'''
2. Print the numpy version
hint: search on Google "numpy version", the first entries.
'''

print(np.version.version)

'''
3. Create a vector of zeros with the size 10
hint: use np.zeros()
'''

newVector = np.zeros(10)

'''
4. How to find the memory size of any array
hint: itemsize returns the size in memory of each element
'''

print(np.size(newVector))

'''
5. How to get the documentation of the numpy add function from the IPython console?
hint: try "?" before the function name
'''

?np.size

'''
6. Create a vector of zeros with the size 10 but the fifth value which is 1
'''

newVector = np.zeros(10)
newVector[4]=1
print(newVector)

'''
7. Create a vector with values ranging from 10 to 49 (★☆☆)
hint: np.arange()
'''

newVector = np.arange(10, 50, 1) # arguments: start, stop, step
print(newVector)

'''
8. Reverse a vector (first element becomes last) (★☆☆)
hint: [::-1]
'''

reversedVector = newVector[::-1]
print(reversedVector)

'''
9. Create a 3x3 matrix with values ranging from 0 to 8 (★☆☆)
hint: reshape
'''

newVector = np.arange(0, 9, 1)
reshapeVector = np.reshape(newVector,(3,3))
print(reshapeVector)

'''
10. Find indices of non-zero elements from \[1,2,0,0,4,0\] (★☆☆)
hint: nonzero
'''

newVector = np.array([1,2,0,0,4,0])
print(np.nonzero(newVector))

'''
11. Create a 3x3 identity matrix (★☆☆)
hint: the best numpy documentation is in the scipy website :)
try https://docs.scipy.org/doc/numpy/reference/generated/numpy.eye.html
'''
newIdentityMatrix = np.eye(3)

'''
12. Create a 3x3x3 array with random values (★☆☆)
hint: Random sampling (numpy.random) — NumPy v1.21 Manual
'''

newRandom3DMatrix = np.random.rand(3,3,3)
print(newRandom3DMatrix)

'''
13. Create a 10x10 array with random values and find the minimum and maximum values
hint: a.min,a.max
'''
newRandom3DMatrix = np.random.rand(10,10)
print(newRandom3DMatrix.max(), newRandom3DMatrix.min())

'''
14. Create a random vector of size 30 and find the mean value
hint: a.mean
'''

newRandomVector = np.random.rand(30)
print(newRandomVector.mean())

'''
15. Create a 2d array with 1 on the border and 0 inside
hint: ones()
hint2: for an array a, which elements are chosen by a[1:-1] ?
'''

newOnesArray = np.ones([5,5], dtype=int)
newOnesArray[1:-1, 1:-1] = 0
print(newOnesArray)

'''
16. How to add a border (filled with 0's) around an existing array?
hint: https://docs.scipy.org/doc/numpy/reference/generated/numpy.pad.html
'''

newOnesArray = np.ones([5,5], dtype=int)
newOnesArray = np.pad(newOnesArray, pad_width=1)
print(newOnesArray)

'''
17. What is the result of the following expression? (★☆☆)
```python
0 * np.nan
np.nan == np.nan
np.inf > np.nan
np.nan - np.nan
0.3 == 3 * 0.1
```
hint 1: nan is not a number
hint 2: try printing the expressions to see what is printed
'''

print(np.nan == np.nan) # False
print(np.inf > np.nan) # False
print(np.nan - np.nan) # nan
print(0.3 == 3 * 0.1) # False

'''
18. Create a 5x5 matrix with values 1,2,3,4,7 on the diagonal
hint: np.diag
'''

newDiagonalMartix = np.diag([1,2,3,4,7])
print(newDiagonalMartix)

'''
19. Create a 8x8 matrix and fill it with a checkerboard pattern
hint1: slicing an array you can use - list[start:end:step]
hint2: [::2] - for even. [1::2] - for odd
'''

checkerboardMatrix = np.ones([8,8],dtype=int)
for columnNumber in range(0,8):
    if columnNumber%2==0:
        checkerboardMatrix[columnNumber][::2] = 0
    else:
        checkerboardMatrix[columnNumber][1::2] = 0
print(checkerboardMatrix)

'''
20. Consider a (6,7,8) shape array, what is the index (x,y,z) of the 100th element?
hint: https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.unravel_index.html
'''

print(np.unravel_index(100,(6,7,8)))

'''
21. Create a checkerboard 8x8 matrix using the tile function (★☆☆)
hint: numpy.tile(A, reps)
Construct an array by repeating A the number of times given by reps.
'''

singleLineArray = np.array([1,2,3,4,5,6,7,8])
newCheckerboardMatrix = np.tile(singleLineArray,(8,1))
print(newCheckerboardMatrix)

'''
22. Normalize a 5x5 random matrix
hint: create a random 5*5 matrix
subtract the min value, then divide by (max-min)
'''

newRandomMatrix = np.random.rand(5,5)
updatedRandomMatrix = newRandomMatrix - newRandomMatrix.min()
normalizer = newRandomMatrix.max()-newRandomMatrix.min()
print(updatedRandomMatrix/normalizer)

'''
23. Create an array of 2x4 with dtype numpy.int16. print the dtype of the array
'''

newArray = np.array([1,2,3], dtype=int16)
print(newArray.dtype())

'''
24. Multiply a 5x3 matrix by a 3x2 matrix (real matrix product)
hint: numpy.dot()
'''

firstMatrix = np.random.rand(5,3)
secondMatrix = np.random.rand(3,2)
print(np.dot(firstMatrix,secondMatrix))
print(firstMatrix@secondMatrix)

'''
25. Given a 1D array, negate all elements which are between 3 and 8, in place.
(hint: >, <=)
'''

randomArray = np.random.uniform(1,10,50)
randomArray = np.int64(randomArray)
print([value for value in randomArray if 3>value and value<8])

'''
26. What is the output of the following script? (★☆☆)
(hint: np.sum)
# Author: Jake VanderPlas
print(sum(range(5),-1))
from numpy import *
print(sum(range(5),-1))
'''

print(sum(range(5),-1)) # 9
from numpy import *
print(sum(range(5),-1)) # 10

'''
27. Consider an integer vector Z, which of these expressions are legal? (★☆☆)
Z**Z
2 << Z >> 2
Z <- Z
1j*Z
Z/1/1
Z<Z>Z
'''

Z = np.random.uniform(1,100,100)
print(Z**Z)        # ok
print(2 << Z >> 2) # the inputs could not be safely coerced to any supported types
print(Z <- Z)      # ok, all FALSE
print(1j*Z)        # ok
print(Z/1/1)       # ok
print(Z<Z>Z)       # The truth value of an array with more than one element is ambiguous

'''
28. What are the result of the following expressions?
np.array(0) / np.array(0)
np.array(0) // np.array(0)
np.array([np.nan]).astype(int).astype(float)
'''

print(np.array(0) / np.array(0))  # nan
print(np.array(0) // np.array(0))  # 0
print(np.array([np.nan]).astype(int).astype(float)) # [-2.14748365e+09]

'''
29. How to round away from zero a float array ? (★☆☆)
(hint: np.uniform, np.copysign, np.ceil, np.abs)
'''

newArray = np.array([1.1,2.2,3.3,4.4,5.5])
absArray = np.ceil(newArray)
print(absArray)

'''
30. How to find common values between two arrays? (★☆☆)
(hint: np.intersect1d)
'''

a1 = np.int64(np.random.uniform(1,100,100))
a2 = np.int64(np.random.uniform(1,100,100))
print(np.intersect1d(a1, a2))

'''
31. How to ignore all numpy warnings (not recommended)? (★☆☆)
(hint: np.seterr, np.errstate)
'''

np.errstate(all='ignore')

'''
32. Is the following expressions true? (★☆☆)
(hint: imaginary number)
np.sqrt(-1) == np.emath.sqrt(-1)
'''

# nope

'''
33. How to get the dates of yesterday, today and tomorrow? (★☆☆)
(hint: np.datetime64, np.timedelta64)
'''

timeData = np.datetime64('today')
print(timeData)
print(timeData + np.timedelta64(1, 'D'))
print(timeData - np.timedelta64(1, 'D'))

'''
34. How to get all the dates corresponding to the month of July 2016? (★★☆)
(hint: np.arange(dtype=datetime64['D']))
'''

print(np.arange('2016-06', '2016-07', dtype='datetime64[D]'))

'''
36. Extract the integer part of a random array using 5 different methods (★★☆)
(hint: %, np.floor, np.ceil, astype, np.trunc)
'''

randomArray1 = np.random.uniform(1,10,5)
print(randomArray1)
randomArray1 = np.int64(randomArray1)
print(randomArray1)

randomArray2 = np.random.uniform(1,10,5)
print(randomArray2)
randomArray2 = np.floor(randomArray2)
print(randomArray2)

randomArray3 = np.random.uniform(1,10,5)
print(randomArray3)
randomArray3 = randomArray3.astype('int64')
print(randomArray3)

randomArray4 = np.random.uniform(1,10,5)
print(randomArray4)
randomArray4 = np.trunc(randomArray4)
print(randomArray4)

randomArray5 = np.random.uniform(1,10,5)
print(randomArray5)
randomArray5 = randomArray5 - randomArray5 % 1
print(randomArray5)
65

'''
37. Create a 5x5 matrix with row values ranging from 0 to 4 (★★☆)
(hint: np.arange)
'''

gradedArray = np.zeros((5,5))
gradedArray += np.arange(5)
print(gradedArray)

'''
39. Create a vector of size 10 with values ranging from 0 to 1, both excluded (★★☆)
(hint: np.linspace)
'''

gradedVector = np.linspace(0.1,1,num=10,endpoint=False)
print(gradedVector)

'''
40. Create a random vector of size 10 and sort it (★★☆)
(hint: sort)
'''

sortedArray = np.random.uniform(1,10,10)
sortedArray = np.sort(sortedArray)
print(sortedArray)

'''
41. How to sum a small array faster than np.sum? (★★☆)
(hint: np.add.reduce)
'''

newArray = np.random.uniform(1,10,10)
print(np.add.reduce(newArray))

'''
42. Consider two random array A and B, check if they are equal (★★☆)
(hint: np.allclose, np.array_equal)
'''

newArray1 = np.random.uniform(1,10,10)
newArray2 = np.random.uniform(1,10,10)
print(np.array_equal(newArray1,newArray2))
newArray1 = np.zeros((5))
newArray2 = np.zeros((5))
print(np.array_equal(newArray1,newArray2))

'''
43. Make an array immutable (read-only) (★★☆)
(hint: flags.writeable)
'''

newArray = np.random.uniform(1,10,10)
print(newArray.flags.writeable)
newArray.flags.writeable = False
print(newArray.flags.writeable)
newArray[0] = 1  # ValueError: assignment destination is read-only

'''
44. Consider a random 10x2 matrix representing cartesian coordinates, convert them to
polar coordinates (★★☆)
(hint: np.sqrt, np.arctan2)
'''

cartesianArray = np.reshape(np.int64(np.random.uniform(1,10,20)),(10,2))
print(cartesianArray)
polarArray = []
for column in cartesianArray:
    polarArray.append(np.sqrt(np.power(column[0],2)+np.power(column[1],2)))
    polarArray.append(np.arctan2(column[1],column[0]))
polarArray = np.reshape(polarArray,(10,2))
print(polarArray)

'''
45. Create random vector of size 10 and replace the maximum value by 0 (★★☆)
(hint: argmax)
'''

newArray = np.random.uniform(1,10,10)
print(newArray)
newArray[np.argmax(newArray)] = 0
print(newArray)

'''
48. Print the minimum and maximum representable value for each numpy scalar type
(★★☆)
(hint: np.iinfo, np.finfo, eps)
'''

for dtype in [np.int8, np.int32, np.int64]:
    print(dtype)
    print('min: ', np.iinfo(dtype).min)
    print('max: ', np.iinfo(dtype).max, '\n')
for dtype in [np.float32, np.float64]:
    print(dtype)
    print('min: ', np.finfo(dtype).min)
    print('max: ', np.finfo(dtype).max)

'''
50. How to find the closest value (to a given scalar) in a vector? (★★☆)
(hint: argmin)
'''

newArray = np.int64(np.random.uniform(1,50,20))
startingValue = np.int64(np.random.uniform(1,50,1))
nearestValueIndex = (np.abs(newArray-startingValue)).argmin()
print(newArray, startingValue, newArray[nearestValueIndex])

'''
52. Consider a random vector with shape (100,2) representing coordinates, find point by
point distances (★★☆)
(hint: np.atleast_2d, T, np.sqrt, think what is the equation for calculating distances)
'''

randomVector = np.reshape(np.int64(np.random.uniform(1,10,200)),(100,2))
xValues = np.atleast_2d(randomVector[:,0])
xValuesT = np.transpose(xValues)
yValues = np.atleast_2d(randomVector[:,1])
yValuesT = np.transpose(yValues)

distancesArray = []
distancesArray = np.sqrt((xValues-xValuesT)**2 + (yValues-yValuesT)**2)
print(distancesArray)

'''
53. How to convert a float (32 bits) array into an integer (32 bits) in place?
(hint: astype(copy=False))
'''

randomArray = np.random.uniform(1,10,200)
randomArray.astype('float32')
randomArray.astype('int32')

'''
58. Subtract the mean of each row of a matrix (★★☆)
(hint: mean(axis=,keepdims=))
'''

randomMatrix = np.reshape(np.int64(np.random.uniform(1,10,100)),(10,10))

print('sum: ' , np.sum(randomMatrix,axis=1))
print('mean: ' , np.mean(randomMatrix,axis=1))

'''
60. How to tell if a given 2D array has null columns? (★★☆)
(hint: any, ~)
'''

randomMatrix = np.reshape(np.random.uniform(1,10,100),(10,10))
print((~randomMatrix.any(axis=0)).any())
randomMatrix[5][5] = np.nan
randomMatrix[4][4] = 0
print((~randomMatrix.any(axis=0)).any())

'''
64. Consider a given vector, how to add 1 to each element indexed by a second vector (be
careful with repeated indices)? (★★★)
(hint: np.bincount | np.add.at)
'''

newVector = np.random.randint(1,50,20)
print(newVector)

onesVector = np.int64(np.ones(20))
newVector = newVector + onesVector
print(newVector)

'''
67. Considering a four dimensions array, how to get sum over the last two axes at once?
(★★★)
(hint: sum(axis=(-2,-1)))
'''

#new4DArray = np.reshape(np.random.randint(1,50,625),(5,5,5,5))
new4DArray = np.reshape(np.int64(np.ones(625)),(5,5,5,5))
print(new4DArray)

print(np.sum(new4DArray,axis=(-2,-1)))

'''
68. Considering a one-dimensional vector D, how to compute means of subsets of D using a
vector S of same size describing subset indices? (★★★)
(hint: np.bincount)
'''

newVector1 = np.random.randint(1,50,20)
print(newVector1)
newVector2 = np.random.randint(1,50,20)
print(newVector2)
print(np.bincount(newVector2, weights=newVector1) / np.bincount(newVector2))

'''
69. How to get the diagonal of a dot product? (★★★)
(hint: np.diag)
'''

newVector1 = np.random.randint(1,50,(5,5))
newVector2 = np.random.randint(1,50,(5,5))
newVector2Transposed = np.transpose(newVector2)
print(np.sum(newVector1 * newVector2Transposed, axis=1))

'''
70. Consider the vector [1, 2, 3, 4, 5], how to build a new vector with 3 consecutive zeros
interleaved between each value? (★★★)
(hint: array[::4])
'''

newVector = np.array([1,2,3,4,5])
updatedVector = []
for i in range(0,len(newVector)-1):
    updatedVector.append(newVector[i])
    for j in range(0,3):
        updatedVector.append(0)
updatedVector.append(newVector[len(newVector)-1])
print(updatedVector)

'''
71. Consider an array of dimension (5,5,3), how to multiply it by an array with dimensions
(5,5)? (★★★)
(hint: array[:, :, None], or np.expand_dims)
'''

new3DArray = np.random.randint(1,50,(3,5,5))
new2DArray = np.random.randint(1,50,(5,5))

multipyOutcome = np.ones((3,5,5))

for i in range(0,3):
    multipyOutcome[:][:][i] = new3DArray[:][:][i] * new2DArray

print(multipyOutcome)

'''
72. How to swap two rows of an array? (★★★)
(hint: array[[]] = array[[]])
'''

newArray = np.random.randint(1,50,(2,5))
print(newArray)
newArray[[1,0]] = newArray[[0,1]]
print(newArray)

'''
75. How to compute averages using a sliding window over an array? (★★★)
(hint: np.cumsum)
'''
newArray = np.random.randint(1,50,(2,5))
print(newArray)
print(np.cumsum(newArray))

'''
77. How to negate a Boolean, or to change the sign of a float in place? (★★★)
(hint: np.logical_not, np.negative)
'''

zeroesOnces = np.random.randint(0,2,10)
print(zeroesOnces)
print(np.logical_not(zeroesOnces, out=zeroesOnces))

flipSignsArray = np.random.uniform(-1.0,1.0,10)
print(flipSignsArray)
print(np.negative(flipSignsArray, out=flipSignsArray))

'''
78. Consider 2 sets of points P0,P1 describing lines (2d) and a point p, how to compute
distance from p to each line i (P0[i],P1[i])? (★★★)
'''
def distance_from_2_lines_to_point(P0,P1,singlePoint):

    distanceP0 = np.power(P0-singlePoint,2)
    distanceP0sum = np.sum(distanceP0, axis = 1)
    distanceP0sumFinal = np.sqrt(distanceP0sum)

    distanceP1 = np.power(P1-singlePoint,2)
    distanceP1sum = np.sum(distanceP1, axis = 1)
    distanceP1sumFinal = np.sqrt(distanceP1sum)

    print(distanceP0sumFinal)
    print(distanceP1sumFinal)

P0 = np.random.randint(1,50,(5,2))
P1 = np.random.randint(1,50,(5,2))
singlePoint = np.random.randint(1,50,(1,2))
print('the distances from point ' , singlePoint)
print(distance_from_2_lines_to_point(P0,P1,singlePoint))

'''
79. Consider 2 sets of points P0,P1 describing lines (2d) and a set of points P, how to
compute distance from each point j (P[j]) to each line i (P0[i],P1[i])? (★★★)
'''

P0 = np.random.randint(1,50,(5,2))
P1 = np.random.randint(1,50,(5,2))
singlePointArray = np.random.randint(1,50,(5,2))

for startingPoint in singlePointArray:
    print('the distances from point ' , startingPoint)
    distance_from_2_lines_to_point(P0,P1,startingPoint)

'''
80. Consider an arbitrary array, write a function that extract a subpart with a fixed shape and
centered on a given element (pad with a fill value when necessary) (★★★)
(hint: minimum, maximum)
'''

import random
arbitraryArray = np.random.randint(1,10,(random.randint(1,10),random.randint(1,10)))
fixedShape = (random.randint(1,10),random.randint(1,10))
startingPoint = (random.randint(1,len(arbitraryArray[0])),random.randint(1,len(arbitraryArray[0])))

newArray = np.zeros((fixedShape[0],fixedShape[1]))

i = startingPoint[0]
j = startingPoint[1]

newarrayi = 0
newarrayj = 0

while i < len(arbitraryArray[0]):

    while j < len(arbitraryArray[1]):

        if newarrayi < fixedShape[0] and newarrayj < fixedShape[1]:
           newArray[newarrayi][newarrayj] = arbitraryArray[i][j]

        newarrayj += 1
        j += 1

    newarrayj = 0
    j = startingPoint[1]

    newarrayi += 1
    i += 1

print(newArray)

'''
83. How to find the most frequent value in an array?
(hints: np.unique, return_counts, argmax)
'''

newArray = np.random.randint(1,50,10)
print(np.bincount(newArray).argmax())

'''
89. How to get the n largest values of an array (★★★)
(hint: np.sort)
'''

import random
nValue =  random.randint(1,1000)
newArray = np.random.randint(1,100,1000)
sortedArray = np.sort(newArray)
print(sortedArray[-nValue:])

'''
93. Consider two arrays A and B of shape (8,3) and (2,2). How to find rows of A that contain
elements of each row of B regardless of the order of the elements in B? (★★★)
(hint: np.where)
'''

A = np.random.randint(1,10,(8,3))
B = np.random.randint(1,10,(2,2))

for singleRowA in A:
    for singleRowB in B:
        if singleRowB[0] in singleRowA and singleRowB[1] in singleRowA:
            print(singleRowB, 'was found i ', singleRowA)

'''
94. Considering a 10x3 matrix, extract rows with unequal values (e.g. [2,2,3]) (★★★)
'''

newMatrix = np.random.randint(1,3,(10,3))
print(newMatrix)
for singleRow in newMatrix:
    if singleRow[0] != singleRow[1] or singleRow[0] != singleRow[2]:
        print(singleRow)

'''
96. Given a two dimensional array, how to extract unique rows? (★★★)
(hint: np.ascontiguousarray | np.unique)
'''

twoDimensionalArray = np.random.randint(1,3,(10,3))
print(twoDimensionalArray,'\n')
print(np.unique(twoDimensionalArray, axis=0))


from math import sqrt

import numpy as np
from scipy.linalg import cholesky

class UnscentedTransform:
    def __init__(self, f, alpha, beta, k):
        self.f = f
        self.alpha = alpha
        self.beta = beta
        self.k = k
        self.l = None
        self.wm0 = None
        self.wmi = None
        self.wc0 = None
        self.wci = None

    def __call__(self, x, p):
        x = np.asarray(x).reshape((-1, 1))
        p = np.atleast_2d(np.asarray(p))
        n = len(x)

        if self.l is None:
            self.l = self.alpha**2 * (n + self.k) - n
            self.wm0 = self.l / (n + self.l)
            self.wc0 = self.l / (n + self.l) + 1 - self.alpha**2 + self.beta
            self.wmi = 1 / (2 * (n + self.l))
            self.wci = 1 / (2 * (n + self.l))

        # sqrt((L + lambda) * P)
        sqrtllp = sqrt(n + self.l) * cholesky(p, lower=False)

        # Construct sigma points and propagate
        sigmas = [x]
        ys = [np.asarray(self.f(x)).reshape((-1, 1))]
        for i in range(1, n + 1):
            delta = sqrtllp[:, i - 1].reshape((-1, 1))

            sigma = x + delta
            y = np.asarray(self.f(sigma)).reshape((-1, 1))
            sigmas.append(sigma)
            ys.append(y)

            sigma = x - delta
            y = np.asarray(self.f(sigma)).reshape((-1, 1))
            sigmas.append(sigma)
            ys.append(y)

        # Estimate mean and covariance
        mu = self.wm0 * ys[0]
        for y in ys[1:]:
            mu += self.wmi * y

        su = self.wc0 * ((ys[0] - mu) @ (ys[0] - mu).T)
        for y in ys[1:]:
            su += self.wci * ((y - mu) @ (y - mu).T)

        cu = self.wc0 * ((sigmas[0] - x) @ (ys[0] - mu).T)
        for sigma, y in zip(sigmas, ys[1:]):
            cu += self.wci * ((sigma - x) @ (y - mu).T)

        return mu.squeeze(), su, cu

if __name__ == '__main__':
    A = np.array([[1., -2.1],
                  [1.4, 4.1]])

    def f(x):
        return A @ x

    x0 = [1.3, -2]
    p0 = np.array([[7, 1.2],
                   [1.2, 8.4]])

    ut = UnscentedTransform(f, 1e-1, 2, (3 - 2))

    x1, sk, ck = ut(x0, p0)

    print(x1, A @ x0)
    print()

    print(sk, "\n")
    print(A @ p0 @ A.T, "\n")
    print(sk - A @ p0 @ A.T, "\n")