# imports and tools
from pyspark.mllib.stat import Statistics
from pyspark.mllib.linalg.distributed import RowMatrix
from pyspark.sql import SparkSession
import os
import numpy as np

# create SparkContext object
spark = SparkSession.builder.appName("Unit03_BasicStats").getOrCreate()
sc = spark.sparkContext

# summary statistics
mat = sc.parallelize(
    [np.array([1.0, 10.0, 100.0]), np.array([2.0, 20.0, 200.0]), np.array([3.0, 30.0, 300.0])]
)

summary = Statistics.colStats(mat)
print(summary.mean())
print(summary.variance())
print(summary.numNonzeros())

## correlation
# vectors
seriesX = sc.parallelize([1.0, 2.0, 3.0, 3.0, 5.0])
seriesY = sc.parallelize([11.0, 22.0, 33.0, 33.0, 55.0])

print('Pearson correlation is: {}'.format(Statistics.corr(seriesX, seriesY, method = 'pearson')))
print('Spearman correlation is: {}'.format(Statistics.corr(seriesX, seriesY, method = 'spearman')))

# matrix
print('Correlation of matrix: {}'.format(Statistics.corr(mat, method = 'pearson')))

## sampling
# sampling methods can be performed on RDD's of key-value pairs
data = sc.parallelize([
    (1, 'a')
    ,(1, 'b')
    ,(2, 'c')
    ,(2, 'd')
    ,(2, 'e')
    ,(3, 'f')
])

fractions = {1 : 0.1, 2 : 0.6, 3 : 0.3}
approxSample = data.sampleByKey(False, fractions)

## hypothesis testing
from pyspark.mllib.linalg import Matrices, Vectors
from pyspark.mllib.regression import LabeledPoint
from pyspark.mllib.stat import Statistics

vec = Vectors.dense(0.1, 0.15, 0.2, 0.3, 0.25)
# compute goodness of fit. either compare two vectors to each other or compare one vector to a uniform distribution
goodnessOfFitTestResults = Statistics.chiSqTest(vec)
print(goodnessOfFitTestResults)

# pearson's independence test on a matrix
mat = Matrices.dense(3, 2, [1.0, 3.0, 5.0, 2.0, 4.0, 6.0])
independenceTestResults = Statistics.chiSqTest(mat)
print(independenceTestResults)

# a contingency table can be constructed from an RDD of LabeledPoint/vector pairs. The resulting test returns
# a Chi-squared test results for every feature against the label
obs = sc.parallelize([
    LabeledPoint(1.0, [1.0, 0.0, 3.0])
    ,LabeledPoint(1.0, [1.0, 2.0, 0.0])
    ,LabeledPoint(1.0, [-1.0, 0.0, -0.5])
])
featureTestResults = Statistics.chiSqTest(obs)

for i, result in enumerate(featureTestResults):
    print('column {0}: \n {1}'.format(i, result))

## random data generation
from pyspark.mllib.random import RandomRDDs

# generate a random RDD that contains a million iid values drawn from a normal distribution N(0, 1)
# distribute evenly to 10 partitions
u = RandomRDDs.normalRDD(sc, size = 1000000, numPartitions = 10)
print(u.take(20))

# apply a transformation to return a random RDD that follow a normal distribution N(1, 4)
v = u.map(lambda x: 1.0 + 2.0 * x)
print(v.take(20))


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

def computeCost(X, y, theta):
    inner = np.power(((X * theta.T) - y), 2)
    return np.sum(inner) / (2 * len(X))

def gradientDescent(X, y, theta, alpha, iters):
    temp = np.matrix(np.zeros(theta.shape))
    parameters = int(theta.ravel().shape[1])
    cost = np.zeros(iters)

    for i in range(iters):
        error = (X * theta.T) - y

        for j in range(parameters):
            term = np.multiply(error, X[:, j])
            temp[0, j] = theta[0, j] - ((alpha / len(X)) * np.sum(term))

        theta = temp
        cost[i] = computeCost(X, y, theta)

    return theta, cost

if __name__ == '__main__':
    # path = os.getcwd() + '\data\ex1data1.txt'
    path = "D:\git\Coursera-ML\johnwittenauer\data\ex1data1.txt"
    data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
    print(data.head())
    print(data.describe())
    # data.plot(kind='scatter', x='Population', y='Profit', figsize=(12,8))
    # plt.show()

    # append a ones column to the front of the data set
    data.insert(0, 'Ones', 1)

    # set X (training data) and y (target variable)
    cols = data.shape[1]
    X = data.iloc[:, 0:cols - 1]
    y = data.iloc[:, cols - 1:cols]

    print(X.shape)
    print(y.shape)

    # convert from data frames to numpy matrices
    X = np.matrix(X.values)
    y = np.matrix(y.values)
    theta = np.matrix(np.array([0, 0]))

    print(X.shape)
    print(y.shape)
    print(theta.shape)

    print(computeCost(X, y, theta))

    # initialize variables for learning rate and iterations
    alpha = 0.01
    iters = 1000

    # perform gradient descent to "fit" the model parameters
    g, cost = gradientDescent(X, y, theta, alpha, iters)
    print("final theta:", g)
    print("final ccost:", cost[-1])

    x = np.linspace(data.Population.min(), data.Population.max(), 100)
    f = g[0, 0] + (g[0, 1] * x)

    fig, ax = plt.subplots(figsize=(12,8))
    ax.plot(x, f, 'r', label='Prediction')
    ax.scatter(data.Population, data.Profit, label='Traning Data')
    ax.legend(loc=2)
    ax.set_xlabel('Population')
    ax.set_ylabel('Profit')
    ax.set_title('Predicted Profit vs. Population Size')

    # plt.show()

    fig, ax = plt.subplots(figsize=(12, 8))
    ax.plot(np.arange(iters), cost, 'r')
    ax.set_xlabel('Iterations')
    ax.set_ylabel('Cost')
    ax.set_title('Error vs. Training Epoch')

    plt.show()


import numpy as np

#---------------------------------------------------------------------------------------------
def sigmoid(z):
    s = 1/(1+np.exp(-1*z))

    return s

#---------------------------------------------------------------------------------------------
def initialize_with_zeros(dim):
    w = np.zeros([dim,1])
    b = 0

    assert(w.shape == (dim, 1))
    assert(isinstance(b, float) or isinstance(b, int))

    return w, b

#---------------------------------------------------------------------------------------------
def propagate(w, b, X, Y):
    m = X.shape[1]

    A = sigmoid(np.dot(w.T,X)+b)                          # compute activation
    cost = (1/m)*np.sum(-Y*np.log(A)-(1-Y)*np.log(1-A))   # compute cost

    dw = (1/m)*(np.dot(X,(A-Y).T))
    db = (1/m)*np.sum(A-Y)

    assert(dw.shape == w.shape)
    assert(db.dtype == float)
    cost = np.squeeze(cost)
    assert(cost.shape == ())

    grads = {"dw": dw,
             "db": db}

    return grads, cost

#---------------------------------------------------------------------------------------------
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    costs = []

    for i in range(num_iterations):
        grads, cost = propagate(w, b, X, Y)

        w = w-learning_rate*grads["dw"]
        # b = b-learning_rate*grads["db"]

        if i % 100 == 0:
            costs.append(cost)

        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))

    params = {"w": w,
              "b": b}

    grads = {"dw": grads["dw"],
             "db": grads["db"]}

    return params, grads, costs

#---------------------------------------------------------------------------------------------
def predict(w, b, X):
    m = X.shape[1]
    Y_prediction = np.zeros((1,m))
    w = w.reshape(X.shape[0], 1)

    A = sigmoid(np.dot(w.T,X)+b)
    Y_prediction = np.where(A > 0.5,1,0)

    assert(Y_prediction.shape == (1, m))

    return Y_prediction

#---------------------------------------------------------------------------------------------
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
    w, b = np.zeros([X_train.shape[0],1]),0

    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)

    w = parameters["w"]
    b = parameters["b"]

    Y_prediction_train = predict(w, b, X_train)
    Y_prediction_test = predict(w, b, X_test)

    print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
    print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))

    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test,
         "Y_prediction_train" : Y_prediction_train,
         "w" : w,
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}

    return d
#---------------------------------------------------------------------------------------------


from sympy import *
from sympy.matrices import *
import operator, functools
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from furlib.alpha import *

def euler_angles(H):
    """
    Calcula los ángulos de Euler ZXZ a partir de la matriz de transformación
    homogénea H.
    """
    R = H[:3,:3]
    r33,r13,r23,r31,r32,r11,r12 = R[2,2],R[0,2],R[1,2],R[2,0],R[2,1],R[0,0],R[0,1]
    if r23!=0 and r13!=0:
        theta = atan2(sqrt(1-r33**2),r33)
        phi = atan2(r23,r13)
        psi = atan2(r32,-r31)
    elif r33==1:
        theta = 0
        phi = 0
        psi = atan2(-r12,r11)
    elif r33==-1:
        theta = pi
        psi = 0
        phi = atan2(-r12,-r11)
    else:
        theta = atan2(sqrt(1-r33**2),r33)
        phi = atan2(r23,r13)
        psi = atan2(r32,-r31)
    return phi.evalf(),theta.evalf(),psi.evalf()

def dhs(a,alpha,d,theta):
    """
    Calcula la matriz de Denavit-Hartenberg de manera simbólica
    """
    M = Matrix([[cos(theta),-sin(theta)*cos(alpha),sin(theta)*sin(alpha),a*cos(theta)],
                  [sin(theta),cos(theta)*cos(alpha),-cos(theta)*sin(alpha),a*sin(theta)],
                  [0,sin(alpha),cos(alpha),d],
                  [0,0,0,1]])
    return M

class Robot(object):
    """
    Define un manipulador dados los parámetros de DH.
    """
    def __init__(self,*args):
        self.Ts = [] # Transformation matrices i to i-1
        self.type = [] # Joint type -> "r" revolute, "p" prismatic
        for k in args:
            self.Ts.append(dhs(k[0],k[1],k[2],k[3])) # Compute Ti->i-1
            if len(k)>4:
                self.type.append(k[4])
            else:
                self.type.append('r')
        self.dof = len(args) # Degree of freedom

    def z(self,i):
        """
        z-dir of every i-Frame
        """
        if i == 0: return Matrix([[0],[0],[1]])
        MTH = eye(4)
        for k in range(i):
            MTH = MTH*self.Ts[k]
        return MTH[:3,2]

    def p(self,i):
        """
        Position for every i-Frame
        """
        if i == 0: return Matrix([[0],[0],[0]])
        MTH = eye(4)
        for k in range(i):
            MTH = MTH*self.Ts[k]
        return MTH[:3,3]

    @property
    def J(self):
        """
        Jacobian matrix
        """
        n = self.dof
        M_ = zeros(6,n)
        for i in range(self.dof):
            if self.type[i]=='r':
                jp = self.z(i).cross(self.p(n) - self.p(i))
                jo = self.z(i)
            else:
                jp = self.z(i)
                jo = zeros(3,1)
            jp = jp.col_join(jo)
            M_[:,i] = jp
        return simplify(M_)

    @property
    def T(self):
        """
        T_n^0
        Homogeneous transformation matrix of N-Frame respect to Base-Frame
        """
        return simplify(functools.reduce(operator.mul, self.Ts))

    def Ti_0(self,i):
        return simplify(functools.reduce(operator.mul, self.Ts[:i+1]))

    def plot_diagram(self,vals):
        #return None
        fig = plt.figure()
        ax = fig.gca(projection='3d')

        Ts = self.Ts
        points = []
        Ti_0 = []
        points.append(zeros(1,3))
        for i in range(self.dof):
            Ti_0.append(self.Ti_0(i).subs(vals))
            points.append((self.Ti_0(i)[:3,3]).subs(vals))

        X = [k[0] for k in points]
        Y = [k[1] for k in points]
        Z = [k[2] for k in points]
        ax.plot(X,Y,Z, "o-", color="#778877", lw=3)
        ax.plot([0],[0],[0], "mo", markersize=6)
        ax.set_axis_off()
        ax.view_init(90,0)

        px,py,pz = float(X[-1]),float(Y[-1]),float(Z[-1])
        dim = max([px,py,pz])

        self.draw_uvw(eye(4),ax, dim)
        for T in Ti_0:
            self.draw_uvw(T, ax, dim)

        ax.set_xlim(-dim, dim)
        ax.set_ylim(-dim, dim)
        ax.set_zlim(-dim, dim)

    def draw_uvw(self,H,ax,sz=1):
        u = H[:3,0]
        v = H[:3,1]
        w = H[:3,2]
        o = H[:3,3]
        L = sz/5
        ax.quiver(o[0],o[1],o[2],u[0],u[1],u[2],color="r", length=L)
        ax.quiver(o[0],o[1],o[2],v[0],v[1],v[2],color="g", length=L)
        ax.quiver(o[0],o[1],o[2],w[0],w[1],w[2],color="b", length=L)

