import numpy
import math

from base.polynomial import Polynomial
from base.matrix import Matrix
from newtons_method import Newtons_method, get_roots
from legendre_polynomial import Legendre

class GaussianQuad:
    """
    Gaussian Quadrature is a numerical approximation technique to calculate the
    the definite integral of a function.
     - It utilises a weighted-sum of function values at prescribed points within the
        definitive domain described by the integral
     - To utilise this method, the definite interval needs to be translated to
        the interval: [-1; 1]
    """

    # Define the order of Gaussian Quadrature:
    #   - n_order = number of points / order of gaussian quadrature
    order = 1                     # Int (>= 1)

    # Define the legendre polynomials up to a point
    legendre = []              # Legendre Polynomial for specified order

    # Define the Quadrature Matrix:
    #   - Size: Order x 2:
    #       [n,0] = node position
    #       [n,1] = node weight value
    quadrature = []                 # Matrix (nx2)

    def __init__(self, n_points) -> None:
        self.order = n_points
        self.quadrature = numpy.zeros((n_points, 2))

        if n_points == 1:   # If first simple case, avoid generating legendre polynomials
            self.quadrature[0, 0] = 0
            self.quadrature[0, 1] = 2
        else:
            # generate legendre equation polynomial of degree n
            self.legendre = Legendre(n_points)
            # Print Legendre polynomial info
            #print(f"legendre_polynomial:{self.legendre.co_array}, roots:{self.legendre.roots}")

            # use legendre polynomial to
            for i in range(n_points):
                self.quadrature[i, 0] = self.legendre.roots[i]
                self.quadrature[i, 1] = self.calculate_weight_function(i, self.legendre.roots[i])

    def __str__(self) -> str:
        ret_str = "{}\n".format(self.order)
        for i in range(self.order):
            ret_str += "{}:{},\n".format(self.quadrature[i,0], self.quadrature[i, 1])
        return ret_str

    # Calculate the weight value correlating to specified root of Legendre polynomial
    def calculate_weight_function(self, i, root_i) -> float:
        # w_i = 2/(1 - (x_i)^2) * 2/(P'n(x_i)^2)
        #   - see https://en.wikipedia.org/wiki/Gaussian_quadrature for details on formula.
        derivative = self.legendre.derive()
        deriv_x_i = derivative.evaluate(root_i)

        # Print root info used for calculating corresponding weight values
        #print(f"i:{i}|root_i:{root_i}, dx_i:{deriv_x_i}, deriv:{repr(derivative)}")
        return 2/((1-root_i**2)*(deriv_x_i**2))

    # Calulate definite integral (a numerical approximation) of a polynomial function
    #  between limits a and b
    #   - translates [a,b] limits to [-1,1]
    #   - calculate the weighted sum of function values at each Gaussian point
    def calculate_definite_integral(self, polynomial, a, b):
        ba = (b - a)/2
        ab = (a + b)/2
        fret = 0.0
        for p in range(self.gaussian.order):
            val = ba*self.quadrature[p, 0] + ab
            fret += self.quadrature[p, 1]*polynomial.evaluate(val)
        fret *= ba
        return fret

# Tests for the Gaussian Quadrature method
def main():
    # test function for legendre_polynomial + binomial_coefficient
    n_test = 4
    gquad = GaussianQuad(n_test)
    print("n_test:", gquad)

if __name__ == "__main__":
    main()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ch5Python.py Statistical significance of regression.
"""
import numpy as np
from scipy import stats
import statsmodels.api as sm

x = [1.00,1.25,1.50,1.75,2.00,2.25,2.50,2.75,3.00,3.25,3.50,3.75,4.00]
y = [3.34,4.97,4.15,5.40,5.21,4.56,3.69,5.86,4.58,6.94,5.57,5.62,6.87]

# Convert data to vectors.
x = np.array(x)
y = np.array(y)
n = len(y)
xmean = np.mean(x)
ymean = np.mean(y)
# Find zero mean versions of x and y.
xzm = x - xmean
yzm = y - ymean
covxy = np.sum(xzm * yzm)/n
varx = np.var(x)
vary = np.var(y)
print("Variance of x = %0.3f." %varx) # 0.875
print("Variance of y = %0.3f." %vary) # 1.095
print("Covariance of x and y = %0.3f." %covxy) # 0.669

# Find slope b1.
b1 = covxy/varx # 0.764
# Find intercept b0.
b0 = ymean - b1*xmean # 3.225
print('\nslope b1 = %6.3f\nintercept b0 = %6.3f.' % (b1, b0))

# Find vertical projection of y onto best fitting line.
yhat = b1*x + b0

numparams = 2 # number of parameters=2 (slope and intercept).

# SLOPE
# Find sem of slope.
num = ( (1/(n-numparams)) * sum((y-yhat)**2) )**0.5 # 0.831.
den = sum((x-xmean)**2)**0.5 # 3.373
semslope = num/den
print('semslope = %6.3f.' % (semslope))

# Find t-value of slope.
tslope = b1/semslope # 3.101

# Find p-value of slope.
# two-tailed pvalue = Prob(abs(t)>tt).
pvalue = stats.t.sf(np.abs(tslope), n-numparams)*2 # 0.0101
print('\nSLOPE:\nt-statistic = %6.3f.' % tslope)
print('pvalue = %6.4f.\m' % pvalue)

# INTERCEPT
# Find sem of intercept.
a = ( (1/(n-numparams)) * sum((y-yhat)**2) )**0.5 # 0.831
b = ( (1/n) + xmean**2 / sum( xzm**2 ))**0.5 # 0.791
semintercept = a * b  # 0.658
print('\na = %6.3f\nb = %6.3f\nsemintercept = a/b = %6.3f.'
% (a, b, semintercept))

# Find t-value of intercept.
tintercept = b0 / semintercept
pintercept = stats.t.sf(np.abs(tintercept), n-numparams)*2
print('\nINTERCEPT:\nt-statistic = %6.3f.'% tintercept)
print('pvalue = %6.4f.\n'% pintercept)

# Overall model fit to data.
# Find coefficient of variation r2.
r2 = covxy * covxy / (varx * vary)
print("coefficient of variation = %0.3f." %r2) # 0.466.

# Find F ratio.
A = r2 / (numparams-1)
B = (1-r2) / (n-numparams)
F = A/B # 9.617
print("F ratio = %0.4f." % F)

pfit = stats.f.sf(F, numparams-1, n-numparams)
print("p overall fit = %0.4f." % pfit) # 0.0101.

# Run standard library regression method for comparison.
ones = np.ones(len(x))
X = [ones, x]
X = np.transpose(X)
y = np.transpose(y)
res_ols = sm.OLS(y, X).fit()
print(res_ols.summary()) # Print table of results.

# END OF FILE.

import numpy,from scipy,import statsmodels


"""
Simple implementation of classical MDS.
See http://www.stat.cmu.edu/~ryantibs/datamining/lectures/09-dim3-marked.pdf for more details.
"""

import numpy as np
import numpy.linalg as linalg

def mds(D, dim=2):
    """ Classical multidimensional scaling algorithm.
        Given a matrix of interpoint distances D, find a set of low dimensional points
        that have a similar interpoint distances.
    """
    (n,n) = D.shape
    A = (-0.5 * D**2)
    M = np.ones((n,n))/n
    I = np.eye(n)
    B = np.dot(np.dot(I-M, A),I-M)

    '''Another way to compute inner-products matrix B
    Ac = np.mat(np.mean(A, 1))
    Ar = np.mat(np.mean(A, 0))
    B = np.array(A - np.transpose(Ac) - Ar + np.mean(A))
    '''

    [U,S,V] = linalg.svd(B)
    Y = U * np.sqrt(S)
    return (Y[:,0:dim], S)

def main():
    """ @Todo: Adding simple test code for mds."""
    pass

if __name__ == '__main__':
    main()


import numpy as np

np.random.seed(12)
num_observations = 5000

x1 = np.random.multivariate_normal([0, 0], [[1, .75],[.75, 1]], num_observations)
x2 = np.random.multivariate_normal([1, 4], [[1, .75],[.75, 1]], num_observations)

simulated_separableish_features = np.vstack((x1, x2)).astype(np.float32)
simulated_labels = np.hstack((np.zeros(num_observations),
                              np.ones(num_observations)))

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

def log_likelihood(features, target, weights):
    scores = np.dot(features, weights)
    ll = np.sum( target*scores - np.log(1 + np.exp(scores)) )
    return ll

def logistic_regression(features, target, num_steps, learning_rate, add_intercept = False):
    if add_intercept:
        intercept = np.ones((features.shape[0], 1))
        features = np.hstack((intercept, features))

    weights = np.zeros(features.shape[1])

    for step in xrange(num_steps):
        scores = np.dot(features, weights)
        predictions = sigmoid(scores)

        # Update weights with gradient
        output_error_signal = target - predictions
        gradient = np.dot(features.T, output_error_signal)
        weights += learning_rate * gradient

        # Print log-likelihood every so often
        if step % 10000 == 0:
            print log_likelihood(features, target, weights)

    return weights

weights = logistic_regression(simulated_separableish_features, simulated_labels,
     num_steps = 300000, learning_rate = 5e-5, add_intercept=True)

data_with_intercept = np.hstack((np.ones((simulated_separableish_features.shape[0], 1)),
                                 simulated_separableish_features))
final_scores = np.dot(data_with_intercept, weights)
preds = np.round(sigmoid(final_scores))

print 'Accuracy from scratch: {0}'.format((preds == simulated_labels).sum().astype(float) / len(preds))
print 'Accuracy from sk-learn: {0}'.format(clf.score(simulated_separableish_features, simulated_labels))


# -*- coding: utf-8 -*-
"""
Created on Fri Dec 15 21:04:26 2017

