# Copyright (c) 2020, salesforce.com, inc.
# All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# For full license text, see the LICENSE file in the repo root
# or https://opensource.org/licenses/BSD-3-Clause

import numpy as np

def get_gini(endowments):
    """Returns the normalized Gini index describing the distribution of endowments.

    https://en.wikipedia.org/wiki/Gini_coefficient

    Args:
        endowments (ndarray): The array of endowments for each of the agents in the
            simulated economy.

    Returns:
        Normalized Gini index for the distribution of endowments (float). A value of 1
            indicates everything belongs to 1 agent (perfect inequality), whereas a
            value of 0 indicates all agents have equal endowments (perfect equality).

    Note:
        Uses a slightly different method depending on the number of agents. For fewer
        agents (<30), uses an exact but slow method. Switches to using a much faster
        method for more agents, where both methods produce approximately equivalent
        results.
    """
    n_agents = len(endowments)

    if n_agents < 30:  # Slower. Accurate for all n.
        diff_ij = np.abs(
            endowments.reshape((n_agents, 1)) - endowments.reshape((1, n_agents))
        )
        diff = np.sum(diff_ij)
        norm = 2 * n_agents * endowments.sum(axis=0)
        unscaled_gini = diff / (norm + 1e-10)
        gini = unscaled_gini / ((n_agents - 1) / n_agents)
        return gini

    # Much faster. Slightly overestimated for low n.
    s_endows = np.sort(endowments)
    return 1 - (2 / (n_agents + 1)) * np.sum(
        np.cumsum(s_endows) / (np.sum(s_endows) + 1e-10)
    )

def get_equality(endowments):
    """Returns the complement of the normalized Gini index (equality = 1 - Gini).

    Args:
        endowments (ndarray): The array of endowments for each of the agents in the
            simulated economy.

    Returns:
        Normalized equality index for the distribution of endowments (float). A value
            of 0 indicates everything belongs to 1 agent (perfect inequality),
            whereas a value of 1 indicates all agents have equal endowments (perfect
            equality).
    """
    return 1 - get_gini(endowments)

def get_productivity(coin_endowments):
    """Returns the total coin inside the simulated economy.

    Args:
        coin_endowments (ndarray): The array of coin endowments for each of the
            agents in the simulated economy.

    Returns:
        Total coin endowment (float).
    """
    return np.sum(coin_endowments)


# -*- coding: utf-8 -*-
"""
Author: Nathan Rice
Flattening the Curve
Using SIR model for spread of disease from epidemiology
to model effects of different levels of disease spread prevention measures
(value of Rnot is infection reproduction value)
"""
import matplotlib.pyplot as plt
import numpy as np

def sirModel(x):
    '''Generates plot for SIR model using parameter value for Rnot'''
    # lists to save values of S, I, and R after each hour
    S_day = []
    I_day = []
    R_day = []

    gamma = 20*24 #typical recovery time in hours
    Rnot = x #basic reproduction number of an infection
    #Iof0
    I = 100000 #number of infections on day 0 - March 29 2020
    #Rof0 =  0 recovered
    R = 0
    P = 300000000 #total population of the United States
    #Sof0 = P - Iof0 #susceptible
    S = 299900000

    hours = list(range(7200))

    for t in hours:
        #print("\nt = ",t)

        # _now variables are S, I, R at time t
        S_now = S
        I_now = I
        R_now = R

        # S, I, and R are now known at time t+1
        I = I_now + ((Rnot*I_now*S_now)/(gamma*P)) - (I_now/gamma)
        I_day.append(I)
        S = S_now - ((Rnot*I_now*S_now)/(gamma*P))
        S_day.append(S)
        R = R_now + (I_now/gamma)
        R_day.append(R)
        #print("S+I+R = %f"%(S+I+R))
        #print("S_day = ",S_day)
        #print("I_day = ",I_day)
        #print("R_day = ",R_day)
    print("Maximum people infected for rnot value = ",x,":",round(max(I_day)))

    sarr = np.array(S_day)
    iarr = np.array(I_day)
    rarr = np.array(R_day)
    hoursarr = np.array(hours)
    days = hoursarr/24

    plt.plot(days, sarr, 'b-')
    plt.plot(days, iarr, 'r-')
    plt.plot(days, rarr, 'g-')
    plt.yscale('linear')
    plt.xscale('linear')
    plt.xlabel('Days')
    plt.ylabel('Number of People in hundreds of millions')
    plt.legend(['Susceptible','Infected','Recovered'])
    string_for_title = "SIR model for infection reproduction value " + str(x)
    plt.title(string_for_title)
    name_of_plot = "rnot"+str(x)+".png"
    plt.savefig(name_of_plot,bbox_inches="tight")

sirModel(10)
plt.clf()
sirModel(4)
plt.clf()
sirModel(1.7)

#Maximum people infected for rnot value =  10 : 201,288,775
#Maximum people infected for rnot value =  4 : 121,176,853
#Maximum people infected for rnot value =  1.7 : 29,959,267