def ea2htm(phi,theta,psi):
    """
    Calculate HTM from ZXZ Euler Angles.
    """
    return htmr(phi)*htmr(theta,"X")*htmr(psi)

def htmr(t,axis="z"):
    """
    Calculate the homogeneous transformation matrix of a rotation
    respect to x,y or z axis.
    """
    from sympy import sin,cos,tan
    if axis in ("z","Z",3):
        M = Matrix([[cos(t),-sin(t),0,0],
                  [sin(t),cos(t),0,0],
                  [0,0,1,0],
                  [0,0,0,1]])
    elif axis in ("y","Y",2):
        M = Matrix([[cos(t),0,sin(t),0],
                  [0,1,0,0],
                  [-sin(t),0,cos(t),0],
                  [0,0,0,1]])
    elif axis in ("x","X",1):
        M = Matrix([[1,0,0,0],
                  [0,cos(t),-sin(t),0,],
                  [0,sin(t),cos(t),0],
                  [0,0,0,1]])
    else:
        return eye(4)
    return M

def htmt(dx,dy,dz):
    """
    Calculate the homogeneous transformation matrix of a traslation
    """
    M = Matrix([[1,0,0,dx],
                [0,1,0,dy],
                [0,0,1,dz],
                [0,0,0,1]])
    return M

if __name__=="__main__":
    rr = Robot((0,pi/2,0,t1),(0,0,d2,0))
    print(rr.T)
    #rr.plot_diagram({t1:pi/2,d2:100})
    #plt.show()


#from: http://people.duke.edu/~ccc14/sta-663-2016/03A_Numbers.html#Example:-Calculating-pairwise-distance-matrix-using-broadcasting-and-vectorization
#setup: import numpy as np ; X = np.linspace(0,10,20000).reshape(200,100)
#run: pairwise(X)

#pythran export pairwise(float [][])

import numpy as np
def pairwise(pts):
    return np.sum((pts[None,:] - pts[:, None])**2, -1)**0.5


import scipy.integrate as integrate
import numpy as np
from math import inf

'''
Given privacy parameters epsilon, global_sensitivity, and scale parameter k, return the function for the probability density function
for asymmetric laplace distribution.
'''
def asymmetric_laplace(epsilon, global_sensitivity, k):
    lam = epsilon / global_sensitivity
    def f(x):
        if x < 0:
            return lam / (k + 1 / k) * np.exp(lam * x / k)
        else :
            return lam / (k + 1 / k) * np.exp(-lam * x * k)
    return f

'''
Given epsilon, global_sensitivity, and k, return the L1 cost for the asymmetric laplacian mechanism.
'''
def asymmetric_laplace_L1_eval(epsilon, global_sensitivity, k):
    return integrate.quad(lambda x: abs(x) * asymmetric_laplace(epsilon, global_sensitivity, k)(x), -inf, inf)[0]

'''
Given epsilon, global_sensitivity, and k, return the L2 cost for the asymmetric laplacian mechanism.
'''
def asymmetric_laplace_L2_eval(epsilon, global_sensitivity, k):
    return integrate.quad(lambda x: x**2 * asymmetric_laplace(epsilon, global_sensitivity,k)(x), -inf, inf)[0]

# Example for getting resutls for asymmetric laplacian
def asymmetric_laplacian_example():
    epsilon = 1e-4 # change here for different epsilon values
    global_sensitivity = 1 # changee here for different global sensitivity values
    k = 3 # change here for different scale parameter values
    L1_cost = asymmetric_laplace_L1_eval(epsilon, global_sensitivity, k)
    L2_cost = asymmetric_laplace_L2_eval(epsilon, global_sensitivity, k)

asymmetric_laplacian_example()


# Lagrange interpolating polynomial

import numpy as np

def lagrange(x, y, X):
    '''
    Lagrange interpolation method
    Input:
      - x : precomputed points x axis values
      - y : precomputed points y axis values
      - X : query points x axis
    Output:
      - Y : query points y axis (interpolation results)
    '''
    n = len(x)
    Y = np.zeros(len(X))
    for i in range(n):
        L = np.ones(len(X))
        for j in range(n):
            if i != j:
                L *= (X - x[j])/(x[i]-x[j])
        Y += y[i]*L
    return Y

def lagrange_poly(x, y):
    '''
    Return a Lagrange interpolating polynomial.

    Input
      - x : array-like x-coordinates of a set of datapoints.
      - y : array-like y-coordinates of a set of datapoints, i.e. f(x).
    Output:
      - p: Lagrange interpolating polynomial as a `numpy.poly1d` instance.
    '''
    n = len(x)
    p = np.poly1d(0.0)
    for j in range(n):
        pt = np.poly1d(y[j])
        for k in range(n):
            if k != j:
                pt *= np.poly1d([1.0, -x[k]])/(x[j]-x[k])
        p += pt
    return p


# -*- coding: utf-8 -*-
#
# Copyright 2018 Amir Hadifar. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import numpy as np

# our data
X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)
y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)
# normilize our data
X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())

# initilization
a, b = 0, 0
# number of iteration
num_epoch = 10000
learning_rate = 1e-3
# our main loop
for e in range(num_epoch):
    y_pred = a * X + b  # our line equation
    grad_a = (y_pred - y).dot(X)  # calculate gradient for a
    grad_b = (y_pred - y).sum()  # calculate gradient for b
    # Update parameters.
    a = a - learning_rate * grad_a
    b = b - learning_rate * grad_b
print(a, b)


# Numerical differentiation error test.
#
# Finite Forward Difference (FFD) and Symmetric Differential Quotient (SDQ)
# errors magnitude are compared for a known function on a fixed point x0.
#
# The test also shows that, after a certain threshold, both the methods are
# heavily affected by numerical cancellation.
#
# Note that the SDQ method produces smaller errors.

import matplotlib.pyplot as plt
import numpy as np

a = 0.5
ddx = np.cos(a)
# Get 100 numbers between 10^-17 and 10^0 evenly spaced on a log scale.
hh = np.logspace(-17, 0, 100, endpoint=True)
ffd = []
sdq = []
ffd_err = []
sdq_err = []
for h in hh:
    val = (np.sin(a+h)-np.sin(a))/h
    ffd.append(val)
    ffd_err.append(abs(ddx - val))
    val = (np.sin(a+h)-np.sin(a-h))/(2*h)
    sdq.append(val)
    sdq_err.append(abs(ddx - val))
# Print a tabular form of the results
print('ddx = {}'.format(ddx))
print('| h                     | FFD         | Err         | SDQ         | Err')
for h,a,b,c,d in zip(hh,ffd, ffd_err, sdq, sdq_err):
    print('| {:.19f} | {:.9f} | {:.9f} | {:.9f} | {:.9f}'.format(h,a,b,c,d))
# Plot the graph
plt.loglog(hh, ffd_err)
plt.loglog(hh, sdq_err)
plt.grid(True)
plt.xlabel('h')
plt.ylabel('error')
plt.legend(['ffd','sdq'])
plt.show()


import numpy as np
import pandas as pd
from numpy.linalg import inv, norm

def normalize(X):
    mu = X.mean(0)
    std = X.std(0)
    return (X - mu) / std, mu, std

def add_bias_column(X):
    ones = np.ones(X.shape[0]).reshape(X.shape[0], 1)
    return np.concatenate((ones, X), 1)

def normal_equation(X, y):
    w = inv(X.T.dot(X)).dot(X.T).dot(y)
    return w

def compute_error(X, y, w):
    return (y - X.dot(w)).T.dot(y - X.dot(w))

def gradient_descent(X, y, alpha=0.001, epsilon=0.0001):
    m, n = X.shape
    w_old = np.random.rand(n).reshape(n, 1)
    w_new = np.zeros((n, 1))
    i = 0
    while norm(w_new - w_old)  > epsilon:
        w_old = w_new
        RSS = compute_error(X, y, w_old)
        grad_RSS = (X.T.dot(X).dot(w_old) - X.T.dot(y))
        w_new = w_old - alpha * grad_RSS
        print(i, np.squeeze(RSS))
        i += 1
    return w_new

if __name__ == '__main__':
    train_data = pd.read_csv('kc_house_train_data.csv')
    test_data = pd.read_csv('kc_house_test_data.csv')

    features = ['bedrooms', 'bathrooms', 'sqft_living', 'sqft_lot']
    target = 'price'

    X = train_data[features].values
    y = train_data[target].values.reshape(X.shape[0], 1)

    X, mu, std = normalize(X)
    X = add_bias_column(X)

    ne_w = normal_equation(X, y)
    gd_w = gradient_descent(X, y, alpha=0.00001)

    print('-'*70)
    print('Normal Equation Model:\n', ne_w, '\n', '-'*70)
    print('Gradient Descent Model:\n', gd_w, '\n', '-'*70)
    print('Difference between them:\n', ne_w - gd_w, '\n', '-'*70)


###_______________________________ SymPy ___________________________________###
# SymPy es una biblioteca de Python para matemática simbólica. Apunta a convertirse
# en un sistema de algebra computacional (CAS) con todas sus prestaciones manteniendo
# el código tan simple como sea posible para manterlo comprensible y fácilmente extensible.
# SymPy está escrito totalmente en Python y no requiere bibliotecas adicionales.
# Este proyecto comenzó en 2005, fue lanzado al público en 2007 y a él han contribuido
# durante estos años cientos de personas.

##_________________________ Creating Symbols ___________________________##
from sympy import init_session, Symbol, symbols, pi, I, E, cos, sin, exp, tan, simplify, expand, factor, collect, apart, cancel, expand_trig, diff, Derivative, Function, integrate, limit, series, Eq, solve, dsolve, Matrix, N
init_session(use_latex='matplotlib')

# Creamos el símbolo a
a = Symbol('a')
print((a + pi) ** 2)
print(type(a))
b = 2 * a
print(type(b))
print(b)
# Como Python permite que el tipo de una variable cambie, si ahora le asigno a
# a un valor float deja de ser un símbolo
a = 2.26492
print(type(a))

# __Las conclusiones son:_
    # * __Si quiero usar una variable como símbolo debo crearla previamente.__
    # * Las operaciones con símbolos devuelven símbolos.
    # * Si una varibale que almacenaba un símbolo recibe otra asignación, cambia de tipo.
# Las variables de tipo Symbol actúan como contenedores en los que no sabemos qué
# hay (un real, un complejo, una lista...). Hay que tener en cuenta que: una cosa es
# el nombre de la variable y otra el símbolo con el que se representa
#creación de símbolos
coef_traccion = Symbol('c_T')
# Diferencia entre variable y símbolo
a = symbols('b')
x, y, z, t = symbols('x y z t')
#  símbolos griegos:
w = symbols('omega')
W = symbols('Omega')

# Por defecto, SymPy entiende que los símbolos son números complejos. Esto puede
# producir resultados inesperados ante determinadas operaciones como, por ejemplo,
# lo logaritmos. Podemos indicar que la variable es real, entera... en el momento
# de la creación

# Creamos símbolos reales
x, y, z, t = symbols('x y z t', real=True)
# Podemos ver las asunciones de un símbolo
print(x.assumptions0)

##_________________________ Expressions ___________________________##
expr = cos(x)**2 + sin(x)**2
print(expr)

# Podemos pedirle que simplifique la expresión anterior
print(simplify(expr))

# En algunas ocasiones necesitaremos sustituir una variable por otra, por otra
# expresión o por un valor, using 'subs'
# Sustituimos x por y ** 2
print(expr.subs(x, y**2))
# ¡Pero la expresión no cambia!
# Para que cambie
expr = expr.subs(sin(x), exp(x))
print(expr)
# Particulariza la expresión sin(x)+3x en x=pi
print((sin(x) + 3 * x).subs(x, pi))

# Aunque si lo que queremos es obtener el valor numérico lo mejor es .evalf()
print((sin(x) + 3 * x).subs(x, pi).evalf(25)) # Nº of decimal places is the n isnide ()

# el mismo resultado se obtiene ocn la función N()
print(N(pi,25))

##_________________________ Simplification ___________________________##
# SymPy ofrece numerosas funciones para __simplificar y manipular expresiones__.
 # Entre otras, destacan:
    # * `expand()`
    # * `factor()`
    # * `collect()`
    # * `apart()`
    # * `cancel()`
