"""
Implementation of K-nearest neighbor (KNN) from scratch
where you can either use 2-loops (inefficient), 1-loop (better)
or a heavily vectorized zero-loop implementation.

Programmed by Aladdin Persson <aladdin.persson at hotmail dot com>
*    2020-04-24 Initial coding
"""

import numpy as np

class KNearestNeighbor:
    def __init__(self, k):
        self.k = k
        self.eps = 1e-8

    def train(self, X, y):
        self.X_train = X
        self.y_train = y

    def predict(self, X_test, num_loops=0):
        if num_loops == 0:
            distances = self.compute_distance_vectorized(X_test)

        elif num_loops == 1:
            distances = self.compute_distance_one_loop(X_test)

        else:
            distances = self.compute_distance_two_loops(X_test)

        return self.predict_labels(distances)

    def compute_distance_two_loops(self, X_test):
        """
        Inefficient naive implementation, use only
        as a way of understanding what kNN is doing
        """

        num_test = X_test.shape[0]
        num_train = self.X_train.shape[0]
        distances = np.zeros((num_test, num_train))

        for i in range(num_test):
            for j in range(num_train):
                # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
                distances[i, j] = np.sqrt(
                    self.eps + np.sum((X_test[i, :] - self.X_train[j, :]) ** 2)
                )

        return distances

    def compute_distance_one_loop(self, X_test):
        """
        Much better than two-loops but not as fast as fully vectorized version.
        Utilize Numpy broadcasting in X_train - X_test[i,:]
        """
        num_test = X_test.shape[0]
        num_train = self.X_train.shape[0]
        distances = np.zeros((num_test, num_train))

        for i in range(num_test):
            # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
            distances[i, :] = np.sqrt(
                self.eps + np.sum((self.X_train - X_test[i, :]) ** 2, axis=1)
            )

        return distances

    def compute_distance_vectorized(self, X_test):
        """
        Can be tricky to understand this, we utilize heavy
        vecotorization as well as numpy broadcasting.
        Idea: if we have two vectors a, b (two examples)
        and for vectors we can compute (a-b)^2 = a^2 - 2a (dot) b + b^2
        expanding on this and doing so for every vector lends to the
        heavy vectorized formula for all examples at the same time.
        """
        X_test_squared = np.sum(X_test ** 2, axis=1, keepdims=True)
        X_train_squared = np.sum(self.X_train ** 2, axis=1, keepdims=True)
        two_X_test_X_train = np.dot(X_test, self.X_train.T)

        # (Taking sqrt is not necessary: min distance won't change since sqrt is monotone)
        return np.sqrt(
            self.eps + X_test_squared - 2 * two_X_test_X_train + X_train_squared.T
        )

    def predict_labels(self, distances):
        num_test = distances.shape[0]
        y_pred = np.zeros(num_test)

        for i in range(num_test):
            y_indices = np.argsort(distances[i, :])
            k_closest_classes = self.y_train[y_indices[: self.k]].astype(int)
            y_pred[i] = np.argmax(np.bincount(k_closest_classes))

        return y_pred

if __name__ == "__main__":
    X = np.loadtxt("example_data/data.txt", delimiter=",")
    y = np.loadtxt("example_data/targets.txt")

    X = np.array([[1, 1], [3, 1], [1, 4], [2, 4], [3, 3], [5, 1]])
    y = np.array([0, 0, 0, 1, 1, 1])

    KNN = KNearestNeighbor(k=1)
    KNN.train(X, y)
    y_pred = KNN.predict(X, num_loops=0)
    print(f"Accuracy: {sum(y_pred == y) / y.shape[0]}")


from __future__ import print_function

import numpy as np
from numgrad import eval_numerical_gradient

def tanh_grad(x):
    return 1 - np.tanh(x) ** 2

if __name__ == '__main__':
    x = np.array([1.0, 2.1, 0.3, 0.7])
    print('tanh', np.tanh(x))
    print('tanh_grad', tanh_grad(x))

    # Note: eval_numerical_gradient works for scalar functions. Therefore we'll
    # run it for each element of tanh separately.
    print('Numerical gradient')
    for i in range(x.shape[0]):
        print(i, eval_numerical_gradient(lambda z: np.tanh(z)[i], x))


"""Is channel trace-preserving."""
from typing import List, Union

import numpy as np

from toqito.matrix_props import is_identity
from toqito.channels import partial_trace

def is_trace_preserving(
    phi: Union[np.ndarray, List[List[np.ndarray]]],
    rtol: float = 1e-05,
    atol: float = 1e-08,
    sys: Union[int, List[int]] = 2,
    dim: Union[List[int], np.ndarray] = None,
) -> bool:
    r"""
    Determine whether the given channel is trace-preserving [WatTP18]_.

    A map :math:`\Phi \in \text{T} \left(\mathcal{X}, \mathcal{Y} \right)` is
    *trace-preserving* if it holds that

    .. math::
        \text{Tr} \left( \Phi(X) \right) = \text{Tr}\left( X \right)

    for every operator :math:`X \in \text{L}(\mathcal{X})`.

    Given the corresponding Choi matrix of the channel, a neccessary and sufficient condition is

    .. math::
        \text{Tr}_{\mathcal{Y}} \left( J(\Phi) \right) = \mathbb{I}_{\mathcal{X}}

    In case :code:`sys` is not specified, the default convention is that the Choi matrix
    is the result of applying the map to the second subsystem of the standard maximally
    entangled (unnormalized) state.

    The dimensions of the subsystems are given by the vector :code:`dim`. By default,
    both subsystems have equal dimension.

    Alternatively, given a list of Kraus operators, a neccessary and sufficient condition is

    .. math::
        \sum_{a \in \Sigma} A_a^* B_a = \mathbb{I}_{\mathcal{X}}

    Examples
    ==========

    The map :math:`\Phi` defined as

    .. math::
        \Phi(X) = X - U X U^*

    is not trace-preserving, where

    .. math::
        U = \frac{1}{\sqrt{2}}
        \begin{pmatrix}
            1 & 1 \\
            -1 & 1
        \end{pmatrix}.

    >>> import numpy as np
    >>> from toqito.channel_props import is_trace_preserving
    >>> unitary_mat = np.array([[1, 1], [-1, 1]]) / np.sqrt(2)
    >>> kraus_ops = [[np.identity(2), np.identity(2)], [unitary_mat, -unitary_mat]]
    >>> is_trace_preserving(kraus_ops)
    False

    As another example, the depolarizing channel is trace-preserving.

    >>> from toqito.channels import depolarizing
    >>> from toqito.channel_props import is_trace_preserving
    >>> choi_mat = depolarizing(2)
    >>> is_trace_preserving(choi_mat)
    True

    References
    ==========
    .. [WatTP18] Watrous, John.
        "The theory of quantum information."
        Section: "Linear maps of square operators".
        Cambridge University Press, 2018.

    :param phi: The channel provided as either a Choi matrix or a list of Kraus operators.
    :param rtol: The relative tolerance parameter (default 1e-05).
    :param atol: The absolute tolerance parameter (default 1e-08).
    :param sys: Scalar or vector specifying the size of the subsystems.
    :param dim: Dimension of the subsystems. If :code:`None`, all dimensions are assumed to be
                equal.
    :return: True if the channel is trace-preserving, and False otherwise.
    """
    # If the variable `phi` is provided as a list, we assume this is a list
    # of Kraus operators.
    if isinstance(phi, list):
        phi_l = [A for A, _ in phi]
        phi_r = [B for _, B in phi]

        k_l = np.concatenate(phi_l, axis=0)
        k_r = np.concatenate(phi_r, axis=0)

        mat = k_l.conj().T @ k_r
    else:
        mat = partial_trace(input_mat=phi, sys=sys, dim=dim)
    return is_identity(mat, rtol=rtol, atol=atol)


# region Starting Values
import numpy as np
np.set_printoptions(suppress=True)
from math import sin

rounding = 5
N = 5
numberOfUnknown = 12
lengthOfRowForMatrix = numberOfUnknown + 1

differenceBetweenTwoPoints = 2

firstIndex = 1
secondIndex = 3
thirdIndex = 7
lastIndex = 12

Xarray = [3, 5, 7, 9, 11]

# region xValues for Faults

XarrayForFault1 = [3, 3.25, 3.5, 3.75, 4]
XarrayForFault2 = [4.25, 4.5, 4.75, 5, 5.25]
XarrayForFault3 = [5.5, 5.75, 6, 6.25, 6.5]
XarrayForFault4 = [6.75, 7, 7.25, 7.5, 7.75]
XarrayForFault5 = [8, 8.25, 8.5, 8.75, 9]
XarrayForFault6 = [9.25, 9.5, 9.75, 10, 10.25]
XarrayForFault7 = [10.5, 10.75, 11]

# endregion xValues for Faults

# My Sin Function
def MySinFun(x) -> float:
    element = sin(3 / 2 * x) + (x * 3) ** (1 / 3)
    return element

Yarray = [MySinFun(Xarray[0]), MySinFun(Xarray[1]), MySinFun(Xarray[2]), MySinFun(Xarray[3]), MySinFun(Xarray[4])]

# region yValues Faults

YarrayForFault1 = [MySinFun(XarrayForFault1[0]), MySinFun(XarrayForFault1[1]), MySinFun(XarrayForFault1[2]), MySinFun(XarrayForFault1[3]), MySinFun(XarrayForFault1[4])]
YarrayForFault2 = [MySinFun(XarrayForFault2[0]), MySinFun(XarrayForFault2[1]), MySinFun(XarrayForFault2[2]), MySinFun(XarrayForFault2[3]), MySinFun(XarrayForFault2[4])]
YarrayForFault3 = [MySinFun(XarrayForFault3[0]), MySinFun(XarrayForFault3[1]), MySinFun(XarrayForFault3[2]), MySinFun(XarrayForFault3[3]), MySinFun(XarrayForFault3[4])]
YarrayForFault4 = [MySinFun(XarrayForFault4[0]), MySinFun(XarrayForFault4[1]), MySinFun(XarrayForFault4[2]), MySinFun(XarrayForFault4[3]), MySinFun(XarrayForFault4[4])]
YarrayForFault5 = [MySinFun(XarrayForFault5[0]), MySinFun(XarrayForFault5[1]), MySinFun(XarrayForFault5[2]), MySinFun(XarrayForFault5[3]), MySinFun(XarrayForFault5[4])]
YarrayForFault6 = [MySinFun(XarrayForFault6[0]), MySinFun(XarrayForFault6[1]), MySinFun(XarrayForFault6[2]), MySinFun(XarrayForFault6[3]), MySinFun(XarrayForFault6[4])]
YarrayForFault7 = [MySinFun(XarrayForFault7[0]), MySinFun(XarrayForFault7[1]), MySinFun(XarrayForFault7[2])]

# endregion xValues Faults

# endregion Starting Values

# region Prints

# Print matrix
def PrintMatrixAsNp(matrixName,matrix):
    print("\n", matrixName,"=")
    npMatrix = np.array(matrix)
    print(npMatrix.round(rounding))

# Print vector
def PrintVectorAsNp(vectorName, vector):
    print("\n", vectorName,"=")
    npMatrix = np.array(vector)
    print(npMatrix.round(rounding))

# Print Lagrange
def PrintLagrange(Xarray, Yarray):
    PrintVectorAsNp("X", Xarray)
    PrintVectorAsNp("Y", Yarray)
    print("\nLagrange Polynom:")
    firstPart = f"\n{Yarray[0]} * ((x - {Xarray[1]})/({Xarray[0]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[0]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[0]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[0]} - {Xarray[4]})) +"
    secondPart = f"{Yarray[1]} * ((x - {Xarray[0]})/({Xarray[1]} - {Xarray[0]})) * ((x - {Xarray[2]})/({Xarray[1]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[1]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[1]} - {Xarray[4]})) +"
    thirdPart = f"{Yarray[2]} * ((x - {Xarray[0]})/({Xarray[2]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[2]} - {Xarray[1]})) * ((x - {Xarray[3]})/({Xarray[2]} - {Xarray[3]})) * ((x - {Xarray[4]})/({Xarray[2]} - {Xarray[4]})) +"
    fourthPart = f"{Yarray[3]} * ((x - {Xarray[0]})/({Xarray[3]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[3]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[3]} - {Xarray[2]})) * ((x - {Xarray[4]})/({Xarray[3]} - {Xarray[4]})) +"
    fifthPart = f"{Yarray[4]} * ((x - {Xarray[0]})/({Xarray[4]} - {Xarray[0]})) * ((x - {Xarray[1]})/({Xarray[4]} - {Xarray[1]})) * ((x - {Xarray[2]})/({Xarray[4]} - {Xarray[2]})) * ((x - {Xarray[3]})/({Xarray[4]} - {Xarray[3]}))"
    print(firstPart, secondPart, thirdPart, fourthPart, fifthPart, "\n")

# endregion Prints

# Implementing Lagrange Interpolation
def Lagrange(X_array, Y_array, pointToShow, show) -> float:
    # Create some prerequisites
    resultAsYpoint = 0
    for k in range(len(Y_array)):
        # Create some prerequisites
        tempPoint1 = 0
        tempPoint2 = 0

        tempPointArray = [tempPoint1, tempPoint2]

        tempPointArray[0] = 1
        tempPointArray[1] = 1

        for m in range(len(X_array)):
            if m == k:
                for z in range(2):
                    tempPointArray[z] = tempPointArray[z] * 1

            else:
                tempPointArray[1] = tempPointArray[1] * (X_array[k] - X_array[m])
                tempPointArray[0] = tempPointArray[0] * (pointToShow - X_array[m])

        resultAsYpoint = resultAsYpoint + Y_array[k] * tempPointArray[0] / tempPointArray[1]

    if show:
        print("Coef of",pointToShow,"element =",resultAsYpoint)

    return resultAsYpoint