"""
  Name     : c12_19_up_and_out_call.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""

import scipy as sp
from scipy import log,exp,sqrt,stats
#
def bsCall(S,X,T,r,sigma):
    d1=(log(S/X)+(r+sigma*sigma/2.)*T)/(sigma*sqrt(T))
    d2 = d1-sigma*sqrt(T)
    return S*stats.norm.cdf(d1)-X*exp(-r*T)*stats.norm.cdf(d2)
#
def up_and_out_call(s0,x,T,r,sigma,n_simulation,barrier):
    n_steps=100.
    dt=T/n_steps
    total=0
    for j in sp.arange(0, n_simulation):
        sT=s0
        out=False
        for i in range(0,int(n_steps)):
            e=sp.random.normal()
            sT*=sp.exp((r-0.5*sigma*sigma)*dt+sigma*e*sp.sqrt(dt))
            if sT>barrier:
               out=True
        if out==False:
            total+=bsCall(s0,x,T,r,sigma)
    return total/n_simulation
#
s0=40.              # today stock price
x=40.               # exercise price
barrier=42          # barrier level
T=0.5               # maturity in years
r=0.05              # risk-free rate
sigma=0.2           # volatility (annualized)
n_simulation=100    # number of simulations
sp.random.seed(12)  # fix a seed
#
result=up_and_out_call(s0,x,T,r,sigma,n_simulation,barrier)
print('up-and-out-call = ', round(result,3))


##========================================================================================
import numpy as np
from scipy import linalg
from sklearn.preprocessing import OneHotEncoder

def fit(x,y,x_test,y_test,niter_max,l2):
    onehot_encoder = OneHotEncoder(sparse=False,categories='auto')
    y_onehot = onehot_encoder.fit_transform(y.reshape(-1,1))  # y[t,:] = {0,1,...,0}

    #print(niter_max)
    n = x.shape[1]
    m = y_onehot.shape[1] # number of categories

    x_av = np.mean(x,axis=0)
    dx = x - x_av
    c = np.cov(dx,rowvar=False,bias=True)

    # 2019.07.16:  l2 = lamda/(2L)
    c += l2*np.identity(n) / (2*len(y))
    c_inv = linalg.pinvh(c)

    H0 = np.zeros(m)
    W = np.zeros((n,m))

    for i in range(m):
        y = y_onehot[:,i]  # y = {0,1}
        y1 = 2*y - 1       # y1 = {-1,1}
        # initial values
        h0 = 0.
        w = np.random.normal(0.0,1./np.sqrt(n),size=(n))

        cost = np.full(niter_max,100.)
        for iloop in range(niter_max):
            h = h0 + x.dot(w)
            y1_model = np.tanh(h/2.)

            # stopping criterion
            p = 1/(1+np.exp(-h))

            cost[iloop] = ((p-y)**2).mean()

            # 2019.07.12: lost function
            #cost[iloop] = ((y1[:]-y1_model[:])**2).mean()
            #cost[iloop] = (-y[:]*np.log(p) - (1-y)*np.log(1-p)).mean()

            h_test = h0 + x_test.dot(w)
            p_test = 1/(1+np.exp(-h_test))
            cost[iloop] = ((p_test-y_test)**2).mean()

            if iloop > 0 and cost[iloop] >= cost[iloop-1] : break

            # update local field
            t = h!=0
            h[t] *= y1[t]/y1_model[t]
            h[~t] = 2*y1[~t]

            # find w from h
            h_av = h.mean()
            dh = h - h_av
            dhdx = dh[:,np.newaxis]*dx[:,:]

            dhdx_av = dhdx.mean(axis=0)
            w = c_inv.dot(dhdx_av)
            h0 = h_av - x_av.dot(w)

        H0[i] = h0
        W[:,i] = w

    return H0,W

#=========================================================================================
def predict(x,h0,w):
    h = h0[np.newaxis,:] + x.dot(w)
    p = 1/(1+np.exp(-h))
    p /= p.sum(axis=1)[:,np.newaxis]
    y = np.argmax(p,axis=1)

    return y


#!/usr/bin/env python3
# Basic imports
import numpy as np
import matplotlib.pyplot as plt
from time import sleep

# sklearn Imports
from sklearn.datasets import load_iris, load_diabetes, load_breast_cancer
from sklearn.metrics import mean_squared_error, r2_score

# SaML Imports
from Regression import LinearRegression, LogisticRegressionClassifier
from Clustering import KMeansClustering, AggHierClustering
from Plotting import plot_clusters, plot_linear, plot_roc, plot_sse
from utils import print_header, read_data, backspace, write, train_test_split

# Path to Datasets
DATA_PATH = ('test_data/{}')

def test_linear_regression():
    """Taken from http://scikit-learn.org/stable/auto_examples/linear_model/plot_ols.html
    """
    print_header("Linear Regression on sklearn diabetes dataset")

    diabetes = load_diabetes()

    # Use only one feature
    diabetes_X = diabetes.data[:, np.newaxis, 2]

    # Split the data into training/testing sets
    diabetes_X_train = diabetes_X[:-20]
    diabetes_X_test = diabetes_X[-20:]

    # Split the targets into training/testing sets
    diabetes_y_train = diabetes.target[:-20]
    diabetes_y_test = diabetes.target[-20:]

    # Create and fit classifier
    LRClass = LinearRegression()
    LRClass.fit(diabetes_X_train, diabetes_y_train)

    # Make predictions
    diabetes_y_pred = LRClass.predict(diabetes_X_test)

    # The coefficients
    print('Coefficients: \n', LRClass._weights)
    # The mean squared error
    print("Mean squared error: %.2f"
        % mean_squared_error(diabetes_y_test, diabetes_y_pred))
    # Explained variance score: 1 is perfect prediction
    print('Variance score: %.2f' % r2_score(diabetes_y_test, diabetes_y_pred))

    # Plot outputs
    plt.scatter(diabetes_X_test, diabetes_y_test,  color='black')
    plot_linear(diabetes_X_test, diabetes_y_pred)

def test_logistic_regression():
    features,targets = load_breast_cancer(True)

    logreg = LogisticRegressionClassifier()
    train_x, train_y, test_x, test_y = train_test_split(features, targets, 1/3)

    logreg.fit(train_x, train_y)
    probabilities = logreg.predict_proba(test_x)
    plot_roc("ROC Curve for Wisconsin Breast Cancer Dataset", test_y, probabilities)

def test_k_means():
    from Clustering import calculate_sse
    data = read_data(DATA_PATH.format("k_means_data"))

    print_header("SSE with varying k")

    k_means_sse = {}
    for k in range(2, 11):
        k_means = KMeansClustering(data, k, large_output=True)
        sse = calculate_sse(k_means[1], k_means[2])
        k_means_sse[k] = sse
        print("SSE for k={} is {}".format(k, sse))
    plot_sse(k_means_sse)

    print_header("Clustering with k = 3")

    k_3_clustering = KMeansClustering(data, 3)
    plot_clusters(k_3_clustering)

def test_agg_hier():
    data = read_data(DATA_PATH.format("agg_hier_data"))
    method = 'min'

    print_header("Agglogmerative Hierarchical Clustering with {} and k = 2".format(method))

    agg_hier_clustering = AggHierClustering(data, 2, method)
    plot_clusters(agg_hier_clustering)

if __name__ == "__main__":
    print_header("Select Algorithm to Test")
    case = input(   "1: Linear Regression\n"+
                    "2: Logistic Regression\n"+
                    "3: K Means Clustering\n"+
                    "4: Agglomerative Hierarchical Testing\n")
    case = int(float(case.strip()))
    if case == 1:
        test_linear_regression()
    elif case == 2:
        test_logistic_regression()
    elif case == 3:
        test_k_means()
    elif case == 4:
        test_agg_hier()
    else:
        print("Invalid input")
        quit(1)


import numpy as np
X = np.array(([2, 9], [1, 5], [3, 6]), dtype=float)
y = np.array(([92], [86], [89]), dtype=float)
X = X/np.amax(X,axis=0) # maximum of X array longitudinally
y = y/100

#Sigmoid Function
def sigmoid (x):
 return 1/(1 + np.exp(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
 return x * (1 - x)

#Variable initialization
epoch=7000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = 2 #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))
bh=np.random.uniform(size=(1,hiddenlayer_neurons))
wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))
bout=np.random.uniform(size=(1,output_neurons))
#draws a random range of numbers uniformly of dim x*y

for i in range(epoch):
#Forward Propogation
 hinp1=np.dot(X,wh)
 hinp=hinp1 + bh
 hlayer_act = sigmoid(hinp)
 outinp1=np.dot(hlayer_act,wout)
 outinp= outinp1+ bout
 output = sigmoid(outinp)

#Backpropagation
 EO = y-output
 outgrad = derivatives_sigmoid(output)
 d_output = EO * outgrad
 EH = d_output.dot(wout.T)
 hiddengrad = derivatives_sigmoid(hlayer_act) #how much hidden layer wts contributed to err
 d_hiddenlayer = EH * hiddengrad
 wout += hlayer_act.T.dot(d_output) * lr# dotproduct of nextlayererror and #currentlayerop
 wh += X.T.dot(d_hiddenlayer) * lr

print("Input: \n" + str(X))
print("Actual Output: \n" + str(y))
print("Predicted Output: \n" ,output)


import math
import numpy as np
from utils.functions.math.coordinate_trans import coordinate_transformation_in_angle

def circle_make(center_x, center_y, radius):
    '''
    Create circle matrix(2D)

    Parameters
    -------
    center_x : float in meters
        the center position of the circle coordinate x
    center_y : float in meters
        the center position of the circle coordinate y
    radius : float in meters

    Returns
    -------
    circle x : numpy.ndarray
    circle y : numpy.ndarray
    '''

    point_num = 100

    circle_xs = []
    circle_ys = []

    for i in range(point_num + 1):
        circle_xs.append(center_x + radius * math.cos(i*2*math.pi/point_num))
        circle_ys.append(center_y + radius * math.sin(i*2*math.pi/point_num))

    return np.array(circle_xs), np.array(circle_ys)

def circle_make_with_angles(center_x, center_y, radius, angle):
    '''
    Create circle matrix with angle line matrix(2D)

    Parameters
    -------
    center_x : float in meters
        the center position of the circle coordinate x
    center_y : float in meters
        the center position of the circle coordinate y
    radius : float in meters
    angle : float in radians

    Returns
    -------
    circle xs : numpy.ndarray
    circle ys : numpy.ndarray
    angle line xs : numpy.ndarray
    angle line ys : numpy.ndarray
    '''

    point_num = 100

    circle_xs = []
    circle_ys = []

    for i in range(point_num + 1):
        circle_xs.append(center_x + radius * math.cos(i*2*math.pi/point_num))
        circle_ys.append(center_y + radius * math.sin(i*2*math.pi/point_num))

    angle_line_xs = [center_x, center_x + math.cos(angle) * radius]
    angle_line_ys = [center_y, center_y + math.sin(angle) * radius]

    return np.array(circle_xs), np.array(circle_ys), np.array(angle_line_xs), np.array(angle_line_ys)

def square_make_with_angles(center_x, center_y, size, angle):
    '''
    Create square matrix with angle line matrix(2D)

    Parameters
    -------
    center_x : float in meters
        the center x position of the square
    center_y : float in meters
        the center y position of the square
    size : float in meters
        the square's half-size
    angle : float in radians

    Returns
    -------
    square xs : numpy.ndarray
        lenght is 5 (counterclockwise from right-up)
    square ys : numpy.ndarray
        length is 5 (counterclockwise from right-up)
    angle line xs : numpy.ndarray
    angle line ys : numpy.ndarray
    '''

    # start with the up right points
    # create point in counterclockwise
    square_xys = np.array([[size, size], [-size, size], [-size, -size], [size, -size], [size, size]])
    trans_points = coordinate_transformation_in_angle(square_xys.T, -angle) # this is inverse type
    trans_points += np.array([[center_x], [center_y]])

    square_xs = trans_points[0, :]
    square_ys = trans_points[1, :]

    angle_line_xs = [center_x, center_x + math.cos(angle) * size]
    angle_line_ys = [center_y, center_y + math.sin(angle) * size]

    return square_xs, square_ys, np.array(angle_line_xs), np.array(angle_line_ys)


import sympy as sp
import numpy as np
import matplotlib.pyplot as plt

# x = -0.57860

def plot_func(y, start=-1, stop=0, steps=100):
    func = sp.lambdify(_x, y)
    xx = np.linspace(start, stop, steps)
    yy = func(xx)

    x_intersects = [float(r) for r in sp.solve(y, _x) if r.as_real_imag()[1] == 0]
    x_intersects = [r for r in x_intersects if start <= r <= stop]
    plt.axhline(0, color='black')
    if start <= 0 <= stop:
        plt.axvline(0, color='black', ymax=1)
    plt.plot(xx, yy)
    plt.plot(x_intersects, [0] * len(x_intersects), color='red', marker='o')
    plt.show()

_x = sp.Symbol('x', real=True)

# ----------------- Newton's method -------------

y = _x ** 3 - 10 * _x ** 2 + 44 * _x + 29
y_dx = y.diff()

func = sp.lambdify(_x, y)
func_dx = sp.lambdify(_x, y_dx)

eps = 1e-4

plot_func(y)

xx = [-0.6]
while abs(func(xx[-1])) > eps:
    x = xx[-1] - func(xx[-1]) / func_dx(xx[-1])
    xx.append(x)

print(xx)

# ------------------ Simple Iteration Method ------------------
y = -1 * _x ** 3 / 44 + 10 / 44 * _x ** 2 - 29 / 44
func_fi = sp.lambdify(_x, y)

xx = [-0.6]
while abs(func(xx[-1])) > eps:
    x = func_fi(xx[-1])
    xx.append(x)

print(xx)


import numpy as np
from scipy.stats import t

def linreg(X, Y):
    """
    Summary
        Linear regression of y = ax + b
    Usage
        real, real, real = linreg(list, list)
    Returns coefficients to the regression line "y=ax+b" from x[] and y[], and R^2 Value
    """
    N = len(X)

    if N != len(Y):  raise(ValueError, 'unequal length')

    Sx = Sy = Sxx = Syy = Sxy = 0.0
    for x, y in zip(X, Y):
        Sx = Sx + x
        Sy = Sy + y
        Sxx = Sxx + x*x
        Syy = Syy + y*y
        Sxy = Sxy + x*y

    det =  Sx * Sx - Sxx * N # see the lecture

    a,b = (Sy * Sx - Sxy * N)/det, (Sx * Sxy - Sxx * Sy)/det

    meanerror = residual = residualx = 0.0

    for x, y in zip(X, Y):
        meanerror = meanerror + (y - Sy/N)**2
        residual = residual + (y - a * x - b)**2
        residualx = residualx + (x - Sx/N)**2

    RR = 1 - residual/meanerror
    # linear regression, a_0, a_1 => m = 1
    m = 1
    nu = N - (m+1)

    sxy = np.sqrt(residual / nu)

    # Var_a, Var_b = ss * N / det, ss * Sxx / det

    Sa = sxy * np.sqrt(1/residualx)
    Sb = sxy * np.sqrt(Sxx/(N*residualx))

    # We work with t-distribution, ()
    # t_{nu;\alpha/2} = t_{3,95} = 3.18
    tvalue = t.ppf(1-(1-0.95)/2, nu)

    print("Estimate: y = ax + b")
    print("N = %d" % N)
    print("Degrees of freedom $\\nu$ = %d " % nu)
    print("a = %.2f $\\pm$ %.3f" % (a, tvalue*Sa/np.sqrt(N)))
    print("b = %.2f $\\pm$ %.3f" % (b, tvalue*Sb/np.sqrt(N)))
    print("R^2 = %.3f" % RR)
    print("Syx = %.3f" % sxy)
    print("y = %.2f x + %.2f $\\pm$ %.3f V" % (a, b, tvalue*sxy/np.sqrt(N)))
    return a, b, RR, sxy


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

def gauss_elimination(A, b):

    temp_mat = np.c_[A, b] #combine A and b into one matrix

    n = temp_mat.shape[0] #number of rows

    #Loop over rows
    for i in range(n):

        p = np.abs(temp_mat[i:, i]).argmax() #pivot index

        p += i #pivot index with respect to original matrix

        if p != i:                              #swap rows
            temp_mat[[p, i]] = temp_mat[[i, p]]

        factor = temp_mat[i+1:, i] / temp_mat[i, i]             #Eliminate all entries below the pivot
        temp_mat[i+1:] -= factor[:, np.newaxis] * temp_mat[i]

    x = np.zeros_like(b, dtype=np.double);

    #Back substitution
    x[-1] = temp_mat[-1,-1] / temp_mat[-1, -2]

    for i in range(n-2, -1, -1):
        x[i] = (temp_mat[i,-1] - np.dot(temp_mat[i,i:-1], x[i:])) / temp_mat[i,i]    #Solve for each row

    return x

def fit_polynomials(x,y,degree):
    x_range = degree+4
    y_range = degree+1
    n = len(x)

    sum_x_list = []
    sum_y_list = []
    new_x  = np.zeros((y_range,y_range))
    new_y = np.zeros((y_range,1))

    for i in range(x_range):
        sum_x_list.append(np.sum(x**i))
    for i in range(y_range):
        sum_y_list.append(np.sum((x**i)*y))
    for i in range(y_range):
        try:
            new_x[i,0] = sum_x_list[i]
            new_x[i,1] = sum_x_list[i+1]
            new_x[i,2] = sum_x_list[i+2]
            new_x[i,3] = sum_x_list[i+3]
        except:
            pass
    for i in range(y_range):
        new_y[i,0] = sum_y_list[i]

    coefficients = gauss_elimination(new_x,new_y)

    if degree == 1:
        f = lambda x: coefficients[1][0]*x + coefficients[0][0]
    elif degree == 2:
        f = lambda x: coefficients[2][0]*x**2 + coefficients[1][0]*x + coefficients[0][0]
    elif degree == 3:
        f = lambda x: coefficients[3][0]*x**3 + coefficients[2][0]*x**2 + coefficients[1][0]*x + coefficients[0][0]

    plt.plot(np.arange(-0.1,2,0.01),[f(x) for x in np.arange(-0.1,2,0.01)],label=f"Order {degree} Fit",color="red")

    plt.legend()

    plt.scatter(x,y,s=10)
    plt.show()

    Sr = np.sum((y-f(x))**2)

    y_hat = np.sum(y)/n

    St = np.sum((y-y_hat)**2)

    r = np.sqrt((St-Sr)/St)
    table = np.array([[degree,Sr,St,r]])
    print(tabulate(table,headers=["Degree","Sr","St","r"]))

if __name__ == "__main__":

    x = np.array([-0.08782,0.084523,0.263619,0.293331,0.472033,
    0.529981,0.603711,0.783421,0.885681,0.914265,1.006573,1.070965,1.232576,
    1.359112,1.327033,1.477735,1.647759,1.75348,1.882376,1.813837,1.92543 ])

    y = np.array([0.078597,0.308436,0.62902,0.90405,0.880547,1.072706,1.081378,0.849039,
    0.669272, 0.449664,0.191648,-0.20313,-0.42675,-0.77451,-0.85954,-0.99787,-1.06124,
    -0.88792,-0.73878,-0.57943,-0.2393])

    for i in range(1,4):
        fit_polynomials(x,y,i)


import scipy.stats
# According to [1] There is a ~1.45% chance of a drive failing each year
# .. [1] https://www.backblaze.com/blog/backblaze-hard-drive-stats-q2-2020/

# We can model a Poisson distribution to ask some questions
λ = 1.45 / 100   # probability of failure within a year
y = 1            # number of years
k = 1            # number of events (failures)

def probabilities_for_y_years(y):
    ##
    ##
    # The PMF is the probability that exactly k failures occur in y years
    print('\nIn y={} years we can expect'.format(y))

    rv = scipy.stats.poisson(mu=λ * y)

    k = 1
    p_one_fail = rv.pmf(k)
    print('p_one_fail = {:.4f}%'.format(p_one_fail * 100))
    k = 2
    p_two_fail = rv.pmf(k)
    print('p_two_fail = {:.4f}%'.format(p_two_fail * 100))

    # The CDF(k) is the probability the k or fewer failures occur in y years.
    # So, the probability k or more events occur is 1 - CDF(k - 1)
    # k or fewer, so 1 - CDF is the probability more than k events occur
    k = 1
    p_atleast_one_fail = 1 - rv.cdf(k - 1)
    print('p_atleast_one_fail = {:.4f}%'.format(p_atleast_one_fail * 100))

    k = 2
    p_atleast_two_fail = 1 - rv.cdf(k - 1)
    print('p_atleast_two_fail = {:.4f}%'.format(p_atleast_two_fail * 100))

probabilities_for_y_years(y=1)
probabilities_for_y_years(y=5)
probabilities_for_y_years(y=10)
probabilities_for_y_years(y=15)


import numpy as np

def jacobi(A, b, eps, maxIterations):
  size = len(A)
  x = np.zeros(size)

  D = np.diag(A)
  Dm = np.diagflat(D)
  Dneg = np.diagflat([1 / n for n in D])
  R = A - Dm

  w, v = np.linalg.eig(np.dot(Dneg, R))
  if max(abs(w)) > 1:
    print("Spectral radius of matrix is less than 1, aborting calculation")
    return

  for i in range(size):
    if sum(abs(R[i])) >= abs(D[i]):
      print("Warning: Matrix is not diagonally dominant")
      break

  for i in range(maxIterations):
    xPrev = np.copy(x)
    x = (b - np.dot(R,x)) / D

    print(f"{i}-th iteration:")
    print(x)
    if np.linalg.norm(np.dot(A, x) - b) <= eps * np.linalg.norm(b):
      print("^^^^^^^^^^^^^^")
      print(">> Малость невязки << Solution found")
      return
    if np.linalg.norm(x - xPrev) <= eps * np.linalg.norm(xPrev):
      print("^^^^^^^^^^^^^^")
      print(">> Малость нормы приближений << Solution found")
      return

  print("Number of iterations exceeded")


from sympy import *
from math import comb
'''
requirements
    - pip install sympy
'''

def leibniz_formula(u, v, n: int):
    '''
    Compute the nth order derivative of the product of two functions u and v

    Parameters
    ----------
    u : function
        function with one variable - x
    v : function
        function with one variable - x
    n : integer
        the order of the derivative to be computed

    Returns
    -------
    function
        nth order derivative of the product of u and v computed
        using leibniz formula
    '''

    x = symbols('x')
    f = 0
    for i in range(n+1):
        f += comb(n, i) * diff(u, x, n-i) * diff(v, x, i)
    return simplify(f)

x = symbols('x')

# examples

# 1
u = exp(x)
v = sin(x)
n = 4
f = leibniz_formula(u, v, n)
print(f"The {n}th order derivative of {u}*{v} is : {f}")

# 2
u = x
v = log(x)
n = 10
f = leibniz_formula(u, v, n)
print(f"The {n}th order derivative of {u}*{v} is : {f}")


import numpy as np
import math
from copy import deepcopy
from numpy.linalg import norm, eig
import functools

def PrintStep(stepName):
    print("\n------------------------------\n{}\n------------------------------\n".format(stepName))

def ReadPrecision():
    return float(input())

def ReadMatrix():
    matrixSize = int(input())

    matrix = [list(map(float, input().split())) for i in range(matrixSize)]

    return matrix

def MakeHouseholder(column, iteration):
    v = np.zeros(len(column))

    v[iteration] = column[iteration] + np.sign(column[iteration]) * norm(column[iteration:])
    for i in range(iteration + 1, len(column)):
        v[i] = column[i]
    h = np.eye(len(column)) - (2 / (v @ v)) * (v[:, None] @ v[None, :])

    return h

def MakeQRDecomposition(matrix):
    PrintStep("QR Decomposition")
    qMatrix = np.eye(len(matrix))
    rMatrix = matrix.copy()

    for i in range(len(matrix) - 1):
        print("QR step number {}".format(i))
        house = MakeHouseholder(rMatrix[:, i], i)
        print("householder matrix:\n{}".format(house))
        qMatrix = qMatrix @ house
        print("Q matrix:\n{}".format(qMatrix))
        rMatrix = house @ rMatrix
        print("R matrix:\n{}".format(rMatrix))

    return qMatrix, rMatrix

def FindEigens(mtx, idx):
    matrix = mtx.tolist()
    a11 = matrix[idx][idx]
    a12 = matrix[idx][idx + 1]
    a21 = matrix[idx + 1][idx]
    a22 = matrix[idx + 1][idx + 1]
    print("my beatifur submatrix:\n{} {}\n{} {}".format(a11, a12, a21, a22))
    return np.roots((1, -a11 - a22, a11 * a22 - a12 * a21))

def Check(matrix, eps):
    PrintStep("Checking if we are close")
    result = []
    idx = 0
    while idx < len(matrix):
        if norm(matrix[idx + 1:, idx]) <= eps:
            print("{} is good".format(matrix.item(idx, idx)))
            result.append(matrix.item(idx, idx))

        elif norm(matrix[idx + 2:, idx]) <= eps:
            roots = FindEigens(matrix, idx)
            print("these roots are stable:\n{}".format(roots))
            result.extend(roots)
            idx += 1

        idx += 1

    print("got something in the check:\n{}".format(result))
    if len(result) + 1 == len(matrix):
        print("!!!!ALERT!!!!check didnt find all eigen values!")
        #result.append(matrix.item(-1, -1))

    return result

def FindEigenValues(matrix, prec, maxSteps = math.inf):
    PrintStep("Finding eigen values")

    currMatrix = deepcopy(matrix)
    result = []
    iterCount = 0
    isCheckPassed = False

    while True and iterCount != maxSteps:
        qMatrix, rMatrix = MakeQRDecomposition(currMatrix)

        PrintStep("After QR decomp")
        print("iteration = {}".format(iterCount))
        print("Q matrix:\n{}".format(qMatrix))
        print("R matrix:\n{}".format(rMatrix))

        currMatrix = rMatrix @ qMatrix
        print("Current matrix:\n{}".format(currMatrix))
        result = Check(currMatrix, prec)
        print("current eigen values:\n{}".format(result))

        if len(result) == len(matrix):
            if isCheckPassed == False:
                isCheckPassed = True
            else:
                break
        iterCount += 1

    return result, iterCount

if __name__ == "__main__":
    prec = ReadPrecision()
    matrix = np.matrix(ReadMatrix())

    eVal, iterations = FindEigenValues(matrix, prec)
    eigenValues = sorted(eVal)
    npEigens = sorted(eig(matrix)[0])
    PrintStep("Found all eigen values")
    print("wasted {} iterations with precision {}".format(iterations, prec))
    print("final eigen values:\n{}".format(eigenValues))
    print("numpy found these eigens:\n{}".format(npEigens))
    print("Are they close?\n{}".format(np.isclose(eigenValues, npEigens, prec, prec)))


'''
author: bg
usage:
Utils module.
Collection of functions for creating different types of kernels
'''
import numpy as np

def make_gaussian_kern(size, sig=1):
    ## size better if odd b/c balance
    ## The larger the size of the kernel, more blur <<< the lower the sensitivity to noise <<
    outk = np.zeros( (size, size) )

    k = (size-1)//2
    sig_const = 1/( 2 * np.pi * sig)

    for i in range(size):
        for j in range(size):
            outk[i,j] = sig_const * np.exp( ( ( (i+1)-(k+1))**2 +( (j+1)-(k+1))**2 )*(-1/(2*sig)))
    return outk

def make_box_kern(size, k=None):
    ## average or box blur
    outk = np.ones( (size, size) )
    kn = outk.sum() if k is None else k
    outk = outk * (1/kn)
    return outk

def make_laplacian_ker(size):
    ##TODO: more options
    return np.array([
            [0, -1, 0],
            [-1, 4, -1],
            [0, -1, 0]
        ])

def make_edge_soft1(size):
    ##TODO: more
    return np.array([
                [1, 0, -1],
                [0, 0, 0],
                [-1, 0, 1]
            ])
def make_edge_harsh1(size):
    ##TODO: more
    return  np.array([
                [-1, -1, -1],
                [-1, 8, -1],
                [-1, -1, -1]
            ])


import numpy

def normal_peak(t_predict, location, stddev=1.0, unit_integral=None, height=None):
    """Generates a peak whose shape is the gaussian distribution function
    Args:
      t_predict:
        Numpy array with points in time where you want the model to generate predictions.
      location (float):
        Where you want to place the peak of the curve.
      stddev (float):
        Shape factor that affects the width of the distribution.
      height (float):
        What the peak height should be.
      unit_integral (bool):
        If true, area under the curve sums to unity

    Returns:
      Numpy array with shape equal to t_predict, containing the y values for the normal peak curve.
    """

    if unit_integral is None:
        # pylint: disable=simplifiable-if-statement
        if height is None:
            # user did not specify unit_integral or height
            unit_integral = True
        else:
            # user specified height
            unit_integral = False
    elif unit_integral is False:
        assert height is not None, "You need to specifiy height when unit_integral is False."
    elif unit_integral is True:
        assert height is None, "Either define height or set unit_integral to True, but not both."
    else:
        raise AssertionError("unit_integral should be None, True, or False")

    # determine scaling parameter
    if unit_integral is True:
        scale = 1.0
    elif unit_integral is False:
        max_height = 1 / (stddev * numpy.sqrt(2 * numpy.pi))
        scale = height / max_height
    else:
        raise AssertionError("expected unit_integral to be True, False, or None")

    z = (t_predict - location) / stddev
    term1 = 1 / (stddev * numpy.sqrt(2 * numpy.pi))
    term2 = numpy.exp((-1 / 2) * numpy.power(z, 2))
    return term1 * term2 * scale


# For arrs of same size, ufunc add works intuitively.
import numpy as np
a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b

# Adding a scalar to an array, has similarly intuitive definition.
a + 5

# Broadcasting: Add a 1d array to a 2d array.
# Note that len(M[0]) == len(a)
M = np.ones((3, 3))
M + a
# 'a' is Broadcasted - 'a' is added for each inner array.

# Broadcasting: Add a 1d array to a 1d array, on seperate axes.
# Result is a 3x3 Matrix
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]     # form into 2d array of 1 column
a + b

# Rules of Broadcasting:

# If arrays differ in their number of dimensions, the array with fewer
#    dimensions is padded with ones on its leading (left) side.

# If the shape of the two arrays does not match in any dimension, the array
#    with size=1 in the mismatched dimension is stretched to match.

# If the shape of the two arrays does not match in any dimension, and the
#    and size!=1 for either arr in mismatched dimension, error thrown.

# Demonstrates rules 1, 2
M = np.ones((2, 3))
a = np.arange(3)

M.shape     # returns (3, 3)
a.shape     # returns (3,)
M + a

# by rule 1, a has fewer dimensions, so begin padding on left with ones:
# M.shape --> (2, 3)
# a.shape --> (1, 3)

# by rule 2, 'a' has a first dimension which disagrees, so it is stretched
# M.shape --> (2, 3)
# a.shape --> (2, 3)

# Operation: ([[1., 2., 3.],
#              [1., 2., 3.]])

# Broadcasting ex2:
a = np.arange(3).reshape((3, 1))
b = np.arange(3)

a.shape     # returns (3, 1)
b.shape     # returns (3,)

# by Rule 1, we pad shape of b with ones (on the left!):
# a.shape --> (3, 1)
# b.shape --> (1, 3)

# by Rule 2, we upgrade each of these ones to match the corresponding size:
# a.shape --> (3, 3)
# b.shape --> (3, 3)

# The result matches, and shapes are compatible.
a + b

# Broadcasting ex3: (demonstrates incompatible shapes, rule 3)
M = np.ones((3, 2))
a = np.arange(3)

M.shape     # returns: (3, 2)
a.shape     # returns: (3,)

# by Rule 1, we pad shape of a with ones:
# M.shape --> (3, 2)
# a.shape --> (1, 3)

# by Rule 2, first dimension of 'a' is stretched to match M
# M.shape --> (3, 2)
# a.shape --> (3, 3)

# But we can't stretch M, as the dimension which doesn't match is != 1

# in this case, we could define an add for 'a' and M by padding a's shape
# with ones on the RIGHT, rather than left.
a[:, np.newaxis].shape
M + a[:, np.newaxis]

# NOTE: broadcasting behavior applies to all ufuncs, not just addition.

# Broadcasting in Practice:

# Centering an Array:
# Center an array of 10 observations, each containing 3 values.
X = np.random.random((10, 3))

Xmean = X.mean(0)       # Compute mean on first axis
X_centered = X - Xmean
X_centered.mean(0)      # Centered mean ~~= 0

# Plotting a 2D Function:
# We can display images based on two-dimensional functions.
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]

z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)

# %matplotlib inline
import matplotlib.pyplot as plt

plt.imshow(z, origin='lower', extent=[0, 5, 0, 5], cmap='viridis')
plt.colorbar()


# Title: Programming Assignment 1 Part A
# Due date: Wednesday, September 1, 2021 at 11:59pm
# Author: Sotheanith Sok
# Description: Practice working with matrices

# ------------------------------------------------------------------------------
# imports
import numpy as np

# Given
A = np.array([[1, -2, 4, 5], [3, -1, 9, -7], [8, 5, 4, 0], [0, -3, 2, 1]])
B = np.array([[3, 1, 2, 7], [4, 6, 5, 0], [-1, 3, 2, 5], [-6, -13, 0, -2]])
print("Given:\nA:\n%s\nB:\n%s\n" % (A, B))

# 1. How many rows A has?
num_row_A = A.shape[0]
print("1. How many rows A has?")
print("Ans: %s\n" % num_row_A)

# 2. Show the whole first to third rows of A?
first_to_third_rows_A = A[0:3, :]
print("2. Show the whole first to third rows of A?")
print("Ans:\n%s\n" % first_to_third_rows_A)

# 3. Show the sub-matrix of A starting from second row to the last row, and third column to the fourth one.
sub_matrix_A = A[1:, 2:]
print(
    "3. Show the sub-matrix of A starting from second row to the last row, and third column to the fourth one."
)
print("Ans:\n%s\n" % sub_matrix_A)

# 4. Add 10 to the first row of B, then add the first row to the second row (row1 = 10 + row1, row2 = row1 + row2). Next replace the first row of A with the second row of B.
B[0, :] = 10 + B[0, :]
B[1, :] = B[0, :] + B[1, :]
A[0, :] = B[1, :]
print(
    "4. Add 10 to the first row of B, then add the first row to the second row (row1 = 10 + row1, row2 = row1 + row2). Next replace the first row of A with the second row of B."
)
print("Ans:\nA:\n%s\nB:\n%s\n" % (A, B))

# 5. Find the elements of A less than 5 and greater or equal to -2. What are their indices?
indices = np.where(np.logical_and(A < 5, A >= -2))
indices = list(zip(indices[0], indices[1]))
print(
    "5. Find the elements of A less than 5 and greater or equal to -2. What are their indices?"
)
print("Ans: %s\n" % indices)

# 6. Find the first 6 indices corresponding to the nonzero entries of A.
indices = np.where(A != 0)
indices = list(zip(indices[0][:6], indices[1][:6]))
print("6. Find the first 6 indices corresponding to the nonzero entries of A.")
print("Ans: %s\n" % indices)

# 7. What is the smallest, largest, and average value of A?
smallest = A.min()
largest = A.max()
average = np.average(A)
print("7. What is the smallest, largest, and average value of A?")
print("Ans:\nSmallest: %s\nLargest: %s\nAverage: %s\n" % (smallest, largest, average))

# 8. Write a vector with equally spaced elements from 5 to 0, with a step of 0.3, but in decreasing order. What will be the size?
vec = np.arange(5, 0, -0.3)
print(
    "8. Write a vector with equally spaced elements from 5 to 0, with a step of 0.3, but in decreasing order. What will be the size?"
)
print("Ans: %s\n" % vec.shape[0])

# 9. Create a 3x4 matrix of random numbers between 0 and 1
matrix = np.random.rand(3, 4)
print("9. Create a 3x4 matrix of random numbers between 0 and 1")
print("Ans:\n%s\n" % matrix)


"""
@file           gram_schmidt.py
@description    Orthogonalize set using Gram-Schmidt Process
@author         Hiukong Dan
@version        1.0
@copyright      (c) Hiukong Dan 2021
@contact        ifsoboy9148@hotmail.com
"""

#!/bin/python
import numpy as np
from numpy import linalg
import random

def gram_schmidt(M):
    """
    @param M:
        A mxn matrix whose columns to be orthogonalized
    @return ret
        Matrix whose columns being orthogonalized
    """
    columns = M.T
    res = []
    res.append(columns[0])

    for x in range(1, columns.shape[0]):
        tmp = np.array([0 for x in range(M.shape[0])])
        for vec in res:
            y = (np.dot(vec, columns[x]) / np.dot(vec, vec)) * vec
            tmp = tmp + y

        res.append(columns[x] - tmp)

    return np.array(res).T

def orthonormalize(M):
    """
    @param M:
        The matrix whose columns to be orthonormalized
    @return ret:
        The outcome matrix
    """
    ret = M.T
    for x in range(ret.shape[0]):
        ret[x] = ret[x] / np.linalg.norm(ret[x])

    return ret.T

if __name__ == "__main__":
    """
    ret = gram_schmidt(np.arange(16).reshape(4,4))
    northonormal = orthonormalize(ret)
    """


"""
Please see
https://computationalmindset.com/en/neural-networks/ordinary-differential-equation-solvers.html#sys1
for details
"""

import numpy as np
import matplotlib.pyplot as plt

from scipy.integrate import solve_ivp

def ode_sys(t, X):
	x=X[0]
	dx_dt=X[1]
	d2x_dt2=-dx_dt - 2*x
	return [dx_dt, d2x_dt2]

an_sol_x = lambda t : \
	np.exp(-t/2.) * (np.cos(np.sqrt(7) * t / 2.) + \
	np.sin(np.sqrt(7) * t / 2.)/np.sqrt(7.))

t_begin=0.
t_end=12.
t_nsamples=100
t_space = np.linspace(t_begin, t_end, t_nsamples)
x_init = 1.
dxdt_init = 0.

x_an_sol = an_sol_x(t_space)

method = 'RK45' #available methods: 'RK45', 'RK23', 'DOP853', 'Radau', 'BDF', 'LSODA'
num_sol = solve_ivp(ode_sys, [t_begin, t_end], [x_init, dxdt_init], method=method, dense_output=True)
X_num_sol = num_sol.sol(t_space)
x_num_sol = X_num_sol[0].T

plt.figure()
plt.plot(t_space, x_an_sol, '--', linewidth=2, label='analytical')
plt.plot(t_space, x_num_sol, linewidth=1, label='numerical')
plt.title('ODE 2nd order IVP solved by SciPy with method=' + method)
plt.xlabel('t')
plt.ylabel('x')
plt.legend()
plt.show()


import numpy as np

# (NOTE) Sigmoid function used for logistic regression (non-linear regression)
# TODO Logistic function (Sigmoid function)
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

# TODO return gradient (derivative) of sigmoid function
def sigmoid_gradient(z):
    return sigmoid(z) * (1-sigmoid(z))


# Code by Desh Iyer

# Python program to generate a z-Table using numpy and pandas
# This is an easier alternative to scouring the internet to find a csv file of a z-Table imo

# Import libs
from scipy.integrate import quad
import numpy as np
import pandas as pd

# Function to calculate the y value for corresponding x using
# https://www.thoughtco.com/thmb/pYkVzv3wJGeULFip-z1t75557WA=/1500x1124/filters:no_upscale():max_bytes(150000):strip_icc()/bellformula-56b749555f9b5829f8380dc8.jpg
# Where for a standard normal dist, mean = 0, sigma = 1
def normalProbabilityDensity(x):
    constant = 1.0 / np.sqrt(2 * np.pi)
    returnValue = constant * np.exp((-x ** 2) / 2.0)

    return returnValue

# Create a pandas dataframe with rows and cols indexed appropriately for positive and negative z-Tables

# P.S: I know that I could've just created one z-Table with both pos and neg values but its a lot easier to read if there are two separate tables for pos and neg

zPositiveTable = pd.DataFrame(
    data = [],
    index = np.round(np.arange(0, 4.1, .1), 2),
    columns = np.round(np.arange(0.00, .1, .01), 2)
)

zNegativeTable = pd.DataFrame(
    data = [],
    index = np.round(np.arange(0, -4.1, -.1), 2),
    columns = np.round(np.arange(0.00, .1, .01), 2)
)

# Set the cell values for the dataframe using the function to calc PDF of a Standard Normal Distribution
for index in zPositiveTable.index:
    for column in zPositiveTable.columns:
        z = np.round(index + column, 2)
        # Calculate the definite integral of the PDF using quad() and formula https://miro.medium.com/max/523/1*kIhMH2ny-EwkZJm-jV4BdQ.png
        value, _ = quad(normalProbabilityDensity, np.NINF, z)
        # Assign cell the value
        zPositiveTable.loc[index, column] = value

# Same for negative table in case i want to generate a different number of rows and cols than the positive one
for index in zNegativeTable.index:
    for column in zNegativeTable.columns:
        z = np.round(index + column, 2)
        value, _ = quad(normalProbabilityDensity, np.NINF, z)
        zNegativeTable.loc[index, column] = value

print(f'Positive Z Table\n\n{zPositiveTable}\n\n\nNegative Z Table\n\n{zNegativeTable}')


"""
Dado un array 1D que tiene una cantidad enorme de entradas,
por ejemplo demasiado grande como para poder graficar en un heatmap,
obtener un array 1D nuevo como resultado de promediar el array
original "de a cachos".
"""
#  Chunking function
def chunks(l, n):
    for i in xrange(0, len(l), n):
        yield l[i:i+n]

# Resampling function
def resample(arr, newLength):
    chunkSize = len(arr)/newLength
    return [np.mean(chunk) for chunk in chunks(arr, chunkSize)]

"""
Example:
import numpy as np
x = np.linspace(-1,1,15)
y = resample(x, 5)
print y
# Result:
# [-0.85714285714285721, -0.4285714285714286, -3.7007434154171883e-17, 0.42857142857142844, 0.8571428571428571]
"""


import numpy as np
from matplotlib import pyplot as plt
from math import sqrt, exp
from scipy.linalg import eig
from scipy.integrate import quad

# function shifting problem
def shift_function(f, a, b):
    """ 'f' is a callable funciton, 'a' and 'b' are
    the limits of the interval you want to consider."""
    return lambda x: f((b - a) * x / 2. + (b + a) / 2)

# plotting part of the shifting problem
# The easy way, not the way in the lab.
def funcplot(f, a, b, n=401):
    """ Constructs and plots the example given in the
    problem on shifting the domain of a function to [-1, 1].
    'n' is the number of points to use to generate the plot."""
    X1 = np.linspace(a, b, n)
    X2 = np.linspace(-1, 1, n)
    Y = f(X1)
    plt.plot(X1, Y)
    plt.show()
    plt.plot(X2, ((b - a) / 2.) * Y)
    plt.show()

# alternate version matching what the lab describes
def funcplot2(f, a, b, n=401):
    """ Constructs and plots the example given in the
    problem on shifting the domain of a function to [-1, 1].
    'n' is the number of points to use to generate the plot."""
    X1 = np.linspace(a, b, n)
    g = shift_function(f, a, b)
    plt.plot(X1, f(X1))
    plt.show()
    X2 = np.linspace(-1, 1, n)
    plt.plot(X2, ((b - a) / 2.) * g(X2))
    plt.show()

# example in the function shifting problem
def shift_example(n=401):
    """ Plot the example given in the function shifting problem."""
    f = np.poly1d([1, 0, 0])
    funcplot(f, 1, 4)

# integral estimation problem
def estimate_integral(f, a, b, points, weights):
    """ Estimate the value of an integral given
    the function 'f', the interval bounds 'a' and 'b',
    the nodes to use for sampling, and their
    corresponding weights."""
    g = lambda x: f((b - a) * x / 2. + (b + a) / 2)
    return ((b - a) / 2.) * np.inner(weights, g(points))

# jacobi construction problem
def construct_jacobi(a, b, c):
    """ Construct the Jacobi matrix given the
    sequences 'a', 'b', and 'c' from the
    three term recurrence relation."""
    alpha = - b / a
    beta = np.sqrt(c[1:] / (a[:-1] * a[1:]))
    i = np.arange(1, a.size+1, dtype=float)
    j = np.zeros((a.size, a.size))
    np.fill_diagonal(j, alpha)
    np.fill_diagonal(j[1:], beta)
    np.fill_diagonal(j[:,1:], beta)
    return j

# points and weights problem
def points_and_weights(n):
    """ Find the set of 'n' nodes and their
    corresponding weights for the interval [-1, 1]."""
    i = np.arange(1, n + 1, dtype=float)
    a = (2 * i - 1) / i
    b = np.zeros_like(i)
    c = (i - 1) / i
    j = construct_jacobi(a, b, c)
    # terribly slow to do it this way...
    evals, evects = eig(j)
    return evals, evects[0]**2 * 2

# normal distribution cdf problem
def normal_cdf(x):
    """Compute the CDF of the standard normal
    distribution at the point 'x'."""
    pdf = lambda x: exp(- x**2 / 2.) / sqrt(2 * np.pi)
    return quad(pdf, -5, x)[0]


"""
This file contains the solution to the problem
2.8.4 of the book `Nonlinear Dynamics and Chaos` by Steven H. Strogatz.
"""
import numpy as np
import matplotlib.pyplot as plt

def exact_solution(t, x0=1.0):
    """
    The implementation of the exact solution to the differential equation
    x' = -x
    with the initial condition x(t=0)=`x0` (default x0=1, as in the problem).
    """
    return x0 * np.exp(-t)

def improved_euler_method(f, t0: float, x0: float, timestep: float, end: float, exact_solution=None):
    """
    Implementation of the improved euler method to numerically compute the solution
    to the differential equation

                x'=f(x)

    Parameters
    ----------
    f: function
        The implementation of the function `f` appearing in the differential
        equation.
    t0: float
        The initial time.
    x0: float
        The initial condition to the differential equation, i.e. the value
        of x(t=t0).
    timestep: float
        The timestep to employ for the numerical solution of the differential
        equation.
    end: float
        The maximal time step up to which to compute the the solution.
    exact_solution: function
        The exact solution. If the value is different from `None` the exact
        solution will
        be evaluated at each time step and the corresponding values will be
        returned in order
        to be able to check the convergence of the numerical solution.

    """
    if end < t0:
        raise ValueError("Initial time is larger than the end time!")

    # Store the time steps
    time_steps = [t0]
    # Store the value at each time step
    values = [x0]
    # Store the exact values of the solutions at each time step, if the exact
    # solution is provided
    if exact_solution:
        exact_values = [exact_solution(t0)]

    # Now start solving the differential equation numerically
    t = t0
    x = x0
    while t < end:
        t = t + timestep
        time_steps.append(t)
        x_tilde = x + f(x) * timestep
        x = x + 0.5 * (f(x) + f(x_tilde)) * timestep
        values.append(x)
        if exact_solution:
            exact_values.append(exact_solution(t))

    return time_steps, values, None if not exact_solution else exact_values

if __name__ == "__main__":

    x0 = 1
    t0 = 0
    stop = 1.0

    # Part a) of the exercise
    print(f"\nPart a):")
    print(f"The exact value of x(t) is 1/e, or approximately 1/e = {np.exp(-1)}")

    # Part b) of the exercise
    print(f"\nPart b):")
    time_steps, values, exact_values = improved_euler_method(lambda x: -x, t0, x0, 1.0, stop, exact_solution)
    print(f"time steps  : {time_steps}")
    print(f"values      : {values}")
    print(f"exact values: {exact_values}")

    step_size_solutions = {}
    for n in [1, 2, 3, 4]:
        time_steps, values, exact_values = improved_euler_method(lambda x: -x, t0, x0, 10**(-n), stop, exact_solution)
        step_size_solutions[n] = {"time_steps": time_steps, "values": values, "exact": exact_values}

    # Plot the different solutions and compare
    # Exact
    exact_time = np.linspace(0, 1, 100)
    exact = exact_solution(exact_time)
    plt.plot(exact_time, exact, label='exact')
    for n in [1, 2, 3, 4]:
        plt.plot(step_size_solutions[n]['time_steps'], step_size_solutions[n]['values'], label=f"n={n}")

    plt.title(r'Numerical vs. exact solution for different time steps ($10^{-n}$)')
    plt.xlabel(r'$t$')
    plt.ylabel(r'$x(t)$')
    plt.legend()
    plt.show()

    # Part a) of the exercise
    print(f"\nPart c):")
    print(f"Plotting error vs. step size.")
    errors = []
    for n in [1, 2, 3, 4]:
        error = np.abs(step_size_solutions[n]['values'][-1] - step_size_solutions[n]['exact'][-1])
        errors.append(error)

    plt.plot([1, 2, 3, 4], errors)
    plt.title(r'Error vs step size $10^{-n}$')
    plt.xlabel(r'$n$')
    plt.ylabel(r'$|\hat{x}(1)-x(1)|$')
    plt.tight_layout()
    plt.show()
    # plt.savefig('error_vs_stepsize.pdf')

    # Plot ln(E) vs ln(t)
    print("Plotting error evolution with time")
    for n in [1, 2, 3, 4]:
        t = step_size_solutions[n]['time_steps']
        values = np.array(step_size_solutions[n]['values'])
        exact = np.array(step_size_solutions[n]['exact'])
        errors = np.abs(values-exact)
        plt.plot(np.log(t), np.log(errors), label=f"n={n}")

    plt.title(r'Error evolution with time for different step sizes ($10^{-n}$).')
    plt.xlabel(r'$\ln{(t)}$')
    plt.ylabel(r'$\ln{(|\hat{x}(t)-x(t)|)}$')
    plt.legend()
    plt.minorticks_on()
    plt.grid(True, which='both')
    plt.show()
    # plt.savefig('error_evolution_in_time_log_scale.pdf')


'''
Author: zyq
Date: 2020-12-01 09:56:13
LastEditTime: 2020-12-14 22:02:45
LastEditors: Please set LastEditors
Description: Least squares approximation, 最佳平方逼近, 上机题 3
FilePath: /numerical_analysis/code/chapter4/q4_2.py
'''
import numpy as np
import sys, os
import matplotlib.pyplot as plt
from pylab import mpl
import math

'''
description:
param {*} x n+1 个插值点
param {*} y n+1 个插值点
return {*} n
'''
def PrejudgmentShape(x, y):
    n1 = len(x)
    n2 = len(y)
    if n1 != n2:
        print('x 与 y 长度不相等')
        sys.exit()

    n = n1-1
    return n

'''
description: 求最佳平方逼近的正规方程
param {*} m m次最佳平方逼近
param {*} x n个自变量x
param {*} y n个因变量y
return {*} (A, b)
'''
def GetNormalEquation(m, x, y):
    A = []
    b = []
    PrejudgmentShape(x, y)
    for j in range(0,m+1):
        AAline = []
        for i in range(0, m+1):
            tmp = np.dot(np.power(x, j), np.power(x, i))
            AAline.append(tmp)
        A.append(AAline)
        tmp = np.dot(y, np.power(x, j))
        b.append(tmp)
    return A, b

'''
description: 获取最佳平方逼近的多项式
param {*} A 正规方程系数A
param {*} b 正规方程系数b
return {*} c 多项式系数，q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m
'''
def GetExpression(A, b):
    c = MGauss_Caculate(A, b)
    return c

'''
description: 计算最佳平方逼近多项式
param {*} m m次最佳平方逼近
param {*} x n个自变量x
param {*} y n个因变量y
return {*} c 最佳平方逼近多项式系数，q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m
'''
def GetLeastSquaresApproximationExpression(m, x, y):
    A, b = GetNormalEquation(m, x, y)
    c = GetExpression(A, b)
    return c

'''
description: 计算多项式，q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m
param {*} c 最佳平方逼近多项式系数
return {*} 多项式结果
'''
def CaculateExpression(c, x):
    res = 0
    for i, ci in enumerate(c):
        res = res + ci * pow(x, i)
    return res

'''
description: 计算多项式, x form x0 to xn, q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m
param {*} c 最佳平方逼近多项式系数
param {list} xn
return {*}
'''
def CaculateInterval(c, xn):
    res = []
    for x in xn:
        res.append(CaculateExpression(c, x))
    return res

"""
功能：将函数绘制成图像
参数：data_x, data_y 为原始值，new_data_x, new_data_y 为预测计算的值。
返回值：空
"""
def Draw(data_x,data_y,new_data_x,new_data_y, title):
    plt.plot(new_data_x, new_data_y, label="拟合曲线", color="black")
    plt.scatter(data_x,data_y, label="离散数据",color="red")
    mpl.rcParams['font.sans-serif'] = ['SimHei']
    mpl.rcParams['axes.unicode_minus'] = False
    plt.title(title)
    plt.legend(loc="upper right")
    plt.savefig(os.path.join(os.path.dirname(os.path.abspath(__file__)), title+'.png'), dpi=300)
    plt.show()

'''
description: 计算拟合误差 Q = \Sigma_{k=1}^{n} (q(x_k) - y_k)^2
param {*} c 最佳平方逼近多项式系数, q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m
param {*} x
param {*} y
return {*} Q
'''
def GetFittingError(c, x, y):
    Err = 0
    for n, xn in enumerate(x):
        qx = CaculateExpression(c, xn)
        Err += pow((qx - y[n]), 2)
    return Err

'''
description: 别求出其 2, 3, 4 次最佳平方逼近多项式.画出图形, 并比较拟合误差
param {*}
return {*}
'''
def Q1():
    x = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    y = [58, 50, 44, 38, 34, 30, 29, 26, 25, 24]

    result_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '最佳平方逼近_result.txt')

    if os.path.exists(result_file):
        os.remove(result_file)

    # 最佳平方逼近
    for m in range(2, 5):
        c = GetLeastSquaresApproximationExpression(m, x, y)
        # 画图
        new_x = np.arange(x[0]-0.5, x[-1]+0.6, 0.1)
        new_y = CaculateInterval(c, new_x)
        Draw(x, y, new_x, new_y, "{0}次最佳平方逼近".format(m))
        # 拟合误差
        FittingErr = GetFittingError(c, x, y)
        # 写入文件
        with open(result_file, "a+") as fo:
            fo.write("\n\\********************************************************\\\n")
            fo.write("{0} 次最佳平方逼近, m = {1}\n".format(m, m))
            fo.write("x = ")
            for xi in x:
                fo.write(str(xi))
                fo.write("\t")
            fo.write("\n")
            fo.write("y = ")
            for yi in y:
                fo.write(str(yi))
                fo.write("\t")
            fo.write("\n")
            fo.write("近似函数：q(x) = c0 + c1*x + c2*x^2 + ... + cm*x^m \n")
            fo.write("c = ")
            for ci in c:
                fo.write(str(format(ci, '.6g')))
                fo.write("\t")
            fo.write("\n")
            fo.write("拟合误差：{0}\n".format(format(FittingErr, '.8g')))
            fo.write("\\********************************************************\\\n")

# a+b÷x近似
def Q2_1():
    x = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    y = [58, 50, 44, 38, 34, 30, 29, 26, 25, 24]

    result_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'a+b÷x近似_result.txt')

    # a + b/x
    x1 = [1.0/xi for xi in x]
    c = GetLeastSquaresApproximationExpression(1, x1, y)
    # 画图
    new_x = np.arange(x[0]-0.5, x[-1]+0.6, 0.1)
    new_x1 = [1.0/xi for xi in new_x]
    new_y = CaculateInterval(c, new_x1)
    Draw(x, y, new_x, new_y, "a+b÷x近似")
    # 拟合误差
    FittingErr = GetFittingError(c, x1, y)
    # 写入文件
    with open(result_file, "w+") as fo:
        fo.write("\n\\********************************************************\\\n")
        fo.write("a+b÷x近似\n")
        fo.write("x = ")
        for xi in x:
            fo.write(str(xi))
            fo.write("\t")
        fo.write("\n")
        fo.write("y = ")
        for yi in y:
            fo.write(str(yi))
            fo.write("\t")
        fo.write("\n")
        fo.write("近似函数：q(x) = {} + {} / x \n".format(c[0], c[1]))
        fo.write("拟合误差：{0}\n".format(FittingErr))
        fo.write("\\********************************************************\\\n")

# a+blnx近似
def Q2_2():
    x = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    y = [58, 50, 44, 38, 34, 30, 29, 26, 25, 24]

    result_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'a+blnx近似_result.txt')

    # a + blnx
    x1 = [math.log(xi) for xi in x]
    c = GetLeastSquaresApproximationExpression(1, x1, y)
    # 画图
    new_x = np.arange(x[0]-0.5, x[-1]+0.6, 0.1)
    new_x1 = [math.log(xi) for xi in new_x]
    new_y = CaculateInterval(c, new_x1)
    Draw(x, y, new_x, new_y, "a+blnx近似")
    # 拟合误差
    FittingErr = GetFittingError(c, x1, y)
    # 写入文件
    with open(result_file, "w+") as fo:
        fo.write("\n\\********************************************************\\\n")
        fo.write("a+blnx近似\n")
        fo.write("x = ")
        for xi in x:
            fo.write(str(xi))
            fo.write("\t")
        fo.write("\n")
        fo.write("y = ")
        for yi in y:
            fo.write(str(yi))
            fo.write("\t")
        fo.write("\n")
        fo.write("近似函数：q(x) = {} + {} ln(x) \n".format(c[0], c[1]))
        fo.write("拟合误差：{0}\n".format(FittingErr))
        fo.write("\\********************************************************\\\n")

'''
description: 计算拟合误差 Q = \Sigma_{k=1}^{n} (q(x_k) - y_k)^2
param {*} c 最佳平方逼近多项式系数, q(x) = a*exp(bx)
param {*} x
param {*} y
return {*} Q
'''
def GetFittingError_q2_3(c, x, y):
    Err = 0
    for n, xn in enumerate(x):
        qx1 = CaculateExpression(c, xn)
        qx = math.exp(qx1)
        Err += pow((qx - y[n]), 2)
    return Err

# a*exp(bx)近似
def Q2_3():
    x = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    y = [58, 50, 44, 38, 34, 30, 29, 26, 25, 24]

    result_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'a*exp(bx)近似_result.txt')

    # a + blnx
    y1 = [math.log(yi) for yi in y]
    c = GetLeastSquaresApproximationExpression(1, x, y1)
    # 画图
    new_x = np.arange(x[0]-0.5, x[-1]+0.6, 0.1)
    new_y1 = CaculateInterval(c, new_x)
    new_y = [math.exp(yi) for yi in new_y1]
    Draw(x, y, new_x, new_y, "a*exp(bx)近似")
    # 拟合误差
    FittingErr = GetFittingError_q2_3(c, x, y)
    # 写入文件
    with open(result_file, "w+") as fo:
        fo.write("\n\\********************************************************\\\n")
        fo.write("a*exp(bx)近似\n")
        fo.write("x = ")
        for xi in x:
            fo.write(str(xi))
            fo.write("\t")
        fo.write("\n")
        fo.write("y = ")
        for yi in y:
            fo.write(str(yi))
            fo.write("\t")
        fo.write("\n")
        fo.write("近似函数：q(x) = {0} * exp({1} * x) \n".format(math.exp(c[0]), c[1]))
        fo.write("拟合误差：{0}\n".format(FittingErr))
        fo.write("\\********************************************************\\\n")

'''
description: 计算拟合误差 Q = \Sigma_{k=1}^{n} (q(x_k) - y_k)^2
param {*} c 最佳平方逼近多项式系数, q(x) = 1/(a+bx)
param {*} x
param {*} y
return {*} Q
'''
def GetFittingError_q2_4(c, x, y):
    Err = 0
    for n, xn in enumerate(x):
        qx1 = CaculateExpression(c, xn)
        qx = 1.0/qx1
        Err += pow((qx - y[n]), 2)
    return Err

# 1÷(a+bx)近似
def Q2_4():
    x = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    y = [58, 50, 44, 38, 34, 30, 29, 26, 25, 24]

    result_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '1÷(a+bx)近似_result.txt')

    # a + blnx
    y1 = [1.0/yi for yi in y]
    c = GetLeastSquaresApproximationExpression(1, x, y1)
    # 画图
    new_x = np.arange(x[0]-0.5, x[-1]+0.6, 0.1)
    new_y1 = CaculateInterval(c, new_x)
    new_y = [1.0/yi for yi in new_y1]
    Draw(x, y, new_x, new_y, "1÷(a+bx)近似")
    # 拟合误差
    FittingErr = GetFittingError_q2_4(c, x, y)
    # 写入文件
    with open(result_file, "w+") as fo:
        fo.write("\n\\********************************************************\\\n")
        fo.write("1÷(a+bx)近似\n")
        fo.write("x = ")
        for xi in x:
            fo.write(str(xi))
            fo.write("\t")
        fo.write("\n")
        fo.write("y = ")
        for yi in y:
            fo.write(str(yi))
            fo.write("\t")
        fo.write("\n")
        fo.write("近似函数：q(x) = 1 ÷ ( {0} + {1} * x) \n".format(math.exp(c[0]), c[1]))
        fo.write("拟合误差：{0}\n".format(FittingErr))
        fo.write("\\********************************************************\\\n")

def main():
    Q1()
    Q2_1()
    Q2_2()
    Q2_3()
    Q2_4()

if __name__ == "__main__":

    # 获取当前文件路径
    current_path = os.path.abspath(__file__)
    sys.path.append(os.path.abspath(os.path.join(os.path.dirname(current_path), '../')))
    # print(sys.path)
    # 调用 chapter3 中的列主元高斯消去法
    from chapter3.q3 import MGauss_Caculate

    main()


##Problema 1

#Mi RUT: 19.687.559-K

#Se pide comparar la precision de 2 metodos de derivacion discreta, uno de error de orden 4 (O(h^4))
#descrito en el enunciado y otro de orden 1 (O(h)) ensenado en clases. Esto para f(x) = -cos(x); f'(x) = sin(x), para
#x = 1.XXX, donde XXX son los 3 ultimos numeros de mi RUT antes del digito verificador (XXX = 559). Primero se
#realizara la comparacion usando numeros float32 y luego float64 (este computador no puede usar float128),
#comparando tambien el resultados entre ellos.

#f'(x) =(-f(x+2*h)+8*f(x+h)-8*f(x-h)+f(x-2*h))/(12*h) + O(h^4)

#f'(x) = (f(x+h) - f(x))/h + O(h)

#Antes que nada, se deben importar los modulos a utilizar

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

#Definimos ahora las derivadas, para float32 y float64 respectivamente

#Float32:

def derivada_orden_1_float32(x,h):
    """ x en radianes
    """
    y = np.float32(x)
    e = np.float32(h)
    df_dy_1 =  (np.float32(-np.cos(y+e)) - (np.float32(-np.cos(y))))/h
    return np.float32(df_dy_1)

def derivada_orden_4_float32(x,h):
    """ x en radianes
    """
    y = np.float32(x)
    e = np.float32(h)
    df_dy_4 = (-(np.float32(-np.cos(y+2*e)))+8*(np.float32(-np.cos(y+e)))-8*(np.float32(-np.cos(y-e)))+(np.float32(-np.cos(y-2*e))))/(12*e)
    return np.float32(df_dy_4)

#Float64 (default):

def derivada_orden_1(x,h):
    """ x en radianes
    """
    df_dx_1 =  (-np.cos(x+h) - (-np.cos(x)))/h
    return df_dx_1

def derivada_orden_4(x,h):
    """ x en radianes
    """
    df_dx_4 = (-(-np.cos(x+2*h))+8*(-np.cos(x+h))-8*(-np.cos(x-h))+(-np.cos(x-2*h)))/(12*h)
    return df_dx_4

k = 1.559  #x con el que se probara la derivada

seno_float32 = np.float32(m.sin(k)) #valor de la derivada con float32
seno = m.sin(k)  #valor real al que se quiere llegar con la aproximacion con foat 64

#Se debe escoger un rango apropiado de h pequeños, tal
#que la aproximacion sea cercana al valor real. Para esto, se probara con potencias
#negativas de 10.

H = np.logspace(-0, -18, 15, base=10.)   #vector de h

#Primero se haran los graficos de las derivadas para cada h en H, y se comparara con el valor de sin(1.559)

#Grafico orden 1
"""
plt.title("Valor de la derivada (error de orden 1)")