@author: Anastasios Tzavellas
"""

import numpy as np
import matplotlib.pyplot as plt

def f(x):
    return np.exp(2 * x) - 1.

def L(x, i, xValues):
    """Evaluates ith degree
    Lagrange Polynomial"""
    xi = xValues[i]
    product = 1.
    for j, xj in enumerate(xValues):
        if j != i:
            product = product * (x - xj) / (xi - xj)
    return product

def Lagrange(x, xValues, fValues):
    """Evaluates Lagrange Polynomial"""
    val = 0.
    for i, fi in enumerate(fValues):
        val = val + L(x, i, xValues) * fi
    return val

xValues = np.array([1, 1.1, 1.2, 1.3, 1.4])
fValues = f(xValues)
p = Lagrange(1.25, xValues, fValues)
print('Function Value f(1.25)=', f(1.25))
print('Lagrange polyn p(1.25)=', p)

plt.close('all')
x = np.arange(1.0, 1.41, 0.01)
ps = Lagrange(x, xValues, fValues)
plt.plot(x, f(x), '.', label='f(x)')
plt.plot(xValues, fValues, '*')
plt.plot(x, ps, '-.', label='p(x)')
plt.legend()
plt.grid()


# from sympy import
from utils import r

def secant_method(fn, x1, x0, iter=1):
    out_str = ''

    if iter == 1:
        out_str += f"Secant method is given as:\n"
        out_str += f"$$ x_{{i+1}} = x_i - \\left[\\frac{{f(x_i)(x_{{i-1}} - x_{{i}})}}{{f(x_{{i-1}}) - f(x_{{i}})}}\\right] $$\n"

    out_str += f"\\textbf{{Iteration {iter}}}\n\n"
    out_str += f"$$ x_{iter} = x_{iter - 1} - \\left[\\frac{{f(x_{iter - 1})(x_{{{iter - 2}}}-x_{iter - 1})}}{{f(x_{{{iter - 2}}}) - f(x_{{{iter - 1}}})}}\\right] $$\n"

    out_str += f"$$ f(x_{{{iter - 2}}}) = f({r(x0)}) = {r(fn(x0))} $$\n"
    out_str += f"$$ f(x_{{{iter - 1}}}) = f({r(x1)}) = {r(fn(x1))} $$\n"

    out_str += f"$$ x_{iter} = {r(x0)} - \\left[\\frac{{{r(fn(x1))}({r(x0)}-({r(x1)}))}}{{{r(fn(x0))} - ({r(fn(x1))}))}}\\right] $$\n"
    x_val = fn(x0) * (x0 - x1) / (fn(x0) - fn(x1))
    out_str += f"$$ x_{iter} = {r(x0)} - ({r(x_val)}) $$\n"
    x_val = x0 - x_val
    out_str += f"$$ x_{iter} = {r(x_val)} $$\n"

    error = None
    if iter > 1:
        out_str += "\n\\textbf{Error}\n\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|\\text{{latest value}} - \\text{{previous value}}\\right|}}{{\\left|\\text{{latest value}}\\right|}} \\times 100 $$\n"
        out_str += f"$$ \\text{{Error}} = \\frac{{\\left|{r(x_val)} - ({r(x1)})\\right|}}{{\\left|{r(x_val)}\\right|}} \\times 100 $$\n"
        error = abs(x_val - x1)/abs(x_val) * 100
        out_str += f"$$ \\text{{Error}} = {r(error, 2)} \\% $$\n"

    if error is not None and error < 0.0001 or iter > 3:
        return out_str

    out_str += secant_method(fn, x_val, x1, iter+1)
    return out_str

if __name__ == '__main__':
    from sympy import lambdify, sin, cos, symbols

    x = symbols('x')
    print(secant_method(lambdify(x, sin(x) + cos(1-x**2) - 1), 3, 1))


#! /usr/bin/env python

"""
File: read_2columns.py

Copyright (c) 2016 Taylor Patti

License: MIT

This module demonstrates three different iteration techniques, lists and loops,
vectorization with default summation, and vectorization wtih numpy sumation,
through the implementation of a midpoint integration function.