def CreateMatrixForCramer(Xarray, Yarray) -> [list, list]:
    # Create some prerequisites
    matrixForCramer = list()
    rightPartForCramer = [0] * (lengthOfRowForMatrix - 1)

    # Call Our functions to create matrix
    FirstPartOfEquation(Xarray, Yarray, matrixForCramer)

    SecondPartOfEquation(Xarray, Yarray, matrixForCramer)

    ThirdPartOfEquation(Xarray, Yarray, matrixForCramer)

    FourthPartOfEquation(Xarray, Yarray, matrixForCramer)

    FifthPartOfEquation(Xarray, Yarray, matrixForCramer)

    # Create Right Part
    for i in range(len(matrixForCramer)):
        rightPartForCramer[i] = matrixForCramer[i][-1]

    # Clean matrix
    matrixForCramer = np.delete(matrixForCramer, np.s_[-1:], axis=1)

    return rightPartForCramer, matrixForCramer

#region PartsOfEquation

def FirstPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray)):
        queue = [0] * lengthOfRowForMatrix

        queue[i-firstIndex] = differenceBetweenTwoPoints; queue[i+secondIndex] = differenceBetweenTwoPoints ** 2
        queue[i+thirdIndex] = differenceBetweenTwoPoints ** 3; queue[lastIndex] = Yarray[i] - Yarray[i - 1]

        matrixForCramer.append(queue)

def SecondPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray) - 1):
        queue = [0] * lengthOfRowForMatrix

        queue[i] = 1; queue[i-firstIndex] = -1; queue[lastIndex] = 0
        queue[i+secondIndex] = -2 * differenceBetweenTwoPoints; queue[i+thirdIndex] = -3 * differenceBetweenTwoPoints ** 2

        matrixForCramer.append(queue)

def ThirdPartOfEquation(Xarray, Yarray, matrixForCramer):
    for i in range(1, len(Xarray) - 1):
        queue = [0] * lengthOfRowForMatrix

        queue[i+secondIndex+1] = 1; queue[i+secondIndex] = -1; queue[i+thirdIndex] = -3 * differenceBetweenTwoPoints; queue[lastIndex] = 0

        matrixForCramer.append(queue)

def FourthPartOfEquation(Xarray, Yarray, matrixForCramer):
    queue = [0] * lengthOfRowForMatrix

    queue[thirdIndex] = 1; queue[lastIndex-1] = 3 * differenceBetweenTwoPoints; queue[lastIndex] = 0

    matrixForCramer.append(queue)

def FifthPartOfEquation(Xarray, Yarray, matrixForCramer):
    queue = [0] * lengthOfRowForMatrix

    queue[secondIndex+1] = 1; queue[lastIndex] = 0

    matrixForCramer.append(queue)

#endregion PartsOfEquation

# Do Cramer Method
def Cramer(matrixForComputations, matrixForCramer, rightPartForCramer) -> list:
    # Create some prerequisites
    SIPcoeffs = list()

    for k in range(0, len(rightPartForCramer)):
        for h in range(0, len(rightPartForCramer)):
            # Get values for right part
            matrixForComputations[h][k] = rightPartForCramer[h]

            if k > 0:
                matrixForComputations[h][k - 1] = matrixForCramer[h][k - 1]

        # Add value to the vector
        SIPcoeffs.append(np.linalg.det(matrixForComputations) / np.linalg.det(matrixForCramer))

        # Rounding values
        SIPcoeffs[k] = round(SIPcoeffs[k], rounding)

    return SIPcoeffs

# Printing Lagrange Equation
PrintLagrange(Xarray, Yarray)

# region Faults
for i in range(N):
    Lagrange(Xarray, Yarray, Xarray[i], True)
    print("Fault of element", Xarray[i], "=", abs(MySinFun(Xarray[i]) - Lagrange(Xarray, Yarray, Xarray[i], False)))

print("\n")

for i in range(N):
    Lagrange(XarrayForFault1, YarrayForFault1, XarrayForFault1[i], True)
    print("Fault of element", XarrayForFault1[i], "=", abs(MySinFun(XarrayForFault1[i]) - Lagrange(XarrayForFault1, YarrayForFault1, XarrayForFault1[i], False)))

for i in range(N):
    Lagrange(XarrayForFault2, YarrayForFault2, XarrayForFault2[i], True)
    print("Fault of element", XarrayForFault2[i], "=", abs(MySinFun(XarrayForFault2[i]) - Lagrange(XarrayForFault2, YarrayForFault2, XarrayForFault2[i], False)))

for i in range(N):
    Lagrange(XarrayForFault3, YarrayForFault3, XarrayForFault3[i], True)
    print("Fault of element", XarrayForFault3[i], "=", abs(MySinFun(XarrayForFault3[i]) - Lagrange(XarrayForFault3, YarrayForFault3, XarrayForFault3[i], False)))

for i in range(N):
    Lagrange(XarrayForFault4, YarrayForFault4, XarrayForFault4[i], True)
    print("Fault of element", XarrayForFault4[i], "=", abs(MySinFun(XarrayForFault4[i]) - Lagrange(XarrayForFault4, YarrayForFault4, XarrayForFault4[i], False)))

for i in range(N):
    Lagrange(XarrayForFault5, YarrayForFault5, XarrayForFault5[i], True)
    print("Fault of element", XarrayForFault5[i], "=", abs(MySinFun(XarrayForFault5[i]) - Lagrange(XarrayForFault5, YarrayForFault5, XarrayForFault5[i], False)))

for i in range(N):
    Lagrange(XarrayForFault6, YarrayForFault6, XarrayForFault6[i], True)
    print("Fault of element", XarrayForFault6[i], "=", abs(MySinFun(XarrayForFault6[i]) - Lagrange(XarrayForFault6, YarrayForFault6, XarrayForFault6[i], False)))

for i in range(len(XarrayForFault7)):
    Lagrange(XarrayForFault7, YarrayForFault7, XarrayForFault7[i], True)
    print("Fault of element", XarrayForFault7[i], "=", abs(MySinFun(XarrayForFault7[i]) - Lagrange(XarrayForFault7, YarrayForFault7, XarrayForFault7[i], False)))

# endregion Faults

rightPartForCramer, matrixForCramer = CreateMatrixForCramer(Xarray.copy(), Yarray.copy())
matrixForComputations = list(map(list, matrixForCramer))

# Coefficients of spline interpolation polynomials
SIPcoeffs = Cramer(matrixForComputations, matrixForCramer, rightPartForCramer)
PrintVectorAsNp("Coefficients of spline interpolation polynomial", SIPcoeffs)


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

# Jacobian
def jacobian(x):
    dx = 2 * x[0]
    dy = 2 * x[1]
    return np.array((dx, dy))

# Objective function formalization
def obj_fun():
    return lambda x: x[0] ** 2 + x[1] ** 2

# Objective function for plot
def plot_obj_fun(x):
    return x[0] ** 2 + x[1] ** 2

# Minimize objective function
def min_func(x):
    return minimize(obj_fun(), x, method='SLSQP', bounds=bounds, constraints=cons, options={'ftol': 1e-9,'disp':True})

# Minimize objective function with gradient
def min_func_jacobian(x):
    return minimize(obj_fun(), x, method='SLSQP', bounds=bounds, constraints=cons, jac=jacobian, options={'ftol': 1e-9,'disp':True})

# Constraint functions (inequalities have to be >= 0)
cons = ({'type': 'ineq', 'fun': lambda x: -0.5 + x[0]},
        {'type': 'ineq', 'fun': lambda x: x[0] + x[1] - 1},
        {'type': 'ineq', 'fun': lambda x: x[0]**2 + x[1]**2 - 1},
        {'type': 'ineq', 'fun': lambda x: 9*x[0]**2 + x[1]**2 - 9},
        {'type': 'ineq', 'fun': lambda x: x[0]**2 - x[1]},
        {'type': 'ineq', 'fun': lambda x: x[1]**2 - x[0]})

# Bounds, if any, e.g. x1 and x2 have to be positive
bounds = ((None, None),) * 2

# Initial guess
x0s = [np.asarray((-3, -5)), np.asarray((1, 1)), np.asarray((10, -10))]

# Save minimal points and variables to plot
minimals = []    # minimals[i][0] = x, minimals[i][1] = y, minimals[i][2] = z
for x0 in x0s:
    # Method SLSQP uses Sequential Least SQuires Programming to minimize a function
    # of several variables with any combination of bounds, equality and inequality constraints.
    res = min_func(x0)
    print(res)
    minimals.append((res.x[0], res.x[1], res.fun))

# define range for input
r_min, r_max = -5.0, 5.0
# sample input range uniformly at 0.7 increments
xaxis = np.arange(r_min, r_max, 0.2)
yaxis = np.arange(r_min, r_max, 0.2)
# create a mesh from the axis
x0, x1 = np.meshgrid(xaxis, yaxis)
# compute targets
results = np.array(plot_obj_fun((x0, x1)))
# create a surface plot with the jet color scheme
figure = plt.figure()
axis = figure.gca(projection='3d')
label_1 = "global minimum [" + str(minimals[1][0]) + ", " + str(minimals[1][1]) + ", " + str(minimals[1][2]) + "]"
axis.scatter(minimals[1][0], minimals[1][1], minimals[1][2], color='red', edgecolor='black', label=label_1)
plt.legend(prop={'size': 6})
axis.plot_surface(x0, x1, results, cmap='jet')
# show the plot
plt.show()


import numpy as np
import pickle
import cvxopt
from cvxopt import matrix, solvers

import sklearn
from sklearn.metrics import classification_report, confusion_matrix , accuracy_score, mean_squared_error
from sklearn.model_selection import train_test_split

from python.svmutil import *

import time
import sys
import warnings

if not sys.warnoptions:
	warnings.simplefilter("ignore")

path_train = sys.argv[1];
path_test = sys.argv[2];
binary = int(sys.argv[3]);
part = sys.argv[4];

train_array = np.genfromtxt(path_train,delimiter=',');
train_array_x = train_array[:,0:784]/255;
train_array_y = train_array[:,784].reshape(train_array.shape[0],1);

test_array = np.genfromtxt(path_test,delimiter=',');
test_array_x = test_array[:,0:784]/255;
test_array_y = test_array[:,784].reshape(test_array.shape[0],1);

def linear_kernel(X,y):
	M = y * X;
	return np.dot(M, M.T);

def gaussian_kernel(X, sigma=0.05):
	m = X.shape[0];
	X2 = np.sum(np.multiply(X, X),axis=1, keepdims=True);
	K0 = X2 + X2.T - 2 * np.matmul(X, X.T)
	return np.power(np.exp(-sigma),K0);

def gaussian_kernel_elem(X1,X2, sigma = 0.05):
	return np.exp(-sigma * np.linalg.norm(X1-X2)**2)

class SVM(object):
	def __init__(self, kernel=linear_kernel, C=1.0):
		self.kernel = kernel;
		self.C = C;

	def fit(self, X, y):
		m,n = X.shape;
		if(self.kernel == linear_kernel):
			P = matrix(self.kernel(X,y));
		else:
			K = gaussian_kernel(X);
			P = matrix(y * y.T * K);
		q = matrix(np.ones(m) * -1.0);
		A = matrix(y,(1,m));
		b = matrix(0.0);

		G1 = np.eye(m) * -1.0;
		G2 = np.eye(m);
		G = matrix(np.vstack((G1, G2)));
		h1 = np.zeros(m);
		h2 = np.ones(m) * self.C;
		h = matrix(np.hstack((h1, h2)));
		solvers.options['show_progress'] = False
		solution = solvers.qp(P, q, G, h, A, b);

		alpha = np.ravel((solution['x'])).reshape(m,1);
		self.supp_vec_flag = (alpha > 1e-5).ravel();
		self.indices = np.arange(len(alpha))[self.supp_vec_flag];
		self.alpha = alpha[self.supp_vec_flag];
		self.supp_vec = X[self.supp_vec_flag];
		self.supp_vec_y = y[self.supp_vec_flag];

		if(self.kernel==linear_kernel):
			self.w = np.sum(self.alpha * self.supp_vec * self.supp_vec_y, axis = 0, keepdims=True).T;
			b1 = np.min(np.matmul(X[(y == 1).ravel()], self.w));
			b2 = np.max(np.matmul(X[(y == -1).ravel()], self.w));
			self.b = -(b1+b2)/2;
		else:
			self.w = None;
			self.b = 0.0;

	def predict(self, X):
		if (self.kernel == linear_kernel):
			return np.sign(np.dot(X, self.w) + self.b);
		else:
			m = X.shape[0];
			pred = np.zeros(m);
			for i in range(m):
				temp = 0;
				s=0;
				#if(i%100==0):
					#print(i)
				for alpha, supp_vec, supp_vec_y in zip(self.alpha, self.supp_vec, self.supp_vec_y):
					s += alpha * supp_vec_y * gaussian_kernel_elem(X[i],supp_vec);
				pred[i] = s + self.b;
			return np.sign(pred);