# Puedes consultar en la documentación de SymPy lo que hace cada una y algunos ejemplos.
# Existen también funciones específicas de simplificación para funciones trigonométricas,
# potencias y logaritmos. Abre [esta documentación](http://docs.sympy.org/latest/tutorial/simplification.html) si lo necesitas.

#_____________ Examples _____________##
expr1 = (x**3 + 3*y +2)**2
print(expand(expr1))

expr2 = (3*x**2 - 2*x +1) / ((x-1)**2)
print(apart(expr2))

expr3 = (x**3 + 9*x**2 +27*x + 27)
print(factor(expr3))

expr4 = sin(x + 2 * y)
print(expand_trig(expr4))
print(expand(expr4, trig=True))

##_____________ Derivatives _____________##
# Puedes derivar una expresion usando el método .diff() y la función dif()
#creamos una expresión
expr = cos(x)
#obtenemos la derivada primera con funcion
print(diff(expr, x)) #or:
print(expr.diff(x))

# Third derivative?
print(expr.diff(x,x,x))
print(expr.diff(x,3))

# Many derivatives?
expr_xy = y ** 3 * sin(x) ** 2 + x ** 2 * cos(y)
print(diff(expr_xy, x, 2, y, 2))

# Si queremos que la deje indicada, usamos Derivative()
print(Derivative(expr_xy, x, 2, y))

# Can SymPy apply the chain rule?
# Creamos una función F #
F = Function('F')
print(F(x))
# Creamos una función G
G = Function('G')
print(G(x))
# Derivamos la función compuesta F(G(x))
print(F(G(x)).diff(x))

# If we know the functions: #
# definimos una f
f = 2 * y * exp(x)
# definimos una g(f)
g = f **2 * cos(x) + f
#la derivamos
print(diff(g,x))

##_____________ Integrals _____________##
int1 = cos(x)**2
print(int1.integrate(x))

int2 = 1 / sin(x)
print(int2.integrate(x))

x, a = symbols('x a', real=True)

int3 = 1 / (x**2 + a**2)**2
print(int3.integrate(x))

##_________________________ Limits ___________________________##
# Calculemos este límite sacado del libro _Cálculo: definiciones, teoremas y
# resultados_, de Juan de Burgos:
    # $$\lim_{x \to 0} \left(\frac{x}{\tan{\left (x \right )}}\right)^{\frac{1}{x^{2}}}$$
# Primero creamos la expresión:
x = symbols('x', real=True)
expr = (x / tan(x)) ** (1 / x**2)
#Obtenemos el límite con la función limit() y si queremos dejarlo indicado,
# podemos usar Limit():
print(limit(expr, x, 0))

##_________________________ Series ___________________________##
#creamos la expresión
expr = exp(x)
print(series(expr))
# Se puede especificar el número de términos pasándole un argumento n=....
# El número que le pasemos será el primer término que desprecie.
# Indicando el número de términos
print(series(expr, n=10))

# Si nos molesta el O(x**10) lo podemos quitar con removeO():
print(series(expr, n=10).removeO())
print(series(sin(x), n=8, x0=pi/3).removeO().subs(x, x-pi/3))

##_________________________ Eq resolution ___________________________##
# Como se ha mencionado anteriormente las ecuaciones no se pueden crear con el =
#creamos la ecuación
equation = Eq(x ** 2 - x, 3)
# También la podemos crear como
print(Eq(x ** 2 - x -3))
# Solve it:
print(solve(equation))

# To solve using symbols: #
# Creamos los símbolos y la ecuación
a, x, t, C = symbols('a, x, t, C', real=True)
equation2 = Eq(a * exp(x/t), C)
print(solve(equation2 ,x))

##_________________________ Diff equations ___________________________##
# dsolve is the solver for differential equations
x = symbols('x')
y = Function('y')
ecuacion_dif = Eq(y(x).diff(x,2) + y(x).diff(x) + y(x), cos(x))
#resolvemos
# print(dsolve(ecuacion_dif, f(x)))

##_________________________ Matrices ___________________________##
#creamos una matriz llena de símbolos
a, b, c, d = symbols('a b c d')
A = Matrix([
            [a, b],
            [c, d]
    ])
#sacamos autovalores
print(A.eigenvals())
#inversa
print(A.inv())
#elevamos al cuadrado la matriz
print(A ** 2)


"""
Purpose of this script is to demostrate the creation of numpy arrays

Code is partially inspired by the Linked in Learning Course
https://www.linkedin.com/learning/numpy-data-science-essential-training/create-arrays-from-python-structures

"""
import numpy as np

# check numpy version
print(f'numpy version is {np.__version__}')

# create a simply python list array
# reminder that python list are not typed bound ie my_list = [1, "1", "one", 1.0]
my_list = [1, 6, 3, -5, 10, 0, 25]

# to create a numpy array using a list
my_array_from_list = np.array(my_list)

# printing both we can se that they containing the same data
print(f'my_list is : {my_list}')
print(f'my_array_from_list is : {my_array_from_list}')

# Note that operators will behave differently from list and numpy array
print('multiply each by *3 produce different results')
print(my_list * 3)              # will treble the list by replicating the list
print(my_array_from_list *3)    # will multiple each element by 3

# if we create a tuple with different types of number, int, double and complex
my_tuple = (1, 2-2.5, 3+4j)
# the numpy array will promote all to complex so that the array elements is of the same type
print(f'numpy array from tuple is {np.array(my_tuple)}')

# Creating numpy arrays using functions

# np.arrange(n) will construct list from 0 (default) to n-1
a = np.arange(7)
print(f'np.arrange(7) produces : {a}')

# specifying a start value
b = np.arange(10,23)
print(f'np.arrange(10,23) produces : {b}')

# we find some properties of a numpy array
print(f'the length of b is {len(b)}')
print(f'the length of b is {b.size}')
print(f'the shape of b is {b.shape}')

# we can create using linspace, numpy uses [] set theory
l1 = np.linspace(5,15,9)
print(f'np.linspace(5,15,9) is {l1}')
l2 = np.linspace(5,15,9, retstep=True)
# creates array of (l1, step_value)
print(f'np.linspace(5,15,9, retstep=True) is {l2}')

# creating zeros and ones arrays
print(f'using np.zeros(5) creates {np.zeros(5)}')
# tuple size
tuple_size = (5,3)
print(f'using np.zeros((5,3)) creates\n {np.ones(tuple_size)}')


import numpy as np

def solve(fvals, x0, debug=False):
    tol = 1e-14
    maxiter = 20
    x = x0
    if debug:
        print "Initial guess: x = %22.15e" % x0
    for k in range(maxiter):
        f,fp = fvals(x)
        if abs(f) < tol:
            break
        delta_x = f / fp
        x = x - delta_x
        if debug:
            print "After %s iterations, x = %22.15e" % (k+1,x)

    if k==maxiter-1:
        f,fp = fvals(x)
        if abs(f) > tol:
            print "*** warning: has not yet converged"
        return x, k+1
    else:
        return x, k

def fvals_sqrt(x):
    """
    Return f(x) and f'(x) for applying Newton to find a square root.
    """
    f = x**2 - 4.
    fp = 2.*x
    return f, fp

def test1(debug_solve=False):
    """
    Test Newton iteration for the square root with different initial
    conditions.
    """
    from numpy import sqrt
    for x0 in [1., 2., 100.]:
        print " "  # blank line
        x,iters = solve(fvals_sqrt, x0, debug=debug_solve)
        print "solve returns x = %22.15e after %i iterations " % (x,iters)
        fx,fpx = fvals_sqrt(x)
        print "the value of f(x) is %22.15e" % fx
        assert abs(x-2.) < 1e-14, "*** Unexpected result: x = %22.15e"  % x


import matplotlib.pyplot as plt
import numpy as np
from scipy.special import gamma

from sdia_python.lab2.utils import get_random_number_generator

class BallWindow:
    def __init__(self, center, radius):
        """Create new ball window.

        Args:
            center (float array): coordinates of the center of the ball window.
            radius (float): radius of the ball window.
        """
        self.center = center
        self.radius = radius

    def dist_to_center(self, x):
        """Compute the distance of a given point to the center of the ball window.

        Args:
            x (float array): coordinates of the point of interest.

        Returns:
            float: distance of the given point to the center of the ball window.
        """
        # * Nice vectorization using numpy
        return np.linalg.norm(x - self.center.squeeze())

    def __contains__(self, x):
        """Compute whether a given point is contained in the ball window or not.

        Args:
            x (float array): coordinates of the point of interest.

        Returns:
            bool: point is in ball window or not.
        """

        # we need to compute the distance of the point of interest to the center of the ball window
        return self.dist_to_center(x) < self.radius

    def __str__(self):
        """BallWindow: (center, radius)

        Returns:
            str: string representation of the ball window.
        """
        # * nice use of f-strings
        return f"BallWindow (center : {self.center}, radius : {self.radius})"

    def __len__(self):
        """Indicates the number of dimension of the ball window.

        Returns:
            int: number of dimension of the ball window.
        """
        return len(self.center[0])

    def indicator_function(self, args):
        """Returns if args is in the ball window.

    Args:
        args (float array): The ball window  [description]

    Returns:
        bool: True if point in ball window else False.
    """
        return np.array([point in self for point in args])

    def volume(self):
        """Computes the volume of the ball.

        Returns:
            float: volume of the ball window.
        """
        # Using the gamma function to compute the volume of the ball
        return np.power(np.pi,
                        len(self) / 2) * np.power(
                            self.radius, len(self)) / gamma(len(self) / 2 + 1)

    def rand(self, n=1, rng=None):
        """Generate ``n`` points uniformly at random inside the :py:class:`BallWindow`.

        Args:
            n (int, optional): [description]. Defaults to 1.
            rng ([type], optional): [description]. Defaults to None.

        Returns:
            returns a float array with points in the bounds of the box
        """
        rng = get_random_number_generator(rng)

        # Using the Muller Method in d dimensions
        # first computing for a ball centered in O
        u = rng.normal(
            0, 1, (n, len(self))
        )  # an array of n x dimension normally distributed random variables

        norm = np.linalg.norm(u)
        r = self.radius * rng.random()**(1.0 / len(self))
        p = r * u / norm

        # we offset the points by the center of the ball
        return self.center + p

class UnitBallWindow(BallWindow):
    def __init__(self, center):
        """Create new unit ball window.

        Args:
            center (float array): coordinates of the center of the ball window.
        """
        super(UnitBallWindow, self).__init__(center, radius=1)


from scipy.special import binom
import numpy as np

from hypergeo.binomial_distribution import binomial_tail, binomial_tail_inverse

def test_binomial_tail():
    k, m, p = 5, 13, .3
    cdf = sum(binom(m, i) * p**i * (1-p)**(m-i) for i in range(k+1))
    assert np.isclose(binomial_tail(k, m, p), cdf)

def test_binomial_tail_inverse_is_inverse():
    k, m, p = 5, 13, .3
    assert np.isclose(binomial_tail_inverse(k, m, binomial_tail(k,m,p)), p)


print('')
print('====================================================================================================')
print('== 문제 344. (점심시간) 확률 0.4 의 엔트로피 지수를 구하시오!')
print('====================================================================================================')
import math
print(-0.4*math.log2(0.4))

print('')
print('====================================================================================================')
print('== 문제 345. 카드 유무의 컬럼의 데이터의 분할 전 엔트로피를 구하시오.')
print('====================================================================================================')
import math
print(-(4/5)*math.log2((4/5)) - (1/5)*math.log2((1/5)))

print('')
print('====================================================================================================')
print('== 문제 346. 확률을 입력했을 때 엔트로피를 출력하는 파이썬 함수를 생성하시오.')
print('====================================================================================================')
def entropy(p_list):
    return sum(-(p)*math.log2(p) for p in p_list)

print(entropy([4/5, 1/5]))

print('')
print('====================================================================================================')
print('== 문제 347. 아래의 리스트 변수에 있는 y와 n이 y가 4개 있고 n이 1개 있다는게 자동으로 구분되어지게 하려면?')
print('====================================================================================================')
import math
import collections
card_yn = ['Y', 'Y', 'N', 'Y', 'Y', 'K', 'K']

def class_probabilities(labels):
    return collections.Counter(labels).values()

print(class_probabilities(card_yn))

print('')
print('====================================================================================================')
print('== 문제 348. 위의 코드를 이용해서 아래의 결과가 출력될수 있도록 하시오!')
print('====================================================================================================')
card_yn = ['Y', 'Y', 'N', 'Y', 'Y']

def class_probabilities(labels):
    return [value/len(labels) for value in collections.Counter(labels).values()]

print(class_probabilities(card_yn))