"""

import numpy as np

def midpointint(f, a, b, n):
    """Uses lists and for loop to iterate through a midpoint integration function"""
    h = (b - a) / float(n)
    sum = 0
    for i in range(n):
        sum = sum + h * f(a - (h /2) + (i+1) * h)
    return sum

def midpointvecdefault(f, a, b, n):
    """Uses arrays and default summation for a midpoint integration function"""
    h = (b - a) / float(n)
    info = np.array([h * f(a - (h /2) + (i+1) * h) for i in range(n)])
    return sum(info)

def midpointvec(f, a, b, n):
    """Uses arrays and numpy summation for a midpoint integration function"""
    h = (b - a) / float(n)
    info = np.array([h * f(a - (h /2) + (i+1) * h) for i in range(n)])
    return np.sum(info)

def x_func(i):
    """The function x, for implementation in other functions."""
    return i

def test_midpointint_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs((midpointint(x_func, 1, 3, 1000)) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg

def test_midpointvecdefault_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs(midpointvecdefault(x_func, 1, 3, 1000) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg

def test_midpointvec_triangle():
    """Ensures proper integration of the straight line x from 1 to 3."""
    apt = (abs(midpointvec(x_func, 1, 3, 1000) - 4) <1e-6)
    msg = 'Unsuccessful integration.'
    assert apt, msg


'''
Are the Belmont Stakes results Normally distributed?
100xp

Since 1926, the Belmont Stakes is a 1.5 mile-long race of 3-year old thoroughbred horses.
Secretariat ran the fastest Belmont Stakes in history in 1973. While that was the fastest
year, 1970 was the slowest because of unusually wet and sloppy conditions. With these two
outliers removed from the data set, compute the mean and standard deviation of the Belmont
winners' times. Sample out of a Normal distribution with this mean and standard deviation
using the np.random.normal() function and plot a CDF. Overlay the ECDF from the winning
Belmont times. Are these close to Normally distributed?

Note: Justin scraped the data concerning the Belmont Stakes from the Belmont Wikipedia page.

Instructions
-Compute mean and standard deviation of Belmont winners' times with the two outliers removed.
The NumPy array belmont_no_outliers has these data.
-Take 10,000 samples out of a normal distribution with this mean and standard deviation using
np.random.normal().
-Compute the CDF of the theoretical samples and the ECDF of the Belmont winners' data, assigning
the results to x_theor, y_theor and x, y, respectively.
-Hit submit to plot the CDF of your samples with the ECDF, label your axes and show the plot.
'''
import numpy as np
import matplotlib.pyplot as plt

belmont_no_outliers = np.array([148.51,  146.65,  148.52,  150.7,  150.42,  150.88,  151.57,
                                147.54,  149.65,  148.74,  147.86,  148.75,  147.5,  148.26,
                                149.71,  146.56,  151.19,  147.88,  149.16,  148.82,  148.96,
                                152.02,  146.82,  149.97,  146.13,  148.1,  147.2,  146.,
                                146.4,  148.2,  149.8,  147.,  147.2,  147.8,  148.2,
                                149.,  149.8,  148.6,  146.8,  149.6,  149.,  148.2,
                                149.2,  148.,  150.4,  148.8,  147.2,  148.8,  149.6,
                                148.4,  148.4,  150.2,  148.8,  149.2,  149.2,  148.4,
                                150.2,  146.6,  149.8,  149.,  150.8,  148.6,  150.2,
                                149.,  148.6,  150.2,  148.2,  149.4,  150.8,  150.2,
                                152.2,  148.2,  149.2,  151.,  149.6,  149.6,  149.4,
                                148.6,  150.,  150.6,  149.2,  152.6,  152.8,  149.6,
                                151.6,  152.8,  153.2,  152.4,  152.2])

def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

    # Number of data points: n
    n = len(data)

    # x-data for the ECDF: x
    x = np.sort(data)

    # y-data for the ECDF: y
    y = np.arange(1, n + 1) / n

    return x, y

# Seed random number generator
np.random.seed(42)

# Compute mean and standard deviation: mu, sigma
mu = np.mean(belmont_no_outliers)
sigma = np.std(belmont_no_outliers)

# Sample out of a normal distribution with this mu and sigma: samples
samples = np.random.normal(mu, sigma, 10000)

# Get the CDF of the samples and of the data
x_theor, y_theor = ecdf(samples)
x, y = ecdf(belmont_no_outliers)

# Plot the CDFs and show the plot
_ = plt.plot(x_theor, y_theor)
_ = plt.plot(x, y, marker='.', linestyle='none')
plt.margins(0.02)
_ = plt.xlabel('Belmont winning time (sec.)')
_ = plt.ylabel('CDF')
plt.show()


import numpy as np

'''
Softmax Function is used for MultiClass Classification.
Recall that for binary classification, sigmoid function is used.

*However, when n=2, given softmax, works actually the same as the sigmoid function.

the formula for softmax is:
P(class_i) = e^(Z_i)/(e^Z_1 + e^Z_2 + ... + e^Z_n)
'''

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    # numerator - convert the list into exponents
    expL = np.exp(L)

    # denominator - summation of the exponential values from the list
    sumExpL = sum(expL)

    # we need answers for each of the classes or value in the list
    result = []
    for i in expL:
        result.append(i*1.0/sumExpL)
    return result

Lesson 3: Introduction to Neural Networks/2 Softmax.py


import numpy as np
from scipy import linalg

def QR(X):
    """Compute the Gram Schmidt of the column vectors in X

    The formula: x_k := x_k-<x_k, q_1>*q_1  (k=2,...,n)
    Then we normalize x_k

    label: QR
    """

    #transpose so we are dealing with rows instead of columns
    Q = X.T.copy()
    nrows, ncols = X.shape
    R = np.zeros((nrows, ncols))

    for i in xrange(nrows):
        R[i,i] = linalg.norm(Q[i])
        Q[i] = Q[i]/R[i,i]
        for j in xrange(i+1,nrows):
            R[i,j] = Q[j].dot(Q[i])
            Q[j] = Q[j]-(R[i,j]*Q[i])

    return Q.T, R

def detQR(X):
    """Computes the determinant of X using the QR decomposition
    This will give you the determinant up to, but without sign.

    The determinant of Q is +1 or -1 which may or may not change the sign of
    the determinant of R"""
    Q, R = QR(X)
    return np.diagonal(R).prod()

def leastsq(A, b):
    """Compute a least squares solution using the QR Decomposition"""
    Q, R = linalg.qr(A)

    #We solve the triangular system instead of inverting R
    return linalg.solve_triangular(R, Q.T.dot(b))

def eigvv(A, niter=50):
    Qlist = [A]
    x0 = np.random.rand(A.shape[1])
    for i in range(niter):
        Q,R = QR(Qlist[-1])
        A = Q.T.dot(A.dot(Q))
        Qlist.append(A)

    eigvals = np.diag(Qlist[-1])

    L = np.eye(Q.shape[0])
    for qm in Qlist:
        L = L.dot(qm)

    return eigvals, x0, L


import math
import numpy as np

def rigid_transform_3D(A, B):
    """ given the coordinates of GCPs in source system in array A
        and the coordinates in the destination system in array B
    """
    centroid_A = np.mean(A, 0)
    centroid_B = np.mean(B, 0)
    N = A.shape[0]
    H = (A - centroid_A).T.dot(B - centroid_B)
    U, S, V = np.linalg.svd(H)
    # rotation matrix
    R = V.T.dot(U.T)
    if np.linalg.det(R) < 0:
        R[:,3] *= -1
    # translation
    t = -R.dot(centroid_A.T) + centroid_B.T
    # scale
    sc = np.linalg.norm(B - centroid_B, 2) / np.linalg.norm(A - centroid_A, 2)
    return R, t, sc

if __name__ == "__main__":

    A = np.loadtxt('gcp.txt', delimiter=' ')
    B = np.loadtxt('gcp_photo.txt', delimiter=' ')
    R, t, sc = rigid_transform_3D(A, B)
    print(R)
    print(t)
    print(sc)
    # check
    A2 = (R.dot(A.T)).T
    for i in range(A2.shape[0]):
        A2[i,:] = A2[i,:] + t
    err = A2 - B
    err = err * err
    err = np.sum(err)
    rmse = math.sqrt(err / A.shape[0])
    print("RMSE: {:.3f}".format(err))
    print("If RMSE is near zero, the function is correct!")


import numpy as np

def cosine_sim(vec1, vec2):
    """
    Computes the cosine similarity between two vectors
    :param vec1:
    :param vec2:
    :return:
    """
    cos_sim = np.dot(vec1, vec2) / \
        (np.linalg.norm(vec1) * np.linalg.norm(vec2))
    return cos_sim

def cosine_sim_matrix(matrix, vec):
    """
    Computes the cosine similarities between a matrix and a vector
    :param matrix:
    :param vec:
    :return:
    """
    # cos_sim is array like
    cos_sim = np.dot(matrix, vec) / \
        (np.linalg.norm(matrix, axis=1) * np.linalg.norm(vec))
    return cos_sim


#!/usr/bin/env python3
# Copyright (c) 2018 Wei-Kai Lee. All rights reserved

# coding=utf-8
# -*- coding: utf8 -*-

import numpy as np

def dx(x):
    return x[1:]-x[0:x.size-1]
def yave(y):
    xszie = y.shape[-1]
    return ( y[...,1::]+y[...,0:(xszie-1):] )/2
def myNumericalIntegration(x,y):
    """
    myNumericalIntegration is a function to calculate the area of
    a y = function(x) by trapezoid method.
    x must be ascending.

    >>> x = np.linspace(0,1,100, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.33335033840084355
    >>> x = np.linspace(0,1,1000, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.3333335003338339
    >>> x = np.linspace(0,1,10000, dtype=np.float64)
    >>> y = x**2
    >>> value = myNumericalIntegration(x,y)
    >>> print(value)
    0.3333333350003337
    """
    x = np.array(x)
    y = np.array(y)
    return np.einsum('...i,i->...',yave(y),dx(x))
def yave2(y):
    xszie = y.shape[0]
    return ( y[1::,...]+y[0:(xszie-1):,...] )/2
def myNumericalIntegration2(x,y):
    x = np.array(x)
    y = np.array(y)
    return np.einsum('i...,i->...',yave2(y),dx(x))
def myTRAPEZOIDAL(fun,x0,x1,xPts=200):
    """
    myTRAPEZOIDAL is a function to calclate the integration of function f from
    x0 to x1 with equal spacing. (xPts: points of x)

    >>> import numpy as np
    >>> f = lambda x: [np.sin(x), np.cos(x)]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi)
    >>> print(Int)
    [1.99995846e+00 9.02056208e-17]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=300)
    >>> print(Int)
    [1.99998160e+00 2.68882139e-16]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=400)
    >>> print(Int)
    [1.99998967e+00 1.02348685e-16]
    >>> Int, xList, yListList = myTRAPEZOIDAL(f,0,np.pi,xPts=1000)
    >>> print(Int)
    [ 1.99999835e+00 -8.96418356e-16]
    """
    xList = np.linspace(x0, x1, num=int(xPts) )
    data = np.array(fun(xList))
    Int = myNumericalIntegration(xList,data)
    return Int, xList, data
def myFunIntegration(f, x0, x1, tol=1e-5, recursiveLim=1e4, xCountStart=100, intfun=myTRAPEZOIDAL):
    """
    >>> import numpy as np
    >>> f = lambda x: [np.sin(x), np.cos(x)]
    >>> Sn, err, nodes, count =  myFunIntegration(f, 0, np.pi, tol=1e-5)
    >>> print(Sn)
    [1.99999934e+00 2.22044605e-16]
    >>> print(err)
    2.781470994472901e-06
    >>> print(nodes.size)
    17
    >>> print(count)
    8
    >>> f = lambda x: [ x**2, x**3]
    >>> Sn, err, nodes, count =  myFunIntegration(f, -2, 2, tol=1e-5)
    >>> print(Sn)
    [5.333334 0.      ]
    >>> print(err)
    4.433938502017287e-06
    >>> print(count)
    24
    >>> print(nodes.size)
    49
    >>> f = lambda x: [ np.exp(x), np.exp(x**2)]
    >>> Sn, err, nodes, count =  myFunIntegration(f, -2, 2, tol=1e-5)
    >>> print(Sn)
    [ 7.25372109 32.90525734]
    >>> print(err)
    5.414152568605779e-06
    >>> print(count)
    68
    >>> print(nodes.size)
    137
    """
    S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    Sn, err, nodes, count = recursive_integration1(f, x0, x1, S, tol=tol, recursiveLim=recursiveLim, xCountStart=xCountStart, intfun=intfun)
    return Sn, err, nodes, count

    # S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    # Sn, err, xList, count = recursive_integration2(f, x0, x1, S, xList, tol=tol, recursiveLim=recursiveLim)
    # return Sn, err, count

    # S, xList, yListList = intfun(f, x0, x1, xPts=xCountStart)
    # Sn, err, xListNew, yListListNew, count, dxmin = recursive_integration3(f, S, xList, yListList, tol=tol, recursiveLim=recursiveLim, xCountStart=xCountStart, dxMin=dxMin)
    # return Sn, err, xListNew, count
def recursive_integration1(f, x0, x1, S, tol=1e-3, recursiveLim=1e4, xCountStart=100, intfun=myTRAPEZOIDAL):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    xc = float(x0+x1)/2
    SL, xListL, dataL = intfun(f,x0,xc,xPts=xCountStart)
    SR, xListR, dataR = intfun(f,xc,x1,xPts=xCountStart)
    Sn = SL+SR
    err = max( np.abs(Sn-S) )
    if err <= tol or recursiveLim==1:
        nodes = np.array([x0,xc,x1])
        count = 1
        return Sn, err, nodes, count
    fac = 0.5
    SL, err1, nodes1, countL = recursive_integration1(f, x0, xc, SL, tol=tol*fac, recursiveLim=recursiveLim-1, xCountStart=xCountStart, intfun=intfun)
    SR, err2, nodes2, countR = recursive_integration1(f, xc, x1, SR, tol=tol*(1-fac), recursiveLim=recursiveLim-1, xCountStart=xCountStart, intfun=intfun)
    err = err1 + err2
    nodes = np.append(nodes1, nodes2[1::])
    count = countL+1 if countL>=countR else countR+1 # countL+countR # countL+1 if countL>=countR else countR+1
    Sn = SL+SR
    return Sn, err, nodes, count
def myMidpointList_Integration2(f, xList, S):
    # Mid point
    xList = np.array(xList)
    xList2 = (xList[0:xList.size-1]+xList[1:xList.size])/2
    data = f(xList2)
    # Sum
    temptsum = np.einsum('...i,i->...', data, dx(xList) )
    Sn = (S+temptsum)/2
    # Merge List
    xListNew = np.zeros( xList.size+xList2.size, dtype=xList.dtype )
    xListNew[0::2] = xList
    xListNew[1::2] = xList2
    return Sn, xListNew
def recursive_integration2(f, x0, x1, S, xList, tol=1e-5, recursiveLim=1e4):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    Sn, xListNew = myMidpointList_Integration2(f, xList, S)
    err = max( np.abs(Sn-S) )
    if err <= tol or recursiveLim==1:
        count = 1
        return Sn, err, xListNew, count
    Sn, err, xListNew, count = recursive_integration2(f, x0, x1, Sn, xList=xListNew, tol=tol, recursiveLim=recursiveLim-1)
    count = count + 1
    return Sn, err, xListNew, count
def myMidpointList_Integration3(f, xList, yListList):
    # Mid point
    xList = np.array(xList)
    xList2 = (xList[0:xList.size-1]+xList[1:xList.size])/2
    data = f(xList2)
    # Sum
    temptsum = np.einsum('...i,i->...', data, dx(xList) )
    # Merge x List
    xListNew = np.zeros( xList.size+xList2.size, dtype=xList.dtype )
    xListNew[0::2] = xList
    xListNew[1::2] = xList2
    # Merge y List
    yListListNew = np.zeros( (yListList.shape[0], xListNew.size) , dtype=yListList.dtype)
    yListListNew[:,0::2] = yListList
    yListListNew[:,1::2] = data
    # Sum
    Sn = myNumericalIntegration(xListNew,yListListNew)
    return Sn, xListNew, yListListNew
def recursive_integration3(f, S, xList, yListList, tol=1e-5, recursiveLim=1e4, xCountStart=100, dxMin=None):
    """
    f: function of f(x)
    [a,b] : the interval of integration
    S : the previous integration result
    tol : the tolerance

    This is a subfunction of adapt_simpson.
    """
    indMid = int(len(xList)/2)
    SL, xListL, yListListL = myMidpointList_Integration3(f, xList[:indMid+1:], yListList[:,:indMid+1:] )
    SR, xListR, yListListR = myMidpointList_Integration3(f, xList[indMid::],   yListList[:,indMid::] )
    Sn = SL+SR
    err = max( np.abs(Sn-S) )
    # End Case
    xListNew = np.append( xListL, xListR[1::] )
    dxmin = min(dx(xListNew))
    if err <= tol or recursiveLim==1 or dxMin==None or dxmin<dxMin:
        count = 1
        yListListNew = np.append( yListListL, yListListR[:,1::] )
        return Sn, err, xListNew, yListListNew, count, dxmin
    # Iterative Case
    sL, sR = np.sum(SL), np.sum(SR)
    fac = sL/(sL+sR) if (sL+sR)!=0 else 0.5
    SL, errL, xListL, yListListL, countL, dxL = recursive_integration3(f, SL, xList=xListL, yListList=yListListL, tol=tol*fac, recursiveLim=recursiveLim-1, dxMin=dxMin)
    SR, errR, xListR, yListListR, countR, dxR = recursive_integration3(f, SR, xList=xListR, yListList=yListListR, tol=tol*(1-fac), recursiveLim=recursiveLim-1, dxMin=dxMin)
    Sn = SL+SR
    err = errL + errR
    count = countL+1 if countL>countR else countR+1 #countL+countR # countL+1 if countL>countR else countR+1
    xListNew = np.append( xListL, xListR[1::] )
    dxmin = dxL if dxL<dxR else dxR
    yListListNew = np.append( yListListL, yListListR[:,1::] )
    return Sn, err, xListNew, yListListNew, count, dxmin

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    """
    x = []
    y = []
    value = myNumericalIntegration(x,y)
    """


import math
import pandas as pd
import numpy as np
import statistics
import matplotlib.pyplot as plt

a = 51749
b = 1352
m = 244944
x0 = 3

# Plots Histogram along side acutal distribution
def plotHistogram(sample, method):
    hist, bins, _ = plt.hist(sample, density=True, bins=100)

    plt.title('Sample Distribution for N = {}, {}'.format(len(sample), method))
    plt.show()

### Generates x_i's from given recurrence
def generateX(a, curr, N):
    x = []
    x.append(curr);
    for i in range (N-1):
        prev = x[len(x)-1]
        curr = (a*prev + b) % m
        x.append(curr)

    return x

### Generates u_i's from given recurrence
def generateU(x):
    ulist = []
    for i in x:
        ulist.append(i/m)
    return ulist

def LCG(N):
    curr = 3
    x = generateX(a, curr, N)
    ulist = generateU(x)

    plotHistogram(ulist, 'Linear Congruence Generator')

def generateVC(N):
    data = [i for i in range(N)]

    sample = []
    for t in data:
        binary = []
        n = t

        if n==0:
            binary.append(0)

        while n!=0:
            bit = n%2
            binary.append(bit)
            n = n//2

        curr = 0.00
        temp = 1.00/2.00
        for mult in binary:
            curr += mult*temp
            temp = temp/2

        sample.append(curr)

    df = pd.DataFrame()
    df['N'] = pd.Series([i for i in range(N)])
    df['Van der Corput(N)'] = pd.Series(sample)

    return df

def generateHalton(N):
    data = [i for i in range(N)]

    sampleX = []
    sampleY = []
    for t in data:
        binary = []
        ternary = []

        n = t
        if n==0:
            binary.append(0)

        while n!=0:
            bit = n%2
            binary.append(bit)
            n = n//2

        n = t
        if n==0:
            ternary.append(0)

        while n!=0:
            bit = n%3
            ternary.append(bit)
            n = n//3

        curr = 0.00
        temp = 1.00/2.00
        for mult in binary:
            curr += mult*temp
            temp = temp/2.00
        phi_2 = curr

        curr = 0.00
        temp = 1.00/3.00
        for mult in ternary:
            curr += mult*temp
            temp = temp/3.00
        phi_3 = curr

        sampleX.append(phi_2)
        sampleY.append(phi_3)

    return sampleX, sampleY

def partA():
    df1 = generateVC(25)
    df2 = generateVC(1000)

    print("The first 25 values of the Van der Corput sequence are: ")
    print(df1)

    xAxis = []
    yAxis = []

    for i in range(len(df2['Van der Corput(N)'])-1):
        xAxis.append(df2['Van der Corput(N)'][i])
        yAxis.append(df2['Van der Corput(N)'][i+1])

    plt.plot(xAxis, yAxis)
    plt.xlabel('x(i)')
    plt.ylabel('x(i+1)')
    plt.show()

    df3 = generateVC(100)
    plotHistogram(df3['Van der Corput(N)'], 'Van der Corput')
    LCG(100)

    df4 = generateVC(100000)
    plotHistogram(df4['Van der Corput(N)'], 'Van der Corput')
    LCG(100000)

def partB():
    H_sample_X, H_sample_Y = generateHalton(100)

    plt.plot(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Actual')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    plt.scatter(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Scattered')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    H_sample_X, H_sample_Y = generateHalton(10000)

    plt.plot(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Actual')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

    plt.scatter(H_sample_X, H_sample_Y)
    plt.title('Halton Sequence Scattered')
    plt.xlabel('phi_2(i)')
    plt.ylabel('phi_3(i)')
    plt.show()

def main():
    partA()
    partB()

if __name__ == '__main__':
    main()


__all__ = ['cholInvert']
import numpy as np

def cholInvert(A):
    """
    Return the inverse of a symmetric matrix using the Cholesky decomposition. The log-determinant is
    also returned

    Args:
        A : (N,N) matrix

    Returns:
        AInv: matrix inverse
        logDeterminant: logarithm of the determinant of the matrix
    """
    L = np.linalg.cholesky(A)
    LInv = np.linalg.inv(L)
    AInv = np.dot(LInv.T, LInv)
    logDeterminant = -2.0 * np.sum(np.log(np.diag(LInv)))   # Why the minus sign?
    return AInv, logDeterminant


import numpy as np

def gaussian_1d(height: float = 1, mu: float = 0, sigma: float = 2, offset: float = 0):
    """
    Return a parametrized 1D Gaussian function.

    Parameters:
        height: float
            Distance between the lowest and peak value of the Gaussian.
        mu: float
            Expected value of the Gaussian.
        sigma: float
            Width of the Gaussian.
        offset: float
            Shifts the Gaussian `up` or `down` i.e. the background signal.
    """
    return lambda x: offset + height * np.exp(-((x - mu) ** 2 / (2 * sigma ** 2)))

def gaussian_3d(
    height: float = 1,
    mu_z: float = 0,
    mu_y: float = 0,
    mu_x: float = 0,
    sigma_z: float = 2,
    sigma_y: float = 2,
    sigma_x: float = 2,
    offset: float = 0,
):
    """
    Return a parametrized 3D Gaussian function.

    Parameters:
        height: float
            Distance between the lowest and peak value of the Gaussian.
        mu_z: float
            Expected value of the Gaussian in Z dimension.
        mu_y: float
            Expected value of the Gaussian in Y dimension.
        mu_x: float
            Expected value of the Gaussian in X dimension.
        sigma_z: float
            Width of the Gaussian in Z dimension.
        sigma_y: float
            Width of the Gaussian in Y dimension.
        sigma_x: float
            Width of the Gaussian in X dimension.
        offset: float
            Shifts the Gaussian `up` or `down` i.e. the background signal.
    """
    return lambda z, y, x: offset + height * np.exp(
        -(
            ((z - mu_z) ** 2 / (2 * sigma_z ** 2))
            + ((y - mu_y) ** 2 / (2 * sigma_y ** 2))
            + ((x - mu_x) ** 2 / (2 * sigma_x ** 2))
        )
    )


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

style.use('fivethirtyeight')
xs = np.array([1,2,3,4,5,6], dtype= np.float64)
ys = np.array([5,4,6,5,6,7], dtype= np.float64)

def create_dataset(hm, varinace, step=2, correlation= False):
	val = 1
	ys = []
	for i in range(hm):
		y = val + random.randrange(-varinace, varinace)
		ys.append(y)
		if correlation and correlation == 'pos':
			val += step
		elif correlation and correlation =='neg':
			val -= step

	xs = [i for i in range (len(ys))]
	return np.array(xs, dtype= np.float64), np.array(ys, dtype= np.float64)

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

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

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

xs, ys = create_dataset(40, 5, 2, correlation='pos')

m,b = best_fit_slope_and_intercept(xs, ys)

regression_line = [(m*x) + b for x in xs]

predict_x = 8
predict_y = (m*predict_x) + b
r_squared = coeffiecent_of_determination(ys, regression_line)
print(r_squared)

plt.scatter(xs,ys)
plt.plot(xs, regression_line)
plt.scatter(predict_x, predict_y, s = 100 ,color ='g')
plt.show()


#!/usr/bin/python3

"""
    The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.