if(binary == 0):
	X_train_bin = train_array_x[(train_array_y==3).ravel() | (train_array_y==4).ravel()];
	Y_train_bin = train_array_y[(train_array_y==3).ravel() | (train_array_y==4).ravel()];
	Y_train_bin = -1.0 * (Y_train_bin==3) + 1.0 * (Y_train_bin==4);

	X_test_bin = test_array_x[(test_array_y==3).ravel() | (test_array_y==4).ravel()];
	Y_test_bin = test_array_y[(test_array_y==3).ravel() | (test_array_y==4).ravel()];
	Y_test_bin = -1.0 * (Y_test_bin==3) + 1.0 * (Y_test_bin==4);

	if(part == 'a'):
		clf = SVM();
		clf.fit(X_train_bin, Y_train_bin);
		preds = clf.predict(X_test_bin);
		print("Linear Score: ", round(accuracy_score(preds,Y_test_bin),5))

	if(part == 'b'):
		clf = SVM(kernel=gaussian_kernel);
		clf.fit(X_train_bin, Y_train_bin);
		preds = clf.predict(X_test_bin);
		print("RBF Score: ", round(accuracy_score(preds,Y_test_bin),5))

	if(part == 'c'):
		model = svm_train(Y_train_bin.ravel(),X_train_bin, '-s 0 -t 0 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(Y_test_bin.ravel(),X_test_bin,model, '-q');
		print("Linear Score: ", round(accuracy_score(label_predict,Y_test_bin),5))

		model = svm_train(Y_train_bin.ravel(),X_train_bin, '-s 0 -t 2 -g 0.05 -q');
		label_predict_g, accuracy_g, decision_values_g=svm_predict(Y_test_bin.ravel(),X_test_bin,model,'-q');
		print("RBF Score: ", round(accuracy_score(label_predict_g,Y_test_bin),5))

else:
	if(part == 'a'):
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				globals()["Xtrain_" + suffix] = train_array_x[(train_array_y==i).ravel() | (train_array_y==j).ravel()];
				temp = train_array_y[(train_array_y==i).ravel() | (train_array_y==j).ravel()];
				globals()["Ytrain_" + suffix] = -1.0 * (temp==i) + 1.0 * (temp==j);
				globals()["clf_" + suffix] = SVM(kernel=gaussian_kernel);
				globals()["clf_" + suffix].fit(globals()["Xtrain_" + suffix], globals()["Ytrain_" + suffix]);

		preds_train = np.zeros((train_array_y.shape[0],10,10))
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				temp = globals()["clf_" + suffix].predict(train_array_x);
				preds_train[:,i,j] = (temp-1)/(-2);
				preds_train[:,j,i] = (temp+1)/2;

		preds_train = np.abs(preds_train)
		preds_train = np.argmin(np.sum(preds_train, axis=1), axis = 1)
		print("Training Accuracy: ", accuracy_score(preds_train,train_array_y))

		preds_test = np.zeros((test_array_y.shape[0],10,10))
		for i in range(10):
			for j in range(i+1,10):
				suffix = str(i) + "-_-" + str(j);
				temp = globals()["clf_" + suffix].predict(test_array_x);
				preds_test[:,i,j] = (temp-1)/(-2);
				preds_test[:,j,i] = (temp+1)/2;

		preds_test = np.abs(preds_test)
		preds_test = np.argmin(np.sum(preds_test, axis=1), axis = 1)
		print("Test Accuracy: ", accuracy_score(preds_test,test_array_y))

	if(part=='b'):
		model = svm_train(train_array_y.ravel(),train_array_x, '-s 0 -t 2 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(train_array_y.ravel(),train_array_x,model,'-q');
		print("Train Accuracy :", accuracy_score(label_predict, train_array_y));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Accuracy :", accuracy_score(label_predict, test_array_y));

	if(part =='c'):
		model = svm_train(train_array_y.ravel(),train_array_x, '-s 0 -t 2 -g 0.05 -q');
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');

		conf_mat = np.zeros((10,10));
		for i in range(test_array_y.shape[0]):
			conf_mat[int(test_array_y[i])][int(label_predict[i])] = conf_mat[int(test_array_y[i])][int(label_predict[i])] + 1;
		print(conf_mat.astype(int));

	if(part == 'd'):
		x_train, x_dev,y_train, y_dev = train_test_split(train_array_x, train_array_y, test_size = 0.1);
		x_train = train_array_x;
		y_train = train_array_y;
		print("C = 1e-5 (Accuracies - Dev & Test");
		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1e-5 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 1e-3 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1e-3 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 1 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 1 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 5 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 5 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));

		print("C = 10 (Accuracies - Dev & Test");

		model = svm_train(y_train.ravel(),x_train, '-s 0 -t 2 -g 0.05 -c 10 -q');
		label_predict, accuracy, decision_values=svm_predict(y_dev.ravel(),x_dev,model, '-q');
		print("Dev Set :", accuracy_score(label_predict, y_dev));
		label_predict, accuracy, decision_values=svm_predict(test_array_y.ravel(),test_array_x,model, '-q');
		print("Test Set :", accuracy_score(label_predict, test_array_y));


import numpy as np
import sys

def compute_hypothesis_for_a_line(theta0, theta1, dataset):
    e = 0
    length = len(dataset)
    for el in dataset:
        e += (1/length) * pow(el[1] - (theta0 * el[0] + theta1), 2)

    return e

def comp(dataset, current_thetha_zero, current_thetha_one, learning_rate):
    theta_zero = 0
    theta_one = 0
    length = len(dataset)
    for el in dataset:
        x = el[0]
        y = el[1]
        theta_zero += -(2/length) * (y - (current_thetha_one*x + current_thetha_zero))
        theta_one += -(2/length) * x * (y - (current_thetha_one * x + current_thetha_zero))
    new_theta_zero = current_thetha_zero - (learning_rate * theta_zero)
    new_theta_one = current_thetha_one - (learning_rate * theta_one)

    return [new_theta_zero, new_theta_one]

def run_gradient_descent(dataset, theta0, theta1, learning_rate, num_interations):
    temp_theta_zero = theta0
    temp_theta_one = theta1
    for i in range(num_interations):
        temp_theta_zero, temp_theta_one = comp(
            np.array(dataset), temp_theta_zero, temp_theta_one, learning_rate)

    return [temp_theta_zero, temp_theta_one]

def runScript():
    dataset = np.genfromtxt('dataset.csv', delimiter=',')
    learning_rate = 0.000009999
    starting_m = 0
    starting_b = 0
    num_interations = 1000
    print('Starting gradient descent x={0} y={1} e={2}'.format(starting_m, starting_b, compute_hypothesis_for_a_line(starting_m, starting_b, dataset)))
    [theta1, theta0] = run_gradient_descent(dataset, starting_b, starting_m, learning_rate, num_interations)
    print('Result {0} {1}'.format(theta0, theta1))
    print('Ending gradient descent x={0} y={1} e={2}'.format(theta0, theta1, compute_hypothesis_for_a_line(theta0, theta1, dataset)))

runScript()


#El promedio histórico del salario de los egresados de una universidad
#es salario_historico, la desviación estándar es
#de std_salario_historico.

#Calcule con un método Monte Carlo el salario promedio
#salario_promedio que una muestra aleatoria de  n_egresados debe tener
#para considerar que cualquier otro grupo de n_egresados tiene una
#probabilidad p_excede de tener un salario promedio que excede
#salario_promedio.

#Esta probabilidad se debe calcular con una función llamada
#probabilidad_excede. Las variables de entrada de las función son (en ese orden):
#salario_historico, std_salario_historico, n_egresados, p_excede, donde
#todas las variables de entrada son de tipo entero, excepto p_excede
#que es de tipo float. Suponga que el valor de p_excede es un múltiplo
#entero de 0.001. La función solamente debe devolver el valor pedido
#del salario (un número de tipo entero).

#La solución debe estar en un archivo llamado
#"ApellidoNombre_Ejercicio14.py" donde Apellido y Nombre debe
#reemplazarlos con su apellido y nombre.  Suba ese archivo como
#respuesta a esta actividad. Al ejecutar "python
#ApellidoNombre_Ejercicio14.py" o al llamar la función no se debe
#producir ningún error ni escribir nada en la terminal.  Calificaremos
#la función con valores de n_egresados hasta 10000. Se considera que la
#función no corre si cada llamada de la función se demora más de 10
#segundos en correr.

#Solamente puede utilizar las funciones y métodos vistos en clase
#(videos o clases sincrónicas, o que ya se encuentren en el
# repositorio).

import numpy as np

def probabilidad_excede(salario_historico, std_salario_historico, n_egresados, p_excede):
    n_sample = 10000000
    x = np.random.normal(loc=salario_historico,
                         scale=std_salario_historico/np.sqrt(n_egresados),
                         size=n_sample)
    x = np.sort(x)
    salario_promedio = int(x[n_sample - int(p_excede*n_sample)])
    return salario_promedio

#a = probabilidad_excede(50000, 1000, 4, 0.158)


'''
Generic file to set up the decision problem (i.e., optimization problem) under consideration
Must have functions:
  get_num_decisions(): returns number of decision variables (i.e., dimension of cost vector)
  find_opt_decision(): returns for a matrix of cost vectors the corresponding optimal decisions for those cost vectors

This particular file sets up a shortest path decision problem over a 4 x 4 grid network, where driver starts in
southwest corner and tries to find shortest path to northeast corner.
'''

from gurobipy import *
import numpy as np

dim = 4 #(creates dim * dim grid, where dim = number of vertices)
Edge_list = [(i,i+1) for i in range(1, dim**2 + 1) if i % dim != 0]
Edge_list += [(i, i + dim) for i in range(1, dim**2 + 1) if i <= dim**2 - dim]
Edge_dict = {} #(assigns each edge to a unique integer from 0 to number-of-edges)
for index, edge in enumerate(Edge_list):
    Edge_dict[edge] = index
D = len(Edge_list) # D = number of decisions

def get_num_decisions():
  return D

Edges = tuplelist(Edge_list)
# Find the optimal total cost for an observation in the context of shortes path
m_shortest_path = Model('shortest_path')
m_shortest_path.Params.OutputFlag = 0
flow = m_shortest_path.addVars(Edges, ub = 1, name = 'flow')
m_shortest_path.addConstrs((quicksum(flow[i,j] for i,j in Edges.select(i,'*')) - quicksum(flow[k, i] for k,i in Edges.select('*',
  i)) == 0 for i in range(2, dim**2)), name = 'inner_nodes')
m_shortest_path.addConstr((quicksum(flow[i,j] for i,j in Edges.select(1, '*')) == 1), name = 'start_node')
m_shortest_path.addConstr((quicksum(flow[i,j] for i,j in Edges.select('*', dim**2)) == 1), name = 'end_node')

def shortest_path(cost):
    # m_shortest_path.setObjective(quicksum(flow[i,j] * cost[Edge_dict[(i,j)]] for i,j in Edges), GRB.MINIMIZE)
    m_shortest_path.setObjective(LinExpr( [ (cost[Edge_dict[(i,j)]],flow[i,j] ) for i,j in Edges]), GRB.MINIMIZE)
    m_shortest_path.optimize()
    return {'weights': m_shortest_path.getAttr('x', flow), 'objective': m_shortest_path.objVal}

def find_opt_decision(cost):
    weights = np.zeros(cost.shape)
    objective = np.zeros(cost.shape[0])
    for i in range(cost.shape[0]):
        temp = shortest_path(cost[i,:])
        for edge in Edges:
            weights[i, Edge_dict[edge]] = temp['weights'][edge]
        objective[i] = temp['objective']
    return {'weights': weights, 'objective':objective}


import matplotlib
from matplotlib.figure import Figure
from mpl_toolkits.mplot3d import Axes3D
from sympy import *
from sympy.diffgeom import *
import matplotlib.pyplot as plt
import numpy as np

from common1 import plot_latex

R = 10
GRID = 10

def draw_shape():
    fig: Figure = plt.figure()  # figsize=(200, 200)
    ax: Axes3D = fig.add_subplot(111, projection='3d')
    # 经度
    u = np.linspace(0, 2 * np.pi, GRID * 2 + 1)
    # 纬度
    v = np.linspace(0, np.pi, GRID)

    x = R * np.outer(np.cos(u), np.sin(v))
    y = R * np.outer(np.sin(u), np.sin(v))
    z = R * np.outer(np.ones(np.size(u)), np.cos(v))
    ax.plot_wireframe(x, y, z, color='lightblue')
    return ax

def coords():
    rho = R
    u, v = symbols('u, v')
    x, y, z = symbols('x, y, z')
    m = Manifold('M', 3)
    # 定义一个patch
    patch = Patch('P', m)
    # 为这个patch建立坐标系，一个是笛卡尔，一个是极坐标
    rect: CoordSystem = CoordSystem('rect', patch)
    polar: CoordSystem = CoordSystem('spherical', patch)
    polar.connect_to(rect,
                     [u, v],
                     [rho * sin(u) * cos(v), rho * sin(u) * sin(v), rho * cos(u)])

    # jacobian
    j = polar.jacobian(rect, [u, v])
    # print("jocobian=", j)
    print("jacobian latex=", latex(j))
    # plot_latex(latex(j))
    m = j.T @ j
    # print("metrics tensor-----------------------", m)
    # pprint(m)
    print("metrics tensor latex=", latex(m))
    # plot_latex((latex(j)))
    return polar, rect

def plot_uv(polar, u, v, color="green"):
    pt = polar.coord_tuple_transform_to(rect, [u, v])
    pt1: Point3D = Point3D(pt)
    print(pt1)
    # line = Line3D(Point3D(0, 0, 0), Point3D(5, 5, 5))
    # ax.add_line(line)
    ax.plot([0, pt1.x], [0, pt1.y], [0, pt1.z], color=color)
    print(pt)

ax = draw_shape()
polar, rect = coords()