print('')
print('====================================================================================================')
print('== 문제 349. 위에서 만든 함수 2개로 엔트로피를 구하시오!')
print('====================================================================================================')
print(entropy(class_probabilities(card_yn)))

print('')
print('====================================================================================================')
print('== 문제 352. 아래의 데이터 셋에서 scott 을 출력하려면?')
print('====================================================================================================')
inputs = [{'ename':'scott'}, True]
print(inputs[0]['ename'])

print('')
print('====================================================================================================')
print('== 문제 353. 아래의 결과에서 scott 과 smith 를 출력하려면?')
print('====================================================================================================')
inputs = [({'ename':'scott'}, True), ({'ename':'smith'}, False)]
print(','.join(v[0]['ename'] for v in inputs))

print('')
print('====================================================================================================')
print('== 문제 354. 아래의 데이터 셋에서 card_yn 의 y와 n만 출력하려면?')
print('====================================================================================================')
inputs = [({'ename': 'scott', 'card_yn': 'y'}, True), ({'ename': 'smith', 'card_yn': 'n'}, False)]
print(','.join(v[0]['card_yn'] for v in inputs))

print('')
print('====================================================================================================')
print('== 문제 355. 아래의 inputs 데이터셋에서 card_yn 의 y 와 n 을 groups 라는 비어있는 리스트 변수에 넣으시오!')
print('====================================================================================================')
inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

groups = []
for v in inputs:
    groups.append(v[0]['card_yn'])
print(groups)

print('')
print('====================================================================================================')
print('== 문제 356. 위의 코드를 가지고 아래의 결과가 출력되게 하시오!')
print('====================================================================================================')
inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

def setting_data(inputs):
    groups = {}
    for input in inputs:
        for key, value in input[0].items():
            if key != 'cust_name':
                if groups.get(key) is None:
                    groups[key] = []
                groups[key].append(value)
    return groups

print(setting_data(inputs))

print('')
print('====================================================================================================')
print('== 문제 357. 아래와 같이 분할전 엔트로피가 출력되게 위의 코드를 수정하시오.')
print('====================================================================================================')
import math
import collections

inputs = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
          ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
          ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
          ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

def entropy(p_list):
    return sum(-(p)*math.log2(p)-(1-p)*math.log2(1-p) for p in p_list)

def class_probabilities(labels):
    return [value/len(labels) for value in collections.Counter(labels).values()]

def setting_data(inputs):
    groups = {}
    for input in inputs:
        for key, value in input[0].items():
            if key != 'cust_name':
                if groups.get(key) is None:
                    groups[key] = []
                groups[key].append(value)
    return groups

print([(key, entropy(class_probabilities(value))) for key, value in setting_data(inputs).items()])

print('')
print('====================================================================================================')
print('== 문제 362. 구매여부 데이터에 대한 분할 후 엔트로피를 출력하는 코드를 수행하시오')
print('====================================================================================================')
import math
import csv
import time

# dataset = [({'cust_name':'SCOTT', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
#            ({'cust_name':'SMITH', 'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True),
#            ({'cust_name':'ALLEN', 'card_yn':'N', 'review_yn':'N', 'before_buy_yn':'Y'}, False),
#            ({'cust_name':'JONES', 'card_yn':'Y', 'review_yn':'N', 'before_buy_yn':'N'}, True),
#            ({'cust_name':'WARD',  'card_yn':'Y', 'review_yn':'Y', 'before_buy_yn':'Y'}, True)]

dataset = []

def returnCsvData(filename):
    file = open('D:\\KYH\\02.PYTHON\\data\\'+filename, 'r', encoding='utf-8')
    return csv.DictReader(file)

def entropy(data):
    tot = sum([sum(d) for d in data.values()])
    return sum((sum(d) / tot) *
               (-((d[0] / sum(d)) * math.log2(d[0] / sum(d)) if d[0] != 0 else 0)
                - ((d[1] / sum(d)) * math.log2(d[1] / sum(d)) if d[1] != 0 else 0)) for d in data.values())

def setting_data(dataset):
    groups = {}
    for input in dataset:
        for key, value in input[0].items():
            if groups.get(key) is None:
                groups[key] = {}
            if groups[key].get(value) is None:
                groups[key][value] = [0, 0]
            groups[key][value][input[1]] += 1
    return groups

start_time = time.time()
for data in returnCsvData('Cosmetics_decisionTree.csv'):
    *keys, base_key = data.keys()
    dataset.append(({key: data[key] for key in keys}, False if data[base_key] == '0' else True))

print([(key, entropy(value)) for key, value in setting_data(dataset).items()])
end_time = time.time()
print(end_time-start_time)

import numpy.linalg as lin
lin.inv()


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

from math import radians,cos,sin,sqrt

def latitude_longitude_to_x_y(city):
    lat = radians(city[1])
    lon = radians(city[2])
    x = cos(lon)*cos(lat)*6371
    y = cos(lon)*sin(lat)*6371
    return x, y

def distance(city1,city2):
    city1_x, city1_y = latitude_longitude_to_x_y(city1)
    city2_x, city2_y = latitude_longitude_to_x_y(city2)
    x_distance = abs(city1_x - city2_x)
    y_distance = abs(city1_y - city2_y)
    distance = sqrt((x_distance ** 2) + (y_distance ** 2))
    return distance

def total_fittness(route):
    total_distance = 0
    for i in range(0, len(route)):
        this_city = route[i]
        next_city = None
        if i + 1 < len(route):
            next_city = route[i + 1]
        else:
            next_city = route[0]
        total_distance += distance(this_city, next_city)

    fitness_value = 1/total_distance
    return fitness_value

def simulated_annealing(route, temperature, temperature_factor, stop_temperature):
    current_route = route[:]
    current_value = total_fittness(current_route)
    i = 0
    while temperature > stop_temperature:

        temperature *= temperature_factor
        i += 1
        current_cost = 1/current_value

        city1, city2 = np.random.randint(0,len(route),size=2)
        current_route[city1], current_route[city2] = current_route[city2], current_route[city1]

        next_value = total_fittness(current_route)
        delta_E = next_value - current_value
        if delta_E >= 0:
            current_value = next_value
        else:
            x = np.random.uniform()
            if x < np.exp(delta_E / temperature):
                current_value = next_value
            else:
                current_route[city1], current_route[city2] = current_route[city2], current_route[city1]

    return [current_route, current_cost]

def plotting(cities,ax):
    for i in range(0, len(cities)):
        first_city = cities[i]
        ax.plot(cities[i][2],cities[i][1],'ro')
        second_city = None
        if i + 1 < len(cities):
            second_city = cities[i + 1]
        else:
            second_city = cities[0]

        xpoints = [first_city[2],second_city[2]]
        ypoints = [first_city[1],second_city[1]]
        ax.plot(xpoints, ypoints,'b')

if __name__ == '__main__':

    fields = ['Name','Latitude','Longitude']
    df = pd.read_csv('US_States.csv', usecols=fields, skiprows=[2, 9])

    num_of_cities = 49
    cities = df.values.tolist()
    cities = cities[0:num_of_cities]

    # simulated_annealing
    result = simulated_annealing(route=cities, temperature=400, temperature_factor=0.99999, stop_temperature=1e-16)

    print('---------------Best Result---------------')
    print('Route:', '\n', np.array(result[0]), '\n')
    print('Distance:','\n', result[1], '\n')

    # creating csv file for gps apps
    result[0].append(result[0][0])
    df = pd.DataFrame(result[0], columns = ['Name','Latitude','Longitude'])
    tsp = df[["Latitude", "Longitude"]]
    tsp.to_csv('US_TSP_Solution.csv', index=False)

    # plot the result
    fig=plt.figure(figsize=(10,5))
    ax1=fig.add_subplot(121)
    ax2=fig.add_subplot(122)
    plotting(cities,ax1)
    plotting(result[0],ax2)
    plt.show()


import numpy as np
import matplotlib.pyplot as plt
from sympy import *
import time
import csv
import pandas as pd

def csv_to_array(filename):
    df = pd.read_csv(filename)

    return df['X'], df['Y']

def get_var(var):

    return var

class interpolator:
    def __init__(self, var, X, Y):
        self.var = symbols(var)
        self.xmin = min(X)
        self.xmax = max(X)
        self.n = len(X)
        self.X = X
        self.Y = Y

        print(f'Attributes:\nvar = {self.var} | xmin = {self.xmin} |  xmax = {self.xmax} | n = {self.n}\n')
        print(f'X:\n{self.X}\n')
        print(f'Y:\n{self.Y}')

    def calc(self, steps=False):
        L = 0  # initialise L = 0
        print("\ncalculating...\n")
        for j in range(self.n):  # summation operator

            if steps is True:
                print("---------- j=", j, " ----------", sep="")
            else:
                pass

            product = 1  # reset product for each next j-th iteration
            for m in range(self.n):  # product operator
                if m == j:
                    continue  # skip to next j+1-th iteration if true
                product = product * (self.var - self.X[m]) / (self.X[j] - self.X[m])

                if steps is True:
                    print("m=", m, ": ", product, sep="")
                else:
                    pass

            if steps is True:
                print("j=", j, ": ", product * self.Y[j], sep="")
                print("j=", j, " (simplified): ", expand(product * self.Y[j]), sep="")
            else:
                pass

            L = L + self.Y[j] * product

        func = expand(L)

        output = {
            "final_expression": L,
            "simplified_expression": func,
        }

        return output['simplified_expression']

    def reformat(self, eq, prec):
        reformated_eq = eq.as_coefficients_dict()

        latex_var = f'${self.var}$'

        latex_eq = f'$f({latex_var}) = '
        iteration = 0
        for k, v in reversed(reformated_eq.items()):
            iteration += 1
            k = str(k).replace("**", "^")
            if v > 1 and v < 1:
                v = round(v, prec)
            if iteration > 1 and v > 0:
                v = f'+{v}'
            latex_eq += f'{v}{k}'
        latex_eq += '$'

        return latex_eq, latex_var

    def plotter(self, eq, latex_eq, latex_var):
        # plotting original data
        X_cont = np.linspace(self.xmin, self.xmax, self.n*10)
        Y_cont = np.zeros(len(X_cont))

        for i in range(len(Y_cont)):
            Y_cont[i] = eq.subs({self.var:X_cont[i]})

        plt.style.use('seaborn-whitegrid')
        p_points = plt.plot(self.X, self.Y, marker='o',linestyle=' ')
        p_cont = plt.plot(X_cont, Y_cont, marker='',linestyle='-')
        plt.xlabel(latex_var)
        plt.ylabel(latex_eq)
        plt.savefig('static/plot.png')

    def to_file(self, eq):
        f = open("output.txt", "w")
        f.write(str(eq))
        f.close

def main():
    t_start = time.time()

    X, Y = csv_to_array("xy.csv")
    var = get_var('x')

    poly = interpolator(var, X, Y)

    output = poly.calc()
    pprint(output)

    latex_eq, latex_var = poly.reformat(output, 2)

    poly.to_file(output)
    poly.plotter(output, latex_eq, latex_var)

    t_end = time.time()

    dt = t_end - t_start
    print(f'\ntime taken: {dt:.2f}s')

if __name__ == "__main__":
    main()


import numpy as np

WIDTH = HEIGHT = 4
np.random.seed(0)
A = np.round(np.random.uniform(0, 10, (HEIGHT, WIDTH)), 1)
b = np.round(np.random.uniform(0, 10, (HEIGHT, 1)), 1)

def print_matrix(matrix, title):
    print(title)
    for line in matrix:
        print('     '.join(map(str, line)))
    print()

def lu_decomposition(matrix):
    # Get the number of rows
    n = matrix.shape[0]

    tmp_L = np.eye(n, dtype=np.double)
    tmp_U = matrix.copy()
    for i in range(n):
        # get factors of each individual row in tmp_U
        factors = tmp_U[i + 1:, i] / tmp_U[i, i]
        # assign factors to corresponding elements in tmp_L
        tmp_L[i + 1:, i] = factors
        # subtract factor * tmp_U[i] from corresponding element in
        # tmp_U, which is equivalent of doing row subtraction in linalg
        tmp_U[i + 1:] -= factors[:, np.newaxis] * tmp_U[i]
    return tmp_L, tmp_U

def forward_substitution(tmp_l, tmp_b):
    # get size of L matrix
    n = tmp_l.shape[0]

    # initialize empty array with size b
    tmp_y = np.zeros_like(tmp_b, dtype=np.double)

    # computing tmp_y values
    tmp_y[0] = tmp_b[0] / tmp_l[0, 0]
    for i in range(1, n):
        tmp_y[i] = (tmp_b[i] - np.dot(tmp_l[i, :i], tmp_y[:i])) / tmp_l[i, i]
    return tmp_y