There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.
How many circular primes are there below one million?
"""

import numpy as np

def isprime(num: int) -> bool:
    for i in range(2, int(np.sqrt(num))+1):
        if num%i == 0:
            return False
    return True

def rotate(num: int) -> set:
    rot = {num}
    length = len(str(num))
    k = 0
    while k < length:
        tmp = list(str(num))
        dig = tmp[0]
        tmp[:] = tmp[1:]
        tmp.append(dig)
        num = ''.join(tmp)
        rot.add(int(num))
        k = k + 1

    return rot

def euler35() -> int:
    tot = 0
    c_primes = [2]
    flag = False
    for i in range(3, 10**6, 2):
        if isprime(i):
            flag = True
            tmp = set()
            cps = rotate(i)
            for x in cps:
                if isprime(x):
                    tmp.add(x)
                else:
                    flag = False
                    break
        if flag:
            c_primes.extend(list(tmp))

    return len(set(c_primes))

tot = euler35()
print(tot)


# -*- coding: utf-8 -*-
"""
Created on Wed Nov  3 13:06:11 2021

@author: Oliver
"""
import numpy as np
from scipy.integrate import quad
import matplotlib.pyplot as plt

"""__________________REFERENCE SOLUTION________________"""
"""DONE"""

def integrand(x):
    return x**2+4*x-12
ans, err = quad(integrand, -10,10)
def integral(x):
    return 1/3*x**3+2*x**2-12*x
x= np.linspace(1,1000,8000)
plt.plot(x,integral(x),'g',label ='reference')
plt.legend()
plt.show()
print(f'The analytical solution to the integral is: {ans}')
print()

# Excercise 1: Implement the Midpoint/rectangular Rule code and execute
# it to integrate the function x^2+4x-12 in the domain -10<x<10

"""______________________________________________________________________"""
def calculate_dx(a,b,n):
    return (b-a)/float(n)
"""DONE"""

def rect_rule(f,a,b,n):
    total = 0.0
    dx = calculate_dx(a,b,n)
    t = []
    for i in range(0,n):
        total += abs(f((a+(i*dx))))
        t.append(total)
    plt.plot(range(n),t)
    plt.show()
    return dx*total

def f(x):
    return x**2+4*x-12

print(f' Excercise 1, midpoint/rectangular rule result: {rect_rule(f,-10,10,10000)}')

print(f'Analitycal - midpoint/rectangular rule result: {abs(rect_rule(f,-10,10,10000) - ans)}')

print(f'Relative error midpoint/rectangular result: {abs(ans - rect_rule(f,-10,10,10000) )/abs(rect_rule(f,-10,10,10000))}')
print()

"""______________________________________________________________________"""

#Excercise 2: Implement this trapezoid Rule code and execute it to integrate
# the function x**2 +4*x-12 in the domain -10<x<10 (enter inputs first)
"""NOT DONE PLOTTING"""
def trapz(f,a,b,N=50):
    x = np.linspace(a,b,N) # N+1 points make N subintervals
    print(x)
    y = f(x)
    y_right = y[1:] # right endpoints
    y_left = y[:-1] # left endpoints
    total = []
    for i in range(N+1,1,-1):
        dx = (b-a)/i
        Z = (dx/2) *np.sum(y_right+y_left)
        total.append(Z)
    plt.plot(x,total,label = 'traps')
    plt.legend()
    plt.show()
    # while i <= N:
    #     k += 2
    #     g.append((dx/2)*np.sum(f(k)+f(k+1))
    # plt.plot(range(N),g)
    # plt.show
    return Z

a= -10
b= 10
n= 10000
print(f' The trapezoidal rule result is: {trapz(f,a,b,n)}')

print(f'Analitycal - trapezoidal rule: {abs(trapz(f,a,b,n) - ans)}')

print(f'Relative error trapezoidal rule result: {abs(ans - trapz(f,a,b,n) )/abs(trapz(f,a,b,n))}')
print()

"""______________________________________________________________________"""

# Excercise 3: Implement this Simpson's One Third Rule code and execute it
# to integrate the funciton x**2+4x-12 in the domain -10<x<10
"""NOT DONE PLOTTING"""
def simps(f,a,b,N=50):
    if N % 2 == 1:
        raise ValueError("N must be an even integer")
    t=[]
    x = np.linspace(a,b,N+1)
    y = f(x)
    for i in range(1,N+2,1):
        dx = (b-a)/i
        S = dx/3 * np.sum(y[0:-1:2] + 4*y[1::2] + y[2::2]) # s[i:j:k] - "slice of s from i to j with step k
        t.append(S)
    plt.plot(x,t)
    plt.show()
    return S # y[2::2] - start at the 2nd element and skip through in steps of 2 each time

f = lambda x: x**2+4*x-12
solution = simps(f,-10,10,10000)

print(f' The simpson one third rule solution: {solution}')

print(f'Analitycal - simpson one third rule: {abs(solution - ans)}')

print(f'Relative error simpson one third rule result: {abs(ans - solution )/abs(solution)}')
print()

"""______________________________________________________________________"""

# Exercise 4: Implement this Simpson’s three eightths Rule code and execute it to
# integrate the function x2 +4x – 12 in the domain -10 < x < 10 (enter inputs first).
"""DONE"""
def func(x):
    return abs(x**2+4*x-12)

def calculate(lower_limit, upper_limit, interval_limit ):
    interval_size = (float(upper_limit - lower_limit) / interval_limit)
    sum = func(lower_limit) + func(upper_limit);
    # Calculates value till integral limit
    n=0
    k = []
    t = []
    for i in range(1, interval_limit ):
        if (i % 3 == 0):
            k.append(n)
            n +=1
            sum = sum + 2 * func(lower_limit + i * interval_size)
            t.append(sum)
        else:
            sum = sum + 3 * func(lower_limit + i * interval_size)
    plt.plot(k,t,'r')
    return ((float( 3 * interval_size) / 8 ) * sum )

# driver function

interval_limit = 10000
lower_limit = -10
upper_limit = 10
integral_res = calculate(lower_limit, upper_limit, interval_limit)

# rounding the final answer to 6 decimal places

print (f' The simpson three eigthths rule solution: {round(integral_res, 6)}')

print(f'Analitycal - three eightths rule: {abs(round(integral_res, 6) - ans)}')

print(f'Relative error simpson three eightths rule result: {abs(ans - round(integral_res, 6) )/abs(round(integral_res, 6))}')

# Plot the evolution of the integral value as a function of the number of
# integration intervals for each technique (Hint: you will have to modify each
# code to run for different values of N and plot the integral obtained for each
# run). Use arrays to store values and matplotlib to plot Integral v. N).


import math
import decimal as dec
import numpy as np

class MethodResult:
    number_of_steps = 0
    root = 0

def f1(x):
    return dec.Decimal(math.cos(x) * math.cosh(x))

def f1der(x):
    return dec.Decimal(math.cos(x) * math.sinh(x) - math.sin(x) * math.cosh(x))

def f2(x):
    if math.isclose(x, 0.0, abs_tol=1e-9):
        return dec.Decimal(10000000)
    else:
        return dec.Decimal(1/x) - dec.Decimal(math.tan(x))

def f2der(x):
    return dec.Decimal(-1/x**2) - dec.Decimal(1 / math.cos(x)**2)

def f3(x):
    return dec.Decimal(math.pow(2, -x) + math.pow(math.e, x) + 2*math.cos(x)) - 6

def f3der(x):
    return dec.Decimal(math.pow(math.e, x) - math.pow(2, -x) * math.log(2, math.e) - 2 * math.sin(x))

def f4(x):
    return dec.Decimal(x**3 - 3 * x + 1)

def f4der(x):
    return dec.Decimal(3 * x**2 - 3)

def bisection(function, float_precision, result_precision, a, b):
    f_a = function(a)
    f_b = function(b)

    if np.sign(f_a) == np.sign(f_b):
        print("Function has same signs at " + a + " and " + b + "!")
        return None

    dec.getcontext().prec = float_precision
    error = dec.Decimal(b) - dec.Decimal(a)
    number_of_steps = 0

    while not math.isclose(error, 0, abs_tol=result_precision):
        error = dec.Decimal(b) - dec.Decimal(a)
        error = error / 2
        c = dec.Decimal(a) + error
        f_c = function(c)

        number_of_steps += 1

        if math.isclose(error, 0, abs_tol=result_precision):
            result = MethodResult()
            result.number_of_steps = number_of_steps
            result.root = c
            return result

        if np.sign(f_a) != np.sign(f_c):
            b = c
        else:
            a = c
            f_a = f_c

def Newton_method(function, function_derivative, float_precision, max_iterations, result_precision, x):
    dec.getcontext().prec = float_precision
    x = dec.Decimal(x)

    number_of_steps = 0

    while (not math.isclose(function(x), 0, abs_tol=result_precision)) and (number_of_steps < max_iterations):
        x = x - function(x) / function_derivative(x)
        number_of_steps += 1

    if number_of_steps == max_iterations:
        print("Maximum number of iterations achieved!")
        result = MethodResult()
        result.number_of_steps = number_of_steps
        result.root = x
        return result

    result = MethodResult()
    result.number_of_steps = number_of_steps
    result.root = x
    return result

def secant_method(function, float_precision, max_iterations, result_precision, a, b):
    f_a = function(a)
    f_b = function(b)

    if np.sign(f_a) == np.sign(f_b):
        print("Function has same signs at " + a + " and " + b + "!")
        return None

    dec.getcontext().prec = float_precision

    a_n = dec.Decimal(a)
    b_n = dec.Decimal(b)

    root = a_n - f_a * (b_n - a_n) / (f_b - f_a)
    f_root = function(root)

    number_of_steps = 1

    while (not math.isclose(f_root, 0, abs_tol=result_precision)) and (number_of_steps < max_iterations):
        f_a = function(a_n)
        f_b = function(b_n)

        root = a_n - f_a * (b_n - a_n) / (f_b - f_a)
        f_root = function(root)

        number_of_steps += 1

        if np.sign(f_a) != np.sign(f_root):
            b_n = root
        elif np.sign(f_b) != np.sign(f_root):
            a_n = root
        elif math.isclose(f_root, 0, abs_tol=result_precision):
            result = MethodResult()
            result.number_of_steps = number_of_steps
            result.root = root
            return result

    if number_of_steps == max_iterations:
        print("Maximum number of iterations achieved!")
        result = MethodResult()
        result.number_of_steps = number_of_steps
        result.root = root
        return result

    result = MethodResult()
    result.number_of_steps = number_of_steps
    result.root = root
    return result

for i in [math.pow(10, -7), math.pow(10, -15), math.pow(10, -33)]:
    bisection_result = bisection(f1, 16, 1e-8, 1.5*math.pi, 2*math.pi)
    Newton_method_result = Newton_method(f1, f1der, 16, 50, 1e-8, (1.5*math.pi+2*math.pi)/2)
    secant_result = secant_method(f1, 16, 50, 1e-8, 1.5*math.pi, 2*math.pi)

    print("Function f1, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("")

    bisection_result = bisection(f2, 16, 1e-8, 0, math.pi/2)
    Newton_method_result = Newton_method(f2, f2der, 16, 50, 1e-8, math.pi/4)
    secant_result = secant_method(f2, 16, 50, 1e-8, 0, math.pi/2)

    print("Function f2, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("")

    bisection_result = bisection(f3, 16, 1e-8, 1, 3)
    Newton_method_result = Newton_method(f3, f3der, 16, 50, 1e-8, 2)
    secant_result = secant_method(f3, 16, 50, 1e-8, 1, 3)

    print("Function f3, precision : " + str(i))
    print(str(bisection_result.root) + " " + str(bisection_result.number_of_steps))
    print(str(Newton_method_result.root) + " " + str(Newton_method_result.number_of_steps))
    print(str(secant_result.root) + " " + str(secant_result.number_of_steps))

    print("\n")


# -*- coding: utf-8 -*-
"""
Created on Wed Feb  3 16:08:35 2021