plt.plot(H, derivada_orden_1_float32(k,H), label="float32")
plt.plot(H, derivada_orden_1(k,H), label="float64")

plt.xscale('log')

plt.axhline(seno, color='r', label='valor real')

plt.xlabel('h', fontsize=20)
plt.ylabel('$\sin(1.559)_{aprox}$', fontsize=10)

plt.legend()
plt.savefig('derivada_orden_1.png')
plt.show()
"""

#Grafico orden 4
"""
plt.title("Valor de la derivada (error de orden 4)")

plt.plot(H, derivada_orden_4_float32(k,H), label="float32")
plt.plot(H, derivada_orden_4(k,H), label="float64")

plt.xscale('log')

plt.axhline(seno, color='r', label='valor real')

plt.xlabel('h', fontsize=20)
plt.ylabel('$\sin(1.559)_{aprox}$', fontsize=10)

plt.legend()
plt.savefig('derivada_orden_4.png')
plt.show()
"""

#Otros graficos que pueden ser utiles son los de la diferencia del valor real de la derivada
#con el aproximado (valor absoluto) para ambos floats.

diferencia_orden_1_float32 = [np.float32(m.fabs(derivada_orden_1_float32(k,h) - seno_float32)) for h in H]
diferencia_orden_1 = [m.fabs(derivada_orden_1(k,h) - seno) for h in H]

diferencia_orden_4_float32 = [np.float32(m.fabs(derivada_orden_4_float32(k,h) - seno_float32)) for h in H]
diferencia_orden_4 = [m.fabs(derivada_orden_4(k,h) - seno) for h in H]

#Grafico orden 1
"""
plt.title("Diferencia con la derivada (error de orden 1)")