def backward_substitution(tmp_u, tmp_y):
    # get size of U matrix
    n = tmp_u.shape[0]

    # initialize emtpy array with size y
    tmp_x = np.zeros_like(tmp_y, dtype=np.double)

    # computing tmp_x values
    tmp_x[-1] = tmp_y[-1] / tmp_u[-1, -1]
    for i in range(n - 2, -1, -1):
        tmp_x[i] = (tmp_y[i] - np.dot(tmp_u[i, i + 1:], tmp_x[i + 1:])) / tmp_u[i, i]
    return tmp_x

def solve_lin_system_with_lu(mat, x):
    TMP_L, TMP_U = lu_decomposition(mat)
    y = forward_substitution(TMP_L, x)
    return backward_substitution(TMP_U, y)

def determinant_of_matrix(mat):
    TMP_L, TMP_U = lu_decomposition(mat)
    return np.prod(np.diag(TMP_U))

def inverse_of_matrix(mat):
    v = np.identity(len(mat))
    TMP_L, TMP_U = lu_decomposition(mat)
    TMP_L_inverse = np.transpose(np.array([forward_substitution(TMP_L, v[i]) for i in range(len(mat))]))
    TMP_U_inverse = np.transpose(np.array([backward_substitution(TMP_U, v[i]) for i in range(len(mat))]))
    return np.matmul(TMP_U_inverse, TMP_L_inverse)

L, U = lu_decomposition(A)
print_matrix(L, "l")
print_matrix(U, "U")
print_matrix(A, "A")
print_matrix(b, "b=")

result = solve_lin_system_with_lu(A, b)
print_matrix(result, "x")

det = determinant_of_matrix(A)
print(f'det(A)={det}')

inverse = inverse_of_matrix(A)
print_matrix(inverse, "inverse")


"""
Project Euler: Problem 15
Lattice Paths
"""
import numpy as np

def lattice_paths(n, m):
    grid = np.ones([n,m], dtype=np.dtype('int64'))
    grid[0,0] = 0

    for i in range(1,n):
        for j in range(1,m):
            grid[i,j] = grid[i-1,j] + grid[i,j-1]

    #print(grid)

    return grid[-1,-1]

if __name__ == '__main__':
    print(lattice_paths(21, 21))


"""
error_metrics.py

Michael Bloem
03 February 2020

This file contains functions that compute error metrics.
"""

import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error

def mean_absolute_percentage_error(y_true, y_pred):
    # See: https://stats.stackexchange.com/questions/58391/mean-absolute-percentage-error-mape-in-scikit-learn
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

def median_absolute_percentage_error(y_true, y_pred):
    return np.median(np.abs((y_true - y_pred) / y_true)) * 100

def percent_within_n(y_true, y_pred, n=1):
    return np.mean(np.abs(y_true-y_pred) < n) * 100

def rmse(y_true, y_pred):
    return np.sqrt(mean_squared_error(y_true, y_pred))

# TODO: change code that calls this to dynamically pass in the quantile
# rather than relying on this default
def tilted_loss(y_true, y_pred, quantile=0.2):
    return np.mean(
        np.max(
            np.vstack(
                (
                    quantile*(y_true-y_pred).values,
                    (quantile-1)*(y_true-y_pred).values
                )
            ),
            axis=0
        )
    )

def fraction_less_than_actual(y_true, y_pred):
    return np.mean(y_pred < y_true)

def madres(y_true, y_pred):
    return np.median(np.abs((y_true-y_pred)-np.median(y_true-y_pred)))*1.4826

def medianres(y_true, y_pred):
    return np.median(y_true-y_pred)

METRIC_NAME_TO_FUNCTION_DICT = {
    'mean_absolute_error': mean_absolute_error,
    'mean_absolute_percentage_error': mean_absolute_percentage_error,
    'median_absolute_percentage_error': median_absolute_percentage_error,
    'percent_within_n': percent_within_n,
    'rmse': rmse,
    'tilted_loss': tilted_loss,
    'fraction_less_than_actual': fraction_less_than_actual,
    'MAD_residual':madres,
    'median_residual':medianres,
}


'''
Find the area between two curves.
'''

import math
from sympy import Integral, Symbol, sympify
from sympy.core.sympify import SympifyError
from collections import namedtuple

def area_curves( f1, f2, lower, upper ):
	x = Symbol( 'x' )
	return Integral( f1x - f2x, ( x, lower, upper ) ).doit()

if __name__ == '__main__':

	try:
		f1 = sympify( input( 'Enter a function in one variable (upper): ') )
		f2 = sympify( input( 'Enter a function in one variable (lower): ') )
		lower = float( input( 'Enter the lower bound: ' )	)
		upper = float( input( 'Enter the upper bound: ' ) )
	except SympifyError:
		print( 'Error parsing the function' )
	else:
		area = area_curves( f1, f2, lower, upper )
		print( 'The area between the curves formed by in intersection of {0} and {1} bewtwen {2} and {3} is {4}'.format( f1, f2, lower, upper, area ) )


import scipy as sp
import numpy as np
from numpy.polynomial import polynomial as P
import math

def intTrapecio( f, a, b, n ):
     if(n==1):
          resultado = (f(b) + f(a))*(b-a)/2
     else:
          h = (b-a)/n
          x=[ a+h*i for i in range(n+1)]
          resultado = ( h *( ( f(x[0]) + f(x[n]) )/2 + sum([f(x[i+1]) for i in range(n-1)]) ) )

     return resultado

#Rk = R_{k,j-1} RK1 = R_{k-1,j-1}
def rombergParcial( Rk, Rk1, j ):
     return Rk + (1/(4**j-1))*(Rk - Rk1)

def romberg(f, a, b, k):
      R=[]
      R.append( intTrapecio( f, a, b, 1 ) )
      for i in range(k):
           R.append( intTrapecio( f, a, b, 2**(i+1) ) )

      for i in range(k):
           for j in range(k-i):
               R[j]=rombergParcial( R[j+1], R[j], i+1 )

      return R[0]

f = lambda x: 3*x/(x**2 - 4)
a=1
b=1.8
cont = 0
diff = 1

print("Funcion x^3*e^(-x)\n")
while diff >= 10**(-6):
    Rk = romberg(f, a, b, cont)
    Rk1 = romberg(f, a, b, cont+1)
    print("R{0},{0} =".format(cont), Rk )
    print("R{0},{0} =".format(cont+1), Rk1 )
    diff = abs(Rk-Rk1)
    print("|R{0},{0}-R{1},{1}| =".format(cont, cont+1), diff)
    print("\n")
    cont = cont+1

f = lambda x: math.cos(x)*math.exp(3*x)
a=0
b= math.pi/4
cont = 0
diff = 1

print("Funcion cos(x)e^(3x)\n")
while diff >= 10**(-6):
    Rk = romberg(f, a, b, cont)
    Rk1 = romberg(f, a, b, cont+1)
    print("R{0},{0} =".format(cont), Rk )
    print("R{0},{0} =".format(cont+1), Rk1 )
    diff = abs(Rk-Rk1)
    print("|R{0},{0}-R{1},{1}| =".format(cont, cont+1), diff)
    print("\n")
    cont = cont+1


import numpy as np

class ActivationFunction:

    """All functions are defined in this way:

    Args:
        x (np.ndarray): input vector
        derivative (boolean): True -> apply the derivative, False -> apply the function

    Returns:
        np.ndarray: vector after applying activation function
    """

    @staticmethod
    def identity(x, derivative = False):
        if derivative:
            return 1.
        else:
            return x

    @staticmethod
    def sigmoid(x, derivative = False):
        if derivative:
            _f_x = ActivationFunction.sigmoid(x)
            return _f_x * (1 - _f_x)
        else:
            return 1. / (1. + np.exp(-x))

    @staticmethod
    def tanh(x, derivative = False):
        if derivative:
            _f_x = ActivationFunction.tanh(x)
            return 1 - (_f_x * _f_x)
        else:
            return np.tanh(x)

    @staticmethod
    def relu(x, derivative = False):
        if derivative:
            return np.where(x > 0, 1, 0)
        else:
            return np.maximum(x, 0)

    @staticmethod
    def leaky_relu(x, alpha = 0.01, derivative = False):
        if derivative:
            return np.where(x >= 0, 1, alpha)
        else:
            return np.maximum(x, alpha * x)

    @staticmethod
    def init_act_function(name):
        if name == "identity":
            return ActivationFunction.identity
        elif name == "sigmoid":
            return ActivationFunction.sigmoid
        elif name == "tanh":
            return ActivationFunction.tanh
        elif name == "relu":
            return ActivationFunction.relu
        elif name == "leaky_relu":
            return ActivationFunction.leaky_relu
        else:
            raise NameError(name+ " is not recognized! Check for correct names and possible activation functions in init_act_function!")


#!/usr/bin/env python

from sympy import Symbol
from sympy.interactive.printing import init_printing
from sympy.matrices import Matrix, eye, zeros, ones, diag, GramSchmidt

''' Source
    https://docs.sympy.org/latest/modules/matrices/matrices.html
'''
x = Symbol('x')

identity_matrix = eye(3)
M =  identity_matrix * x

print(M)

print(M.subs(x, 4))
print(M.subs(x, 4).det())

M = Matrix(( [1, 2, 3], [3, 6, 2], [2, 0, 1] ))
print(M.transpose())
print(M.inv())


import numpy as np
from matplotlib import pyplot as plt
from math import log

def user_ln(x,n): #return ln(1+x)
    ans=0
    temp=x
    sign=1
    a=n
    n=n+1
    for i in range(a):
        i=i+1
        ans=ans+(sign*x/i)
        sign=sign*-1
        x=x*temp
    return ans

def rel_er(a,t):
    x=abs(a-t)
    x=x*100/t
    return x

#for ques 1
x=eval(input('Enter the value of x:'))
n=eval(input('Enter the iteration number:'))
print('the value of log(1+x) is',user_ln(x,n))

#for ques 2
plt.figure(1)
x=np.arange(-1,1,.1)
y=np.log(1+x)
plt.plot(x,y,label='Built in ln(1+x)',color='black',linewidth=3)
plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('this is built in ln(1+x)')

#for ques 3
plt.figure(2)
x=np.arange(-1,1,.1)
y=np.log(1+x)
y1=user_ln(x,1)
y2=user_ln(x,3)
y3=user_ln(x,5)
y4=user_ln(x,20)
y5=user_ln(x,50)

plt.plot(x,y,label='Built in ln(1+x)',color='black',linewidth=5)
plt.plot(x,y1,label='ln(1+x) with term 1',linewidth=3)
plt.plot(x,y2,label='ln(1+x) with term 3',linewidth=3)
plt.plot(x,y3,label='ln(1+x) with term 5',linewidth=3)
plt.plot(x,y4,label='ln(1+x) with term 20',linewidth=3)
plt.plot(x,y5,label='ln(1+x) with term 50',linewidth=3)

plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('this is ln(1+x)')

#for ques 4
plt.figure(3)
tr=log(1.5)
y=np.arange(1,50,1)
z=[]
tr=log(1.5)
for j in range(1,50):
    z.append(rel_er(user_ln(.5,j),tr))

plt.plot(y,z,label='Relative error',linewidth=3)

plt.legend()
plt.grid()
plt.xlabel('X Label')
plt.ylabel('Y label')
plt.title('Relative error')

offline 1(Taylor series, false position and sacant method)/1.py


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 18 09:58:26 2019