@author: LI Zhengyang
"""

"""
Cutting Stock Problem
The length of a raw pipe is 218cm. Now the client wants 44 pipes of 81cm, 3 pipes of 70cm, 48 pipes of 68cm, what is the best cutting plan?

This problem can be formulated as an mixed integer linear programming (MILP) model:
    parameter:
        x(ni) the number of times item i is cut on roll n.
        y(n) = 1 if roll n is cut.
        l(i) the length of pipe i.
        L the length of a raw pipe.
        b(i) the required number of i type pipe.
    model:
        min = sum_n [ y(n) ]
        s.t.
        sum_n [ x(ni) ] >= b(i)                   for i in I (Set of different kinds of pipes)
        sum_i [ l(i) * x(ni) ] <= L * y(n)        for n in N (Set of stocks)
        x(ni) are integers
        y(n) are binaries
"""

import sys
import numpy as np
import gurobipy as gp
from gurobipy import GRB
import time

timeStart = time.time()

class Material:
    def __init__(self, name, length, num):
        self.name = name
        self.length = length
        self.num = num

class Demand:
    def __init__(self, name, length, num):
        self.name = name
        self.length = length
        self.num = num

matSet = {}
demSet = {}
for i in range(1,201):
    matSet[i] = Material('pipe', 218, 1)
demSet[1] = Demand('pipe1', 81, 44)
demSet[2] = Demand('pipe2', 70, 3)
demSet[3] = Demand('pipe3', 68, 48)

def solver():
    m = gp.Model("MIP")
    x = m.addVars( demSet, matSet, vtype='I', name="x")
    y = m.addVars( matSet, vtype='B', name='y')
    m.setObjective(gp.quicksum(y[mat] for mat in matSet), GRB.MINIMIZE)
    m.addConstrs(gp.quicksum(x.select(dem,'*')) >= demSet[dem].num for dem in demSet.keys())
    m.addConstrs(gp.quicksum(demSet[dem].length * x[dem,mat] for dem in demSet) <= y[mat] * matSet[mat].length for mat in matSet.keys())
    m.optimize()
    sol = m.x
    return m.objVal

obj = solver()
print('Obj = ',obj)
# print('Cutting plan (each column is a plan):')
# print(A)
# print('Number of implementation times for each cutting plan:')
# print(sol)

timeEnd = time.time()
print('Optimal cutting plan found !')
print('running time =', timeEnd-timeStart)


import numpy as np
import math
def getCircleOfBestFitCenter(xList, yList):
    inputList = []
    dList = []
    for i in range(0, len(xList)):
        inputList.append([xList[i], yList[i], 1])
        dList.append([(xList[i] ** 2) + (yList[i] ** 2)])

    inputMatrix = np.array(inputList)
    dMatrix = np.array(dList)

    outputMatrix = (np.linalg.pinv(inputMatrix)) @ dMatrix

    xCenter = outputMatrix[0]/2
    yCenter = outputMatrix[1]/2

    # rSquared = outputMatrix[2] - (xCenter ** 2) - (yCenter ** 2)
    rSquared = math.sqrt(((4 * outputMatrix[2]) + (outputMatrix[0] ** 2) + (outputMatrix[1] ** 2)))/2
    centers = [[xCenter, yCenter, rSquared]]

    return centers

xList = [205.26037743043992, 203.23717997069133, 201.3400034980003, 202.16121837617698]

yList = [95.39624792384349, 62.42071196788877, 84.03825895054133, 73.17519781157725]

circle = getCircleOfBestFitCenter(xList, yList)

print(circle)

# print("X: " + str(convertedCoordinates[0]) + " Y: " + str(convertedCoordinates[1]) + " Z: " + str(convertedCoordinates[2]))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Visualizing the Mandelbrot Set Using Python by Blake Sanie, Nov 29, 2020.
https://medium.com/swlh/visualizing-the-mandelbrot-set-using-python-50-lines-f6aa5a05cf0f

Use Python Numba for C-like performance, Jan 11, 2022.
Part 1 of 4 Just-In-Time (JIT) Compilation.
"""