plt.plot(H, diferencia_orden_1_float32, label="float32")
plt.plot(H, diferencia_orden_1, label="float64")

plt.xscale('log')
plt.yscale('log')

plt.xlabel('h', fontsize=20)
plt.ylabel('$|\sin_(1.559){aprox} - \sin(1.559)_{real}|$', fontsize=10)

plt.legend()
plt.savefig('diferencia_orden_1.png')
plt.show()
"""

#Grafico orden 4
"""
plt.title("Diferencia con la derivada (error de orden 4)")

plt.plot(H, diferencia_orden_4_float32, label="float32")
plt.plot(H, diferencia_orden_4, label="float64")

plt.xscale('log')
plt.yscale('log')

plt.xlabel('h', fontsize=20)
plt.ylabel('$|\sin_(1.559){aprox} - \sin(1.559)_{real}|$', fontsize=10)

plt.legend()
plt.savefig('diferencia_orden_4.png')
plt.show()
"""


import numpy as np
from scipy.interpolate import lagrange

def chebNodes(N):
    "Returns N Chebyshev nodes in (-1,1)."
    return np.array([np.cos(
        np.pi * (2*k-1)/(2*N)) for k in range(1,N+1)])

def l(j,q):
    "Returns the j-th lagrange basis polynomial on the nodes q."
    myf = len(q)*[0]
    myf[j] = 1
    return lagrange(q, myf)

def intL(j,q):
    """Returns the definite integral between -1, 1 of the the
    j-th lagrange basis polynomial on the nodes q."""
    return l(j,q).integ()(1) - l(j,q).integ()(-1)

def getWeights(q):
    "Returns weights associated with nodes q, using lagrange interp."
    return [intL(j,q) for j in range(len(q))]

# Documentation states that lagrange polynomials become numerically instable
# after 20 nodes; we try up to 30.
for n in range(30):
    for x in getWeights(chebNodes(n)):
        if x <= 0:
            print(n, x)
            raise Exception("Found negative weight.")
print("No negative weights found for n<30.\n")

# Actually for 37 nodes we get the first negative weights.
print("Negative weights for 37 Cheb nodes:")
cheb37 = getWeights(chebNodes(37))
for x in cheb37:
        if x <= 0:
            print(x)

# Is this due to numerical errors? To answer this question, notice that Cheb
# weights, as they were defined, should be symmetric with respect to 0.
# However for n=37:
print("\nThe following pairs should be equal:")
for j in range(18):
    print(cheb37[j],cheb37[36-j])


import numpy as np

np.eye(3)
# array([[1., 0., 0.],
#        [0., 1., 0.],
#        [0., 0., 1.]])

mat = np.array([[1, 2], [3, 4]])
mat.transpose()
# array([[1, 3],
#        [2, 4]])
mat.T
# array([[1, 3],
#        [2, 4]])

A = np.array([[1, 2], [3, 4]])
A.trace()  # 5

A = np.array([[1, 2], [3, 4]])
B = np.array([[-1, 1], [0, 1]])
A @ B
# array([[-1, 3],
#        [-3, 7]])
A * B
# array([[-1, 2],
#        [ 0, 4]])

A = np.array([[1, 2], [3, 4]])
I = np.eye(2)
A @ I
# array([[1, 2],
#        [3, 4]])

from numpy import linalg
linalg.det(A)  # -2.0000000000000004
linalg.inv(A)
# array([[-2. , 1. ],
#        [ 1.5, -0.5]])

Ainv = linalg.inv(A)
Ainv @ A
# Approximately
# array([[1., 0.],
#       [0., 1.]])

A @ Ainv
# Approximately
# array([[1., 0.],
#        [0., 1.]])


import numpy as np
import matplotlib.pylab as plt

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

def grad_f(x):
    return np.array([
        40*x[0] * (x[0]**2 - x[1]) + 2*x[0] - 2,
        -20*x[0]**2 + 20*x[1]
    ])

def hessian_f(x):
    return np.array([
        [120 * x[0]**2 - 40*x[1] + 2, -40*x[0]],
        [-40*x[0], 20],
    ])

# plot contours
x = np.linspace(-0.5, 2.0, 100)
y = np.linspace(-1.5, 2.0, 100)
X, Y = np.meshgrid(x, y)
Z = f([X, Y])
plt.contour(X, Y, Z, levels=30)
min_x = np.array([1., 1.])

for x0 in [np.array([0., -1.]), np.array([0., 0.5])]:
    N = 20
    x = np.empty((N, 2), dtype=float)
    plt.plot(x0[0], x0[1], '.')
    plt.plot(min_x[0], min_x[1], '+')
    for i, delta in enumerate(np.linspace(1e-3, 2, N)):
        # if minimum of f is within radius, this is the min point, otherwise it is
        # somewhere on the border of the search radius
        if np.sum((min_x - x0)**2) < delta**2:
            x[i, :] = min_x
        else:
            min_f = 1e10
            for omega in np.linspace(0, 2*np.pi, 200):
                test_x = x0 + delta * np.array([np.cos(omega), np.sin(omega)])
                test_f = f(test_x)
                if test_f < min_f:
                    x[i, :] = test_x
                    min_f = test_f
    plt.plot(x[:, 0], x[:, 1], '-.', linewidth=2)

plt.show()

def line_sphere_intersect(o, u, r2):
    """
    algorithm from https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection

    here we assume that the line intersects the sphere, and we are only interested in
    the most positive solution
    """
    u_dot_o = u.dot(o)
    det = u_dot_o**2 - (np.dot(o, o) - r2)
    return -u_dot_o + np.sqrt(det)

def dogleg(delta, g, B, x0):
    """
    implements the dogleg method of minimising m(p)

    returns min p and a boolean that is True if min p lies on the trust region border
    """
    delta2 = delta**2
    pB = - np.linalg.solve(B, g)

    # if pB is within trust region then use that
    if np.dot(pB, pB) < delta2:
        return pB, False

    pU = - np.dot(g, g) / np.dot(g, B @ g) * g

    # find intersection of segment x0 -> pU -> pB with trust region
    if np.dot(pU, pU) > delta2:
        u = (pU - x0) / np.linalg.norm(pU - x0)
        intersect = line_sphere_intersect(x0, u, delta2)
        return x0 + intersect * u, True
    else:
        u = (pB - pU) / np.linalg.norm(pB - pU)
        intersect = line_sphere_intersect(pU, u, delta2)
        return pU + intersect * u , True

def minimise(f, grad_f, hessian_f, x0, tol=1e-5, max_iter=1000):
    nu = 0.2
    delta_hat = 1.0
    delta = delta_hat
    a = np.copy(x0)
    a_store = np.empty((max_iter+1, len(x0)), dtype=float)
    a_store[0, :] = a
    j = 1
    for i in range(max_iter):
        g = grad_f(a)
        B = hessian_f(a)
        f_a = f(a)
        p, p_equal_delta = dogleg(delta, g, B, a)
        rho = (f_a - f(a + p)) / (-g.dot(p) - 0.5*p.dot(B @ p))
        if rho < 0.25:
            delta *= 0.25
        else:
            if rho > 0.75 and p_equal_delta:
                delta = min(2*delta, delta_hat)
        if rho > nu:
            a += p
            a_store[j, :] = a
            j += 1
            if np.linalg.norm(p) < tol:
                break
    return a_store[:j, :]

# plot contours
x = np.linspace(-0.5, 2.0, 100)
y = np.linspace(-1.5, 2.0, 100)
X, Y = np.meshgrid(x, y)
Z = f([X, Y])
plt.contour(X, Y, Z, levels=30)
min_x = np.array([1., 1.])

# do the minimisation for both points and plot all iterations
for x0 in [np.array([0., -1.]), np.array([0., 0.5])]:
    plt.plot(x0[0], x0[1], '.')
    plt.plot(min_x[0], min_x[1], '+')
    a_store = minimise(f, grad_f, hessian_f, x0)
    print('done in', a_store.shape[0], 'iterations')
    plt.plot(a_store[:, 0], a_store[:, 1])

plt.show()


import numpy as np
import matplotlib.pyplot as plt

def estimate_coef(x, y):
    # number of observations/points
    n = np.size(x)

    # mean of x and y vector
    m_x, m_y = np.mean(x), np.mean(y)

    # calculating cross-deviation and deviation about x
    SS_xy = np.sum(y*x) - n*m_y*m_x
    SS_xx = np.sum(x*x) - n*m_x*m_x

    # calculating regression coefficients
    b_1 = SS_xy / SS_xx
    b_0 = m_y - b_1*m_x

    return(b_0, b_1)

def plot_regression_line(x, y, b):
    # plotting the actual points as scatter plot
    plt.scatter(x, y, color = "m",
                marker = "o", s = 30)

    # predicted response vector
    y_pred = b[0] + b[1]*x

    # plotting the regression line
    plt.plot(x, y_pred, color = "g")

    # putting labels
    plt.xlabel('x')
    plt.ylabel('y')

    # function to show plot
    plt.show()

def main():
    # observations
    x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    y = np.array([1, 3, 2, 5, 7, 8, 8, 9, 10, 12])

    # estimating coefficients
    b = estimate_coef(x, y)
    print("Estimated coefficients:\nb_0 = {} \
    \nb_1 = {}".format(b[0], b[1]))

    # plotting regression line
    plot_regression_line(x, y, b)

    if __name__ == "__main__":
        main()


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

IMAGE_SIZE = 60  # size of zipped image

def pca(data, dim):
    mean = np.mean(data, axis=0)
    mat = data - mean
    cov = np.cov(mat, rowvar=0)
    eigenVal, eigen_vec = np.linalg.eig(np.mat(cov))
    index = np.argsort(eigenVal)[:-(dim + 1):-1]
    eigen_vec = eigen_vec[:, index]
    low_data = mat * eigen_vec
    return low_data, low_data * eigen_vec.T + mean

def psnr(a, b):
    return np.abs(20 * np.log10(255.0 / np.sqrt(np.mean((b-a)**2))))

def read(path):
    image = Image.open(path).convert('L')
    image = image.resize((IMAGE_SIZE, IMAGE_SIZE),
                         Image.ANTIALIAS)
    return np.real(np.mat(image))

if __name__ == '__main__':
    path = ['./jaffe/' + str(i + 1) + '.tiff' for i in range(23)]
    pictures = np.array([read(i) for i in path])
    print(pictures[0].shape)
    pictures = pictures.reshape(
        [pictures.shape[0], pictures.shape[1] * pictures.shape[1]])
    ans = pca(pictures, 5)
    result = ans[1]
    print(result[0].shape)
    # for i in range(len(result)):
    #     pic = pictures[i].reshape(IMAGE_SIZE, IMAGE_SIZE)
    #     res = result[i].reshape(IMAGE_SIZE, IMAGE_SIZE)
    #     print(psnr(pic, res))


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from copy import deepcopy
from sklearn.decomposition import PCA
from sklearn.preprocessing import MinMaxScaler

EPS = 0.001
INF = 10000000
MAX_ITERATIONS = 100
COLORS = ["r", "g", "b", "c", "m", "y", "k"]

def min_index(k, feature, mean):
    return np.argmin([np.linalg.norm(feature - mean[i]) for i in range(k)])

def k_means(k, data):
    mean = [data[np.random.randint(0, data.shape[0])] for i in range(k)]

    for j in range(MAX_ITERATIONS):
        clasters = [[] for i in range(k)]

        for feature in data:
            clasters[min_index(k, feature, mean)].append(feature)

        prev_mean = deepcopy(mean)
        for i in range(k):
            mean[i] = np.average(clasters[i], axis=0)

        is_final = True
        for i in range(k):
            if np.linalg.norm(mean[i] - prev_mean[i]) > EPS:
                is_final = False

        if is_final:
            break

    return [min_index(k, feature, mean) for feature in data]

data = pd.read_csv('dataset_191_wine.csv')
x = data[data.columns[1:]]
y = data.loc[:, 'class']

x_norm = MinMaxScaler(copy=True).fit_transform(x)
x_pca = PCA(n_components=2).fit_transform(x_norm)

def display_clusters(labels, title):
    unique_labels = np.unique(labels)
    for i in range(len(unique_labels)):
        label = unique_labels[i]
        xs = x_pca[labels == label, 0]
        ys = x_pca[labels == label, 1]
        plt.scatter(xs, ys, color=COLORS[i], alpha=0.5, label=label)
    plt.title(title)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.legend()
    plt.show()

def display_metric(ys, title):
    xs = [i + 1 for i in range(len(ys))]
    plt.scatter(xs, ys)
    plt.plot(xs, ys)
    plt.title(title)
    plt.xlabel('K')
    plt.ylabel('metric')
    plt.show()

def rand_index(y_real, y_predicted):
    TP, FN, FP, TN = 0, 0, 0, 0
    for i in range(len(y_real)):
        for j in range(len(y_predicted)):
            res1 = y_real[i] == y_real[j]
            res2 = y_predicted[i] == y_predicted[j]
            if res1 and res2:
                TP += 1
            elif res1 and not res2:
                FP += 1
            elif not res1 and res2:
                TN += 1
            elif not res1 and not res2:
                FN += 1

    return (TP + FN) / (TP + TN + FP + FN)

def group_by_clasters(y):
    clasters = [[] for i in range(max(y) + 1)]
    for i in range(len(y)):
        clasters[y[i]].append(i)

    return clasters

def min_separation(x, clusters):
    value = INF
    min_distance = value
    for i in range(len(clusters)):
        for j in range(len(clusters)):
            if i != j:
                for first in clusters[i]:
                    for second in clusters[j]:
                        distance = np.linalg.norm(x[first] - x[second])
                        if distance < min_distance:
                            min_distance = distance

    return min_distance if min_distance != value else 0

def max_diameter(x, clusters):
    max_distance = 0
    for i in range(len(clusters)):
        for first in clusters[i]:
            for second in clusters[i]:
                distance = np.linalg.norm(x[first] - x[second])
                if first != second and distance > max_distance:
                    max_distance = distance

    return max_distance

def dunn_index(x, y):
    clusters = group_by_clasters(y)
    min_sep = min_separation(x, clusters)
    max_diam = max_diameter(x, clusters)

    return min_sep / max_diam if min_sep != 0 else 0

display_clusters(y, 'real labels')

rand_indices = []
dunn_indices = []

for i in range(len(COLORS)):
    y_predicted = k_means(i + 1, x_norm)
    rand_indices.append(rand_index(y, y_predicted))
    dunn_indices.append(dunn_index(x_norm, y_predicted))
    display_clusters(y_predicted, f'K = {i + 1}')

display_metric(rand_indices, 'rand indices')
display_metric(dunn_indices, 'dunn indices')


import numpy as np
np.set_printoptions(suppress=True, precision=4)

import sys
import matplotlib.pyplot as plt
import sklearn.decomposition as decomp
import sklearn.linear_model as linear_model
import sklearn.datasets as sk_data
from sklearn.preprocessing import StandardScaler
import numpy.linalg as nla
import sklearn.svm as svm
import pandas as pd

def iqr_threshold_method(scores, margin):
    q1 = np.percentile(scores, 25, interpolation='midpoint')
    q3 = np.percentile(scores, 75, interpolation='midpoint')
    iqr = q3-q1
    lower_range = q1 - (1.5 * iqr)
    upper_range = q3 + (1.5 * iqr)
    lower_range = lower_range - margin
    upper_range = upper_range + margin
    return lower_range, upper_range

def get_projected_vectors(X, pca, ssX=None):
    if not ssX:
        ssX = StandardScaler().fit(X)
    centered_data = ssX.transform(X)
    reduced = pca.transform(centered_data)
    return ssX.inverse_transform(pca.inverse_transform(reduced))

def do_pca_anomaly_scores(obs,
    n_components):

    ssX = StandardScaler()
    centered_data = ssX.fit_transform(obs)

    pca = decomp.PCA(n_components = n_components)
    pca.fit(centered_data)

    projected_vectors = get_projected_vectors(obs, pca)
    return nla.norm(obs - projected_vectors, axis=1)

# currently the maximum value of pca score is considered as Anomaly
# hence only one anomaly is returned
# find a better way to get threshold value
# one could be determine outlier in pca score and use it as threshold
# similar issue with testing_svm_based
def PcaOutlier(data, n_components, margin=0):

    """Returns numpy array with data points labelled as outliers
    Parameters
    ----------
    data : numpy array like data_points

    n_components : int, float, None or str
        Number of components to keep.

    margin : int, default=0
        Margin of error