@author: amandaash
"""
import numpy as np
import matplotlib.pyplot as plt
import scipy.integrate as integral

def integrate(function, x0, xf, dx, right = True, left = False):
    x = np.arange(x0,xf,dx)
    y = function(x)
    if right == True:
        for height in y[1:]:
            summation = np.sum(y[1:])
            return dx*summation
    if left == True:
        for height in y[:-1]:
            summation = np.sum(y[:-1])
            return dx*summation
def x2(x):
    y = x**2
    return y
print('numeric integrats of $x^2$')
numeric_integral = integrate(x2, 0, 10, 0.001, True, False)
print('analytic solution = {0}'.format((10**3)/3))
print('numerical solution = {0}'.format(numeric_integral))
trap = np.trapz((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('numpy trapezoidal solution = {0}'.format(trap))

simpson = integral.simps((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('scipy simpsons rule = {0}'.format(simpson))
scipy_trap = integral.trapz((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
print('scipy trapezoidal rile = {0}'.format(scipy_trap))
#romberg = integral.romb((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
#print('scipy romberg = {0}'.format(romberg))
gaussian = integral.fixed_quad(x2, 0, 10)
print('scipy fixed quadrature = {0}'.format(gaussian[0]))

def f1(x):
    y = np.sin(100*x)
    return y
print('numeric integration of sin(100*x)')
numeric_integral = integrate(f1, 0, 2*np.pi, 0.0001, True, False)
print('analytic solution = {0}'.format(0))
print('numerical solution = {0}'.format(numeric_integral))
trap = np.trapz(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('numpy trapezoidal solution = {0}'.format(trap))

simpson = integral.simps(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('scipy simpsons rule = {0}'.format(simpson))
scipy_trap = integral.trapz(f1(np.arange(0,2*np.pi,0.0001)), np.arange(0,2*np.pi,0.0001))
print('scipy trapezoidal rile = {0}'.format(scipy_trap))
#romberg = integral.romb((np.arange(0,10,0.001))**2, np.arange(0,10,0.001))
#print('scipy romberg = {0}'.format(romberg))
gaussian = integral.fixed_quad(f1, 0, 2*np.pi)
print('scipy fixed quadrature = {0}'.format(gaussian[0]))

plt.plot(np.arange(0,2*np.pi,0.0001), f1(np.arange(0,2*np.pi,0.0001)), '-')
plt.show()


"""
@author: Rowan Parker
@date: 10/24/2021
"""
import numpy as np

def incremental_search(f, a, b, dx):
    """
    Searches the interval (a,b) in increments dx for
    the bounds (x1,x2) of the smallest root of f(x)

    Parameters
    ----------
        f  - function
        a  - left bound of the interval, (a, b)
        b  - right bound of the interval, (a, b)
        dx - value by which to increment

    Returns
    -------
    x1 = x2 = None if no roots were detected
    """
    x1 = a
    x2 = x1 + dx

    f1 = f(x1)
    f2 = f(x2)

    while (f1*f2) > 0:
        if x1 >= b:
            return None, None

        x1 = x2
        x2 = x1 + dx

        f1 = f2
        f2 = f(x2)

    return x1, x2

def newton(f, f_prime, x0, tol):
    """
    Find the root of f(x) = 0 using the Newton-Raphson method recursively.

    Parameters
    ----------
        f       - function
        f_prime - derivative of f
        x0      - starting value
        tol     - tolerance

    Returns
    -------
    root of f(x)
    """
    if abs(f(x0)) < tol:
        return x0
    else:
        x1 = x0 - (f(x0)/f_prime(x0))
        print("x0 = ", x0, "f(x0) = ", f(x0), "f'(x0) =", f_prime(x0), "x1 = ", x1)
        return newton(f, f_prime, x1, tol)

def rbisection(f, a, b, tol):
    """
    Find the root of f(x) = 0 by bisection recursively.
    The root must be in the interval (a, b).

    Parameters
    ----------
        f   - function
        a   - left bound of the interval, (a, b)
        b   - right bound of the interval, (a, b)
        tol - tolerance

    Returns
    -------
    root of f(x)
    """
    if np.sign(f(a)) == np.sign(f(b)):
        raise Exception("There is either no root or multiple roots between "
                        "{} and {}".format(a, b))

    # midpoint
    m = (a + b) / 2

    if np.abs(f(m)) < tol:
        return m
    elif np.sign(f(a)) == np.sign(f(m)):
        return bisection(f, m, b, tol)
    elif np.sign(f(b)) == np.sign(f(m)):
        return bisection(f, a, m, tol)

def bisection(f, a, b, tol):
    """
    Find the root of f(x) = 0 by bisection without recursion.
    The root must be in the interval (a, b).

    Parameters
    ----------
        f   - function
        a   - left bound of the interval, (a, b)
        b   - right bound of the interval, (a, b)
        tol - tolerance

    Returns
    -------
    root of f(x)
    """
    x1 = a
    x2 = b
    delta_x = np.abs(x2 - x1)
    steps = int(np.ceil(np.log(delta_x / tol) / np.log(2.0)))

    if np.sign(f(a)) == np.sign(f(b)) or f(a)*f(b) >= 0:
        print("There is either no root or multiple roots between "
              "{} and {}".format(a, b))

    for i in range(steps):
        m = (x1 + x2) / 2

        if f(x1)*f(m) < 0:
            x2 = m
        elif f(x2)*f(m) < 0:
            x1 = m
        elif f(m) == 0:
            return m

    return (x1 + x2) / 2


import numpy
import itertools

def fit_polynomial(image, mask=None, degree=2, poly_args=None):
    """Return an array containing the values of a polynomial fit to the input.

    Parameters:
        image: 2D array.
        mask: if specified, use only the values in the image at points where
            the mask is true
        degree: degree of the fit polynomial in x and y
        poly_args: if not None, must have been generated by get_poly_args(). Useful
            when multiple images of the same shape are to be fit to a polynomial
            so the poly_args array does not need to be recalculated.
    """
    image = numpy.asarray(image)
    shape = image.shape
    if poly_args is None:
        poly_args = get_poly_args(shape, degree)
    if mask is not None:
        mask = numpy.asarray(mask, dtype=bool)
        if not numpy.any(mask):
            return numpy.zeros_like(image)
        a = poly_args[mask, :]
        b = image[mask]
    else:
        a = poly_args.reshape((shape[0]*shape[1], poly_args.shape[-1]))
        b = image.ravel()
    # solve ax = b, where a contains the polynomial expansion of the spatial positions
    # of the image samples, and b contains the image samples
    coefficients, residues, rank, singval = numpy.linalg.lstsq(a, b)
    output = (poly_args * coefficients).sum(axis=-1)
    return output

def subtract_background(image, mask=None, degree=2):
    """Fit the input to a low-order polynomial to approximate the background
    and then subtract that from the image. See fit_polynomial() for further
    details.

    Note: effectively a high-pass filter.
    """
    background = fit_polynomial(image, mask, degree)
    sub = (image - background).clip(0, numpy.inf)
    return sub.astype(image.dtype)

def get_poly_args(shape, degree):
    """Return an array containing the arguments for a polynomial in x and y defined
    over an array of the given shape.

    Let 'binomial(degree)' refer to the number of parameters of a polynomial function
    of the given degree. Then the output shape = (shape[0], shape[1], binomial(degree))

    Example: let degree = 2, and let x, y = numpy.indices(shape)
       result = get_poly_args(shape, degree)
       result[:,:,0] == 1
       result[:,:,1] == x
       result[:,:,2] == y
       result[:,:,3] == x*x
       result[:,:,4] == x*y
       result[:,:,5] == y*y
    """
    x, y = numpy.indices(shape)
    poly_args = [numpy.ones(shape)]
    for i in range(1, degree+1):
        for v in itertools.combinations_with_replacement([x, y], i):
            poly_args.append(numpy.prod(v, axis=0))
    poly_args = numpy.array(poly_args) # shape = binomial(degree), shape[0], shape[1]
    poly_args = poly_args.transpose((1,2,0)) # shape = shape[0], shape[1], binomial(degree)
    return poly_args


import numpy as np

def jacobi(iteration, coefficient_matrix, right_hand_side_vector, precision=1e-20):
    """Algorithm can be found on this wikipedia page: https://en.wikipedia.org/wiki/Jacobi_method"""
    solution = np.array([0] * len(right_hand_side_vector))
    for iteration_step in range(iteration):
        iteration_solution = np.array([0] * len(solution))
        for i in range(len(coefficient_matrix[0])):
            s1 = np.dot(np.array(coefficient_matrix)[i, :i], solution[:i])
            s2 = np.dot(np.array(coefficient_matrix)[i, i + 1:], solution[i + 1:])
            numerator = np.array(right_hand_side_vector)[i] - s1 - s2
            denominator = np.array(coefficient_matrix)[i, i]
            iteration_solution[i] = numerator / denominator
        if np.allclose(solution, iteration_solution, rtol=precision) is True:
            break
        solution = iteration_solution
    return solution

if __name__ == '__main__':
    given_matrix = eval(input("enter A matrix like: [[1, 1, 1], [2, 2, 2], [3, 3, 3]\n"))
    vector = eval(input("enter b vector like: [1, 1, 1]\n"))
    iteration_count = int(input("enter the count of iterations: "))
    print("solution is: ", jacobi(iteration_count, given_matrix, vector))


import math
import time

import numpy
import scipy.special

log_beta = scipy.special.betaln

def posterior_probability_treatment_is_better_slow(
    treatment_conversions, treatment_nonconversions, baseline_conversions, baseline_nonconversions
):
    '''
    http://www.evanmiller.org/bayesian-ab-testing.html
    '''
    treatment_alpha = treatment_conversions + 1
    treatment_beta = treatment_nonconversions + 1
    baseline_alpha = baseline_conversions + 1
    baseline_beta = baseline_nonconversions + 1

    sum_result = 0
    for i in xrange(baseline_alpha):
        sum_result += math.exp(
            log_beta(treatment_alpha + i, treatment_beta + baseline_beta)
            - math.log(baseline_beta + i)
            - log_beta(1 + i, baseline_beta)
            - log_beta(treatment_alpha, treatment_beta)
        )

    return 1 - sum_result

def posterior_probability_treatment_is_better_fast(
    treatment_conversions, treatment_nonconversions, baseline_conversions, baseline_nonconversions
):
    '''
    http://www.evanmiller.org/bayesian-ab-testing.html
    '''
    treatment_alpha = treatment_conversions + 1
    treatment_beta = treatment_nonconversions + 1
    baseline_alpha = baseline_conversions + 1
    baseline_beta = baseline_nonconversions + 1

    i_values = numpy.arange(baseline_alpha)
    sum_result = numpy.sum(
        numpy.exp(
            log_beta(baseline_alpha + i_values, treatment_beta + baseline_beta)
            - log_beta(1 + i_values, baseline_beta)
            - log_beta(treatment_alpha, treatment_beta)
        ) / (baseline_beta + i_values)
    )

    return 1 - sum_result

def time_function_us(function, sample_size, repetitions=1000):
    arguments = (
        int(.1 * sample_size),
        int(.9 * sample_size),
        int(.2 * sample_size),
        int(.8 * sample_size),
    )

    # warm up
    for _ in xrange(10):
        function(*arguments)

    total_time = 0
    for _ in xrange(repetitions):
        start = time.time()
        function(*arguments)
        total_time += time.time() - start
    return 1000 * total_time / repetitions

def main():
    FORMAT_STRING = '{:>10} {:>10} {:>10} {:>10}'
    print FORMAT_STRING.format('Samples', 'Slow', 'Fast', 'Ratio')
    for sample_size in (10, 100, 1000, 10000, 100000):
        slow_us = time_function_us(posterior_probability_treatment_is_better_slow, sample_size)
        fast_us = time_function_us(posterior_probability_treatment_is_better_fast, sample_size)
        print FORMAT_STRING.format(
            sample_size,
            '{:.3f}us'.format(slow_us),
            '{:.3f}us'.format(fast_us),
            '{:.1f}x'.format(slow_us / fast_us),
        )

if __name__ == '__main__':
    main()


import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
data=pd.read_csv("Uni_linear.txt", header=None)
data.head()
data.describe()
plt.scatter(data[0],data[1])
plt.xticks(np.arange(5,30,step=5))
plt.yticks(np.arange(-5,30,step=5))
plt.xlabel("Population of City (10,000s)")
plt.ylabel("Profit ($10,000")
plt.title("Profit Vs Population")

def computeCost(X, y, theta):
    """
    Take in a numpy array X,y, theta and generate the cost function     of using theta as parameter in a linear regression model
    """
    m = len(y)
    predictions = X.dot(theta)
    square_err = (predictions - y) ** 2

    return 1 / (2 * m) * np.sum(square_err)
data_n=data.values
m=len(data_n[:,-1])
X=np.append(np.ones((m,1)),data_n[:,0].reshape(m,1),axis=1)
y=data_n[:,1].reshape(m,1)
theta=np.zeros((2,1))
computeCost(X,y,theta)

def gradientDescent(X, y, theta, alpha, num_iters):
    """
    Take in numpy array X, y and theta and update theta by taking   num_iters gradient steps
    with learning rate of alpha

    return theta and the list of the cost of theta during each  iteration
    """

    m = len(y)
    J_history = []

    for i in range(num_iters):
        predictions = X.dot(theta)
        error = np.dot(X.transpose(), (predictions - y))
        descent = alpha * 1 / m * error
        theta -= descent
        J_history.append(computeCost(X, y, theta))

    return theta, J_history