from PIL import Image
from os.path import exists
from numba import njit, uint8
from timeit import default_timer as timer
import colorsys, os, sys
import numpy as np

# frame parameters
width = 700 # pixels
x, y = -0.5, 0.0
xRange = 3.4
aspectRatio = 4 / 3
precision = 500

height = round(width / aspectRatio)
yRange = xRange / aspectRatio
minX = x - xRange / 2
maxX = x + xRange / 2
minY = y - yRange / 2
maxY = y + yRange / 2

jit_start = timer()

# JIT hsv_to_rgb (Note: return type UniTuple(f8,3) or None)
hsv_to_rgb = njit('(f8, f8, f8)', nogil=True)(colorsys.hsv_to_rgb)

@njit('UniTuple(u1,3)(f8, f8, f8, f8)', nogil=True)
def powerColor(distance, exp, const, scale):
    color = distance**exp
    r,g,b = hsv_to_rgb(const + scale * color, 1 - 0.6 * color, 0.9)
    r = uint8(r * 255 + 0.5)
    g = uint8(g * 255 + 0.5)
    b = uint8(b * 255 + 0.5)
    return r,g,b

@njit('void(u1[:,:,:])', nogil=True)
def mandel(pixels):
    for row in range(height):
        for col in range(width):
            x = minX + col * xRange / width
            y = maxY - row * yRange / height
            oldX = x
            oldY = y
            for i in range(precision + 1):
                a = x*x - y*y # real component of z^2
                b = 2 * x * y # imaginary component of z^2
                x = a + oldX  # real component of new z
                y = b + oldY  # imaginary component of new z
                if x*x + y*y > 4:
                    break
            if i < precision:
                distance = (i + 1) / (precision + 1)
                # Numpy ndarray is [row, col] versus Pillow Image [col, row].
                pixels[row, col] = powerColor(distance, 0.2, 0.27, 1.0)