# 纵向的两个轴，给定的坐标其实是球面的，只是绘制从球心开始绘制而已。
# u是纬度值， v是经度。
# 当纬度是0或者是pi, 经度任取。分别是南极和北极
plot_uv(polar, 0, 0, color='r')
plot_uv(polar, pi, 0, color='black')

# 横向的四个球轴，位于赤道
plot_uv(polar, np.pi / 2, 0, color='r')
plot_uv(polar, np.pi / 2, pi / 2, color="yellow")
plot_uv(polar, np.pi / 2, pi, color="green")
plot_uv(polar, np.pi / 2, 3 * pi / 2, color="blue")

# 在上半球找一个点
plot_uv(polar, np.pi / 4, pi, color="blue")

"""接下来就是利用christoffel symbol计算测地线了"""
# 怎样才能在3d网格上绘制活动标架呢？并最终绘制测地线（两个点之间的大圆）
# 选取球面上的任意一点，计算这一点的变换矩阵，并绘制局部笛卡尔坐标系
# 如果一个曲面是不规则的，那要如何计算雅可比矩阵，Christoffel Symbol呢？

plt.show()


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

e = math.e # e = 2.718281828459045
pi = math.pi # pi = 3.141592653589793

def sin(x):
    return np.sin(x)

def cos(x):
    return np.cos(x)

def tan(x):
    return np.tan(x)

def cosec(x):
    return 1/sin(x)

def sec(x):
    return 1/cos(x)

def cot(x):
    return 1/tan(x)

def factorial(x):
    '''
    Factorial/Repeted Multiplication of number x.
    factorial(3) # 3*2*1
    >>> 6
    '''
    return np.math.factorial(x)

def sqrt(x):
    '''
    Square Root of a number x.
    sqrt(49)
    >>> 7.0
    '''
    return round(x**(0.5), 2)

def cbrt(x):
    '''
    Cube Root of a Number x.
    print(cbrt(125))
    >>> 5.0
    '''
    return round(x**(1./3.), 2)

def ln(x):
    return np.log(x)

def log(x, base=10):
    '''
    Find Logarithm of a number x.
    log(100)
    >>> 2.0

    log(64, 2)
    >>> 6.0
    '''
    return ln(x)/ln(base)

def d(x, dx, func):
    '''
    d(x, dx, func)
    x -> Range/Domain of the Function
    dx -> How small each successive part is
    func -> It is the function to be differenciated.
    '''
    dx = float(dx)
    eq = f"({func.replace('x', f'(x + {dx})') + f' - {func}'})/dx"
    # print(eq)
    return eval(eq)

class Grapher:
    def __init__(self, fx, lb=-10, ub=10, y_lim=[None, None], step=0.01, label=True, grid=False, xlabel='x', title=None, lol="upper left", linestyle='-', mode='light', style=None):
        self.fx = fx
        self.lb = float(lb)
        self.ub = float(ub)
        self.y_lim = y_lim
        self.step = float(step)
        self.grid = grid
        self.label = label
        self.x = np.arange(self.lb, self.ub, step=self.step)
        self.xlabel = xlabel
        self.ylabel = f'f ({xlabel})'
        self.title = title
        self.lol = lol
        self.linestyle = linestyle
        self.mode = mode
        self.style = style

    def valid_function(self, f):
        '''
        Converts a human readable str(equation) to a more executable form.
        sinx -> sin(x)
        2^4 -> 2**4
        '''
        eq = f.replace('^', '**')
        if 'x' not in eq and len(eq) > 0:
            eq += '* x/x'

        eq = eq.replace('sinx', 'sin(x)')
        eq = eq.replace('cosx', 'cos(x)')
        eq = eq.replace('tanx', 'tan(x)')
        eq = eq.replace('cosecx', 'cosec(x)')
        eq = eq.replace('secx', 'sec(x)')
        eq = eq.replace('cotx', 'cot(x)')
        eq = eq.replace('lnx', 'ln(x)')
        eq = eq.replace('logx', 'log(x)')
        eq = eq.replace('d(', f'd(x, {self.step}, ')

        return eq

    def plot(self):
        '''
        Plot the graphs of many equations on one graph to see the relations between the graphs.
        You can use it to see how well Taylor Series match with the actuall graph of a function.

        Eg:
        g = Grapher(['sin(x)', 'x - ((x**3)/factorial(3)) + (x**5)/factorial(5)'], lb=-3, ub=3, grid=True)
        g.plot()
        '''
        if self.mode == 'dark':
            plt.style.use('dark_background')

        if self.style is not None:
            plt.style.use(self.style)

        x = self.x
        for f in self.fx:
            eq = self.valid_function(f)
            y = eval(eq)
            plt.plot(x, y, self.linestyle, label=f'{self.ylabel} = {str(f)}')
        if self.label:
            plt.legend(loc=self.lol)

        if self.grid:
            plt.grid()

        plt.xlabel(self.xlabel)
        plt.ylabel(self.ylabel)
        if self.title is not None:
            plt.title(self.title)

        plt.axis((self.lb, self.ub, self.y_lim[0], self.y_lim[1]))
        plt.show()

    def examples(self):
        '''
        See some graphing examples.
        '''
        g = Grapher(['cos(x)', '1 - ((x^2)/factorial(2)) + (x^4)/factorial(4)'], lb=-3, ub=3, label=True, lol='lower center', title='Taylor Polynomial for cos(x)')
        g.plot()
        g = Grapher(['sinx', 'd("sinx")'], step=0.001, mode='dark')
        g.plot()

    def author(self):
        '''
        Well, wanna know about me? Run this method!
        '''
        plt.text(-9, 3, """Hi, I am Shubham. Hope you are enjoying Grapher.
Try plotting something crazy!
How About x^x or sinx and its derivative.
And now, you need not even calculate it.
Just type d("x^x") or d("sinx").
Well, close this window to see them.""")
        plt.plot(np.arange(-10, 10), np.arange(-10, 10))
        plt.show()
        g = Grapher(['x^x', 'd("x^x")'], 0, 1.5)
        g.plot()
        g = Grapher(['sinx', 'd("sinx")'], mode='dark')
        g.plot()

if __name__ == '__main__':
    # g = Grapher(['cos(x)', '1 - ((x^2)/factorial(2)) + (x^4)/factorial(4)'], lb=-3, ub=3, label=True, lol='lower center', title='Taylor Polynomial for cos(x)')
    g = Grapher(['1 + 1/x', 'x'], step=0.001, y_lim=[-5, 5])
    g.plot()


import numpy as np

print(np.__version__)
# 1.17.3

a_bool = np.array([True, True, False, False])
b_bool = np.array([True, False, True, False])

print(a_bool.dtype)
# bool

print(b_bool.dtype)
# bool

print(a_bool & b_bool)
# [ True False False False]

print(a_bool | b_bool)
# [ True  True  True False]

print(a_bool ^ b_bool)
# [False  True  True False]

print(~a_bool)
# [False False  True  True]

print(type(a_bool & b_bool))
# <class 'numpy.ndarray'>

print((a_bool & b_bool).dtype)
# bool

# print(a_bool and b_bool)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print(np.logical_and(a_bool, b_bool))
# [ True False False False]

print(np.logical_or(a_bool, b_bool))
# [ True  True  True False]

print(np.logical_xor(a_bool, b_bool))
# [False  True  True False]

print(np.logical_not(a_bool))
# [False False  True  True]

c_int = np.arange(4)
print(c_int)
# [0 1 2 3]

print(np.logical_not(c_int))
# [ True False False False]

d_int = c_int + 4
print(d_int)
# [4 5 6 7]

print(np.logical_not(d_int))
# [False False False False]

print(np.logical_and(c_int, d_int))
# [False  True  True  True]

print(c_int & d_int)
# [0 1 2 3]

a_bool_2d = np.array([[True, True, False, False], [False, False, True, True]])
print(a_bool_2d)
# [[ True  True False False]
#  [False False  True  True]]

print(a_bool_2d & b_bool)
# [[ True False False False]
#  [False False  True False]]

print(np.logical_and(a_bool_2d, a_bool))
# [[ True  True False False]
#  [False False False False]]

print(a_bool & True)
# [ True  True False False]

print(np.logical_and(a_bool, True))
# [ True  True False False]

print(c_int)
# [0 1 2 3]

print(c_int < 2)
# [ True  True False False]

print(c_int % 2 == 0)
# [ True False  True False]

print((c_int < 2) & (c_int % 2 == 0))
# [ True False False False]

# print(c_int < 2 & c_int % 2 == 0)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

# print(c_int < (2 & (c_int % 2)) == 0)
# ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

print(np.logical_and(c_int < 2, c_int % 2 == 0))
# [ True False False False]


import numpy as np
from sympy import zeros, eye, simplify, sqrt
from sympy import Matrix as mat

from ..genericas import print_verbose, norma, matriz_inversa
from .funcs_gram_schmidt import gram_schmidt

def householder(x, y, signo="+", normalizar=False):
    """Aplica la transformación de Householder. x e y pueden ser vectores fila o columna,
    en cuyo caso aplicamos la transformación de una forma u otra.

    Args:
        x (vector): vector origen
        y (vector): vector destino
        signo (str, optional): + o -, si - aplica la transformación inversa. Defaults to "+".
        normalizar (bool, optional): Normaliza los vectores para la transformación. Defaults to False.

    Returns:
        dict: {'H': la aplicación de transformación, 'e': el vector de eje de cambio.}
    """
    if signo == "+":
        mult = 1
    else:
        mult = -1

    if normalizar:
        x = simplify(x / norma(x))
        y = simplify(y / norma(y))

    if simplify(norma(x)) != simplify(norma(y)):
        print(
            f"AVISO!!! x tiene norma {norma(x)} e y tiene norma {norma(y)}. "
            "Para Householder las normas tienen que ser iguales."
        )

    if simplify(norma(x - y)) == 0:  # x==y
        e = zeros(x.shape[0], x.shape[1])
    else:
        e = mult * (x - y) / (norma(x - y))

    if (e.shape[0] == 1) and (e.shape[1] >= 1):  # El vector es fila:
        H = eye(x.shape[1]) - 2 * e.T * e
    else:
        H = eye(x.shape[0]) - 2 * e * e.T

    if (e.shape[0] == 1) and (e.shape[1] >= 1) and (simplify(x * H) != y):
        print(
            "AVISO FILAS!!! xH != y. Para Householder las normas tienen que ser iguales."
        )
    elif (e.shape[1] == 1) and (e.shape[0] >= 1) and (simplify(H * x) != y):
        print("AVISO!!! Hx != y. Para Householder las normas tienen que ser iguales.")

    return simplify(H), simplify(e)

def factorizacion_QR(m, verbose=True, metodo="gram_schmidt"):
    """Aplica el método QR para la factorización de la matriz m. m = QR, con Q ortogonal y R triangular superior.

    Args:
        m (matriz): matriz a factorizar.
        verbose (bool, optional): Imprime mensajes informativos. Defaults to True.
        metodo (str, optional): "gram_schmidt" o "householder".
        Householder funciona mejor para vectores proximos,
        donde gram-schmidt deja de ser estable. Defaults to "gram_schmidt".

    Returns:
        dict: {'Q', 'R' , 'D'} ('D' en gram-schmidt solo).
    """
    if metodo == "gram_schmidt":
        print_verbose("Aplicamos QR con Gram Schmidt", verbose)

        dict_gs = gram_schmidt(m)
        P, C = dict_gs["P"], dict_gs["c"]

        D = zeros(m.shape[0], m.shape[1])
        for col in range(m.shape[0]):
            D[col, col] = sqrt((P[:, col].T * P[:, col])[0])

        Q = simplify(P * (matriz_inversa(D)))
        R = simplify(D * (eye(m.shape[0]) + C))

        print_verbose(
            f"Q es \n{np.array(Q)}\nR es \n{np.array(R)}\nD es \n{np.array(D)}", verbose
        )
    elif metodo == "householder":
        print_verbose("Aplicamos QR con Householder", verbose)
        lista_p = [eye(m.shape[0]) for _ in range(m.shape[0])]
        lista_e = []

        Q, R = eye(m.shape[0]), m.copy()
        for col in range(m.shape[1]):
            x = R[col:, col]
            y = mat([1] + [0] * (m.shape[0] - col - 1)) * norma(x)
            p_col = lista_p[col]
            H, e = householder(x, y)
            p_col[col:, col:] = H
            lista_e.append(e)

            R = simplify(p_col * R)
            Q = simplify(Q * p_col)

        print_verbose(f"Q es \n{np.array(Q)}\nR es \n{np.array(R)}", verbose)
    else:
        raise "El método no existe. Los disponibles son gram_schmidt y householder"

    if m != simplify(Q * R):
        print("AVISO!!! A != QR")

    if metodo == "gram_schmidt":
        return {"Q": Q, "R": R, "D": D}
    elif metodo == "householder":
        return {"Q": Q, "R": R}


import sys
import numpy as np
import torch
import json
from torch.autograd import Variable
import math
from random import choices
import random

def exponential_map_sd(x, v):
    r"""Exponential map on \mathbb{S}^D.
    Args:
        x: points on \mathbb{S}^D, embedded in \mathbb{R}^{D+1}
        v: vectors in the tangent space T_x \mathbb{S}^D
    Returns:
        Image of exponential map
    """
    v_norm = np.linalg.norm(v, axis=1, keepdims=True)
    return x * np.cos(v_norm) + v / v_norm * np.sin(v_norm)

def sample_sd(d=2, num_samples=1):
    xs = np.random.randn(d + 1, num_samples)
    xs /= np.linalg.norm(xs, axis=0)
    return xs.transpose()