theta, J_history = gradientDescent(X, y, theta, 0.01, 1500)
print("h(x) =" + str(round(theta[0, 0], 2)) + " + " + str(round(theta[1, 0], 2)) + "x1")
from mpl_toolkits.mplot3d import Axes3D
#Generating values for theta0, theta1 and the resulting cost value
theta0_vals=np.linspace(-10,10,100)
theta1_vals=np.linspace(-1,4,100)
J_vals=np.zeros((len(theta0_vals),len(theta1_vals)))
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t=np.array([theta0_vals[i],theta1_vals[j]])
        J_vals[i,j]=computeCost(X,y,t)
#Generating the surface plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
surf=ax.plot_surface(theta0_vals,theta1_vals,J_vals,cmap="coolwarm")
fig.colorbar(surf, shrink=0.5, aspect=5)
ax.set_xlabel("$\Theta_0$")
ax.set_ylabel("$\Theta_1$")
ax.set_zlabel("$J(\Theta)$")
#rotate for better angle
ax.view_init(30,120)
plt.plot(J_history)
plt.xlabel("Iteration")
plt.ylabel("$J(\Theta)$")
plt.title("Cost function using Gradient Descent")
plt.scatter(data[0],data[1])
x_value=[x for x in range(25)]
y_value=[y*theta[1]+theta[0] for y in x_value]
plt.plot(x_value,y_value,color="r")
plt.xticks(np.arange(5,30,step=5))
plt.yticks(np.arange(-5,30,step=5))
plt.xlabel("Population of City (10,000s)")
plt.ylabel("Profit ($10,000")
plt.title("Profit vs Population")

def predict(x, theta):
    """
    Takes in numpy array of x and theta and return the predicted value of y based on theta
    """

    predictions = np.dot(theta.transpose(), x)

    return predictions[0]

predict1 = predict(np.array([1, 3.5]), theta) * 10000
print("For population = 35,000, we predict a profit of $" + str(round(predict1, 0)))
predict2=predict(np.array([1,7]),theta)*10000
print("For population = 70,000, we predict a profit of $"+str(round(predict2,0)))


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

def students_t(x, mu, prec, df):
    return gamma(df/2+1/2)/gamma(df/2) * np.sqrt(prec/np.pi/df) * (1+prec/df*(x-mu)**2)**(-df/2-1/2)

def normal(x, mu, sigma):
    return 1/np.sqrt(2*np.pi*sigma**2)*np.exp(-((x-mu)**2)/2/(sigma**2))

x = np.arange(-10, 10, 0.001)
# Student t
dfs = [0.1, 1, 10, 100, 1000000]
ys = []
for df in dfs:
    y = students_t(x, 0, 1, df)
    ys.append(y)

# Gaussian
y = normal(x, 0, 1)

# Plotting
for (i, df) in enumerate(dfs):
    plt.plot(x, ys[i], label="df={}".format(df))
plt.plot(x, y, label="Gaussian")
plt.legend()
plt.title("Student's t distribution")


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

# Constants
G = 6.67408 * 10 ** -11  # m^3 kg^-1 s^-2
M_Earth = 5.972 * 10 ** 24  # kg
# print(G * M_Earth)
R = 6378.137  # km

g = (G * M_Earth) / ((R * 1000) ** 2)  # m s^-2
# print(g)

m_stage_1 = 422000  # kg
m_s_1_propellant = 370000  # kg
m_dot_s_1 = 2312.5  # kg/s
ve_s_1 = 2943  # m/s
t_burn_s_1 = 162  # s

m_stage_2 = 128000  # kg
m_s_2_propellant = 108000  # kg
m_dot_s_2 = 270  # kg/s
ve_s_2 = 3433.5  # m/s
t_burn_s_2 = 397  # s

t_coast = 1000  # s

def stage_1(state_var_launch, t):
    h, v = state_var_launch

    thrust = m_dot_s_1 * ve_s_1

    M = m_stage_1 + m_stage_2
    m = t * m_dot_s_1

    thrust_acc = thrust / (M - m) / 1000
    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000 * np.sin(90)

    dvdt = thrust_acc + g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

def stage_2(state_var_s_2_ignition, t):
    h, v = state_var_s_2_ignition

    thrust = m_dot_s_2 * ve_s_2

    M = m_stage_2
    m = (t - 160) * m_dot_s_2

    thrust_acc = thrust / (M - m) / 1000
    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000

    dvdt = thrust_acc + g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

def coast_traj(state_var_c_dragon_separation, t):
    h, v = state_var_c_dragon_separation

    g = - (G * M_Earth) / ((h * 1000) ** 2) / 1000

    dvdt = g  # - (.3 * 0.25 * 200) / (M - m)

    return [v, dvdt]

# Launch
state_var_launch = [6378.1, 0.0]  # [km, m/s]

t_s_1 = np.linspace(0, t_burn_s_1)

stage_1 = odeint(stage_1, state_var_launch, t_s_1)

h_stage_1, v_stage_1 = stage_1.T
print(h_stage_1)

# Stage 2 Ignition
state_var_s_2_ignition = [h_stage_1[-1], v_stage_1[-1]]

t_s_2 = np.linspace(t_s_1[-1], t_s_1[-1] + t_burn_s_2)

stage_2 = odeint(stage_2, state_var_s_2_ignition, t_s_2)

h_stage_2, v_stage_2 = stage_2.T
print(h_stage_2)

# Crew Dragon Separation & ISS Approach
state_var_c_dragon_separation = [h_stage_2[-1], v_stage_2[-1]]

t_coast_traj = np.linspace(t_s_2[-1], t_s_2[-1] + t_coast)

coast_traj = odeint(coast_traj, state_var_c_dragon_separation, t_coast_traj)

h_coast_traj, v_coast_traj = coast_traj.T
print(h_coast_traj)

plt.figure()

# Altitude(t)
plt.subplot(3, 1, 1)
plt.plot(t_s_1, h_stage_1 - R)
plt.plot(t_s_2, h_stage_2 - R)
plt.plot(t_coast_traj, h_coast_traj - R)
plt.xlabel('time')
plt.ylabel('h(t)')

# Velocity(t)
plt.subplot(3, 1, 2)
plt.plot(t_s_1, v_stage_1 * 3600)
plt.plot(t_s_2, v_stage_2 * 3600)
plt.plot(t_coast_traj, v_coast_traj * 3600)
plt.xlabel('time')
plt.ylabel('v(t)')

# Mass(t)
plt.subplot(3, 1, 3)
plt.plot(t_s_1, 52000 + (m_s_1_propellant - t_s_1 * m_dot_s_1))
plt.plot(t_s_2, 20000 + m_s_2_propellant - (t_s_2 - 160) * m_dot_s_2)
plt.plot(t_coast_traj, 20000 + t_coast_traj * 0)
plt.xlabel('time')
plt.ylabel('m(t)')

plt.show()

'''
Falcon 9 v1.2 Full Thrust

Stage 1
- Thrust(Sea Level): 7,607 kN
- Thrust(Vacuum): 8,227 kN
- Specific Impulse: 282 s
- Dry Mass: 25,600 kg
- Propellant Mass: 395,700 kg

- Initial Mass: 421,300 kg
- Final Mass: 25,600 kg

- ISP = Ve / g
- Ve = ISP · g
- Ve = 282 · 9.807 = 2,765.574 m/s = 2.765574 km/s

Falcon9 v1.2 Stage 1 (7.75, 16.46)

- ∆V Stage 1 =  7.75 km/s = 27,900 km/h = 17,336.256 m/h
- Mass Ratio = 16.46

Stage 2
- Thrust: 934 kN
- Specific Impulse: 348 s
- Dry Mass: 3, 900 kg
- Propellant Mass: 92, 670 kg

- Initial Mass: 96, 570 kg
- Final Mass: 3, 900 kg

- ISP = Ve / g
- Ve = ISP · g
- Ve = 348 · 9.807 = 3, 412.836 m/s = 3.412836 km/s

Falcon9 v1.2 Stage 2 (10.95, 24.76)

- ∆V Stage 2 = 10.95 km/s = 39, 420 km/h = 24, 494.452 m/h
- Mass Ratio = 24.76

'''


#%%
# import libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#%%
# read data
path = 'ex1data1.txt'
data = pd.read_csv(path, header = None, names= ['Population', 'Profit'])

#%%
# show imported data details
# print('data = \n', data.head(10))
# print('**************************************')
# print('data.describe = \n', data.describe())
# print('**************************************')

#%%
# draw data
fig, ax = plt.subplots(figsize= (6,6))
plt.scatter(data['Population'], data['Profit'], marker= 'o', s= 12, label= 'Training data')
ax.set(title= 'Best Fit Line', xlabel= 'Population', ylabel= 'Profit')

#%%
# adding a new column called ones before the data
data.insert(0, 'Ones', 1)
# print('new data = \n', data.head(10))
# print('**************************************')

#%%
# separate X (training data) from y (target variable)
cols = data.shape[1]
X = data.iloc[:, 0 : cols-1]
y = data.iloc[:, cols-1 : cols]

# print('X data = \n', X.head(10))
# print('**************************************')
# print('y data = \n', y.head(10))
# print('**************************************')

#%%
# Convert data from data frames to numpy matrices
X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.array(np.zeros((cols-1, 1))))

# print('X \n', X)
print('X.shape = ', X.shape)
print('**************************************')
# print('y \n', y)
print('y.shape = ', y.shape)
print('**************************************')
# print('theta \n', theta)
print('theta.shape = ', theta.shape)
print('**************************************')

#%%
# Cost function
def computeCost(X, y, theta):
    J = np.sum(np.power(((X * theta) - y), 2)) / (2 * len(y))
    return float(J)

print('With theta = [0 : 0]\nCost computed =', computeCost(X, y, theta))
print('Expected cost value (approx) 32.07')
print('**************************************')
print('With theta = [-1 : 2]\nCost computed =', computeCost(X, y, np.array([[-1],[2]])))
print('Expected cost value (approx) 54.24')
print('**************************************')

#%%
# Running Gradient Descent
def gradientDescent(X, y, theta, alpha, num_iters):
    temp = np.matrix(np.zeros(theta.shape))
    J_history = np.zeros((num_iters, 1))

    for i in range(num_iters):
        for j in range(len(theta)):
            temp[j, 0] = theta[j, 0] - ((alpha / len(y)) * np.sum(np.multiply(((X * theta) - y), X[:, j])))

        theta = temp.copy()
        J_history[i] = computeCost(X, y, theta)

    return theta, J_history

iterations = 1500
alpha = 0.01

theta, J_history = gradientDescent(X, y, theta, alpha, iterations)

print('Theta found by gradient descent:\n', theta[0,0], '\n ', theta[1,0])
print('Expected theta values (approx)\n -3.6303\n  1.1664')
print('**************************************')
# print('J_history\n', J_history)
# print('**************************************')

#%%
# Plot the linear fit
plt.plot(X[:, 1], X*theta, 'r-', linewidth= 1.5, label= 'Linear regression')
plt.legend()

#%%
# Predict values for population sizes of 35,000 and 70,000
predict1 = [1., 3.5] * theta
print('For population = 35,000, we predict a profit of %f' %(predict1 * 10000))
predict2 = [1., 7.] * theta
print('For population = 70,000, we predict a profit of %f' %(predict2 * 10000))
print('**************************************')

#%%
# draw error graph
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(np.arange(1, iterations+1), J_history, 'r', linewidth=2)
ax.set(xlabel= 'No. of Iterations', ylabel= 'Cost', title= 'Error vs. Training Epoch')

#%%
# Visualizing J(theta_0, theta_1)

# Grid over which we will calculate J
theta0_vals = np.linspace(-10, 10, 100)
theta1_vals = np.linspace(-1, 4, 100)

# initialize J_vals to a matrix of 0's
J_vals = np.zeros((len(theta0_vals), len(theta1_vals)))

# Fill out J_vals
for i in range(len(theta0_vals)):
    for j in range(len(theta1_vals)):
        t = np.array([[theta0_vals[i]], [theta1_vals[j]]])
        J_vals[i,j] = computeCost(X, y, t)

J_vals = J_vals.T
theta0_vals, theta1_vals = np.meshgrid(theta0_vals, theta1_vals)
fig, ax = plt.subplots(figsize= (7,7), subplot_kw={'projection' : '3d'})
ax.plot_surface(theta0_vals, theta1_vals, J_vals, cmap= 'jet')
# ax.view_init(0,90)
ax.set(xlabel= '$\mathrm{\\theta_{0}}$', ylabel= '$\mathrm{\\theta_{1}}$', zlabel= 'Cost Function  $\mathrm{J(\\theta)}$')
plt.plot(theta[0,0], theta[1,0], 'rx', markersize=10, linewidth=2)


# =============================================================================
# Funzioni varie su Lagrange
# =============================================================================

import numpy as np
from numpy import matlib