"""

    pca_score = do_pca_anomaly_scores(data, n_components)

    lower_range, upper_range = iqr_threshold_method(pca_score, margin)

    anomaly_points = []

    for i in range(len(pca_score)):
        if pca_score[i] < lower_range or pca_score[i] > upper_range:
            anomaly_points.append(data[i])

    return anomaly_points

if __name__=='__main__':
    pca_example = np.array([[-3, -1.5], [-2.5, -1.25], [-1.5, -0.75],
                            [-1, -0.5], [-0.5, -0.25], [0, 0], [0.5, 0.26],
                            [1, 0.5],  [1.5, 0.75], [2.5, 1.25], [3, 1.5]])

    res = PcaOutlier(pca_example, 1)
    print(res)


import numpy as np
import math as m
import unittest
import matplotlib.pyplot as plt
import random

###******************/PART II : Conjugate gradient method

"""QUESTION 3"""
"""Resolution of the linear system Ax=b by the conjugate gradient method where A is a symmetric positive definite matrix and b a vector (without preconditioning)"""

def conjgrad(A,b,x) :
    r=b-np.dot(A,x)
    p=r
    rsOld= float(np.dot(np.transpose(r),r))
    tab_x=[x]
    iter=[0]
    for i in range(1,100001):
        Ap=np.dot(A,p)
        n = float(np.dot(np.transpose(p),Ap))
        alpha=rsOld/n
        x=x+ alpha*p
        r=r- alpha*Ap
        rsNew=float(np.dot(np.transpose(r),r))
        tab_x+=[x]
        iter+=[i]
        if m.sqrt(rsNew) < 1e-10 :
            break
        p=r+rsNew/rsOld*p
        rsOld=rsNew
    return x,tab_x,iter

"""QUESTION 4"""
"""Resolution of the linear system Ax=b by the conjugate gradient method where A is a symmetric positive definite matrix and b a vector (with preconditioning)"""

#we can generate a precondition by this method
def generate_jacob_matrix(A):
    M=np.zeros((len(A),len(A)))
    for i in range(len(A)):
        if A[i][i]!=0:
            M[i][i]=1/A[i][i]
    return M

def somme(T, i, j):
    return sum(np.fromiter((T[i][k] * T[j][k] for k in range(j)),float))

def facto_dense_inc(A):
    (n, n1) = A.shape
    T = np.zeros((n,n))
    for i in range(n):
        for j in range(i + 1):
            if A[i][j] != 0:
                if i==j:
                    T[i][j] = m.sqrt((A[i][i] - somme(T, i, j)))
                else:
                    T[i][j] = (A[i][j] - somme(T, i, j)) / T[j][j]
    return T

# generating a precondition with incomplete cholsky method
def preconditioner(A):
    T= facto_dense_inc(A)
    return np.dot(T,np.transpose(T))

def PreconditionedConjgrad(A,b,x):
    tab_x=[x]
    iter=[0]
    r=b-np.dot(A,x)
    M=generate_jacob_matrix(A) #lwe can use either the incomplete cholsky precondition or the jacob matrix precondition
    #or M=np.linalg.inv(preconditionner(A))
    z=np.dot(M,r)
    p=z
    for k in range(1,100001):
        alpha=(np.dot(np.transpose(r),z))/(np.dot(np.transpose(p),np.dot(A,p)))
        x=x+ alpha*p
        tab_x+=[x]
        iter+=[k]
        r2=r-alpha*(np.dot(A,p))
        rsNew=float(np.dot(np.transpose(r2),r2))
        if m.sqrt(rsNew) < 1e-10 :
            break
        z2=np.dot(M,r2)
        beta=(np.dot(np.transpose(z2),r2))/(np.dot(np.transpose(z),r))
        p=z2+beta*p
        z=z2
        r=r2
    return x,tab_x,iter

def MdpGenerator(size) :          #this auxiliary function generates, for a given size, a matrix A
                                  #symmetric definite positive
                                  #T is an upper trinagular matrix with positive diagonal coefficients
                                  #The diagonal coefficients of A are generated randomly between 5 and 10
    T=np.zeros((size,size))
    for i in range(size):
        for j in range(size):
            if j>=i:
                T[i,j] = random.randint(5,10)
            else:
                T[i,j] = 0
    A = np.dot(T,T.transpose())
    return A

##****************TEST: Conjugate gradient and Preconditionned Conjugate gradient methods

class Test_gradient(unittest.TestCase):
    def test_conjgrad(self):
        A= np.array([[4,1],[1,3]])
        b= np.array([1,2])
        x= np.array([2,1])
        expected= np.array([0.0909,0.6363])
        result= conjgrad(A,b,x)
        for i in range(len(A)):
                self.assertAlmostEqual(result[0][i],expected[i],3)
    def test_conjgrad_precond(self):
        A= np.array([[4,1],[1,3]])
        b= np.array([1,2])
        x= np.array([2,1])
        expected= np.array([0.0909,0.6363])
        result= PreconditionedConjgrad(A,b,x)
        print()
        for i in range(len(A)):
            self.assertAlmostEqual(result[0][i],expected[i],3)

##We generate a curve representing the variations of the relative error according to the number of iterations
##relative error = the difference in magnitudes between the expected solution and the solution found by the algorithm

def test1_conjgrad(size):
    R=[]
    A=MdpGenerator(size)
    Xs = np.random.rand(size,1)
    x=np.zeros((size,1))
    b=np.dot(A,Xs)
    s=conjgrad(A,b,x)
    for i in range(len(s[1])):
        R+=[abs(np.linalg.norm(s[1][i])-np.linalg.norm(Xs))]
    return R,s[2]

def test2_conjgrad(size):
    R=[]
    A=MdpGenerator(size)
    Xs = np.random.rand(size,1)
    x=np.zeros((size,1))
    b=np.dot(A,Xs)
    s=PreconditionedConjgrad(A,b,x)
    for i in range(len(s[1])):
        R+=[abs(np.linalg.norm(s[1][i])-np.linalg.norm(Xs))]
    return R,s[2]

def show_tests(size):
    s=test1_conjgrad(size)
    t=test2_conjgrad(size)
    plt.plot(t[1],t[0], label="PrecCond_Conjgrad_Method")
    plt.plot(s[1],s[0], label="Conjgrad_Method")
    plt.xlabel('nbrs iterations')
    plt.ylabel('Relative Error')
    plt.legend()
    plt.show()

#show_tests(15)

#In this test we generate the solutions given by np.linalg, conjgrad and precond_conjgrad
#for matrix A whose size is varying from 25 to 'size-1'
##relative error = the difference in magnitudes between the expected solution and
##the solution found by the algorithm

def TestConjgrad(size):
    R1=[]
    R2=[]
    R3=[]
    iter=[]
    for i in range(25,size):
        iter+=[i]
        A=MdpGenerator(i)
        Xs = np.random.rand(i,1)
        x_conjgrad=np.zeros((i,1))
        b=np.dot(A,Xs)
        x_conjgrad=conjgrad(A,b,x_conjgrad)
        conjgrad(A,b,x_conjgrad)
        x_resultpreconjgrad=PreconditionedConjgrad(A,b,x_conjgrad)
        PreconditionedConjgrad(A,b,x_conjgrad)
        x_linalg=np.linalg.solve(A,b)
        R1+=[abs(np.linalg.norm(x_resultconjgrad[0])-np.linalg.norm(Xs))]
        R2+=[abs(np.linalg.norm(x_resultpreconjgrad[0])-np.linalg.norm(Xs))]
        R3+=[abs(np.linalg.norm(x_linalg)-np.linalg.norm(Xs))]
    plt.plot(iter, R1, label="Conjgrad Method")
    plt.plot(iter, R3, label="Linalg from numpy")
    plt.plot(iter, R2, label="PreConjgrad Method")
    plt.xlabel('Size of matrix')
    plt.ylabel('Relative error')
    plt.title('Relative error in the resolution of Ax=b')
    plt.legend()
    plt.show()
    plt.savefig('relative_error.png')

TestConjgrad(50)

# unittests
if __name__ == '__main__':
    unittest.main(Test_gradient(), verbosity = 2)


import numpy.linalg as la
import numpy as np

# Find the Givens coefficients c, s such that the rotation
# [[c, s], [-s, c]] will zero out the i+1 element of x.
def findGivensCoefficients(x, i):
    hyp = np.sqrt(x[i+1]*x[i+1] + x[i]*x[i])
    s = x[i+1]/hyp
    c = x[i]/hyp

    return (c, s)

# Apply a Givens rotation at the rows [i:i+2] of the vector x
def applyGivens(x, c, s, i):

    xi = x[i]
    xi1 = x[i+1]

    x[i] = c*xi + s*xi1
    x[i+1] = -s*xi + c*xi1

    return x

# Apply a Givens rotation at the rows [i:i+2] of the vector x. The
# input vector is modified.
def applyGivensInPlace(x, c, s, i):

    xi = x[i]
    xi1 = x[i+1]

    x[i] = c*xi + s*xi1
    x[i+1] = -s*xi + c*xi1

if __name__=='__main__':

    print('======= Test of Givens reduction from Hessenberg to triangular')
    # Test Hessenberg matrix
    H = np.array([
        [1.0, -2.0, 3.0],
        [4.0, 5.0, 6.0],
        [0.0, 7.0, 8.0],
        [0.0, 0.0, 9.0]
    ])

    n,m = H.shape # Here we use Saad's convention: n=rows, m=cols

    print('\n %d by %d Hessenberg matrix H= \n' %(n,m), H)
    print('\n||H||=', la.norm(H))
    initialNormH = la.norm(H)

    # Test RHS
    g = np.array([1.0,0.0,0.0,0.0])

    print('\ng= \n', g)
    initialNormRHS = la.norm(g)
    print('\n||g||=', initialNormRHS)

    # For comparison, compute a QR factorization using Numpy's built-in
    # function. This uses a sequence of Householder reflections.
    QR = la.qr(H, 'reduced')
    print('\n\n--- Numpy\'s QR factorization of H\n')
    print('Q=\n', QR[0])
    print('\nR=\n', QR[1])

    # For comparison, use normal equations to find the least squares solution
    # to H*y = g.
    yLS = la.solve(np.matmul(H.transpose(), H), np.matmul(H.transpose(), g))

    print('\n\n--- Solution of least squares problem via normal equations\n')
    print(yLS)

    print('\nResidual\n')
    r = g - np.matmul(H,yLS)
    residNorm = la.norm(r)
    print(residNorm)

    # Do Givens rotations to transform H to upper triangular form. At the
    # same time, apply the same operations to the RHS
    print('\n\n--- Doing Givens rotations on H')
    # Create an array in which we'll store all the rotation cosines and sines
    CS = np.zeros([m,2])

    # Loop over columns of H
    for i in range(0,m):
        # Apply the previous Givens rotations to the current column
        for j in range(0,i):
            #H[:,i] = applyGivens(H[:,i], CS[j,0], CS[j,1], j)
            applyGivensInPlace(H[:,i], CS[j,0], CS[j,1], j)
        # Find the Givens rotation that will zap the single subdiagonal entry
        # in the current column
        CS[i,:] = findGivensCoefficients(H[:,i], i)
        # Apply that rotation to zap the subdiagonal
        #H[:,i] = applyGivens(H[:,i], CS[i,0], CS[i,1], i)
        applyGivensInPlace(H[:,i], CS[i,0], CS[i,1], i)
        # Apply the same rotation to the RHS of the least squares problem
        #g = applyGivens(g, CS[i,0], CS[i,1], i)
        applyGivensInPlace(g, CS[i,0], CS[i,1], i)

    print('\nH after Givens procedure=\n')
    print(H)
    print('\n||H||=', la.norm(H))
    print('\nRHS after Givens procedure')
    print('g=\n', g)
    print('\n||g||=', la.norm(g))

    print('\nRelative error in norm:')
    print('H error: ', np.abs(la.norm(H)-initialNormH)/initialNormH)
    print('RHS error: ', np.abs(la.norm(g)-initialNormRHS)/initialNormRHS)

    # Solve the triangular system
    print('\n\n--- Triangular solve')
    y = la.solve(H[0:m,:], g[0:m])
    print('\nSolution: y=\n', y)

    print('For comparison, y from normal equations=\n', yLS)


"""This file contains code for use with "Think Stats",
by Allen B. Downey, available from greenteapress.com