def save_image(pixels, filename, show_image=False):
    height, width, dim = pixels.shape
    pixels = pixels.reshape((height * width * dim,))

    img = Image.frombuffer("RGB", (width, height), pixels, "raw", "RGB", 0, 1)
    img.save(filename)
    print(f"image saved as {filename}")

    if show_image:
        if sys.platform == "darwin":
            os.system(f"open {filename}")
        elif sys.platform.startswith("linux") and exists("/usr/bin/eog"):
            os.system(f"eog {filename}")

if __name__ == '__main__':
    print("     jit time {:.3f} seconds".format(timer() - jit_start))
    pixels = np.empty((height, width, 3), dtype=np.uint8)

    s = timer()
    mandel(pixels)
    print("   mandelbrot {:.3f} seconds".format(timer() - s))

    save_image(pixels, "img1.png", show_image=False)


import sympy as sp, math as m

t, x = sp.symbols('t x')
sp.init_printing(use_unicode=True)

#f=sp.Lambda(x, x*sp.cos(x)-x**2*sp.sin(x))
#f=sp.Lambda(x, sp.cos(x))
#f=sp.Lambda(x, 1/(1+x**2))
f=sp.Lambda(x, x**2*sp.exp(x))

"""
def f(x):
    #return x*m.cos(x)-x**2*m.sin(x)
    return m.cos(x)
"""

def legendre(f, raices_legendre):

    n = len(raices_legendre)-1

    l = [1]*(n+1)

    result = 0

    for i in range(n+1):
        for j in range(n+1):
            if i != j:
                l[i]*=sp.poly((x-raices_legendre[j])/(raices_legendre[i]-raices_legendre[j]))

        #print("alpha_"+str(i)+"="+str((l[i].integrate()(1)-l[i].integrate()(-1))))

        result += (l[i].integrate()(1)-l[i].integrate()(-1))*f(raices_legendre[i])

    return result

def gaussiana(f, a, b, n, raices_legendre):

    g=sp.Lambda(t, f(((b-a)*t+b+a)/2))

    return legendre(g, raices_legendre)*(b-a)/2

raices_legendre1=[-0.5773502692, 0.5773502692]
raices_legendre2=[-0.7745966692, 0, 0.7745966692]
raices_legendre3=[-0.8611363116, -0.3399810436, 0.3399810436, 0.8611363116]
raices_legendre4=[-0.9061798459, -0.5384693101, 0, 0.5384693101, 0.9061798459]

raices_legendre=raices_legendre3

n = len(raices_legendre)-1
a, b = 0, 1/2

result = gaussiana(f, a, b, n, raices_legendre)

print(result)

# Compruebo exactitud
"""
p = sp.Poly(1,x)

for i in range(2*n+3): # Todos los errores deberian dar 0 menos el último
    #print(p)
    r = gaussiana(p, -1, 1, n, raices_legendre)
    print("Grado "+str(i)+" Error =", abs(r-p.integrate()(1)+p.integrate()(-1)))
    p*=sp.poly(x)
"""

# Ej 22
"""
fa=sp.Lambda(x, x**2*sp.log(x))
fb=sp.Lambda(x, x**3*sp.exp(-x))
fc=sp.Lambda(x, 3*x/(x**2-4))
fd=sp.Lambda(x, sp.cos(x)*sp.exp(3*x))

print("a) " + str(gaussiana(fa, 1, 1.5, 2, raices_legendre2)))
print("Error: " + str(abs(0.19225935773048122-gaussiana(fa, 1, 1.5, 2, raices_legendre2))))
print("b) " + str(sp.N(gaussiana(fb, 0, 1, 2, raices_legendre2))))
print("Error " + str(abs(0.11392894084187483-sp.N(gaussiana(fb, 0, 1, 2, raices_legendre2)))))
print("c) " + str(gaussiana(fc, 1, 1.8, 2, raices_legendre2)))
print("Error: " + str(abs(-2.059573828864272-gaussiana(fc, 1, 1.8, 2, raices_legendre2))))
print("d) " + str(gaussiana(fd, 0, m.pi/4, 2, raices_legendre2)))
print("Error " + str(abs(2.6841954140755666-gaussiana(fd, 0, m.pi/4, 2, raices_legendre2))))
"""


from __future__ import print_function

import datetime
import numpy as np
import pandas as pd
#import pandas.io.data as web
import pandas_datareader as web

def annualised_sharpe(returns, N=364):
    """
    Calculate the annualised Sharpe ratio of a returns stream
    based on a number of trading periods, N. N defaults to 252,
    which then assumes a stream of daily returns.
    The function assumes that the returns are the excess of
    those compared to a benchmark.
    """
    return np.sqrt(N) * returns.mean() / returns.std()

def equity_sharpe(ticker):
    """
    Calculates the annualised Sharpe ratio based on the daily
    returns of an equity ticker symbol listed in Google Finance.
    The dates have been hardcoded here for brevity.
    """
    start = datetime.datetime(2000,1,1)
    end = datetime.datetime(2013,1,1)

    # Obtain the equities daily historic data for the desired time period
    # and add to a pandas DataFrame
    pdf = web.DataReader(ticker, 'google', start, end)

    # Use the percentage change method to easily calculate daily returns
    pdf['daily_ret'] = pdf['Close'].pct_change()

    # Assume an average annual risk-free rate over the period of 5%
    pdf['excess_daily_ret'] = pdf['daily_ret'] - 0.05/252

    # Return the annualised Sharpe ratio based on the excess daily returns
    return annualised_sharpe(pdf['excess_daily_ret'])