# For compunting the Lebesgue functions
def l_j(j, x, nodes):
    N = np.shape(nodes)[0]
    product = 1.0
    for i in range(0, N):
        if not i==j:
            product *= (x - nodes[i]) / (nodes[j] - nodes[i])
    return product

def lS_j(j, x, nodes,S):
    N = np.shape(nodes)[0]
    product = 1.0
    for i in range(0, N):
        if not i==j:
            product *= (S(x) - S(nodes[i])) / (S(nodes[j]) - S(nodes[i]))
    return product

#def y(x,nodes):
#    N = np.shape(nodes)[0]
#    csum = 0
#    for j in range(0, N):
#        csum += np.abs(l_j(j, x, nodes))
#    return csum

def drange(start, stop, step):
    r = start
    while r < stop:
        yield r
        r += step

#def d(nodes,interval):
#    greatest = None
#    for x in interval:
#        current = y(x,nodes)
#        if greatest is None or current > greatest:
#            greatest = current
#    return greatest

def lebesgue(x,xx):
    N = np.shape(x)[0]
    lsum = np.zeros(np.shape(xx)[0])
    for i in range(0, N):
        lsum += np.abs(l_j(i,xx, x))
    return lsum

def lagrange_interp(xx,x,y):
    n=len(x); m=len(xx)
    L=np.zeros((m,n))
    for k in range(n):
        x_k = np.concatenate((x[:k],x[k+1:]))
        L[:,k] = np.prod(np.matlib.repmat(xx.reshape(m,1),1,n-1) - np.matlib.repmat(x_k,m,1) , axis=1) / np.prod(x[k]-x_k)
    return np.dot(L,y)

def ChebyL(n):
    #compute the n Chebyshev-Lobatto points in [-1,1]
    return -np.cos(np.pi*np.arange(0.,n)/(n-1))

def Cheby(n):
    #compute the n Chebyshev-Lobatto points in [-1,1]
    return -np.cos(np.pi*(2*np.arange(0.,n)-1)/(2*n))


import random
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
import math
import numpy as np
from statistics import mean
import sys

## Defining the ranges of the values used by us
total_points = 10**6
dim_range = [1,2,4,8,16,32,64]
batch = 100

## Generating the random points. Indexed by math.log2(dim)
random_all = []
for dim in dim_range:
	random_dim = []
	for i in range(total_points):
		random_point = [random.random() for _ in range(dim)]
		random_dim.append(random_point)
	random_all.append(random_dim)

## Generating the values of all the distances for all the dimensions, after taking the samples
d1_list = []
d2_list = []
dinfi_list = []
for dim in dim_range:
	print(int(math.log2(dim)))
	list_all = random_all[int(math.log2(dim))]
	list_sampled = random.sample(list_all, batch)

	d1_list_individual=[]
	d2_list_individual=[]
	dinfi_list_individual=[]

	for i in range(batch):

		temp_list_d1 = []
		temp_list_d2 = []
		temp_list_dinfi = []

		for j in range(total_points):

			point_1 = np.array(list_sampled[i])
			point_2 = np.array(list_all[j])

			if np.array_equal(point_1,point_2):
				continue

			d1 = np.linalg.norm((point_1 - point_2), ord=1)
			d2 = np.linalg.norm((point_1 - point_2), ord=2)
			dinfi = np.linalg.norm((point_1 - point_2), ord=np.inf)

			temp_list_d1.append(d1)
			temp_list_d2.append(d2)
			temp_list_dinfi.append(dinfi)

		min_d1 = min(temp_list_d1)
		max_d1 = max(temp_list_d1)

		min_d2 = min(temp_list_d2)
		max_d2 = max(temp_list_d2)

		min_dinfi = min(temp_list_dinfi)
		max_dinfi = max(temp_list_dinfi)

		d1_list_individual.append(max_d1*1./min_d1)
		d2_list_individual.append(max_d2*1./min_d2)
		dinfi_list_individual.append(max_dinfi*1./min_dinfi)

	## Getting the minimum and maximum values in these lists
	d1_list.append(mean(d1_list_individual))
	d2_list.append(mean(d2_list_individual))
	dinfi_list.append(mean(dinfi_list_individual))

# Making the plots and showing/storing them
plt.plot(dim_range,d1_list,'r',dim_range,d2_list,'b', dim_range, dinfi_list, 'g')
plt.xlabel('Dimensions of the points')
plt.ylabel('Average ratio of max to min distance')
plt.legend(['L1 dist','L2 dist','Linf dist'])

if sys.argv[1]=='log':
	plt.yscale('log')
	plt.title('Plot of average ratio v/s dimensions (Logarithmic y-scale)')
	plt.savefig('q1_plot_log.png')
else:
	plt.title('Plot of average ratio v/s dimensions (Normal y-scale)')
	plt.savefig('q1_plot.png')


import pandas as pd
import numpy as np
import scipy.io
import random, math

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

def Plot2D(T, title, x, y, num_to_plot=40):
  # This method picks a bunch of random samples (images in your case)
  # to plot onto the chart:
  fig = plt.figure()
  ax = fig.add_subplot(111)
  ax.set_title(title)
  ax.set_xlabel('Component: {0}'.format(x))
  ax.set_ylabel('Component: {0}'.format(y))
  x_size = (max(T[:,x]) - min(T[:,x])) * 0.08
  y_size = (max(T[:,y]) - min(T[:,y])) * 0.08
  for i in range(num_to_plot):
    img_num = int(random.random() * num_images)
    x0, y0 = T[img_num,x]-x_size/2., T[img_num,y]-y_size/2.
    x1, y1 = T[img_num,x]+x_size/2., T[img_num,y]+y_size/2.
    img = df.iloc[img_num,:].values.reshape(num_pixels, num_pixels)
    ax.imshow(img, aspect='auto', cmap=plt.cm.gray, interpolation='nearest', zorder=100000, extent=(x0, x1, y0, y1))

  # It also plots the full scatter:
  ax.scatter(T[:,x],T[:,y], marker='.',alpha=0.7)

# A .MAT file is a .MATLAB file. The faces dataset could have came
# in through .png images, but we'll show you how to do that in
# anither lab. For now, you'll see how to import .mats:
mat = scipy.io.loadmat('/Users/szabolcs/dev/git/DAT210x/Module4/Datasets/face_data.mat')
df = pd.DataFrame(mat['images']).T
num_images, num_pixels = df.shape
num_pixels = int(math.sqrt(num_pixels))

# Rotate the pictures, so we don't have to crane our necks:
for i in range(num_images):
  df.loc[i,:] = df.loc[i,:].values.reshape(num_pixels, num_pixels).T.reshape(-1)

print(df.shape)
#
# TODO: Implement PCA here. Reduce the dataframe df down
# to THREE components. Once you've done that, call Plot2D.
#
# The format is: Plot2D(T, title, x, y, num_to_plot=40):
# T is your transformed data, NDArray.
# title is your chart title
# x is the principal component you want displayed on the x-axis, Can be 0 or 1
# y is the principal component you want displayed on the y-axis, Can be 1 or 2
#
from sklearn.decomposition import PCA
pca = PCA(n_components=3)
pca.fit(df)
df_pca = pca.transform(df)

Plot2D(df_pca, "PCA", 1, 2)

#
# TODO: Implement Isomap here. Reduce the dataframe df down
# to THREE components. Once you've done that, call Plot2D using
# the first two components.
#
from sklearn.manifold import Isomap
imap = Isomap(n_components=3, n_neighbors=8)
imap.fit(df)
df_imap = imap.transform(df)

Plot2D(df_imap, "Isomap", 1, 2)

#
# TODO: If you're up for a challenge, draw your dataframes in 3D
# Even if you're not, just do it anyway.
#
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.set_title("PCA 3D")
ax.set_xlabel("Comp1")
ax.set_ylabel("Comp2")
ax.set_zlabel("Comp3")
ax.scatter(df_pca[:, 0], df_pca[:, 1], df_pca[:, 2], c="r", marker=".")

fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.set_title("Isomap 3D")
ax.set_xlabel("Comp1")
ax.set_ylabel("Comp2")
ax.set_zlabel("Comp3")
ax.scatter(df_imap[:, 0], df_imap[:, 1], df_imap[:, 2], c="r", marker=".")

plt.show()


# Basic imports
import numpy as np

# ----------------------------Scale independent metrics------------------------

# The mean absolute percentage error (MAPE), is a measure of prediction
# accuracy of a forecasting method in statistics. The 'min_val' variable is
# used to fill zeros in test data, if any. Filling zero with a constant helps
# to avoid division by zero error
def mean_absolute_percentage_error(y_true, y_pred, min_val=0):
    y_true = y_true + min_val
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

# SMAPE is an alternative for MAPE when there are zeros in the testing data. It
# scales the absolute percentage by the sum of forecast and observed values
def s_mean_absolute_percentage_error(y_true, y_pred):
    return np.mean(np.abs((y_true - y_pred) / ((y_true + y_pred)/2))) * 100

# MAE is used to measure how close the forecast is to observed value
def mean_absolute_error(y_true, y_pred):
    return np.mean(abs(y_true - y_pred))

# MPE represents the percentage of average error occurred, while forecasting.
# It shares similar properties of MAPE except MPE also shows the direction of
# error.
def mean_percentage_error(y_true, y_pred):
    return np.mean((y_true - y_pred) / y_true) * 100

# -----------------------------Scale dependent metrics-------------------------

# In statistics, the mean squared error (MSE) of an estimator measures the
# average of the squares of the errors. The squaring is necessary to remove any
# negative signs. It also gives more weight to larger differences
def mean_squared_error(y_true, y_pred):
    return np.mean((y_true - y_pred)**2)

# RMSE is the standard deviation of the residuals. It shares the same
# properties as MSE. The square root is used to dampen the magnitude of errors
# caused by squaring them.
def root_mean_squared_error(y_true, y_pred):
    return np.sqrt(np.mean((y_true - y_pred)**2))

# MFE is the difference between observed and forecasted value
def mean_forecast_error(y_true, y_pred):
    return np.mean(y_true - y_pred)

# It measures the total squared deviation of forecasted observations, from the
# observed values
def sum_of_squared_error(y_true, y_pred):
    return np.sum((y_true - y_pred)**2)

# It is same as MSE, except that here the original sign is kept for each
# individual squared error. SMSE panelizes extreme errors, occurred while
# forecasting.
def signed_mean_squared_error(y_true, y_pred):
    error = y_true-y_pred
    return np.mean((error/abs(error))*(error**2))

# NMSE normalizes the obtained MSE after dividing it by the test variance. It
# is a balanced error measure and is very effective in judging forecast
# accuracy of a model.
def normalised_mean_squared_error(y_true, y_pred):
    mse = mean_squared_error(y_true, y_pred)
    return mse / (np.sum((y_true - np.mean(y_true)) ** 2)/(len(y_true)-1))

# It is a normalized measure of total forecast error.
def theil_u_statistic(y_true, y_pred):
    error = y_true - y_pred
    mfe = np.sqrt(np.mean(y_pred**2))
    mse = np.sqrt(np.mean(y_true**2))
    rmse = np.sqrt(np.mean(error**2))
    return rmse / (mfe*mse)

# This evaluation metric is used to over come some of the problems of MAPE and
# is used to measure if the forecasting model is better than the naive model or
# not.
def mean_absolute_scaled_error(y_true, y_pred, naive_mod_mae):
    error = y_true - y_pred
    return np.mean(abs(error)/naive_mod_mae)


import numpy as np
from scipy.linalg import svd

def pca(X, n_components = 5):
    #  Use svd to perform PCA on X
    #  Inputs:
    #     X: input matrix
    #     n_components: number of principal components to keep
    #  Output:
    #     X_pca: output matrix of n_components columns (numpy.array)
    #  Write your own code below:
    U, s, Vh = svd(X)
    return X_pca

def normalize(X, norm="Min-Max", axis = 1):
    #  Inputs:
    #     X: input matrix
    #     norm = {"L1", "L2", "Min-Max", "Standard_Score"}
    #     axis = 0: normalize rows
    #     axis = 1: normalize columns
    #  Output:
    #     X_norm: normalized matrix (numpy.array)
    #  Write your own code below:
    X_array = np.asarray(X)
    return X_norm

def stratified_sampling(y, ratio, replacement = True):
    #  Inputs:
    #     y: class labels
    #     0 < ratio < 1: number of samples = len(y) * ratio
    #     replacement = True: sample with replacement
    #     replacement = False: sample without replacement
    #  Output:
    #     sample: indices of stratified sampled points
    #                    (ratio is the same across each class)
    #  Write your own code below:
    y_array = np.asarray(y)
    return sample