def vMF_dens(x, mu, kappa):
    if kappa > 0:
        pdf_constant = kappa / ((2 * np.pi) * (1. - np.exp(-2. * kappa)))
        return pdf_constant * np.exp(kappa * (mu.dot(x) - 1.))
    else:
        return 1. / (4 * np.pi)

def sample_vMF(mu, kappa, num_samples):
    """Generate num_samples N-dimensional samples from von Mises Fisher
    distribution around center mu \in R^N with concentration kappa.
    """
    dim = len(mu)
    result = np.zeros((num_samples, dim))
    for nn in range(num_samples):
        # sample offset from center (on sphere) with spread kappa
        w = _sample_weight(kappa, dim)

        # sample a point v on the unit sphere that's orthogonal to mu
        v = _sample_orthonormal_to(mu)

        # compute new point
        result[nn, :] = v * np.sqrt(1. - w ** 2) + w * mu

    return result

def _sample_weight(kappa, dim):
    """Rejection sampling scheme for sampling distance from center on
    surface of the sphere.
    """
    dim = dim - 1  # since S^{n-1}
    b = dim / (np.sqrt(4. * kappa ** 2 + dim ** 2) + 2 * kappa)
    x = (1. - b) / (1. + b)
    c = kappa * x + dim * np.log(1 - x ** 2)

    while True:
        z = np.random.beta(dim / 2., dim / 2.)
        w = (1. - (1. + b) * z) / (1. - (1. - b) * z)
        u = np.random.uniform(low=0, high=1)
        if kappa * w + dim * np.log(1. - x * w) - c >= np.log(u):
            return w

def _sample_orthonormal_to(mu):
    """Sample point on sphere orthogonal to mu."""
    v = np.random.randn(mu.shape[0])
    proj_mu_v = mu * np.dot(mu, v) / np.linalg.norm(mu)
    orthto = v - proj_mu_v
    return orthto / np.linalg.norm(orthto)

def spherical_to_euclidean(sph_coords):
    if sph_coords.ndim == 1:
        sph_coords = np.expand_dims(sph_coords, 0)
    theta, phi = np.split(sph_coords, 2, 1)
    return np.concatenate((
        np.sin(phi) * np.cos(theta),
        np.sin(phi) * np.sin(theta),
        np.cos(phi)
    ), 1)

def euclidean_to_spherical(euc_coords):
    if euc_coords.ndim == 1:
        euc_coords = np.expand_dims(euc_coords, 0)
    x, y, z = np.split(euc_coords, 3, 1)
    return np.concatenate((
        math.atan2(y, x),
        math.atan2(np.sqrt(x ** 2 + y ** 2) / z)
    ), 1)

def batch_outer(x, y):
    return torch.bmm(x.unsqueeze(2), y.unsqueeze(1))

def softplus(x):
    return torch.logaddexp(x, torch.tensor([0.]))

def softplus_inv(x):
    return torch.log(-1. + torch.exp(x))

def softmax(x):
    ex = torch.exp(x - torch.max(x))
    return ex / torch.sum(ex)

class RadialFlowVmfMixBase:
    def __init__(self, N, inputs, mean_dir_list, conc_list, mix_prob_list):
        self._N = N  # no. of layers
        self._inputs = inputs
        self._mean_dir_list = mean_dir_list
        self._conc_list = conc_list
        self._mix_prob_list = mix_prob_list
        self.RF = RadialFlow(N, inputs)
        self._ncomp = len(mix_prob_list)
        self._log_pdf_const_list = [self.vmf_log_pdf_const(conc) for conc in conc_list]
        self._pdf_const_list = [self.vmf_pdf_const(conc) for conc in conc_list]

    def vmf_pdf_const(self, conc):
        num = conc
        den = 2 * np.pi * (np.exp(conc) - np.exp(-conc))
        return num / den

    def vmf_log_pdf_const(self, conc):
        pdf_const = self.vmf_pdf_const(conc)
        return np.log(pdf_const)

    def vmf_density(self, x, mean_dir, conc, pdf_const):
        return pdf_const * np.exp(conc * np.dot(x, mean_dir))

    def vmf_log_density(self, x, mean_dir, conc, log_pdf_const):
        return conc * np.dot(x, mean_dir) + log_pdf_const

    def loss(self, theta_all, mu_all, beta_all, prop_mini_batch=.2):
        ljs_inputs = 0.
        for x in self._inputs:
            u = torch.rand(1)
            if u < prop_mini_batch:
                z, ljs = self.RF.serial(theta_all, mu_all, beta_all, x)
                # print(ljs)

                dens_base = 0.
                for k in range(self._ncomp):
                    dens_base += self._mix_prob_list[k] * self.vmf_density(x, self._mean_dir_list[k],
                                                                           self._conc_list[k],
                                                                           self._pdf_const_list[k])

                log_dens_base = torch.log(torch.tensor(dens_base))
                # print(log_dens_base)

                ljs_inputs += ljs + log_dens_base

        return - ljs_inputs

    def log_density(self, theta_all, mu_all, beta_all, x):
        ljs = 0.

        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.RF.comp_flow(theta, mu, beta, x)
            ljs += lj

        dens_base = 0
        x = x.detach().numpy()
        for k in range(self._ncomp):
            dens_base += self._mix_prob_list[k] * self.vmf_density(x, self._mean_dir_list[k], self._conc_list[k],
                                                                   self._pdf_const_list[k])
        log_dens_base = torch.log(torch.tensor(dens_base))

        ljs += log_dens_base

        return ljs

class RadialFlow:
    def __init__(self, N, inputs):
        self._N = N  # no. of layers
        self._inputs = inputs

    def comp_flow(self, theta, mu, beta, x):
        """WARNING WORKS ONLY FOR p = 1"""
        x = x.to(torch.float32)
        nx = len(x)
        theta = softmax(theta)
        mu = mu / torch.linalg.norm(mu)
        beta = torch.exp(beta)

        p = len(theta)
        if p > 1:
            sys.exit("This code only works for p = 1")
        alpha = torch.arccos(torch.matmul(x, mu.T))

        num_e = mu - torch.cos(alpha) * x
        den_e = 1. / torch.sin(alpha)
        e = num_e * den_e

        f_prime = - torch.sin(alpha) * torch.exp(beta * (torch.cos(alpha) - 1))

        v_theta = - theta * f_prime * e

        alpha_theta = (torch.linalg.norm(v_theta, dim=1)).unsqueeze(0).T

        e_theta = v_theta / alpha_theta

        H1 = batch_outer(e_theta, e_theta)
        H2 = alpha_theta * torch.cos(alpha_theta) / torch.sin(alpha_theta)
        IdMatrices = torch.eye(3).reshape((1, 3, 3)).repeat(nx, 1, 1)
        H3 = IdMatrices - batch_outer(x, x) - batch_outer(e_theta, e_theta)
        H_theta = H1 + H2.reshape(nx, 1, 1) * H3

        K_sum = torch.zeros(nx, 3, 3)
        for i in range(p):
            f1 = - torch.sin(alpha[:, i]) * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2_1 = - torch.cos(alpha[:, i]) * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2_2 = beta[i] * torch.sin(alpha[:, i]) ** 2 * torch.exp(beta[i] * (torch.cos(alpha[:, i]) - 1))
            f2 = torch.add(f2_1, f2_2)

            K1 = f2.reshape(nx, 1, 1) * batch_outer(e, e)
            K2 = f1 * torch.cos(alpha[:, i]) / torch.sin(alpha[:, i])
            K3 = IdMatrices - batch_outer(x, x) - batch_outer(e, e)
            K = K1 + K2.reshape(nx, 1, 1) * K3
            K_sum += theta[i] * K

        # transformed
        v_theta_norm = (torch.linalg.norm(v_theta, dim=1)).unsqueeze(0).T
        z = x * torch.cos(v_theta_norm) + v_theta / v_theta_norm * torch.sin(v_theta_norm)

        # log jacobian
        lj1 = 2 * torch.log(torch.sin(v_theta_norm) / v_theta_norm)
        lj2 = torch.logdet(batch_outer(x, x) + H_theta + K_sum).reshape(nx, 1)
        lj = lj1 + lj2

        return z, lj

    def serial(self, theta_all, mu_all, beta_all, x):
        ljs = 0.
        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.comp_flow(theta, mu, beta, x)
            ljs += lj
        return x, ljs

    def loss(self, theta_all, mu_all, beta_all, prop_mini_batch=.3):
        # print(beta_all)
        # print(mu_all)
        x_all = self._inputs
        nsamp = np.random.binomial(len(x_all), prop_mini_batch)
        idx = np.random.choice(len(x_all),
                               size=nsamp,
                               replace=False)
        x = x_all[idx, :]
        z, ljs = self.serial(theta_all, mu_all, beta_all, x)

        return - sum(ljs)

    def weighted_loss(self, theta_all, mu_all, beta_all, weights, prop_mini_batch=.3):
        ljs_inputs = 0.

        for x, w in zip(self._inputs, weights):
            u = torch.rand(1)
            if u < prop_mini_batch:
                z, ljs = self.serial(theta_all, mu_all, beta_all, x)
                ljs_inputs += ljs * w
                # z1 = z.detach().numpy()
        return - ljs_inputs

    def log_density(self, theta_all, mu_all, beta_all, x):
        ljs = 0.
        for theta, mu, beta in zip(theta_all, mu_all, beta_all):
            x, lj = self.comp_flow(theta, mu, beta, x)
            ljs += lj
        return ljs

rows = []
with open('pacific.csv') as f:
    for line in f:
        # strip whitespace
        line = line.strip()
        # separate the columns
        line = line.split(',')
        # save the line for use later
        rows.append(line)

data = []
for i in range(2, len(rows)):
    if rows[i][0] != rows[i - 1][0]:
        data.append(rows[i - 1])

theta_vec1 = []
theta_vec2 = []
for d in data:
    theta_vec1.append(float(d[6][:-1]))
    theta_vec2.append(float(d[7][:-1]))
theta_vec2 = [360. - theta_vec2[i] for i in range(len(theta_vec2))]

theta_vec1 = [(theta_vec1[i] + 90.) * np.pi / 180. for i in range(len(theta_vec1))]
theta_vec2 = [(theta_vec2[i] + 180.) * np.pi / 180. for i in range(len(theta_vec2))]
theta_vec = [np.array([theta1, theta2]) for (theta1, theta2) in zip(theta_vec1, theta_vec2)]

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

for i in range(50):

    print("Bootstrap sample ", i+1)

    np.random.seed(20000+i)
    torch.manual_seed(20000+i)
    random.seed(20000+i)

    nobs = len(theta_vec)
    n_samp = np.random.poisson(nobs, size=1)[0]

    theta_vec_samp = choices(theta_vec, k=n_samp)

    #theta_vec_list = [theta.tolist() for theta in theta_vec]

    inputs = np.array([spherical_to_euclidean(theta_vec_samp[i])[0] for i in range(len(theta_vec_samp))])

    inputs = torch.from_numpy(inputs)

    N = 30
    p = 1
    RF = RadialFlow(N, inputs)

    mu_all = Variable(0.5 * torch.randn((N, p, 3)), requires_grad=True)
    theta_all = Variable(0.5 * torch.randn((N, p)), requires_grad=True)
    beta_all = Variable(0.5 * torch.randn((N, p)), requires_grad=True)

    optimizer = torch.optim.SGD([theta_all, mu_all, beta_all], lr=1e-5)
    loss_list = [10 ** 10]

    for t in range(100):
        # Forward pass:

        loss = RF.loss(theta_all, mu_all, beta_all)
        # print(t, loss.item())

        # Zero gradients, perform a backward pass, and update the weights.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    dens_list = []

    ngrids = 200

    theta_vec1 = np.linspace(.001, np.pi - .001, ngrids)
    phi_vec1 = np.linspace(.001, 2 * np.pi - .001, ngrids)

    X_sph = np.array([(x, y) for x in theta_vec1 for y in phi_vec1])
    X = [spherical_to_euclidean(x_sph)[0] for x_sph in X_sph]
    X = np.array(X)
    X = torch.from_numpy(X)

    dens = torch.exp(RF.log_density(theta_all, mu_all, beta_all, X))

    dens = torch.mul(dens, n_samp)

    dens_list = np.concatenate((X_sph, dens.detach()), axis=1).tolist()

    write_path = 'pacific_end_dens_est_radial_np_v' + str(i+20000) + '.json'
    with open(write_path, 'w') as f:
        json.dump(dens_list, f)


import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
from statsmodels.stats.weightstats import ztest
np.random.seed(0)

#%% One-Sample tests
N = 100
mu = 0.2
sigma = 1
x = np.random.randn(100)*sigma + mu

#%% Two-sided test
print(ztest(x))

#%% Two-sided test manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = mu_hat / (sigma_hat / np.sqrt(N))
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z, p)

#%% One-sided test
ztest(x, alternative="larger")

#%% One-sided test manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = mu_hat / (sigma_hat / np.sqrt(N))
p = 1-norm.cdf(abs(z))
print(z,p)

#%% REference value
ztest(x, value=0.05)

#%% REference value manual
mu_hat = x.mean()
sigma_hat = x.std(ddof=1)
z = (mu_hat-0.05) / (sigma_hat / np.sqrt(N))
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z, p)

#%% Two-sample tests
N_0 = 1000
mu_0 = 0.2
sigma_0 = 1
x_0 = np.random.randn(N_0)*sigma_0 + mu_0

N_1 = 100
mu_1= 0.5
sigma_1 = 1
x_1 = np.random.randn(N_1)*sigma_1 + mu_1

#%%
ztest(x_0, x_1)