def market_neutral_sharpe(ticker, benchmark):
    """
    Calculates the annualised Sharpe ratio of a market
    neutral long/short strategy inolving the long of 'ticker'
    with a corresponding short of the 'benchmark'.
    """
    start = datetime.datetime(2000, 1, 1)
    end = datetime.datetime(2013, 1, 1)

    # Get historic data for both a symbol/ticker and a benchmark ticker
    # The dates have been hardcoded, but you can modify them as you see fit!
    tick = web.DataReader(ticker, 'google', start, end)
    bench = web.DataReader(benchmark, 'google', start, end)

    # Calculate the percentage returns on each of the time series
    tick['daily_ret'] = tick['Close'].pct_change()
    bench['daily_ret'] = bench['Close'].pct_change()

    # Create a new DataFrame to store the strategy information
    # The net returns are (long - short)/2, since there is twice
    # the trading capital for this strategy
    strat = pd.DataFrame(index=tick.index)
    strat['net_ret'] = (tick['daily_ret'] - bench['daily_ret'])/2.0

    # Return the annualised Sharpe ratio for this strategy
    return annualised_sharpe(strat['net_ret'])

if __name__ == "__main__":
    print(
        "Google Sharpe Ratio: %s" %
        equity_sharpe('GOOG')
    )
    print(
        "Google Market Neutral Sharpe Ratio: %s" %
        market_neutral_sharpe('GOOG', 'SPY')
    )


# Noise reduction method
#
# utilizing "Gradual Release of Sensitive Data under Differential Privacy"
# by Fragkiskos Koufogiannis, Shuo Han, and George J. Pappas

# Background:
# We wish to release a function f of a private database, say D.
# The true output is a numpy array M = f(D).
# Each entry of M has "sensitivity" s meaning that if D changes to a
# neighboring database D', then the corresponding entry of M' = f(D')
# changes by at most s.
#
# We use differential privacy to release private versions of M.
# The output is a sequence of versions of M that become more accurate
# and less private.
#
# Given M, sensitivity, and a INCREASING list of epsilons eps_1, ..., eps_T
# 1. Construct a random walk starting from M, so that at step t, the {i,j}
#    entry is distributed as M_{ij} + Laplace(1 / eps_{T-t}).
# 2. Return the list of resulting matrices IN REVERSE ORDER of the walk,
#    i.e. from most noisy (most private) to least noisy (least private).
# 3. In particular, releasing all of the first t matrices in the list
#    is eps_t private, for each t, because:
#    - releasing the t-th matrix in the list is eps_t private
#    - all previous matrices are post-processings of this (by the random walk)

import numpy as np

# Helper function
# filter by returning each entry of matrix independently with probability 1 - prob
# and 0 with probability prob
def do_filt(prob, matrix):
  f = lambda x: 0 if np.random.random() <= prob else x
  return np.vectorize(f)(matrix)

# Main function
# Input:
#   numpy array M whose privacy is to be protected
#   sensitivity of the each entry of M
#   a INCREASING list of epsilons
#
# Output: a list of Mhat matrices approximating M, where releasing the first t
# matrices is eps_t private
def gen_list(M, sensitivity, eps_list):
  try:
    steps = len(eps_list)
  except:
    steps = eps_list.shape[0]
  shape = M.shape
  rev_eps_list = eps_list[::-1]
  noise_list = [np.random.laplace(scale=sensitivity/eps, size=shape) for eps in rev_eps_list]

  # first step, just add the noise to M
  walk = [M + noise_list[0]]

  # other steps, add the noise of each entry with only a certain probability
  filt_probs = [ (rev_eps_list[j] / rev_eps_list[j-1])**2 for j in range(1, steps) ]
  walk_steps = [do_filt(p, noise_list[1+j]) for j,p in enumerate(filt_probs)]

  for j in range(1, len(rev_eps_list)):
    walk.append(walk[j-1] + walk_steps[j-1])
  walk.reverse()
  return np.array(walk)


import numpy as np

import matplotlib.pyplot as plt

def gini(p):
    return p * (1 - p) + (1 - p) * (1 - (1 - p))

def entropy(p):
    return -(p * np.log2(p) + (1 - p) * np.log2(1 - p))

def error(p):
    return 1 - np.max([p, 1 - p])

x = np.arange(0.0, 1.0, 0.01)

ent = [entropy(p) if p != 0 else None for p in x]

# Scaled entropy
sc_ent = [e * 0.5 if e else None for e in ent]

err = [error(i) for i in x]

fig = plt.figure()
ax = plt.subplot(111)

for i, lab, ls, c, in zip([ent, sc_ent, gini(x), err],
                          ['Entropy', 'Entropy (scaled)',
                           'Gini impurity',
                           'Misclassification error'],
                          ['-', '-', '--', '-.'],
                          ['black', 'lightgray',
                           'red', 'green', 'cyan']):
    line = ax.plot(x, i, label=lab, linestyle=ls, lw=2, color=c)

ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=5,
          fancybox=True, shadow=False)

ax.axhline(y=0.5, linewidth=1, color='k', linestyle='--')
ax.axhline(y=1.0, linewidth=1, color='k', linestyle='--')

plt.ylim([0, 1.1])
plt.xlabel('p(i=1)')
plt.ylabel('impurity index')
plt.tight_layout()
plt.show()


"""
Linear Regression it assumes a linear relationship between dependent variable(y) and independent variable(X).

So the value of y can be calculated  using a linear combination of input variable X.

Very similar to how the equation of a straight line takes a linear relation between x-axis and y-axis as

y = m(x) + c

where:
    m -> slope of the line
    c -> y-intercept

we can also write equation for predicting value as

y = b0 + b1 * X

where b0 and b1 are the coefficients that we need to estimate from the training data.

Steps:
    1. Calculate the Mean and Variance
    2. Calculate covariance
    3. Estimate coefficients
    4. Make predictions
    5. Evaluate algorithm
"""
# imports
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
import random
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

random.seed(1)

# plt.scatter(X, y)
# plt.show()
# after looking at the scatter plot you can see there is a linear relation between X and  y.

# Step - 1 : Calculating Mean and variance of both X and y.

def mean(values):
    return sum(values) / float(len(values))

def variance(values, mean):
    return sum((np.array(values) - mean) ** 2) / float(len(values))

# Step -2 : Calculating Covariance of X and y

def covariance(x, mean_x, y, mean_y):
    x = np.array(x)
    y = np.array(y)
    return sum((x - mean_x) * (y - mean_y)) / float(len(x))

# Step - 3 :  Estimate coefficients
def coefficient(X, y):
    mean_x, mean_y = mean(X), mean(y)
    b1 = covariance(X, mean_x, y, mean_y) / variance(X, mean_x)
    b0 = mean_y - b1 * mean_x
    return [b0, b1]

def split_data(X, y, test_size=0.2):
    return train_test_split(X, y, test_size=test_size, random_state=42)

# Step - 4 : Make predictions
def simple_linear_regression(X_train, y_train, X_test):
    b0, b1 = coefficient(X_train, y_train)
    predictions = b0 + b1 * X_test
    return predictions

def rmse_metric(actual, predicted):
    actual = np.array(actual)
    predicted = np.array(predicted)
    prediction_error = actual - predicted
    rmse = np.sqrt(sum(prediction_error ** 2) / float(len(actual)))
    return rmse

def evaluate_algorithm(X, y, algorithm, split_size):
    X_train, X_test, y_train, y_test = split_data(X, y, split_size)
    predicted = algorithm(X_train, y_train, X_test)
    rmse = rmse_metric(y_test, predicted)
    return rmse

#
# print("X-stats : mean= %0.3f variance= %0.3f " % (mean_x, var_x))
# print("y-stats : mean= %0.3f variance= %0.3f " % (mean_y, var_y))
# print("Covariance of X, y : %0.3f" % (covarxy))
# print("coefficients  b0=%0.3f,. b1=%0.3f " % (b0, b1))

df = pd.read_excel(io="insurance.xls", encoding='ascii')
X = df["X"]
y = df["Y"]
test_size = 0.2

b0, b1 = coefficient(X, y)
y_hat = b0 + b1 * X

# plt.scatter(X, y, label="Data points")
# plt.plot(X, y_hat, color='#00ff00', label="LinearRegression")
# plt.legend()
# plt.show()

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=42)

def rSquare(X_train, X_test, y_train, y_test, algorithm):
    predicted = algorithm(X_train, y_train, X_test)
    y_mean_line = mean(y_test)
    squared_error_regr = sum((predicted - y_test)**2)
    squared_error_ymean = sum((y_test - y_mean_line)**2)
    return 1 - squared_error_regr / squared_error_ymean

rmse = evaluate_algorithm(X, y, simple_linear_regression, test_size)
print("RMSE from Math : %0.3f" % (rmse))
print("Rsquare error from Math: %0.3f " % (rSquare(X_train, X_test, y_train, y_test, simple_linear_regression)))

lr = LinearRegression()
lr.fit(np.array(X_train).reshape(-1, 1), y_train)
y_pred = lr.predict(np.array(X_test).reshape(-1, 1))
print("RMSE from sklearn ", np.sqrt(mean_squared_error(y_test, y_pred)))
print("Rsquare error : ",r2_score(y_test, y_pred))