Copyright 2011 Allen B. Downey
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html

"""

import numpy
import math
from scipy.special import erf, erfinv

import Cdf
import Pmf

root2 = math.sqrt(2.0)

def StandardNormalCdf(x):
    return (erf(x / root2) + 1) / 2

def NormalCdf(x, mu=0, sigma=1):
    """Evaluates the CDF of the normal distribution.

    Args:
        x: float

        mu: mean parameter

        sigma: standard deviation parameter

    Returns:
        float
    """
    return StandardNormalCdf(float(x - mu) / sigma)

def NormalCdfInverse(p, mu=0, sigma=1):
    """Evaluates the inverse CDF of the normal distribution.

    Args:
        p: float

        mu: mean parameter

        sigma: standard deviation parameter

    Returns:
        float
    """
    x = root2 * erfinv(2*p - 1)
    return mu + x * sigma

spread = 4.0

def MakeNormalCdf(low=-spread, high=spread, digits=2):
    """Returns a Cdf object with the standard normal CDF.

    low: how many standard deviations below the mean?
    high: how many standard deviations above the mean?
    digits:
    """
    n = (high - low) * 10**digits + 1
    xs = numpy.linspace(low, high, n)
    ps = (erf(xs / root2) + 1) / 2
    cdf = Cdf.Cdf(xs, ps)
    return cdf

def MakeNormalPmf(low=-spread, high=spread, digits=2):
    """Returns a Pmf object with the standard normal CDF.

    low: how many standard deviations below the mean?
    high: how many standard deviations above the mean?
    digits:
    """
    cdf = MakeNormalCdf(low, high, digits)
    pmf = Pmf.MakePmfFromCdf(cdf)
    return pmf

class FixedPointNormalPmf(Pmf.Pmf):
    """A Pmf that maps from normal scores to probabilities.

    Values are rounded to the given number of digits.
    """

    def __init__(self, spread=4, digits=2, log=False):
        """Initalizes a FixedPointNormalPmf.

        spread: how many standard deviations in each direction.
        digits: how many digits to round off to
        log: whether to log-transform the probabilities
        """
        Pmf.Pmf.__init__(self)
        self.spread = spread
        self.digits = digits

        n = 2 * spread * 10**digits + 1
        xs = numpy.linspace(-spread, spread, n)
        gap = (xs[1] - xs[0]) / 2

        for x in xs:
            p = StandardNormalCdf(x + gap) - StandardNormalCdf(x - gap)
            self.Set(round(x, self.digits), p)

        # save the last (smallest) probability as the default for
        # values beyond the spread
        self.default = p

        self.Normalize()
        if log:
            self.Log()
            self.default = math.log(self.default)

    def NormalProb(self, x):
        """Looks up the probability for the value closest to x."""
        return self.d.get(round(x, self.digits), self.default)


# ============================================================================ #
#                                 SIGMOID                                      #
# ============================================================================ #
# Renders stacked line charts showing convex and non-convex obj function
#%%
import plotly.offline as py
import plotly.graph_objs as go
import numpy as np
# ---------------------------------------------------------------------------- #
#                                 CONVEX                                       #
# ---------------------------------------------------------------------------- #
# Convex Formula
def sigmoid(z):
    return 1/(1+np.exp(-z))
# ---------------------------------------------------------------------------- #
# Data
x = np.linspace(-10,10,100)
y = sigmoid(x)
# ---------------------------------------------------------------------------- #
# Trace
data = go.Scatter(x=x, y=y,
                      mode='lines',
                      name='Sigmoid Function',
                      marker=dict(
                          size=2,
                          color='#1560bd'
                          )
                    )

# ---------------------------------------------------------------------------- #
# Layout
layout = go.Layout(title='Sigmoid Function',
                   height=400,
                   width=800,
                   showlegend=False,
                   margin=dict(l=10,r=10,t=40,b=10),
                   template='plotly_white'
                   )
# ---------------------------------------------------------------------------- #
# Figure and plot
fig = go.Figure(data=data, layout=layout)
py.plot(fig, filename = "./content/figures/sigmoid.html", auto_open=False)


# --------------
# Importing header files
import numpy as np

# Path of the file has been stored in variable called 'path'

#New record
new_record=[[50,  9,  4,  1,  0,  0, 40,  0]]

#Code starts here

data=np.genfromtxt(path, delimiter=",", skip_header=1)
# print(data)
# print(type(data))

census=np.concatenate([new_record,data])
print(census)

# --------------
#Code starts here
age=census[0:,0]
print(age)

max_age=max(age)
print(max_age)

min_age=min(age)
print(min_age)

age_mean=np.mean(age)
print(age_mean)

age_std=np.std(age)
print(age_std)

# --------------
import numpy as np
#Code starts here
# arr=np.array(census[:,2])

race_0=census[census[:,2]==0]
race_1=census[census[:,2]==1]
race_2=census[census[:,2]==2]
race_3=census[census[:,2]==3]
race_4=census[census[:,2]==4]

len_0=len(race_0)
print(len_0)
len_1=len(race_1)
print(len_1)
len_2=len(race_2)
print(len_2)
len_3=len(race_3)
print(len_3)
len_4=len(race_4)
print(len_4)

minority_race=3
print("Minority Race is: ",minority_race)

# print(np.size(arr))
# # minority_race=3

# # if len(race_0)
# # mask=(race_0==0)
# print(race_0(mask))

# --------------
#Code starts here
senior_citizens=census[census[:,0]>60]
# print(len(senior_citizens))

working_hours_sum=senior_citizens.sum(axis=0)[6]
print(working_hours_sum)
# working_hours_sum=np.concatenate(senior_citizens,ssum)
# print(working_hours_sum)

senior_citizens_len=len(senior_citizens)
# print(senior_citizens_len)

avg_working_hours=(working_hours_sum/senior_citizens_len)
print(avg_working_hours)

# print(avg_working_hours)

# --------------
#Code starts here
high=census[census[:,1]>10]
low=census[census[:,1]<=10]

avg_pay_high=high.mean(axis=0)[7]
print(avg_pay_high)
avg_pay_low=low.mean(axis=0)[7]
print(avg_pay_low)

comp=avg_pay_high==avg_pay_low
print(comp)


from aerosandbox.numpy.linalg import norm, outer
from aerosandbox.numpy.array import array
import numpy as onp

def rotation_matrix_2D(
        angle,
):
    """
    Gives the 2D rotation matrix associated with a counterclockwise rotation about an angle.
    Args:
        angle: Angle by which to rotate. Given in radians.

    Returns: The 2D rotation matrix

    """
    sintheta = onp.sin(angle)
    costheta = onp.cos(angle)
    rotation_matrix = array([
        [costheta, -sintheta],
        [sintheta, costheta]
    ])
    return rotation_matrix

def rotation_matrix_3D(
        angle,
        axis,
        _axis_already_normalized=False
):
    """
    Gives the 3D rotation matrix from an angle and an axis.
    An implmentation of https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
    :param angle: can be one angle or a vector (1d ndarray) of angles. Given in radians. # TODO note deprecated functionality; must be scalar
        Direction corresponds to the right-hand rule.
    :param axis: a 1d numpy array of length 3 (x,y,z). Represents the angle.
    :param _axis_already_normalized: boolean, skips normalization for speed if you flag this true.
    :return:
        * If angle is a scalar, returns a 3x3 rotation matrix.
        * If angle is a vector, returns a 3x3xN rotation matrix.
    """
    if not _axis_already_normalized:
        axis = axis / norm(axis)

    sintheta = onp.sin(angle)
    costheta = onp.cos(angle)
    cpm = array([
        [0, -axis[2], axis[1]],
        [axis[2], 0, -axis[0]],
        [-axis[1], axis[0], 0],
    ])  # The cross product matrix of the rotation axis vector
    outer_axis = outer(axis, axis)

    rot_matrix = costheta * onp.eye(3) + sintheta * cpm + (1 - costheta) * outer_axis
    return rot_matrix