#%% Two-sided test manual
mu_hat_0 = x_0.mean()
mu_hat_1 = x_1.mean()
y = mu_hat_0 - mu_hat_1
s2_hat_0 = x_0.var(ddof=1)
s2_hat_1 = x_1.var(ddof=1)
s_hat = np.sqrt(s2_hat_1/N_1 + s2_hat_0/N_0)
z = y / s_hat
p_right = 1 - norm.cdf(abs(z))
p_left = norm.cdf(-abs(z))
p = p_right + p_left
print(z,p)

#%% False alarm (Type I error) 5% of times
result = []
for i in range(10_000):
    x0 = np.random.randn(100)
    x1 = np.random.randn(100)
    z,p = ztest(x0, x1)
    result.append(p<0.05)

print(np.mean(result))

import numpy,from scipy,from statsmodels


import numpy as np

def compute_cost(X, y, theta):
    # Initialize some useful values
    m = y.size
    cost = 0

    # ===================== Your Code Here =====================
    # Instructions : Compute the cost of a particular choice of theta.
    #                You should set the variable "cost" to the correct value.

    for i in range(m):
        cost += (theta[0]+theta[1]*X[i][1]-y[i])*(theta[0]+theta[1]*X[i][1]-y[i])
    cost/=m*2
    # ==========================================================

    return cost


import numpy as np

angfac = 180.0/np.pi

# some useful subroutines

# get an angle between [0,2pi]
def mod_two_pi(x):
    twopi = 2.0*np.pi
    #y=x
    #while (y > twopi):
    #    y -= twopi;
    #while (y < 0.0):
    #    y += twopi;
    #return y
    return x%twopi

def mod_two_pi_arr(x):
    nvec = np.size(x)
    mvec = x*0.0
    for i in range(0,nvec):
        mvec[i] = mod_two_pi(x[i])
    return mvec

# length of a vector
def len_vec(x,y,z):
    r= np.sqrt(x*x + y*y + z*z)
    return r

# normalize a vector
def normalize_vec(x,y,z):
    r = len_vec(x,y,z)
    return x/r, y/r, z/r

def dotprod(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product
    return z

# return cross product of two vectors
def crossprod(ax,ay,az,bx,by,bz):
    cx = ay*bz-az*by;
    cy = az*bx-ax*bz;
    cz = ax*by-ay*bx;
    return cx,cy,cz

# return normalized cross product of two vectors
def crossprod_unit(ax,ay,az,bx,by,bz):
    cx,cy,cz = crossprod(ax,ay,az,bx,by,bz)
    cc = len_vec(cx,cy,cz)
    return cx/cc, cy/cc, cz/cc

# return the vector part of a that is perpendicular to b direction
def aperp(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product = ab cos theta
    bmag = len_vec(bx,by,bz)
    #theta = np.acos(z/(amag*bmag))
    cx = ax - z*bx/bmag
    cy = ay - z*by/bmag
    cz = az - z*bz/bmag
    return cx,cy,cz

# return the vector part of a that is parallel to b direction
def apar(ax,ay,az,bx,by,bz):
    z = ax*bx + ay*by + az*bz  # dot product = ab cos theta
    bmag = len_vec(bx,by,bz)
    #theta = np.acos(z/(amag*bmag))
    cx = z*bx/bmag
    cy = z*by/bmag
    cz = z*bz/bmag
    return cx,cy,cz


from statistics import mean

import matplotlib.pyplot as plt
import numpy as np
from pandas import read_csv
from sympy import Symbol, pi, pprint

from lsq import trigLSQ

rawData = read_csv('data/data.csv', header=0, names=['x', 'y'])
x = rawData['x']; y = rawData['y']

noMonths = len(rawData)
noYears = int(noMonths/12)

# t = 1,2, 3, 4, 5, ..., 60
# x = t*pi/30 --> pi/30, pi/15, ..., 2pi
data60 = {
    'x': [(2*pi*t)/noMonths for t in range(1, noMonths+1)],
    'y': [p for p in y]
}

data12 = {
    'x': [(2*pi*t)/12 for t in range(1, 12+1)],
    'y': [mean([y[i + n*12] for n in range(noYears)]) for i in range(12)]
}

order60 = 11 # max 29
order12 = 4 # max 5
F60, aux60 = trigLSQ(data60, order60); F60 = F60.subs(Symbol('x'), (2*pi/noMonths)*Symbol('t'))
F12, aux12 = trigLSQ(data12, order12); F12 = F12.subs(Symbol('x'), (2*pi/12)*Symbol('t'))

f60 = lambda t: float(F60.subs(Symbol('t'), t).evalf()) # f60(t)
f12 = lambda t: float(F12.subs(Symbol('t'), t).evalf()) # f12(t)

x12 = np.linspace(0, 12, 100); y12 = 0
for j, i in aux12[0]: y12 += aux12[1][j][0] * (np.cos(i['index'] * 2*np.pi/12 * x12) if i['cos'] else np.sin(i['index'] * 2*np.pi/12 * x12))

x60 = np.linspace(0, noMonths, 100); y60 = 0
for j, i in aux60[0]: y60 += aux60[1][j][0] * (np.cos(i['index'] * 2*np.pi/noMonths * x60) if i['cos'] else np.sin(i['index'] * 2*np.pi/noMonths * x60))

with open('./output/60-month_period.out', 'w') as f:
    f.write(f't: {list(range(1, noMonths+1))}\n\n')
    f.write(f'x: {data60["x"]}\n\n')
    f.write(f'y: {data60["y"]}\n\n')
    f.write(f'F(t) = {F60}\n\n')
    f.write(f'F(72) = {f60(72)}')

with open('./output/12-month_period.out', 'w') as f:
    f.write(f't: {list(range(1, 12+1))}\n\n')
    f.write(f'x: {data12["x"]}\n\n')
    f.write(f'y: {data12["y"]}\n\n')
    f.write(f'F(t) = {F12}\n\n')
    f.write(f'F(72) = {f12(72)}')

print('\nFunção considerando período de 5 anos: \nF(t) = ')
pprint(F60)
print(f'\nF(72) = {f60(72)}', end='\n\n')

print('\nFunção considerando período de 1 ano médio: \nF(t) = ')
pprint(F12)
print(f'\nF(72) = {f12(72)}')

plt.figure(1)
plt.title('Sobreposição da função aproximada com os pontos resultantes da média entre 5 anos')
plt.scatter(range(1, 13), data12['y'], label='Pontos fornecidos')
plt.plot(range(1, 12+1), data12['y'], '--', label='Ligação entre os pontos')
plt.plot(x12, y12, '-', lw=2.5, label=f'Função aproximada F(t) (ordem {order12})')
plt.legend(loc='upper right')
plt.ylabel('Precipitação')
plt.xlabel('Mês (t)')
plt.gcf().set_size_inches(9,6)
plt.savefig('output/12-month_period.png', dpi=100)

plt.figure(2)
plt.title('Sobreposição da função aproximada com os pontos fornecidos')
plt.scatter(range(1, noMonths+1), data60['y'], label='Pontos fornecidos')
plt.plot(range(1, noMonths+1), data60['y'], '--', label='Ligação entre os pontos')
plt.plot(x60, y60, '-', lw=2.5, label=f'Função aproximada F(t) (ordem {order60})')
plt.legend(loc='upper left')
plt.ylabel('Precipitação')
plt.xlabel('Mês (t)')
plt.gcf().set_size_inches(9,6)
plt.savefig('output/60-month_period.png', dpi=100)

plt.show()


"""
  Name     : c9_07_find_minimum_expential_function.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 scipy import optimize
import matplotlib.pyplot as plt
# define a function
a=3.4
b=2.0
c=0.8
def f(x):
    return a-b*np.exp(-(x - c)**2)

x=np.arange(-3,3,0.1)
y=f(x)
plt.title("y=a-b*exp(-(x-c)^2)")
plt.xlabel("x")
plt.ylabel("y")
plt.plot(x,y)
plt.show()

# find the minimum
solution= optimize.brent(f)
print(solution)


import numpy as np
from numba import jit

def mean_square_error(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate MSE loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    MSE of given predictions
    """
    return np.mean(np.power(y_true - y_pred, 2)).item()

def misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    sumy = 0
    for i in range(len(y_true)):
        if y_true[i] != y_pred[i]:
            sumy += 1
    return sumy / len(y_true) if normalize else sumy

@jit(nopython=True)
def weighted_misclassification_error(y_true: np.ndarray, y_pred: np.ndarray, normalize: bool = True) -> float:
    """
    Calculate misclassification loss

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values
    normalize: bool, default = True
        Normalize by number of samples or not

    Returns
    -------
    Misclassification of given predictions
    """
    sumy = 0
    for i in range(len(y_true)):
        if np.sign(y_true[i]) != np.sign(y_pred[i]):
            sumy += np.abs(y_true[i]).item()
    return sumy / np.sum(np.abs(y_true)) if normalize else sumy

def accuracy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate accuracy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Accuracy of given predictions
    """
    sumy = 0
    for i in range(len(y_true)):
        if y_true[i] == y_pred[i]:
            sumy += 1
    return sumy / len(y_true)

def cross_entropy(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    Calculate the cross entropy of given predictions

    Parameters
    ----------
    y_true: ndarray of shape (n_samples, )
        True response values
    y_pred: ndarray of shape (n_samples, )
        Predicted response values

    Returns
    -------
    Cross entropy of given predictions
    """
    raise NotImplementedError()

if __name__ == '__main__':
    y_true = np.array([279000, 432000, 326000, 333000, 437400, 555950])
    y_pred = np.array(
        [199000.37562541, 452589.25533196, 345267.48129011, 345856.57131275, 563867.1347574, 395102.94362135])
    print(mean_square_error(y_true, y_pred))


import numpy as np

def loadDataSet():
	dataMat = []; labelMat = []
	fr = open('testSet.txt')
	for line in fr.readlines():
		lineArr = line.strip().split()
		dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
		labelMat.append(int(lineArr[2]))
	return dataMat, labelMat

def sigmod(inX):
	return 1.0 / (1+np.exp(-inX))

def gradAscent(dataMatIn, classLabel):
	'''
	batch gradient ascent
	批量梯度上升算法
	'''
	dataMatrix = np.mat(dataMatIn) #100*3
	labelMatrix = np.mat(classLabel).transpose() #100*1
	m, n = np.shape(dataMatrix)
	alpha = 0.001
	maxCycles = 500
	weights = np.ones((n,1)) #3*1

	for k in range(maxCycles):
		h = sigmod(dataMatrix*weights) #100*1
		error = labelMatrix - h #100*1
		weights = weights + alpha*dataMatrix.transpose()*error #3*1

		#getA()将矩阵转换为ndarray
	return weights.getA()

def stochasticGradAsent0(dataMatrix, classLabel):
	m, n = np.shape(dataMatrix)

	alpha = 0.01
	weights = np.ones(n) #3

	for i in range(m):
		h = sigmod(np.dot(dataMatrix[i], weights))
		error = classLabel[i] - h
		weights = weights + alpha*dataMatrix[i]*error

	return weights

def stochasticGradAsent1(dataMatrix, classLabel, numIter=150):
	m, n = np.shape(dataMatrix)

	# alpha = 0.01
	weights = np.ones(n) #3

	for j in range(numIter):
		dataIndex = list(range(m))

		for i in range(m):
			alpha = 4 / (1.0+j+i) + 0.01

			randIndex = int(np.random.uniform(0, len(dataIndex)))

			h = sigmod(np.dot(dataMatrix[randIndex], weights))
			error = classLabel[randIndex] - h
			weights = weights + alpha*dataMatrix[randIndex]*error
			del(dataIndex[randIndex])

	return weights

def plotBestFit(weights):
	import matplotlib.pyplot as plt

	#将矩阵转换为ndarray
	# weights = wei.getA()

	dataMat, labelMat = loadDataSet()
	dataArr = np.array(dataMat)
	n = np.shape(dataArr)[0]

	xcord1 = []; ycord1 = []
	xcord2 = []; ycord2 = []
	for i in range(n):
		if int(labelMat[i]) == 1:
			xcord1.append(dataArr[i,1])
			ycord1.append(dataArr[i,2])
		else:
			xcord2.append(dataArr[i,1])
			ycord2.append(dataArr[i,2])

	fig = plt.figure()
	ax = fig.add_subplot(111)
	ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
	ax.scatter(xcord2, ycord2, s=30, c='green')
	x = np.arange(-3.0, 3.0, 0.1)
	y = (-weights[0] - weights[1]*x) / weights[2]
	ax.plot(x, y)
	plt.xlabel('X')
	plt.ylabel('Y')
	plt.show()

#使用logistic回归从疝气病症预测病马的死亡率
def classifyVector(inX, weights):
	prob = sigmod(sum(inX*weights))
	# prob = sigmod(np.dot(inX, weights))
	# print(prob)

	if prob > 0.5:
		return 1
	else:
		return 0

def colicTest():
	frTrain = open('horseColicTraining.txt')
	frTest = open('horseColicTest.txt')

	trainingSet = []; trainingLabels = []
	for line in frTrain.readlines():
		# \t 相当于tab键
		currLine = line.strip().split('\t')
		lineArr = []
		for i in range(21):
			lineArr.append(float(currLine[i]))
		trainingSet.append(lineArr)
		trainingLabels.append(float(currLine[21]))

	trainWeights = stochasticGradAsent1(np.array(trainingSet), trainingLabels, 500)

	errorCount = 0
	numTestVec = 0.0

	for line in frTest.readlines():
		numTestVec += 1
		currLine = line.strip().split('\t')
		lineArr = []

		for i in range(21):
			lineArr.append(float(currLine[i]))

		classifyRes = classifyVector(np.array(lineArr), trainWeights)
		# print(classifyRes, ' and ', currLine[21])
		if classifyRes != int(currLine[21]):
			errorCount += 1

	errorRate = float(errorCount) / numTestVec

	print('the error rate of this test is: ', errorRate)

	return errorRate

def multiTest():
	numTests = 10
	errorSum = 0.0

	for k in range(numTests):
		errorSum += colicTest()

	print('after %d iterations the average error rate is: %f' %(numTests, errorSum/float(numTests)))

if __name__ == '__main__':

	dataArr, labelMat = loadDataSet()
	# weights = gradAscent(dataArr, labelMat)
	# plotBestFit(weights)

	# weights2 = stochasticGradAsent1(np.array(dataArr), labelMat)
	# plotBestFit(weights2)

	multiTest()


#!/usr/bin/python3

import numpy as np
from sympy.matrices import Matrix
from sympy import symbols, atan2, sqrt

# Conversion Factors
rtd = 180/np.pi
dtr = np.pi/180

# Fixed Axis X-Y-Z Rotation Matrix
R_XYZ = Matrix([[ 0.353553390593274, -0.306186217847897, 0.883883476483184],
                [ 0.353553390593274,  0.918558653543692, 0.176776695296637],
                [-0.866025403784439,               0.25, 0.433012701892219]])

# Identify useful terms from rotation matrix
r31 = R_XYZ[2,0]
r11 = R_XYZ[0,0]
r21 = R_XYZ[1,0]
r32 = R_XYZ[2,1]
r33 = R_XYZ[2,2]

# Euler Angles from Rotation Matrix
# sympy synatx for atan2 is atan2(y, x)
beta  = atan2(-r31, sqrt(r11 * r11 + r21 * r21)) * rtd
gamma = atan2(r32, r33) * rtd
alpha = atan2(r21, r11) * rtd

print("alpha is = ",alpha*dtr, "radians", "or ", alpha, "degrees")
print("beta  is = ",beta*dtr,  "radians", "or ", beta, "degrees")
print("gamma is = ",gamma*dtr, "radians", "or ", gamma, "degrees")


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

#function to compute the cost function
def compute_cost(X,Y,theta):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1 )
           Y=response vector of dim (no of samples,1)
           theta=parameter vector of dim (no of features+1,1)
    Output: cost= mean squared error of the hypothesis function of dim (1,1)
    '''
    #dimension check when X contains a single sample/row(incase of stochastic gradient descent)
    if(X.ndim==1):
        X=X.reshape(1,-1)
        Y=Y.reshape(-1,1)
    cost=np.sum(np.power(Y-np.dot(X,theta),2))/(2*X.shape[0])
    cost=cost.reshape(-1,1)
    return cost

#function to update parameters
def update_parameters(X,Y, theta, learning_rate):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1)
           Y=response vector of dim (no of samples,1)
           theta=parameter vector of dim (no of features+1,1)
           learning_rate= learning rate is a scalar, dim=0
    Output: theta= updated parameter vector of dim (no of features+1, 1)
    '''
    #dimension check when X contains a single sample/row(incase of stochastic gradient descent)
    if(X.ndim==1):
        X=X.reshape(1,-1)
        Y=Y.reshape(-1,1)
    error=Y-np.dot(X,theta)
    theta=theta+learning_rate*(1/X.shape[0])*np.transpose(np.sum(error*X,axis=0,keepdims=True))
    return theta

#function to perform batch gradient descent
def gradient_descent(X,Y,eps,learning_rate):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1)
           Y=response vector of dim (no of samples,1)
           learning_rate= learning rate is a scalar, dim=0
           eps= hreshold value for stopping criterion
    Output: values is a dictionary contains "cost_array"= stores cost, dim(no of iterations,1) and
                        "theta_array"= stores parameter, dim(no of feattures+1,no of iteeraton) and
                        "theta"= stores final parameters, dim(no of features+1,1) and
                        "count"= scalar, stores no of iterations, dim=0
    '''
    #initializing parameters
    theta=np.array([0,0]).reshape(-1,1)

    cost_new=compute_cost(X,Y,theta)

    #to keep track of no of iterations
    count=0

    #arrays to store the cost and parameter values for plotting
    cost_array=np.array([])
    theta_array=np.array([])

    while True:
        cost_old=cost_new
        count=count+1

        #updating the parameters and computing the cost
        theta=update_parameters(X,Y, theta, learning_rate)
        cost_new=compute_cost(X,Y,theta)

        cost_array=np.append(cost_array,cost_new)
        theta_array=np.append(theta_array,theta)

        #check for stopping criterion
        if(np.abs(cost_old-cost_new)<eps):
            break
    cost_array=cost_array.reshape(-1,1)
    theta_array=theta_array.reshape(cost_array.shape[0],-1).T

    values={"cost_array":cost_array, "theta_array":theta_array,"theta":theta,"count":count}

    return values

#function to print fitted parameter values
def print_values():
    print("Parameter vector is:\n"+str(theta))
    print("No of iterations is: "+str(count))

#function to plot the fitted graph
def plot_graph(X,Y,theta):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1)
           Y=response vector of dim (no of samples,1)
           theta=parameter vector of dim (no of features+1,1)
    '''
    #plot the training data points
    plt.scatter(X[:,1],Y,label="sample points",color="red")

    #plot the fitted line
    y_hat=theta[0]+theta[1]*X[:,1]
    plt.plot(X[:,1],y_hat,label= "predicted function")

    plt.xlabel('Acidity of wine')
    plt.ylabel('Density of wine')
    plt.title("Fitting data using linear regression and gradient Descent")
    plt.legend(loc="lower right")

    plt.show()

#function to plot the contour cost function wrt the parameters
def plot_contour(X,Y,theta0_array,theta1_array):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1)
           Y=response vector of dim (no of samples,1)
           theta0_array=parameter vector of dim (no of iterations,1),intercept term
           theta1_array=parameter vector of dim (no of features+1,1),corresponding to first feature
    '''
    #creating mesh grid
    x=np.linspace(-0.1,1.2,100)
    y=np.linspace(-0.1,1.3,100)
    x,y=np.meshgrid(x,y)

    #reshaping x and y grid for calculation purpose
    grid= np.vstack((x.reshape(1,-1),y.reshape(1,-1)))

    #computing cost at the grid points
    cost=np.sum(np.power((Y-np.dot(X,grid)),2),axis=0,keepdims=True)/(2*m)
    cost=cost.reshape(x.shape)

    fig,ax = plt.subplots()
    plt.xlabel('Acidity of wine')
    plt.ylabel('Density of wine')
    plt.title("Contour plot of the cost function")
    ax.contour(x,y,cost,20)
    plt.scatter(theta0_array,theta1_array,s=4)
    plt.show()

#plot the surface of cost function
def plot_surface(X,Y,theta0_array, theta1_array,cost_array,elev,azim):
    '''
    Input: X=Design matrix of dim (no of samples, no of features+1)
           Y=response vector of dim (no of samples,1)
           theta0_array=parameter vector of dim (no of iterations,1),intercept term
           theta1_array=parameter vector of dim (no of features+1,1),corresponding to first feature
           cost_array=dim(no of iterations,1)
           elev= 3d surface vertical angle view
           azim=3d surface x-y plane angle view
    '''
    x=np.linspace(-1,2.5,100)
    y=np.linspace(-2.5,2,100)
    x,y=np.meshgrid(x,y)

    #reshaping x and y grid for calculation purpose
    grid= np.vstack((x.reshape(1,-1),y.reshape(1,-1)))

    #computing cost at the grid points
    cost=np.sum(np.power((Y-np.dot(X,grid)),2),axis=0,keepdims=True)/(2*m)
    cost=cost.reshape(x.shape)

    fig=plt.figure(figsize=[12,8])
    ax = fig.gca(projection='3d')
    ax.plot_surface(x,y,cost,cmap='viridis', edgecolor='none',alpha=0.6)
    ax.view_init(elev,azim)
    ax.scatter3D(theta0_array,theta1_array,cost_array,color="black",s=8)
    plt.xlabel('Acidity of wine')
    plt.ylabel('Density of wine')
    ax.set_zlabel('cost function')
    plt.title("Surface plot of the cost function")
    plt.show()

#load data from files
trainX=np.loadtxt("linearX.csv").reshape(-1,1)
X=(trainX-np.mean(trainX))/np.std(trainX)
Y=np.loadtxt("linearY.csv").reshape(-1,1)

m=X.shape[0]
#stacking a column vector of 1's and creating the design matrix, X contains sample as row
X=np.hstack((np.ones((m,1)),X))

parser = argparse.ArgumentParser()
parser.add_argument('--lr',type = float)
parser.add_argument('--eps',type = float)
args = parser.parse_args()

learning_rate = args.lr
eps = args.eps

values=gradient_descent(X,Y,eps,learning_rate)

theta=values["theta"]
count=values["count"]
theta0_array=values["theta_array"][0,:]
theta1_array=values["theta_array"][1,:]
cost_array=values["cost_array"]

print_values()
plot_graph(X,Y,values["theta"])
plot_contour(X,Y,theta0_array,theta1_array)
plot_surface(X,Y, theta0_array.T,theta1_array.T,cost_array,elev=10,azim=100)


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 20 20:10:02 2021

@author: joseph.lindquist
"""

import numpy as np
import matplotlib.pyplot as plt

def divDiff(x0,y0):
    """
    Compute the Newton divided difference coefficients for an interpolant:

    Input:
        x0 - vector of "x"
        y0 - vector of "y"
    Output:
        nCoef - vector of newton coefficients
    """
    n = len(x) # Number of data points
    coef = np.zeros((n,n)) # create a matrix that is n x n
    nCoef = np.zeros(n)
    nCoef[0]=y0[0] # seed a_0
    for i in range(n): # fill in f[x_n] using y0 values
        coef[i,0]=y0[i]
    for i in range(1,n): # cycle through 1,2,...,n including the nth
        for j in range(1,i+1):
            coef[i,j]=(coef[i,j-1]-coef[i-1,j-1])/(x0[i]-x0[i-j])
        nCoef[i]=coef[i,i] # record coefficient on diagonal for a_i
    print(coef)
    return nCoef

def newtonEval(coef,x0,xE):
    """
    Evaluate the polynomial given by the Newton coefficients of the form:
        P_n(x)=a0 + a1(x-x0) + a2(x-x0)(x-x1) + ... + a_n(x-x0)...(x-x_n-1)
    Input:
        coef - array returned by the divDiff function
        x0   - vector of data points
        xE   - node to interpolate at
    Output:
        val  - value of interpolant at x=xE, i.e. P(xE)
    Dependencies:
        Requires use of divDiff
    """
    #We need to figure this out in class!
    n = len(coef)-1
    val = coef[n]
    i = n-1
    while i >= 0:
        val = val * (xE-x[i])+coef[i]
        i = i - 1
    return val

def unitStep(n):
    """
    Creates n interpolation points for a unit step function, centered at 0.
    Uses uniformly spaced interpolation points.

    Input:
        n - number of interpolation points desired

    Output:
        x - vector of "x"
        y - vector of "y"
    """
    x = np.linspace(-1,1,n) # create some interpolation points
    y = np.ones(len(x)) # "y" values for the given interpolation points above
    for i in range(len(x)):
        if x[i] <= 0:
            y[i]=0
    return x,y

def unitStepCheb(n):
    """
    Creates n interpolation points for a unit step function, centered at 0
    Uses the roots of Chebychev polynomials for interpolation points.

    Input:
        n - number of interpolation points desired

    Output:
        x - vector of "x"
        y - vector of "y"
    """
    x = np.zeros(n)
    y = np.ones(n)
    for i in range(n):
        x[i] = np.cos(((2*i+1)*np.pi)/(2*n))
        if x[i] <= 0:
            y[i]=0
    return x,y
# Some Examples

#x=np.array([1,1.3,1.6,1.9,2.2]) # Example 3.3.1
#y=np.array([0.7651977,0.620086,0.4554022,0.2818186,0.1103623])

#x = np.linspace(-1,3,10) # x values for the interpolation nodes
#y = np.cos(x) # "y" values for the cosine interpolation nodes

#x,y = unitStep(20)

x,y = unitStepCheb(20)

# Find the Newton Divided Difference Coefficients
coef=divDiff(x,y)
print(coef)

# Interpolate at a point.
print(newtonEval(coef,x,0))

left = -1
right = 1
nPoints = 100
xInterp = np.linspace(left,right,nPoints)
est = np.zeros(nPoints)
for i in range(nPoints):
    est[i]=newtonEval(coef,x,xInterp[i])

# Plotting routine with legend
plt.plot(x,y,"o",color="k", label='Interp Points')
plt.plot(xInterp,est, label = 'Interpolant')
plt.ylim([-3,3])
plt.xlabel('x')
plt.ylabel('y')
plt.title('Plot of the function')
plt.grid(True)
plt.legend()
plt.show


import numpy as np
import pandas as pd

# -----------------------------------
# 회귀
# -----------------------------------
# rmse

from sklearn.metrics import mean_squared_error

# y_true - 실젯값, y_pred - 예측값
y_true = [1.0, 1.5, 2.0, 1.2, 1.8]
y_pred = [0.8, 1.5, 1.8, 1.3, 3.0]

rmse = np.sqrt(mean_squared_error(y_true, y_pred))
print(rmse)
# 0.5532

# -----------------------------------
# 이진 분류
# -----------------------------------
# 혼동행렬

from sklearn.metrics import confusion_matrix

# 0, 1로 표현되는 이진 분류의 실젯값과 예측값
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]

tp = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 1))
tn = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 0))
fp = np.sum((np.array(y_true) == 0) & (np.array(y_pred) == 1))
fn = np.sum((np.array(y_true) == 1) & (np.array(y_pred) == 0))

confusion_matrix1 = np.array([[tp, fp],
                              [fn, tn]])

print(confusion_matrix1)
# array([[3, 1],
#        [2, 2]])

# 사이킷런의 metrics 모듈의 confusion_matrix로도 작성 가능하지만,
# 혼동행렬의 요소 배치가 다르므로 주의가 필요
confusion_matrix2 = confusion_matrix(y_true, y_pred)
print(confusion_matrix2)
# array([[2, 1],
#        [2, 3]])

# -----------------------------------
# 정확도(accuracy)
# -----------------------------------
from sklearn.metrics import accuracy_score

# 0, 1로 표현되는 이진 분류의 실젯값과 예측값
y_true = [1, 0, 1, 1, 0, 1, 1, 0]
y_pred = [0, 0, 1, 1, 0, 0, 1, 1]
accuracy = accuracy_score(y_true, y_pred)
print(accuracy)
# 0.625

# -----------------------------------
# logloss
# -----------------------------------
from sklearn.metrics import log_loss

# 0, 1로 나타나는 이진 분류의 실젯값과 예측 확률
y_true = [1, 0, 1, 1, 0, 1]
y_prob = [0.1, 0.2, 0.8, 0.8, 0.1, 0.3]

logloss = log_loss(y_true, y_prob)
print(logloss)
# 0.7136

# -----------------------------------
# 다중 클래스 분류
# -----------------------------------
# multi-class logloss

from sklearn.metrics import log_loss

# 3 클래스 분류의 실젯값과 예측값
y_true = np.array([0, 2, 1, 2, 2])
y_pred = np.array([[0.68, 0.32, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.60, 0.40, 0.00],
                   [0.00, 0.00, 1.00],
                   [0.28, 0.12, 0.60]])
logloss = log_loss(y_true, y_pred)
print(logloss)
# 0.3626

# -----------------------------------
# 다중 레이블 분류
# -----------------------------------
# mean_f1, macro_f1, micro_f1

from sklearn.metrics import f1_score

# 다중 레이블 분류의 실젯값·예측값은 평가지표 계산상으로는 행 데이터 × 클래스의 두
# 값 행렬로 해야 다루기 쉬움
# 실젯값 - [[1,2], [1], [1,2,3], [2,3], [3]]
y_true = np.array([[1, 1, 0],
                   [1, 0, 0],
                   [1, 1, 1],
                   [0, 1, 1],
                   [0, 0, 1]])

# 예측값 - [[1,3], [2], [1,3], [3], [3]]
y_pred = np.array([[1, 0, 1],
                   [0, 1, 0],
                   [1, 0, 1],
                   [0, 0, 1],
                   [0, 0, 1]])

# mean-f1는 행 데이터마다 F1-score를 계산하여 평균을 취함
mean_f1 = np.mean([f1_score(y_true[i, :], y_pred[i, :]) for i in range(len(y_true))])

# macro-f1에서는 행 데이터마다 F1-score를 계산하여 평균을 취함
n_class = 3
macro_f1 = np.mean([f1_score(y_true[:, c], y_pred[:, c]) for c in range(n_class)])

# micro-f1에서는 행 데이터 × 클래스의 쌍으로 TP/TN/FP/FN을 계산하여 F1-score를 구함
micro_f1 = f1_score(y_true.reshape(-1), y_pred.reshape(-1))

print(mean_f1, macro_f1, micro_f1)
# 0.5933, 0.5524, 0.6250

# scikit-learn 메소드를 사용하여 계산 가능
mean_f1 = f1_score(y_true, y_pred, average='samples')
macro_f1 = f1_score(y_true, y_pred, average='macro')
micro_f1 = f1_score(y_true, y_pred, average='micro')
print(mean_f1, macro_f1, micro_f1)

# -----------------------------------
# 클래스간 순서관계가 있는 다중 클래스 분류
# -----------------------------------
# quadratic weighted kappa

from sklearn.metrics import confusion_matrix, cohen_kappa_score

# quadratic weighted kappa을 계산하는 함수
def quadratic_weighted_kappa(c_matrix):
    numer = 0.0
    denom = 0.0

    for i in range(c_matrix.shape[0]):
        for j in range(c_matrix.shape[1]):
            n = c_matrix.shape[0]
            wij = ((i - j) ** 2.0)
            oij = c_matrix[i, j]
            eij = c_matrix[i, :].sum() * c_matrix[:, j].sum() / c_matrix.sum()
            numer += wij * oij
            denom += wij * eij

    return 1.0 - numer / denom

# y_true는 실젯값 클래스 목록, y_pred는 예측값 클래스 목록
y_true = [1, 2, 3, 4, 3]
y_pred = [2, 2, 4, 4, 5]

# 혼동행렬을 계산
c_matrix = confusion_matrix(y_true, y_pred, labels=[1, 2, 3, 4, 5])

# quadratic weighted kappa를 계산
kappa = quadratic_weighted_kappa(c_matrix)
print(kappa)
# 0.6154 (소수점 5번째자리 반올림)

# scikit-learn의 메소드로도 계산 가능
kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')
print(kappa)
# 0.6154 (소수점 5번째자리 반올림)

# -----------------------------------
# Recommendation(추천)
# -----------------------------------
# MAP@K

# K=3、행의 수는 5개, 클래스는 4종류
K = 3

# 각 행의 실젯값
y_true = [[1, 2], [1, 2], [4], [1, 2, 3, 4], [3, 4]]

# 각 행에 대한 예측값 - K = 3이므로, 일반적으로 각 행에 각각 3개까지 순위를 매겨 예측
y_pred = [[1, 2, 4], [4, 1, 2], [1, 4, 3], [1, 2, 3], [1, 2, 4]]

# 각 행의 average precision을 계산하는 함수
def apk(y_i_true, y_i_pred):
    # y_pred가 K이하의 길이이고 모든 요소가 달라야 함
    assert (len(y_i_pred) <= K)
    assert (len(np.unique(y_i_pred)) == len(y_i_pred))

    sum_precision = 0.0
    num_hits = 0.0

    for i, p in enumerate(y_i_pred):
        if p in y_i_true:
            num_hits += 1
            precision = num_hits / (i + 1)
            sum_precision += precision

    return sum_precision / min(len(y_i_true), K)

# MAP@K을 계산하는 함수
def mapk(y_true, y_pred):
    return np.mean([apk(y_i_true, y_i_pred) for y_i_true, y_i_pred in zip(y_true, y_pred)])

# MAP@K을 요청
print(mapk(y_true, y_pred))
# 0.65

# 정답 수가 같아도 순서가 다르면 점수도 다름
print(apk(y_true[0], y_pred[0]))
print(apk(y_true[1], y_pred[1]))
# 1.0, 0.5833


#!/usr/bin/env python3
"""
Advanced Linear Algebra Module
"""
import numpy as np

def definiteness(matrix):
    """
    Calculates the definiteness of a matrix.
    matrix is a numpy.ndarray of shape (n, n) whose definiteness should be
    calculated
    If matrix is not a numpy.ndarray, raise a TypeError with the message
    matrix must be a numpy.ndarray
    If matrix is not a valid matrix, return None

    Return: the string Positive definite, Positive semi-definite, Negative
    semi-definite, Negative definite, or Indefinite if the matrix is positive
    definite, positive semi-definite, negative semi-definite, negative
    definite of indefinite, respectively

    If matrix does not fit any of the above categories, return None
    """
    if type(matrix) != np.ndarray:
        raise TypeError('matrix must be a numpy.ndarray')
    if len(matrix.shape) == 1 or matrix.shape[0] != matrix.shape[1]:
        return None
    if not np.linalg.eig(matrix):
        return None
    if not (matrix.transpose() == matrix).all():
        return None

    try:
        eig = np.linalg.eigvals(matrix)
        if all(eig > 0):
            return 'Positive definite'
        if all(eig >= 0):
            return 'Positive semi-definite'
        if all(eig < 0):
            return 'Negative definite'
        if all(eig <= 0):
            return 'Negative semi-definite'
        return 'Indefinite'
    except Exception:
        return None


from numpy import linspace, exp

def demand_curve(t0=0.0, t1=5.0, tm=None, ymax=1.0, ymin=0.0, k=6.0):
    '''calcula os pontos da curva de demanda

    parametros
    ----------

    t0 : preço mínimo de compra/venda
    t1 : preço máximo de compra/venda
    ymin : valor mínimo de demanda
    ymax: valor máximo de demanda
    k : inclinação da função logística
    '''
    if not tm:
        tm = t0 + (t1 - t0) / 2.0 # ponto medio da curva

    t = linspace(t0, t1, 50)
    y = ymax * (1.0 / (1.0 + exp(k*(t - tm)))) + ymin * (1.0 / (1.0 + exp(-k*(t - tm))))
    return t, y

def utility_curve(t0=0.0, t1=5.0, min_price=(1.0, 0.0), max_power=(5.0, 50.0)):
    '''calcula os pontos da curva de demanda

    parametros
    ----------

    t0 : preço mínimo de compra/venda
    t1 : preço máximo de compra/venda
    ymin : valor mínimo de demanda
    ymax: valor máximo de demanda
    k : inclinação da função logística
    '''

    t = linspace(t0, t1, 50)
    a = min_price[1] - max_power[1]
    b = min_price[0] - max_power[0]
    c = min_price[0] * max_power[1] - min_price[1] - max_power[0]
    y = - (a / b) * t - c / b
    return t, y


from AutoDiff import AutoDiff
import numpy as np

def all_derivatives(func, in_vars, point):
    '''
    Function that calculates the value of the input function at the input point and all its partial derivatives

    :params func: (function) function to evaluate whose input is a list of variables
            in_vars: (list)  list of variables to use when calculating the values of the input function
            point: (list)    list of values of each input variable at the input point

    :returns: out_val (scalar) the value of the input function at the input point
              out_dervs (numpy array , 1 dimensional) an array of the 1st derivative values of each input variable
                                                      at the input point

    :dependencies: requires class AutoDiff from module AutoDiff, package numpy imported ad np

    Example:

    f(x,y) = x^2 + y^2

    def func(vars):
        return vars[0]**2 + vars[1]**2

    a, b = all_derivatives(func, ['x','y'], [3,2])
    print(a, b)
    13.0, [6.0, 4.0]

    '''
    auto_diff_vars = []   # list to hold AutoDiff objects, one for each var in in_vars
    out_dervs =[]         # list to hold derivative values, one for each var in in_vars

    # Make AutoDiff objects
    for var, pt in zip(in_vars, point):
        var = AutoDiff(pt, 0)
        auto_diff_vars.append(var)

    # Calculate the value of func at the input point
    f = func(auto_diff_vars)
    out_val = f.val

    # Calculate the partial derivatives of func at the input point
    # Go through AutoDiff objects in auto_diff_vars
    # and during each pass change the derv value of 1 variable to 1, while others are held at 0
    # and calculate the partial derivative
    for i in range(len(auto_diff_vars)):
        auto_diff_vars[i].derv = 1
        f = func(auto_diff_vars)
        out_dervs.append(f.derv)
        auto_diff_vars[i].derv = 0

    return out_val, np.array(out_dervs)

def multi_func_all_derivatives(functions, in_vars, point):
    '''
    Function that calculates the value of all input functions at the input point and all their partial derivatives
    producing a numpy array of output values and a numpy 2D array (matrix) of partial derivatives, the Jacobian

    :params func: (functions) list of function to evaluate whose input is a list of variables
            in_vars: (list)   list of variables to use when calculating the values of the input functions
            point: (list)     list of values of each input variable at the input point

    :returns: out_vals (numpy array 1 dimensional) the values of the input functions at the input point
              out_dervs_matrix (numpy array 2 dimensional) a matrix of the 1st derivative values of each input variable
                        at the input point, the Jacobian.

    :dependencies: requires class AutoDiff from module AutoDiff, function all_derivatives from this module,
                            All_derivatives, and the package numpy imported as np

    Example:

    f(x,y) = x^2 + y^2

    (x,y) = x^2*y^2

    def func1(vars):
        return vars[0]**2 + vars[1]**2

    def func2(vars):
        return vars[0]**2*vars[1]**2

    a, b = multi_func_all_derivatives([func1,func2] , ['x','y'], [3,2])
    print(a, b)
    [13. 36.] [[ 6.  4.], [24. 36.]]

    '''
    out_vals = []
    out_dervs_matrix =[]

    # for each function in functions list
    # run all_derivatives and store output
    for func in functions:
        val, dervs = all_derivatives(func, in_vars, point)
        out_vals.append(val)
        out_dervs_matrix.append(dervs)

    return np.array(out_vals), np.array(out_dervs_matrix)


import numpy as np
from scipy.stats import norm

# Underlying informations
S0 = 100.0
sigma = 0.2

# European option informations
T = 1.0
K = 110.0
r = 0.1

def d1():
    return (np.log(S0/K)+(r+0.5*sigma**2)*T)/(sigma*np.sqrt(T))

def d2():
    return d1() - sigma*np.sqrt(T)

def price_BS():
    return S0*norm.cdf(d1())-K*np.exp(-r*T)*norm.cdf(d2())

def price_approx():
    return 0.4*S0*sigma*np.sqrt(T)

print(price_BS())
print(price_approx())
print((price_approx()-price_BS())/price_BS())