import numpy as np

# A self-contained reference implementation of the B-spline basis functions
# and their first derivatives, in addition to some utiliy functions.

def float_is_zero(v, eps=1e-6):
    """ Test if floating point number is zero. """
    return abs(v) < eps

def special_div(num, den):
    """ Return num/dev with the special rule
    that 0/0 is 0. """
    if float_is_zero(num) and float_is_zero(den):
        return 0.0
    else:
        return num / den

def B(j, p, x, knots):
    """ Compute B-splines using recursive definition. """
    if p == 0:
        if knots[j] <= x < knots[j+1]:
            return 1.0
        else:
            return 0.0
    else:
        left = special_div((x-knots[j])*B(j,p-1,x,knots), knots[j+p]-knots[j])
        right = special_div((knots[j+1+p]-x)*B(j+1,p-1,x,knots), knots[j+1+p]-knots[j+1])
        return left + right

def render_spline(p, knots, control_points, ts):
    """
    Compute points on a spline function using the straightforward
    implementation of the recurrence relation for the B-splines.
    """
    ys = []
    for t in ts:
        y = 0.0
        for j in range(0, len(control_points)):
            y += B(j, p, t, knots)*control_points[j]
        ys.append(y)
    return ys

def uniform_regular_knot_vector(n, p, t0=0.0, t1=1.0):
    """
    Create a p+1-regular uniform knot vector for
    a given number of control points
    Throws if n is too small
    """
    # The minimum length of a p+1-regular knot vector
    # is 2*(p+1)
    if n < p+1:
        raise RuntimeError("Too small n for a uniform regular knot vector")

    # p+1 copies of t0 left and p+1 copies of t1 right
    # but one of each in linspace
    return [t0]*p + list(np.linspace(t0, t1, n+1-p)) + [t1]*p

def control_points(p, knots):
    """
    Return the control point abscissa for the control polygon
    of a one-dimensional spline.
    """
    knots = np.array(knots)
    abscissas = []
    for i in range(len(knots)-p-1):
        part = knots[(i+1):(i+1+p)]
        abscissas.append(np.mean(part))
    return abscissas

def B_derivative(j, p, x, knots):
    """
    Evaluate the derivative of Bj,p(x)
    p must be greater than or equal to 1
    """
    if p < 1: raise RuntimeError("p must be greater than or equal to 1")
    left = special_div(B(j, p-1, x, knots), (knots[j+p]-knots[j]) )
    right = special_div(B(j+1,p-1, x, knots), (knots[j+p+1] - knots[j+1]) )
    return (left - right)*p


#!/usr/bin/env python

# Import modules
import math
import numpy
import matplotlib.pyplot as plt # import plot

def trinome(a,b,c,x):
    return (a*x**2 + b*x + c)

# fonction calculant le discriminant
def discriminant(a,b,c):
    return b**2 - 4*a*c

# fonction retournant la solution double
def solutiondouble(a,b):
    return (-b/(2*a))

# fonction retournant les solutions
def solution(a,b,delta):
    x1 = (-b - math.sqrt(delta)) /(2*a)
    x2 = (-b + math.sqrt(delta)) /(2*a)
    return x1, x2

# fonction de base de résolution de l'équation
def resolution(a,b,c):
    print("Soit l'équation : ",a,"x**2 + ",b,"x + ",c, " = 0" )
    delta = discriminant(a,b,c)

    if(delta < 0):
        print("L'équation n'a pas de solution")

        # Sinon, delta peut être positif ou nul
    else:
        # Si delta est nul
        if (delta == 0):
            print("L'équation a une solution double.")
            print ("La solution est x = ", solutiondouble(a,b))

            #Sinon,  delta ne peut plus qu'être positif.
        else:
            print("L'équation a deux solutions solutions.")
            x1, x2 = solution(a,b,delta)
            print ("Les solutions sont x1 = ",x1, " et ", x2)

def plot_trinome(a,b,c):
    # L'intervalle de trace
    x = numpy.linspace(-10,10,100)

    # La courbe
    plt.plot(x,trinome(a,b,c, x))

    # Les axes
    plt.axvline(x=0, color ='r')
    plt.axhline(y=0, color ='r')
    axes = plt.gca()
    axes.set_xlabel('x : abscisse')
    axes.set_ylabel('f(x) : ordonnée')

    plt.show()

# a*x**2 + b*x + c un trinôme du second degré.
# On rentre les valeurs de a, b et c.
# On souhaite que a, b et c soient des nombres rééls (float en anglais)
# et non une chaîne de charactère (comme pour une phrase)
# a = float(input("a = "))
# b = float(input("b = "))
# c = float(input("c = "))

# Sinon, on fait un simple appel à la fonction avec les bon paramètres:
print(" ---------- Exercice 1 ------------")
resolution(1,-120,2000)

print(" ---------- Exercice 2 ------------")
print("Exercice 2.1")
resolution(1,2,-3)

# On peut également appeler le tracer avec matplotlib.
plot_trinome(1,2,-3)

print("Exercice 2.2")
resolution(1,6,9)

print("Exercice 2.3")
resolution(2,1,(1/2))

print("Exercice 2.4")
resolution(3,2,(1/3))


"""
@author: MatteoRaso
"""
from numpy import pi, sqrt
from random import uniform
from statistics import mean

def pi_estimator(iterations: int):
    """
    An implementation of the Monte Carlo method used to find pi.
    1. Draw a 2x2 square centred at (0,0).
    2. Inscribe a circle within the square.
    3. For each iteration, place a dot anywhere in the square.
       a. Record the number of dots within the circle.
    4. After all the dots are placed, divide the dots in the circle by the total.
    5. Multiply this value by 4 to get your estimate of pi.
    6. Print the estimated and numpy value of pi
    """
    # A local function to see if a dot lands in the circle.
    def in_circle(x: float, y: float) -> bool:
        distance_from_centre = sqrt((x ** 2) + (y ** 2))
        # Our circle has a radius of 1, so a distance
        # greater than 1 would land outside the circle.
        return distance_from_centre <= 1

    # The proportion of guesses that landed in the circle
    proportion = mean(
        int(in_circle(uniform(-1.0, 1.0), uniform(-1.0, 1.0)))
        for _ in range(iterations)
    )
    # The ratio of the area for circle to square is pi/4.
    pi_estimate = proportion * 4
    print("The estimated value of pi is ", pi_estimate)
    print("The numpy value of pi is ", pi)
    print("The total error is ", abs(pi - pi_estimate))

def area_under_line_estimator(
    iterations: int, min_value: float = 0.0, max_value: float = 1.0
) -> float:
    """
    An implementation of the Monte Carlo method to find area under
       y = x where x lies between min_value to max_value
    1. Let x be a uniformly distributed random variable between min_value to max_value
    2. Expected value of x = (integration of x from min_value to max_value) / (max_value - min_value)
    3. Finding expected value of x:
        a. Repeatedly draw x from uniform distribution
        b. Expected value = average of those values
    4. Actual value = (max_value^2 - min_value^2) / 2
    5. Returns estimated value
    """
    return mean(uniform(min_value, max_value) for _ in range(iterations)) * (
        max_value - min_value
    )

def area_under_line_estimator_check(
    iterations: int, min_value: float = 0.0, max_value: float = 1.0
) -> None:
    """
    Checks estimation error for area_under_line_estimator func
    1. Calls "area_under_line_estimator" function
    2. Compares with the expected value
    3. Prints estimated, expected and error value
    """

    estimated_value = area_under_line_estimator(iterations, min_value, max_value)
    expected_value = (max_value * max_value - min_value * min_value) / 2

    print("******************")
    print(
        "Estimating area under y=x where x varies from ", min_value, " to ", max_value
    )
    print("Estimated value is ", estimated_value)
    print("Expected value is ", expected_value)
    print("Total error is ", abs(estimated_value - expected_value))
    print("******************")

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import math
import numpy as np
import time

def basic_sigmoid(x):
    s = 1 / (1+math.exp(-x))
    return s

x = np.array([1, 2, 3])

def sigmoid(x):
    s = 1./(1+np.exp(-x))
    return s

def sigmoid_derivative(x):
    s = sigmoid(x)
    ds = s *(1-s)
    return ds

print("sigmoid_derivative(x) = " + str(sigmoid_derivative(x)))

def image2vector(image):
    v = image.reshape(image.size, 1)
    return v

image = np.array([[[ 0.67826139,  0.29380381],
        [ 0.90714982,  0.52835647],
        [ 0.4215251 ,  0.45017551]],

       [[ 0.92814219,  0.96677647],
        [ 0.85304703,  0.52351845],
        [ 0.19981397,  0.27417313]],

       [[ 0.60659855,  0.00533165],
        [ 0.10820313,  0.49978937],
        [ 0.34144279,  0.94630077]]])

def normalizeRows(x):
    x_norm = np.linalg.norm(x, ord=2, axis=1, keepdims=True)
    x = x / x_norm
    return x

x = np.array([
    [0, 3, 4],
    [2, 6, 4]])

def softmax(x):
    x_exp = np.exp(x)
    x_sum = np.sum(x_exp, axis=1, keepdims=True)
    s = x_exp / x_sum
    return s

x = np.array([[9, 2, 5, 0, 0],
              [7, 5, 0, 0, 0]])

#Vectorization
x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### CLASSIC DOT PRODUCT OF VECTORS IMPLEMENTATION ###
tic  = time.process_time()
dot = 0
for i in range(len(x1)):
    dot += x1[i] + x2[i]
toc = time.process_time()

### CLASSIC OUTER PRODUCT IMPLEMENTATION ###
tic = time.process_time()
outer = np.zeros((len(x1), len(x2)))

for i in range(len(x1)):
    for j in range(len(x2)):
        outer[i, j] = x1[i]*x2[j]

toc = time.process_time()

### CLASSIC ELEMENTWISE IMPLEMENTATION ###
tic = time.process_time()
mul = np.zeros(len(x1))
for i in range(len(x1)):
    mul[i] = x1[i]*x2[i]
toc = time.process_time()

### CLASSIC GENERAL DOT PRODUCT IMPLEMENTATION ###
W = np.random.rand(3, len(x1))
tic = time.process_time()
gdot = np.zeros(W.shape[0])

for i in range(W.shape[0]):
    for j in range(len(x1)):
        gdot[i] += W[i, j] * x[j]

toc = time.process_time()

x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### VECTORIZED DOT PRODUCT OF VECTORS ###
tic = time.process_time()
dot = np.dot(x1, x2)
toc =time.process_time()

### VECTORIZED OUTER PRODUCT ###
tic = time.process_time()
outer = np.outer(x1, x2)
toc = time.process_time()

### VECTORIZED ELEMENTWISE MULTIPLICATION ###
tic = time.process_time()
mul = np.mutiply(x1,x2)
toc = time.process_time()

### VECTORIZED GENERAL DOT PRODUCT ###
tic = time.process_time()
mul = np.dot(W,x1)
toc = time.process_time()

def L1(yhat, y):
    loss = np.sum(np.abs((y-yhat)))
    return loss

def L2(yhat, y):
    loss = np.sum(np.square(yhat - y))
    return loss

Neural Networks and Deep Learning/Python_basics_with_numpy.py


from __future__ import division
# from math import sin, cos
import pylab as pl
import numpy as np
from numpy import sin,cos

def f_dash_central_diff(f,x,h):
    '''
    Calculation of the first derivative
    using the central difference formula
    f'(x) = f(x + h) - f(x - h)
            -------------------
                    2*h
    Inputs :-
    f : The function whose first derivative is to be calculated.
        (Assuming the function just takes 1 argument)
    x : The value at which the number is to be calculated.
    h : Interval around x

    Output :-
    Returns the value of f(x) with truncation error
    '''

    return (f(x + h) - f(x - h)) / (2 * h)

if __name__ == '__main__':
    # The value at which the first derivative is to be calculated.
    x = 0.5

    # Interval around x
    h = np.linspace(10 ** -8, 1, 25)

    # The function whose derivative is to be calculated
    f = sin

    # The analytical derivative of the above function
    f_dash = cos

    # Third derivative of f
    f_3dash = lambda x: -1 * cos(x)

    # The number of datapoints for which to calculate the plot
    # n = 250

    # Data points
    error_total = abs(f_dash_central_diff(f,x,h) - f_dash(x))
    error_trunc = ((-1 * (h ** 2)/6) * f_3dash(x))
    error_round = (error_total - error_trunc)
    dp = zip(h, error_total, error_trunc, error_round)

    # for _ in range(n):
    # for hi in h:
    #     # Calculate the value of the function
    #     # error_total = abs(f_dash_central_diff(f, x, h) - f_dash(x))
    #     error_total = abs(f_dash_central_diff(f, x, hi) - f_dash(x))

    #     # Truncation error
    #     # error_trunc = abs((-1 * (h ** 2)/6) * f_3dash(x))
    #     error_trunc = abs((-1 * (hi ** 2)/6) * f_3dash(x))

    #     # Rounding error
    #     error_round = abs(error_total - error_trunc)

    #     dp.append([h, error_total, error_trunc, error_round])
    #

    #     h = h/4

    # log_dp = np.log(dp)
    # dp = log_dp
    print(dp)
    # pl.loglog(dp[0], dp[1], dp[0], dp[2], dp[0], dp[3])
    # pl.plot(dp[0], dp[1], dp[0], dp[2], dp[0], dp[3])
    pl.loglog(dp[0], dp[2])
    pl.legend(["$\epsilon$", "$\epsilon_t$", "$\epsilon_r$"], loc = 4)
    pl.savefig("ex1.pdf")
    pl.show()


#!/usr/bin/python
# -*- coding: UTF-8 -*-

import timeit
import numpy as np

import math

import random as rand

class solutions:

    def take_stairs(self,n):
        """
        走楼梯问题：
        假如这里有 n 个台阶，每次你可以跨 1 个台阶或者 2 个台阶，请问走这 n 个台阶有多少种走法？

        递归解：

        1. 递推公式
        f(n)=f(n-1)+f(n-2)

        2.递归终止条件
        f(1)=1
        f(2)=1

        ref: https://time.geekbang.org/column/article/41440
        :param n:
        :return:
        """
        if n==2 or n==1:
            return 1

        return self.take_stairs_recursion(n-1)+self.take_stairs_recursion(n-2)

    def take_stairs_v2(self, n):
        """
        走楼梯问题 v2
        1.递归解，并加入 缓存机制，解决重复子问题

        :param n:
        :return:
        """
        self.cache={}

        return self.__process(n)

    def __process(self,n):

        if n==2 or n==1:
            return 1

        if self.cache.get(n-1)==None:
            left=self.__process(n-1)

        else:
            left=self.cache[n-1]

        if self.cache.get(n - 2) == None:
            right = self.__process(n - 2)

        else:
            right = self.cache[n - 2]

        return left+right

    def take_stairs_v3(self,n):
        """
        走楼梯问题 v3

        动态规划解
        1.条件：
        （1）重复子问题
        （2）最优子结构

        :param n:
        :return:
        """

        dp=np.zeros(n+1,dtype=int)

        dp[1]=1
        dp[2]=1

        for i in range(3,n+1):
            dp[i]=dp[i-1]+dp[i-2]

        return dp[n]

    def take_stairs_v4(self,n):
        """
        走楼梯问题 v4

        动态规划解
        1.优化空间复杂度

        :param n:
        :return:
        """

        if n<=2:
            return 1

        prev=1
        pre_prev=1

        for i in range(3, n + 1):
            current=prev+pre_prev

            pre_prev=prev
            prev = current

        return current

    def fab(self,n):

        if n<3:
            return 1
        else:
            return self.fab(n-1)+self.fab(n-2)

    def fab_v1(self, n,b1=1,b2=1,c=3):
        """
        具有 线性迭代过程 特性的递归——尾递归
        :param n:
        :param b1:
        :param b2:
        :param c:
        :return:
        """
        if n<3:
            return 1
        else:
            if n==c:
                return b1+b2

            else:
                return self.fab_v1(n,b2,b1+b2,c+1)

if __name__ == '__main__':
    sol = solutions()

    print(sol.take_stairs_v4(6))


import math
from sympy import Symbol, Limit, Derivative, sympify, S, simplify, pprint, init_printing

init_printing( order = 'rev-lex', use_unicode = True )

# 3.1.1

# h(t) = e^t + 2t^2
# simplify the expression:
# ( h( 5 + Dt ) - h( 5 ) ) / Dt

Ht = math.e**t + 2*t**2
t = Symbol( 't' )
delta_t = Symbol( 'delta_t' )

Ht1 = Ht.subs( { t: 5 } )
Ht1_delta = Ht.subs( { t: 5 + delta_t } )

f = ( Ht1_delta - Ht1 ) / delta_t

simplify( f )

Limit( f, delta_t, 0 ).doit()

# 3.1.2

# Suppose f(x) = -2x^2.
# What is the instantaneous rate of change of f(x) when x = 4?

Fx = -2*x**2
x = Symbol( 'x' )
d = Derivative( Fx, x ).doit()
d.subs( { x: 4 } )

# 3.1.3

# A particular bird's flight position in feet is given by the equation P(t) = 12t^2/7 +t, where
# t is the number of seconds that elapse.
# What is the bird's instantaneous velocity when t = a?
Pt = 12*t**2 / 7 + t
t = Symbol( 't' )
a = Symbol( 'a ' )
d = Derivative( Pt, t ).doit()
d.subs( { t: a } )

# 3.1.5

# A biker rides along a horizontal straight line with its location given by the fraction,
# s(t) = (1/10)t^2, where t is measured in minutes and s is in miles.
# To estimate the instantaneous velocity at time t = 2 minutes,
# compute the average rate of change from t = 1.9 to t = 2.0 minutes.

St = 1/10*t**2
t = Symbol( 't' )

t1 = 2.0
St1 = St.subs( { t: t1 } )

t2 = 1.9
St2 = St.subs( { t: t2 } )

( St1 - St2 ) / ( t1 - t2 )

# 3.1.6

# Suppose f(x) = x^2 - 3.
# What is the slope of the line tangent to f(x), at x = 3?

Fx = x**2 - 3
x = Symbol( 'x' )
d = Derivative( Fx, x ).doit()
d.subs( { x: 3 } )

# 3.1.7

# Given taht f(t) = 2t^2 - 4t and f'(x) = 4t - 4,
# find the instantaneous rate of change at t = 3.

FPx = 4*t - 4
t = Symbol( 't' )
FPx.subs( { t: 3 } )

# 3.1.8

# A bowling ball's position as it rolls down the lane is described by the position function,
# s(t) = 5t - 1/8t**2, where t is in seconds and s(t) is in feet. What is the bowling ball's
# instantanous velocity at t = 4?

St = 5*t - (1/8)*t**2
t = Symbol( 't' )
d = Derivative( St, t ).doit()
d.subs( { t: 4 } )

# 3.1.10

# Consider the curve f(x) = 4x^2, 0 <= x <= 3.
# What is the greatest possible slope of a secant line across
# interval of width .1 ?

Fx = 4*x**2
x = Symbol( 'x' )

interval = .1
p_max = 3

( Fx.subs( { x: p_max } ) - Fx.subs( { x: p_max - interval } ) ) / interval


"""
@author: Tony Lin
@description: approximates the value of Pi by first having a circle
of radius r inside a square of length 2r and then randomly sampling
and counting the number of points that fall in the circle. Pi can then
be approximated by dividing the points in the circle by the total number
of points and multiplying by four.
"""

import matplotlib; matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import random
import numpy as np

def check_in_circle(x, y):
    '''
    Checks if point is in the circle by calculating the distance from the center
    :param x: x point
    :param y: y point
    :return: true if in circle, else false
    '''

    dist = (x ** 2 + y ** 2) ** 0.5
    return dist <= 1

def random_point():
    '''
    Finds random x, y points within the dimensions
    :return: x, y points
    '''

    x = random.uniform(-1.0, 1.0)
    y = random.uniform(-1.0, 1.0)
    return [x, y]

def plot_accuracy(num_iter, pi_values):
    '''
    Plots the accuracy score of Pi with respect to the number of
    iterations. Accuracy score calculated as abs(estimated - true) / true
    :param num_iter: number of iterations
    :param pi_values: values of estimated Pi
    :return: The iteration with the lowest error and the respective Pi value
    '''

    fig2, ax2 = plt.subplots()
    accuracy_error = np.abs(np.array(pi_values) - np.pi) / np.pi
    ax2.plot(range(0, num_iter), accuracy_error)
    ax2.set_title("Accuracy of Pi Approximation Over Number of Iterations")
    ax2.set_xlabel("Number of Iterations")
    ax2.set_ylabel("Accuracy Error")
    fig2.canvas.draw_idle()
    plt.show()

    min_error_index = np.argmin(accuracy_error)
    return min_error_index, pi_values[min_error_index]

def estimate_pi(num_iter=1000, live_plotting=False):
    '''
    Gets num_iter amount of random points, counts how many fall within the
    circle of radius 1, and approximates Pi using:
    (number of points in circle / total number of points) * 4.0
    Option to plot the points as well as an accuracy score for each iteration
    :param num_iter: number of iterations
    :param live_plotting: option to display animated plotting
    :return: approximated value of Pi
    '''

    fig1, ax1 = plt.subplots()
    plt.xlim(-1, 1)
    plt.ylim(-1, 1)
    plt.draw()

    pi_values = []
    x, y, color = [], [], []
    num_in_circle = 0.0
    total_points = 0.0

    for i in range(0, num_iter):
        point = random_point()
        x.append(point[0])
        y.append(point[1])

        if check_in_circle(x[i], y[i]):
            num_in_circle += 1
            color.append('blue')
        else:
            color.append('red')

        total_points += 1

        pi_values.append((num_in_circle / total_points) * 4.0)

        if live_plotting:
            ax1.scatter(x[i], y[i], c=color[i])
            fig1.canvas.draw_idle()
            plt.pause(0.1)

    if not live_plotting:
        ax1.scatter(x, y, c=color)
        fig1.canvas.draw_idle()

    min_error_index, min_error_pi = plot_accuracy(num_iter, pi_values)

    return pi_values[num_iter-1], min_error_index, min_error_pi

if __name__ == "__main__":
    NUM_ITER = 100

    approx_pi, min_error_index, min_error_pi = estimate_pi(num_iter=NUM_ITER, live_plotting=True)
    print("The most accurate value of {} occurred at iteration {}".format(min_error_pi, min_error_index))
    print("The Pi value after {} iterations is {}".format(NUM_ITER, approx_pi))


import numpy as np

#Questions on NumPy Mathematics
# How to get element-wise true division of an array using Numpy?
#true_divide Returns a true division of the inputs, element-wise.
np.true_divide(np.arange(5), 4)

# How to calculate the element-wise absolute value of NumPy array?
#absolute Calculate the absolute value element-wise.
np.absolute(np.array([-1, -2, -3]))

# Compute the negative of the NumPy array
#negative Numerical negative, element-wise.
np.negative([1.,-1.])

# Multiply 2d numpy array corresponding to 1d array
(np.array([[1, 2, 3], [2, 4, 5], [1, 2, 3]]).T * np.array([0, 2, 3]) ).T

# Computes the inner product of two arrays
#inner Inner product of two arrays.
np.inner(np.array([[1, 4], [5, 6]]), np.array([[2, 4], [5, 2]]))

# Compute the nth percentile of the NumPy array
#percentile Compute the q-th percentile of the data along the specified axis.
np.percentile(np.array([[10, 7, 4], [3, 2, 1]]), 50)

# Calculate the n-th order discrete difference along the given axis
#diff Calculate the n-th discrete difference along the given axis.
np.diff(np.array([1, 2, 4, 7, 0]))

# Calculate the sum of all columns in a 2D NumPy array
#sum Sum of array elements over a given axis.
np.sum([[1.2, 2.3], [3.4, 4.5]] , axis = 0)

# Calculate average values of two given NumPy arrays
arr1 = np.array([[3, 4], [8, 2]])
arr2 = np.array([[1, 0], [6, 6]])
avg = (arr1 + arr2) / 2
avg

# How to compute numerical negative value for all elements in a given NumPy array?
np.negative(np.array([-1, -2, -3,  1, 2, 3, 0]))

# How to get the floor, ceiling and truncated values of the elements of a numpy array?
#floor Return the floor of the input, element-wise.
#ceil Return the ceiling of the input, element-wise.
#trunc The truncated of each element, with float data-type
np.floor(np.array([-1.8, -1.6, -0.5, 0.5, 1.6, 1.8, 3.0]))
np.ceil(np.array([-1.8, -1.6, -0.5, 0.5, 1.6, 1.8, 3.0]))
np.trunc(np.array([-1.8, -1.6, -0.5, 0.5, 1.6, 1.8, 3.0]))

# How to round elements of the NumPy array to the nearest integer?
# rint Round elements of the array to the nearest integer.
np.rint(np.array([0.2, 0.3, 0.4, 0.5, 0.6, 0.7]))

# Find the round off the values of the given matrix
#matrix.round  Return rounded values in matrix
np.matrix('[6.4, 1.3; 12.7, 32.3]').round()

# Determine the positive square-root of an array
#sqrt Returns the square root of the number in an array.
np.sqrt([1, 4, 9, 16])

# Evaluate Einstein’s summation convention of two multidimensional NumPy arrays
#einsum Evaluates the Einstein summation convention on the operands.
np.einsum("mk,kn", np.array([[1, 2], [0, 2]]) , np.array([[0, 1], [3, 4]]) )

NumpyTutorial/Questions on NumPy Mathematics.py


'''
        # Numpy

- Numeric Python
- Alternative array to Pthon array: Numpy Array
- Calculations overy entire array

pip3 install numpy
'''

import numpy as np

weight = [52, 51, 66, 71, 67, 123]
height = [1.56, 1.47, 1.66, 1.81, 1.75, 2]

np_weight = np.array(weight)
np_height = np.array(height)
print(np_weight)
print(np_height)

## weight / height ** 2 syntax error
bmi = np_weight / np_height ** 2
print(bmi)

# np arrays can contain one data type
# x = np.array('x',12,23.4,True) # TypeError

python_list = [1, 2, 3]
numpy_array = np.array([1, 2, 3])

python_list + python_list  # [1, 2, 3, 1, 2, 3]
numpy_array + numpy_array  # array([2, 4, 6])

print(bmi)  # [21.36752137 23.60127725 23.95122659 21.67211013 21.87755102 30.75      ]
print(bmi > 22)  # [False  True  True False False  True]
print(bmi[bmi > 21.7])  # [23.60127725 23.95122659 21.87755102 30.75      ]

# 2D NUMPY ARRAYS

print(type(np_height))  # np_height_in[100:111] => n-dimensional array

np_2d = np.array([[1.73, 1.68, 1.71, 1.89, 1.79],
                  [65.4, 59.2, 63.6, 88.4, 68.7]])

print(np_2d.shape)  # (2, 5) 2 row 5 column
np_2d[0, 3]  # np_2d[0][3]

print(np_2d[:, 1:4])
# [[ 1.68  1.71  1.89]
# [59.2  63.6  88.4 ]]

print(np_2d[1, :])  # [65.4 59.2 63.6 88.4 68.7]

np_mat = np.array([[1, 2],
                   [3, 4],
                   [5, 6]])

np_mat * 2  # array([[ 2,  4],[ 6,  8],[10, 12]])

# Numpy Statistics
np_city = np.array([[1.64, 71.78],
                    [1.37, 63.35],
                    [1.6, 55.09],
                    [2.04, 74.85],
                    [2.04, 68.72],
                    [2.01, 73.57]])

mean_val = np.mean(np_city[:, 0])  # all member sums / count
print(np_city[:, 0])  # [1.64 1.37 1.6  2.04 2.04 2.01]
print(mean_val)  # 1.7833333333333332

median_val = np.median(np_city[:, 0])
print(median_val)  # middle val, if 2 mid child sums/2

print(np.corrcoef(np_city[:, 0], np_city[:, 1]))  # Function that returns a matrix of
# correlations of x with x, x with y, y with x and y with y. # TODO: I will check this out later

print(np.std(np_city[:, 0]))  # standart deviation 0.2608107018935807

# .sum() .sort() in numpy calculations faster because array has 1 type of data

'''
# heights and positions are available as lists

# Import numpy
import numpy as np

# Convert positions and heights to numpy arrays: np_positions, np_heights
np_positions = np.array(positions)
np_heights = np.array(heights)

# Heights of the goalkeepers: gk_heights
goalkeepers_indexes = np.array(np_positions == 'GK')
#print(goalkeepers_indexes)
#print(np_heights[goalkeepers_indexes])
gk_heights = np_heights[goalkeepers_indexes]

# Heights of the other players: other_heights
other_indexes = np_positions != 'GK'
other_heights = np_heights[other_indexes]

# Print out the median height of goalkeepers. Replace 'None'
print("Median height of goalkeepers: " + str(np.median(gk_heights)))

# Print out the median height of other players. Replace 'None'
print("Median height of other players: " + str(np.median(other_heights)))
'''


import torch
import numpy as np

"""

Exercise 1.1: Diagonal Gaussian Likelihood

Write a function that takes in PyTorch Tensors for the means and
log stds of a batch of diagonal Gaussian distributions, along with a
PyTorch Tensor for (previously-generated) samples from those
distributions, and returns a Tensor containing the log
likelihoods of those samples.

"""

def gaussian_likelihood(x, mu, log_std):
    """
    Args:
        x: Tensor with shape [batch, dim]
        mu: Tensor with shape [batch, dim]
        log_std: Tensor with shape [batch, dim] or [dim]

    Returns:
        Tensor with shape [batch]
    """
    # Referencing the Log-Likelihood equation from the [documentation](https://spinningup.openai.com/en/latest/spinningup/rl_intro.html)

    if len(x.shape) == 1:
        x = torch.unsqueeze(x, dim=0)

    batch, dim = x.shape

    squared_diff = (x - mu)**2 # shape [batch, dim]
    std = torch.exp(log_std) # shape [batch, dim] or [dim]

    # I had to check with the solution to figure out I needed to add 1E-8 to std
    diff_std_ratio = squared_diff / (std + 1E-8)**2 # shape [batch, dim]

    summed = torch.sum(diff_std_ratio + 2*log_std, dim=1) # shape [batch]
    log_likelihood = -0.5*(summed + dim*np.log(2*np.pi)) # shape [batch]
    return log_likelihood

if __name__ == '__main__':
    """
    Run this file to verify your solution.
    """
    from spinup.exercises.pytorch.problem_set_1_solutions import exercise1_1_soln
    from spinup.exercises.common import print_result

    batch_size = 32
    dim = 10

    x = torch.rand(batch_size, dim)
    mu = torch.rand(batch_size, dim)
    log_std = torch.rand(dim)

    your_gaussian_likelihood = gaussian_likelihood(x, mu, log_std)
    true_gaussian_likelihood = exercise1_1_soln.gaussian_likelihood(x, mu, log_std)

    your_result = your_gaussian_likelihood.detach().numpy()
    true_result = true_gaussian_likelihood.detach().numpy()

    correct = np.allclose(your_result, true_result)
    print_result(correct)


import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import matplotlib.cm as cm

def L(A, normalized=True, verbose=False):
    """L: compute a graph laplacian

    Args:
        A (N x N np.ndarray): Adjacency matrix of graph
        normalized (bool, optional): Normalized or combinatorial Laplacian

    Returns:
        L (N x N np.ndarray): graph Laplacian
    """
    # Compute the degree as D = diag(d(i)), d(i)=sum_{j=1}^N W_ij
    if verbose: print('A: {}'.format(A))
    D = np.sum(A, axis=0) # row sum
    if verbose: print('D: {}'.format(D))
    L = np.diag((D))-A
    if normalized:
        D_right = np.diag((D)**-0.5)
        D_left = np.diag((D)**-0.5)
        L = np.matmul(D_right, np.matmul(L,D_left)) # Normalized Markov matrix
    if verbose: print('L: {}'.format(L))
    return L

def SC(L, k, psi=None, nrep=5, itermax=300, show_plot=True, topK = 5, verbose=False):
    """SC: Perform spectral clustering via the Ng method
    Args:
        L (np.ndarray): Normalized graph Laplacian
        k (integer): number of clusters to compute. If k=None, estimate k based on eigengap
        nrep (int): Number of repetitions to average for final clustering
        itermax (int): Number of iterations to perform before terminating
        show_plot: Plot eigenvalues if True
        topK: shows up to K optimal number of clusters
    Returns:
        labels (N x 1 np.array): Learned cluster labels
    """
    if psi is None:
        # compute the first k elements of the Fourier basis
        e, psi = np.linalg.eigh(L) # Compute eigendecomposition
        psi_k = psi[:, :k]

    else:  # just grab the first k eigenvectors
        psi_k = psi[:, :k]

    # normalize your eigenvector rows
    psi_norm = psi_k / np.linalg.norm(psi_k, axis=1, keepdims=True)
    if verbose:
        print('psi_k: {}'.format(psi_k))
        print('psi_norm: {}'.format(psi_norm))
        print('np.sum(psi_norm[0,:]**2): {}'.format(np.sum(psi_norm[0,:]**2)))

    if show_plot:
        fig,ax=plt.subplots(figsize=(10,10))
        plt.title('Largest eigenvalues of input matrix')
        plt.scatter(np.arange(1, 1+len(e)), e)
        plt.xlabel("Eigenval")
        plt.ylabel("Value")
        plt.grid()

    # Identify the optimal number of clusters as the index corresponding
    # to the larger gap between eigen values
    inLargestGap = np.argsort(np.diff(e))[::-1][:topK]
    inNclusters = inLargestGap + 1
    if verbose: print('Optimal number of clusters: ', inNclusters)

    if k is None:
        k = inNclusters[0] #Get the index for the largest gap

    labels = KMeans(n_clusters=k, n_init=nrep,  max_iter=itermax).fit_predict(psi_norm)

    return labels, inNclusters


import numpy as np

"""
Residual Sum of Squares
"""
def rss(y: np.ndarray, y_hat: np.ndarray):
    return ((y - y_hat)**2).sum()

"""
Derivative of Residual Sum of Squares
"""
def drss(y: np.ndarray, y_hat: np.ndarray):
    return (-2*y + 2*y_hat).sum()

"""
Root Mean Square Error
"""
def rmse(y_hat: np.ndarray, y: np.ndarray):
    return  (((y_hat - y)**2).sum() / y_hat.shape[0])**.5

"""
Mean Absolute Error
"""
def mae(y_hat: np.ndarray, y: np.ndarray):
    return np.abs((y_hat - y).sum()) / y_hat.shape[0]

"""
The higher the norm degree, the more it
focuses on larger values and neglects small
ones.

For example: l2 norm, or RMSE is more
sensitive to outliers than l1.
"""
l2_norm = rmse
l1_norm = mae

if __name__ == "__main__":
    y_hat1 = np.array([0, 1, 2, 3, 4, 5, 6, 7])
    y_hat2 = np.array([0, 2, 4, 7, 15, 32, 59, 128])
    y_hat3 = np.array([0, 2, 4, 8, 16, 32, 64, 128])
    y = np.array([0, 2, 4, 8, 16, 32, 64, 128])

    print("rmse y_hat1:", rmse(y_hat1, y))
    print("rmse y_hat2:", rmse(y_hat2, y))
    print("rmse y_hat3:", rmse(y_hat3, y))
    print()
    print("mae y_hat1:", mae(y_hat1, y))
    print("mae y_hat2:", mae(y_hat2, y))
    print("mae y_hat3:", mae(y_hat3, y))


# Jacobi iterative method to solve linear system
# Jacobi implements the regular method
# SparseJacobi implements the Jacobi method using CSR storage and sparse matrix multiplication

import numpy as np
from pprint import pprint
from sparse_methods import MatrixtoCSR, SparseGet
from operations import MMultiply, SparseDot
import time

def Jacobi(A, b, error_tol=10e-6):
    t1 = time.time()
    (m, n) = np.shape(A)
    x = np.zeros_like(b)
    x_prev = np.zeros_like(b)
    error = float('Inf')
    itr = 0
    while error > error_tol:
        for i in range(m):
            sigma = MMultiply(A[i], x_prev)
            sigma -= A[i][i]*x_prev[i]
            x[i] = (b[i] - sigma)/A[i][i]

        x_prev = x
        error = sum(abs(MMultiply(A, x) - b))
        itr += 1
    t2 = time.time() - t1
    return x, error, itr, t2

def SparseJacobi(Acsr, b, error_tol=10e-6):
    t1 = time.time()
    m = Acsr['m']
    x = np.zeros_like(b)
    x_prev = np.zeros_like(b)
    D = np.zeros(m, dtype=Acsr['A'].dtype)
    for i in range(m):
        D[i] = SparseGet(Acsr, i, i)
        #D = Acsr.diagonal()

    error = sum(abs(SparseDot(Acsr, x) - b))
    print (0, error)
    Error = [error]
    itr = 0
    while error > error_tol:
        for i in range(m):
            sigma = SparseDot(Acsr, x_prev, i)
            sigma -= D[i]*x_prev[i]
            x[i] = (b[i] - sigma)/D[i]

        x_prev = x
        error = sum(abs(SparseDot(Acsr, x) - b))
        Error.append(error)
        print (itr+1, error)
        itr += 1

    t2 = time.time() - t1
    print(t2)
    return x#, Error, itr, t2

# Test cases
if __name__ == "__main__":
    A = np.array([[10., -1., 2., 0.],
              [-1., 11., -1., 3.],
              [2., -1., 10., -1.],
              [0.0, 3., -1., 8.]], dtype=np.float64)
    Acsr = MatrixtoCSR(A)
    b = np.array([6., 25., -11., 15.])
    #x, error, itr = Jacobi(A, b, 1e-10)
    xsp = SparseJacobi(Acsr, b, 10e-6)
    #print ("Error:", error, errorsp)
    #print ("Iters:", itr, itrsp)
    print ("x =", xsp)


# -*- coding: utf-8 -*-
"""
Created on Tue Sep 21 01:16:05 2021

@author: Mahfuz_Shazol
"""

import numpy as np

#formula  A=V (A_lambda(Diagonal Matrix)) V**-1

A=np.array([
            [4.,2.],
            [-5.,-3.]
          ])

print(A)
#V
lambdas,V=np.linalg.eig(A)
print('lambdas',lambdas)
print('V',V)
#V**-1
inv_V=np.linalg.inv(V)

diago_lambdas=np.diag(lambdas)
print('diago_lambdas',diago_lambdas)

result=np.dot(V,np.dot(diago_lambdas,inv_V))
print(result)

print((A==result).all())

#if A is real No matrix then without using inverse we can Tanspose like
# formula  A= Q (A_lambda(Diagonal Matrix)) Q**T

A=np.array([
            [2.,1],
            [1.,2]
          ])

lambdas,Q=np.linalg.eig(A)
A_lambda=np.diag(lambdas)
Q_T=Q.T

result_of_real_matrix=np.dot(Q,np.dot(A_lambda,Q_T))
print(result_of_real_matrix)
print((A==result_of_real_matrix).all())

I=np.dot(Q,Q_T)
print(I)

I=np.dot(Q_T,Q)
print(I)


import numpy as np
from scipy.integrate import solve_ivp
from scipy.optimize import OptimizeResult
from typing import List, Union

# The SIR model differential equations.
def sir(t: float, y: List[float, float, float], N: Union[int, float], beta: float, gamma: float):
    """
    System of ODE for the Susceptible-Infected-Recovered model without vital kinetics
    Parameters
    ----------
    t: float
        The time in days
    y: List[float, float, float]
    N: int
        The total population
    beta: float
        The contact rate of the disease (1/days)
    gamma: float
        The recovery rate of the disease (1/days)

    Returns
    -------
    List[float, float, flaot]
        dS/dt, dI/dt, dR/dt
    """
    S, I, R = y
    dSdt = -beta * S * I / N
    dIdt = beta * S * I / N - gamma * I
    dRdt = gamma * I
    return [dSdt, dIdt, dRdt]

def sir_model(t: np.ndarray, N: int, beta: float, gamma: float, **kwargs):
    """
    Solves the Susceptible-Infected-Removed ODE

    Parameters
    ----------
    t: np.ndarray
        The time (days)
    N: int
        The total population
    beta: float
        The contact rate of the disease (1/days)
    gamma: float
        The recovery rate of the disease (1/days)
    kwargs: keyword arguments
        I0: int
            The initial number of infected individuals
        R0: int
            The initial number of recovered individuals

    Returns
    -------
    OptimizeResult:
        The solution
    """

    I0 = kwargs.get('I0', 1)
    R0 = kwargs.get('R0', 0)

    #    # Total population, N.
    #    N = 1000
    #    # Initial number of infected and recovered individuals, I0 and R0.
    #    I0, R0 = 1, 0
    #    # Everyone else, S0, is susceptible to infection initially.
    S0 = N - I0 - R0
    #    # Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
    #    beta, gamma = 0.2, 1./10
    #    # A grid of time points (in days)

    # Initial conditions vector
    y0 = S0, I0, R0
    # Integrate the SIR equations over the time grid, t.
    # ret = odeint(deriv, y0, t, args=(N, beta, gamma))
    sol = solve_ivp(sir, [np.amin(t), np.amax(t)], y0, t_eval=t,
                    args=(N, beta, gamma),
                    method='DOP853',
                    dense_output=True)

    return sol


import numpy as np

# generates random data
height = np.round(np.random.normal(1.75, 0.20, 5000), 2)
weight = np.round(np.random.normal(60.32, 15, 5000), 2)

np_city = np.column_stack((height, weight))

# average

# Print mean height (first column)
avg = np.mean(np_city[:, 0])
print("Average: " + str(avg))

# Print median height. Replace 'None'
med = np.median(np_city[:, 0])
print("Median: " + str(med))

# Print out the standard deviation on height. Replace 'None'
stddev = np.std(np_city[:, 0])
print("Standard Deviation: " + str(stddev))

# Print out correlation between first and second column. Replace 'None'
corr = np.corrcoef(np_city[:, 0], np_city[:, 1])
print("Correlation: " + str(corr))

"""

# Convert positions and heights to numpy arrays: np_positions, np_heights
np_heights = np.array(heights)
np_positions = np.array(positions)

# Heights of the goalkeepers: gk_heights
gk_heights = np_heights[np_positions == "GK"]

# Heights of the other players: other_heights
other_heights = np_heights[np_positions != "GK"]

# Print out the median height of goalkeepers. Replace 'None'
print("Median height of goalkeepers: " + str(np.median(gk_heights)))

# Print out the median height of other players. Replace 'None'
print("Median height of other players: " + str(np.median(other_heights)))
"""


from sympy import *
import numpy as np
from tqdm import tqdm

class dh_solver():
    def __init__(self):
        self.joints_list = []
        self.T_list = []
        self.T = eye(4)
        self.T_sub = eye(4)

    def add(self,dh_param):
        if len(dh_param)!=4:
            raise ValueError('the number of input dh parameters !=4, it should be structure as [d,theta,a, alpha].')
        else:
            self.joints_list.append(dh_param)

    def dh_matrix(self,parameters):
        d,theta,a, alpha = parameters
        #first row
        dh11 = cos(theta)
        dh12 =  -1*sin(theta)*cos(alpha)
        dh13 = sin(theta)*sin(alpha)
        dh14 =  a*cos(theta)
        #second row
        dh21 = sin(theta)
        dh22 = cos(theta)*cos(alpha)
        dh23 = -1*cos(theta)*sin(alpha)
        dh24 = a*sin(alpha)
        #third row
        dh31 =  0
        dh32 =  sin(alpha)
        dh33 =  cos(alpha)
        dh34 =  d
        #forth row
        dh41 = 0
        dh42 = 0
        dh43 = 0
        dh44 = 1
        return Matrix([[dh11, dh12, dh13, dh14],
                     [dh21, dh22, dh23, dh24],
                     [dh31, dh32, dh33, dh34],
                     [dh41, dh42, dh43, dh44]])

    def calc_symbolic_matrices(self):
        self.T_list = []
        self.T = eye(4)
        for i in tqdm(range(len(self.joints_list))):
            d = Symbol("d"+str(i+1))
            theta = Symbol("theta"+str(i+1))
            a = Symbol("a"+str(i+1))
            alpha = Symbol("alpha"+str(i+1))
            parameters = [d,theta,a,alpha]
            Tn = self.dh_matrix(parameters)
            self.T_list.append(Tn)
            self.T=self.T*Tn
        return self.T

    def calc_dh_matrix(self):
        self.calc_symbolic_matrices()
        self.T_sub = self.T
        for i in tqdm(range(len(self.joints_list))):
            ds = "d"+str(i+1)
            thetas = "theta"+str(i+1)
            a_s = "a"+str(i+1)
            alphas = "alpha"+str(i+1)
            parameters = [ds,thetas,a_s,alphas]
            for pair in zip(parameters,self.joints_list[i]):
#                 print(pair)
                self.T_sub = self.T_sub.subs(pair[0], pair[1])
        return self.T_sub

    def get_numpy_matrix(self, list_subs):
#         self.calc_dh_matrix()
        T = (self.T_sub.subs(list_subs)).evalf()
        return np.array(T.tolist()).astype(np.float64)


import numpy as np
from math import sqrt

def rk4(f, x0, y0, x1, n): #Runge-Kutta fourth order
    #vx = [0] * (n + 1)
    vx=np.zeros((n+1,1)) #using numpy array instead of above list
    #print(vx) #for debugging only
    #vy = [0] * (n + 1)
    vy=np.zeros((n+1,1)) #using numpy array instead of above list
    #print(vy) #for debugging only
    h = (x1 - x0) / float(n)
    vx[0] = x = x0 #x0 in (x0,x1)
    vy[0] = y = y0 #initial condition y(x=x0)=y0
    for i in range(1, n + 1): #RK4 loop
        k1 = h * f(x, y) #calls function f(x,y) and evaluates it with x and y values
        k2 = h * f(x + 0.5 * h, y + 0.5 * k1)
        k3 = h * f(x + 0.5 * h, y + 0.5 * k2)
        k4 = h * f(x + h, y + k3)
        vx[i] = x = x0 + i * h
        vy[i] = y = y + (k1 + k2 + k2 + k3 + k3 + k4) / 6
        #print(i) #for debugging purposes only
    return vx, vy

def rk2(f, x0, y0, x1, n): #Runge-Kutta second order
    vx=np.zeros((n+1,1))
    vy=np.zeros((n+1,1))
    h = (x1 - x0) / float(n)
    vx[0] = x = x0 #x0 in (x0,x1)
    vy[0] = y = y0 #initial condition y(x=x0)=y0
    for i in range(1, n + 1): #RK4 loop
        k1 = h * f(x, y) #calls function f(x,y) and evaluates it with x and y values
        k2 = h * f(x + h, y + k1)
        vx[i] = x = x0 + i * h
        vy[i] = y = y + (k1 + k2) / 2
        #print(i) #for debugging purposes only
    return vx, vy

def f(x, y):
    return x * sqrt(y) #RHS of first order differential equation

nodes=100
vx, vy = rk4(f, 0, 1, 10, nodes)
m=nodes/10
print('Fourth order Runge Kutta for dydx = x * sqrt(y), y(0)=0')
print("x,    Approx,       Exact,      Error")
for x, y in list(zip(vx, vy))[::m]: #in [::m], prints every m elements
    print("%4.1f %10.5f %10.5f %+12.4e" % (x, y, (4 + x * x)**2 / 16, y - (4 + x * x)**2 / 16))
print('\n')

vx, vy = rk2(f, 0, 1, 10, nodes)
print('Second order Runge Kutta for dydx = x * sqrt(y), y(0)=0')
print("x,    Approx,       Exact,      Error")
for x, y in list(zip(vx, vy))[::m]: #in [::m], prints every m elements
    print("%4.1f %10.5f %10.5f %+12.4e" % (x, y, (4 + x * x)**2 / 16, y - (4 + x * x)**2 / 16))


import LinearAlgebraPurePython as la
import numpy as np

print('Shtuff from Basic Tools Post')
print()
la.print_matrix(la.zeros_matrix(3, 3))
print()
ID = la.identity_matrix(4)
la.print_matrix(ID)
print()
A = [[1, 2, 3, 4],
     [5, 6, 7, 8],
     [9, 10, 11, 12],
     [13, 14, 15, 16]]
la.print_matrix(A)
print()
AM = la.copy_matrix(A)
la.print_matrix(AM)
print()
AT = la.transpose(A)
la.print_matrix(AT)
print()
C = la.matrix_addition(A, AT)
la.print_matrix(C)
print()
D = la.matrix_subtraction(C, AT)
la.print_matrix(D)
print()
AID = la.matrix_multiply(A, ID)
la.print_matrix(AID)
print()
MatrixList = [A, AT, ID]
Prod3 = la.multiply_matrices(MatrixList)
la.print_matrix(Prod3)
print()
ATA = la.transpose(la.transpose(A))
check = la.check_matrix_equality(A, ATA)
print("A = transpose of transpose of A?", check)
print()
AdotA = la.dot_product(A, A)
print("A.A =", AdotA)
print()
Vector = [[4], [4], [4]]
unitVector = la.unitize_vector(Vector)
la.print_matrix(unitVector)
print()
print()

print('### Recursive Determinant Shtuff')
print()
A = [[-2, 2, -3],
     [-1, 1, 3],
     [2, 0, -1]]  # Matrix from wiki
Det = la.determinant_recursive(A)
npDet = np.linalg.det(A)
print("Determinant of A is", round(Det, 9))
print("The Numpy Determinant of A is", round(npDet, 9))
print()

A = [[1, 2, 3, 4],
     [5, 6, 7, 8],
     [9, 10, 11, 12],
     [13, 14, 15, 16]]
Det = la.determinant_recursive(A)
npDet = np.linalg.det(A)
print("Determinant of A is", Det)
print("The Numpy Determinant of A is", npDet)
print()

A = [[1, 2, 3, 4],
     [8, 5, 6, 7],
     [9, 12, 10, 11],
     [13, 14, 16, 15]]
Det = la.determinant_recursive(A)
npDet = np.linalg.det(A)
print("Determinant of A is", Det)
print("The Numpy Determinant of A is", round(npDet, 9))
print()

A = [[1, 2, 3, 4, 1],
     [8, 5, 6, 7, 2],
     [9, 12, 10, 11, 3],
     [13, 14, 16, 15, 4],
     [10, 8, 6, 4, 2]]
Det = la.determinant_recursive(A)
npDet = np.linalg.det(A)
print("Determinant of A is", Det)
print("The Numpy Determinant of A is", round(npDet, 9))
print()
print()

print('### Fast Determinant Shtuff')
print()
A = [[-2, 2, -3],
     [-1, 1, 3],
     [2, 0, -1]]  # Matrix from wiki
Det = la.determinant_fast(A)
npDet = np.linalg.det(A)
print("Determinant of A is", round(Det, 9))
print("The Numpy Determinant of A is", round(npDet, 9))
print()

A = [[1, 2, 3, 4],
     [5, 6, 7, 8],
     [9, 10, 11, 12],
     [13, 14, 15, 16]]
Det = la.determinant_fast(A)
npDet = np.linalg.det(A)
print("Determinant of A is", round(Det, 9)+0)
print("The Numpy Determinant of A is", round(npDet, 9))
print()

A = [[1, 2, 3, 4],
     [8, 5, 6, 7],
     [9, 12, 10, 11],
     [13, 14, 16, 15]]
Det = la.determinant_fast(A)
npDet = np.linalg.det(A)
print("Determinant of A is", round(Det, 9))
print("The Numpy Determinant of A is", round(npDet, 9))
print()

A = [[1, 2, 3, 4, 1],
     [8, 5, 6, 7, 2],
     [9, 12, 10, 11, 3],
     [13, 14, 16, 15, 4],
     [10, 8, 6, 4, 2]]
Det = la.determinant_fast(A)
npDet = np.linalg.det(A)
print("Determinant of A is", round(Det, 9))
print("The Numpy Determinant of A is", round(npDet, 9))
print()


'''
Linear Algebra on TensorFlow
Author: Rowel Atienza
Project: https://github.com/roatienza/Deep-Learning-Experiments
'''
# On command line: python linear_algebra.py
# Prerequisite: tensorflow (see tensorflow.org)

from __future__ import print_function

import tensorflow as tf
import numpy as np

# Square matrix A of rank 2
A = tf.constant([[1., 2.], [3., 4.]])

# 2x2 Square, Diagonal, Symmetric matrix B
B = tf.diag([5., 6.])

# 2x2 Square matrix
C = tf.constant([[1., 2.], [2., 4.]])

# 2x1 vector will all elements equal to 1
x = tf.ones([2, 1])

# 2x1 vector will all elements equal to 2.0
b = tf.fill([2, 1], 2.)

# 2x1 vector
y = tf.constant([[-1.], [1.]])

# run within a session and print
with tf.Session() as session:
    print("Tensorflow version: " + tf.__version__)
    tf.global_variables_initializer().run()

    print("A = ")
    print(A.eval())

    print("B = ")
    print(B.eval())

    print("C = ")
    print(C.eval())

    print("x = ")
    print(x.eval())

    print("b = ")
    print(b.eval())

    print("y = ")
    print(y.eval())

    # Tensor multiplication
    print("Ax = ")
    print(tf.matmul(A, x).eval())

    # Tensor addition
    print("A + B =")
    print(tf.add(A, B).eval())

    print("A + b =")
    print(tf.add(A, b).eval())

    # Rank of A and B; Number of indices to identify each element
    print("tensorRank(A) = ")
    print(tf.rank(A).eval())
    print("tensorRank(C) = ")
    print(tf.rank(C).eval())

    # Matrix rank
    print("rank(A) = ")
    print(np.linalg.matrix_rank(A.eval()))
    print("rank(C) = ")
    print(np.linalg.matrix_rank(C.eval()))

    # Transpose
    print("tran(A) = ")
    print(tf.matrix_transpose(A).eval())
    print("tran(B) = ")
    print(tf.matrix_transpose(B).eval())

    # Inverse
    print("inv(A) = ")
    print(tf.matrix_inverse(A).eval())
    # Inverse of diagonal matrix has diag elements of the reciprocal of diag elements B
    print("inv(B) = ")
    print(tf.matrix_inverse(B).eval())
    print("inv(C) = ")  # since C has rank 1, this will cause error
    try:
        print(tf.matrix_inverse(C).eval())
    except:
        print("C is not invertible")

    # Product of a matrix and its inverse is an identity (non-singular)
    print("A*inv(A) = Eye(2)")
    print(tf.matmul(A, tf.matrix_inverse(A)).eval())

    # Element-wise multiplication
    print("elem(A)*elem(B) = ")
    print(tf.multiply(A, B).eval())

    # Element-wise addition
    print("elem(A)+elem(B) = ")
    print(tf.add(A, B).eval())

    # Dot product
    print("x dot b")
    print(tf.matmul(x, b, transpose_a=True).eval())

    # Identity matrix of same shape as A
    print("eye(A) = ")
    I = tf.eye(A.get_shape().as_list()[0], A.get_shape().as_list()[1])
    print(I.eval())

    # Multiply eye(A) and A = A
    print("eye(A)*A = A = ")
    print(tf.matmul(I, A).eval())
    print("A * eye(A) = A = ")
    print(tf.matmul(A, I).eval())

    # l1, l2, Frobenius norm
    print("l1(x) = ")
    print(tf.reduce_sum(tf.abs(x)).eval())
    print("l2(x) = ")
    print(tf.sqrt(tf.reduce_sum(tf.square(x))).eval())
    print("Frobenius(A) = ")
    print(tf.sqrt(tf.reduce_sum(tf.square(A))).eval())
    print("Numpy l2(x) =")
    print(np.linalg.norm(x.eval(session=tf.Session())))
    print("Numpy Forbenius(A) =")
    print(np.linalg.norm(A.eval(session=tf.Session())))

    # Can you write the L(inf) ?

    # Orthogonal vectors; How do you make x and y orthonormal?
    print("x dot y")
    print(tf.matmul(x, y, transpose_a=True).eval())

    # Eigenvalues and eigenvectors
    print("Numpy Eigenvalues of (A)=")
    e, v = np.linalg.eig(A.eval())
    print(e)
    print("Numpy Eigenvectors of (A)=")
    print(v)

    # Frobenius norm is equal to the trace of A*tran(A)
    print("Frobenius(A) = Tr(A*tran(A) = ")
    print(tf.sqrt(tf.trace(tf.matmul(A, tf.transpose(A)))).eval())

    # Determinant of A is the product of its eigenvalues
    print("det(A)=")
    print(tf.matrix_determinant(A).eval())
    # Determinant from eigenvalues
    print("det(A) as product of eigenvalues")
    print(tf.reduce_prod(e).eval())


import numpy as np
import matplotlib.pyplot as plt

#rectangle rule
#int 3x^2 from 0..10
#the analytic answer=1000
def function(x):
    return 3*x**2

def error_percentage(exp,exat):
    return abs(exp-exat)/exp

def rectangle_rule(initial,final,N):
    initial=initial
    final=final
    N=N
    h=(final-initial)/(N-1)
    x=np.linspace(initial,final,N)
    y=[]
    for i in x:
        y.append(function(i)*h)
    return np.sum(y)

#test the step vs the error and the percentage
test_step=np.arange(100,5100,100)
area=[]
error=[]
percentage=[]
for i in test_step:
    area.append(rectangle_rule(0,10,i))
    error.append(rectangle_rule(0,10,i)-1000)
    percentage.append((rectangle_rule(0,10,i)-1000)/(rectangle_rule(0,10,i))*100)

#visualize
fig=plt.figure(figsize=(18,6))
ax1=fig.add_subplot(131)
ax1.plot(test_step,area,label="rectangle rule area")
ax1.plot(test_step,[1000]*len(area),label="exact area")
ax1.set_title("exact vs rectangle rule value",fontsize=10)
ax1.set_ylabel("area")
ax1.set_xlabel("the steps")
ax1.grid(True)
ax1.legend()

ax2=fig.add_subplot(132)
ax2.plot(test_step,error)
ax2.set_title("the step versus the error",fontsize=10)
ax2.set_ylabel("error from the exact value")
ax2.set_xlabel("the steps")
ax2.grid(True)

ax3=fig.add_subplot(133)
ax3.plot(test_step,percentage)
ax3.set_title("the step vs the error percentage",fontsize=10)
ax3.set_ylabel("error percentage(%)")
ax3.set_xlabel("the steps")
ax3.grid(True)
plt.show()

print("rectangle rule")
print("test_step")
print(test_step)
print("area")
print(area)

#trapezoid rule (decrease the error from the rectangle rule)
def trapezoid_rule(initial,final,N):
    initial=initial
    final=final
    N=N
    h=(final-initial)/(N-1)
    x=np.linspace(initial,final,N)
    y=[]
    for i in range(len(x)-1):
        y.append((function(x[i])+function(x[i+1]))*h/2)
    return np.sum(y)

#test the step vs the error and the percentage
test_step=np.arange(100,5100,100)
area=[]
error=[]
percentage=[]
for i in test_step:
    area.append(trapezoid_rule(0,10,i))
    error.append(trapezoid_rule(0,10,i)-1000)
    percentage.append((trapezoid_rule(0,10,i)-1000)/(trapezoid_rule(0,10,i)))

#visualize
fig=plt.figure(figsize=(18,6))
ax1=fig.add_subplot(131)
ax1.plot(test_step,area,label="trapezoid rule area")
ax1.plot(test_step,[1000]*len(area),label="exact area")
ax1.set_title("exact vs rectangle rule value",fontsize=10)
ax1.set_ylabel("area")
ax1.set_xlabel("the steps")
ax1.grid(True)
ax1.legend()

ax2=fig.add_subplot(132)
ax2.plot(test_step,error)
ax2.set_title("the step versus the error",fontsize=10)
ax2.set_ylabel("error from the exact value")
ax2.set_xlabel("the steps")
ax2.grid(True)

ax3=fig.add_subplot(133)
ax3.plot(test_step,percentage)
ax3.set_title("the step versus the error percentage",fontsize=10)
ax3.set_ylabel("error percentage(%)")
ax3.set_xlabel("the steps")
ax3.grid(True)
plt.show()

print("trapezoid rule")
print("test_step")
print(test_step)
print("area")
print(area)


"""
01. What is tail-recursive in this case? Will it help perforamnce?
02. Comparision
    In [6]: import fibo

    In [7]: %timeit fibo.fibo_dyn(10)
    9.91 µs ± 283 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

    In [8]: %timeit fibo.fibo_recur(10)
    21.4 µs ± 20.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

    In [9]: %timeit fibo.fibo_recur(10)
    21.6 µs ± 61.9 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

    In [10]: %timeit fibo.fibo_dyn(10)
    9.65 µs ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

    In [11]: %timeit fibo.fibo_dyn2(10)
    731 ns ± 4.05 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
"""

def fibo_recur(n):
    if n == 0:
        return 1
    elif n == 1:
        return 1
    else:
        return fibo_recur(n-1) + fibo_recur(n-2)

def fibo_dyn(n):
    import numpy as np
    cache = np.ones(n+1, dtype=np.uint64)
    for i in range(2, n+1):
        cache[i] = cache[i-1] + cache[i-2]
    return cache[n]

def print_fib_lt(n):
    """
    This is the function on the official website of Python

    return a fibonacci number close to n
    """
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fibo_dyn2(n):
    """
    return
      the n-th fibonacci number
    """
    if n < 2:
        return 1
    else:
        a, b = 1, 1
        for _ in range(1,n):
            a, b = b, a+b
        return b

if __name__ == "__main__":
    import time
    t0 = time.time()
    print(f"fibo_recur(100)={fibo_recur(100)}")
    t1 = time.time()
    print(f"Took {t1-t0}s")
    print()
    t0 = time.time()
    print(f"fibo_dyn(100)={fibo_dyn(100)}")
    t1 = time.time()
    print(f"Took {t1-t0}s")


import sympy

def newtonRaphson(f, t, x0, maxIter=100, minTol=1e-6, printResults=False): #f is the function defined with sympy simbolic expressions, t is the independent variable of f, maxIter is the maximum number of iterations the user allows, minTol is the minimum tolerance the user wants for the result and printResults that if True, the number of iterations and final error will be printed at the end of the iterations.
    fp = f.diff(t); #Derivate f(x) for later use during the iterations.
    xi = x0 #The first guess will be the starting point stated by the user.
    xi1 = 0.0 #The next guess is defined and equaled to 0 (it will be overwritten later during the first loop iteration).
    error = 9223372036854775800 #Max int size in python so that the while loop won't stop before beginning.
    n = 0 #The counter for the iteration.
    while n < maxIter and error > minTol:
        xi1 = xi - f.evalf(subs={t:xi})/fp.evalf(subs={t:xi}) #The next guess is calculated.
        error = abs((xi1-xi)/xi1) #The absolute error is calculated.
        xi = xi1 #The previous guess becomes the current guess (it will be the previous for the next iteration).
        n += 1 #Add one to the iteration counter.
    if(error <= minTol): #Check if the loop ended because the minimum tolerance was reached.
        print("Toleráncia mínima alcanzada")
    else: #If the minimum tolerance wasn't met but the number of iterations reached its maximum.
        print("Número máximo de iteraciones alcanzado")
    if(printResults):
        print("Iteraciones: {} - Tolerancia Final: {:.5f}".format(n,error))
    return xi1

#Test
if(__name__ == "__main__"):

    #Problem parameters
    a = 0.01
    v0 = 0
    di = -5
    df = 0

    #Newton-Raphson method parameters
    x0 = 1
    maxIter = 50
    minTol = 0.1

    #Problem equation and definition
    t = sympy.symbols('t')
    f = (a/2)*t**2+v0*t+di-df

    #Calculations and results
    estimatedValue = newtonRaphson(f,t,x0,maxIter,minTol,True)
    print("Resultado estimado: {:.5f}".format(estimatedValue))
    realValue = (-v0 + (v0**2-2*a*(di-df))**0.5)/a
    print("Resultado real: {:.5f}".format(realValue))
    print("Error porcentual final: {:.5f}%".format(abs(100*(realValue-estimatedValue)/realValue)))


from scipy.interpolate import interp1d, lagrange

X = [0, 20, 40, 60, 80, 100]
Y = [26.0, 48.6, 61.6, 71.2, 74.8, 75.2]

print("X =", X)
print("Y =", Y, end='\n\n')

# Interp1d Function

f = interp1d(X,Y)                   # Linear Interpolation (default)
print("interp1d(X,Y)(50) =", f(50))
print("interp1d(X,Y)(20) =", f(20), end='\n\n')

f = interp1d(X,Y,'quadratic')       # Quadratic Interpolation
print("interp1d(X,Y,'quadratic')(50) = %.3f" % f(50))
print("interp1d(X,Y,'quadratic')(40) = %.3f" % f(40), end='\n\n')

f = interp1d(X,Y,'cubic')           # Cubic Interpolation
print("interp1d(X,Y,'cubic')(50) = %.3f" % f(50))
print("interp1d(X,Y,'cubic')(60) = %.3f" % f(60), end='\n\n')

# Lagrange Function

L = lagrange(X,Y)                   # Lagrange Interpolation
print("lagrange(X,Y)(50) = %.3f" % L(50))
print("lagrange(X,Y)(80) = %.3f" % L(80), end='\n\n')

print("lagrange(X,Y) =", L, sep='\n')

2. Interpolation and Curve Fitting/0. Interpolation functions of SciPy.py


#########################################################################################
################### preparacion S2C1 integracion numerica ###############################
#########################################################################################

#Este ejercicio preparatorio busca que usted implemente correctamente tres metodos de integracion numerica. Haga este ejercicio despues de haber leido y entendido los algoritmos correspondientes a los distintos metodos. Si quiere complementar este ejercicio, puede repetir el proceso para el metodo de Monte Carlo y el metodo del valor medio.

# Ejercicio: Integrales

import numpy as np
import matplotlib.pylab as plt

# Funcion a integrar
def funcion(x1):
	return np.cos(x1)

#El intervalo de integracion es de 0 a 3pi/2. Divida el intervalo de integracion en M secciones para calcular sus integrales.
a = 0
b = 3*np.pi/2
M = 100

def Ianalítica(a,b):
    return np.sin(b)-np.sin(a)

# 1a). Usando el metodo de suma de rectangulos, calcule la integral de la funcion. Compare su valor obtenido numericamente con el valor analitico e imprima ambos valores
'''
def rectangulos(a,b,M):
    h = (b-a)/M
    w=h
    suma=0
    x=a
    for i in range(int(M)):
        suma+=funcion(x)*w
        x+=h
    return suma
'''
def rectangulos(a,b,M):
    h=(b-a)/(M-1)
    w=h
    x=np.linspace(a,b,M)
    f=funcion(x)
    return np.sum(f*w)

print('Integral analítica es: ',Ianalítica(a,b))
print('Integral por suma de rectángulos da: ',rectangulos(a,b,M))

# 1b). Usando el metodo de trapezoide, calcule la integral de la funcion. Compare su valor obtenido numericamente con el valor analitico e imprima ambos valores.
'''
def trapezoide(a,b,M):
    h=(b-a)/(M-1)
    x=a
    suma=0
    for i in range(int(M)):
        if (i==0 or i==M-1):
            w=h/2
        else:
            w=h
        suma+=funcion(x)*w
        x+=h
    return suma
'''

def trapezoide(a,b,M):
    h=(b-a)/(M-1)
    x=np.linspace(a,b,M)
    w=np.zeros(M)
    for i in range(M):
        if(i==0 or i==M-1):
            w[i]=h/2
        else:
            w[i]=h
    f=funcion(x)
    return np.sum(f*w)
print('Integral por el método de trapezoide da: ',trapezoide(a,b,M))

# 1c). Usando el metodo de Simpson, calcule la integral de la funcion. Compare su valor obtenido numericamente con el valor analitico e imprima ambos valores.
'''
def simpson(a,b,M):
    if(M%2==0):
        M=M-1
    h=(b-a)/(M-1)
    x=a
    suma=0
    for i in range(int(M)):
        if (i==0 or i==M-1):
            w=h/3
        elif(i%2==1):
            w=4*h/3
        else:
            w=2*h/3
        suma+=funcion(x)*w
        x+=h
    return suma
'''
def simpson(a,b,M):
    if(M%2==0):
        M=M-1
    h=(b-a)/(M-1)
    x=np.linspace(a,b,M)
    w=np.zeros(M)
    for i in range(int(M)):
        if (i==0 or i==M-1):
            w[i]=h/3
        elif(i%2!=0):
            w[i]=(4/3)*h
        else:
            w[i]=(2/3)*h
    f=funcion(x)
    return np.sum(f*w)
print('Integral por el método de Simpson da: ',simpson(a,b,M))

#########################################################################################
################### S2C1 errores para diferentes metodos  ###############################
#########################################################################################
print('')
print('ERRORES DE MÉTODOS')
print('')
# 1d). Repita el procedimiento para distintios valores de M (10**2 a 10**7 aumentando logaritmicamente, use np.logspace: https://docs.scipy.org/doc/numpy/reference/generated/numpy.logspace.html) para calcular la integral y haga una grafica de error ((valor numerico - valor analitico)/valor analitico) en funcion de M (haga una curva por cada metodo). Guarde dicha grafica sin mostrarla en ErrorRTS.pdf

xlog=np.logspace(2,7,6)
logrectangulos=[]
logtrapezoide=[]
logsimpson=[]
for i in range(len(xlog)):
    logrectangulos.append(rectangulos(a,b,int(xlog[i])))
    logtrapezoide.append(trapezoide(a,b,int(xlog[i])))
    logsimpson.append(simpson(a,b,int(xlog[i])))

ER=np.abs((logrectangulos-Ianalítica(a,b))/Ianalítica(a,b))
ET=np.abs((logtrapezoide-Ianalítica(a,b))/Ianalítica(a,b))
ES=np.abs((logsimpson-Ianalítica(a,b))/Ianalítica(a,b))

plt.figure()
plt.plot(xlog,ER,c='b',label='Error Rectángulos')
plt.plot(xlog,ET,c='g',label='Error Trapezoide')
plt.plot(xlog,ES,c='r',label='Error Simpson')
plt.title('Error de métodos')
plt.xlabel('X')
plt.ylabel('abs(error)')
plt.loglog()
plt.legend()
plt.savefig('ErrorRTS.png')
#########################################################################################
################### S2C1 otros metodos de integracion numerica ##########################
#########################################################################################

# 1e). Usando el metodo de valor medio, calcule la integral de la funcion cos(theta) entre 0 y pi/2 usando N=10000 puntos aleatorios. Compare su valor obtenido numericamente con el valor analitico e imprima ambos valores.
def VM(a,b,N):
    x=np.cos((np.random.random(N)*(b-a))+a)
    suma=np.sum(x)
    I=(suma*(b-a))/N
    return I
print('La integral por valor medio da: ',VM(0,np.pi/2,10000))
print('La integral analítica da: ',Ianalítica(0,np.pi/2))

# 1f). Usando el metodo de sampleo directo de Monte Carlo, calcule la integral de la funcion sin(theta) entre 0 y pi/2 usando N=10000 puntos aleatorios. Compare su valor obtenido numericamente con el valor analitico e imprima ambos valores.

# 1g). Repita el procedimiento para distintios valores de M (10**2 a 10**7 aumentando logaritmicamente) para calcular la integral y haga una grafica de error ((valor numerico - valor analitico)/valor analitico) en funcion de M (haga una curva por cada metodo). Guarde dicha grafica sin mostrarla en ErrorRTS.pdf


"""angular_dist that calculates the angular distance between any two points on
the celestial sphere given their right ascension and declination.
Angular distances have the same units as angles (degrees)."""

#b = np.cos(d1)*np.cos(d2)*np.sin(np.abs(r1 - r2)/2)**2
#d = 2*np.arcsin(np.sqrt(a + b))

import numpy as np

def angular_dist(r1, d1, r2, d2):
"""Trig functions in most languages and libraries (including Python and NumPy)
take angle arguments in units of radians, but the databases we're working with
use angles of degrees.

Fortunately, NumPy provides convenient conversion functions:

a_rad = np.radians(a_deg)
a_deg = np.degrees(a_rad)
The variable a_deg is in units of degrees and a_rad is in radians."""
  r1= np.radians(r1)
  r2= np.radians(r2)
  d1= np.radians(d1)
  d2= np.radians(d2)

"""There are other
equations for calculating the angular distance but this one, called the
haversine formula, is good at avoiding floating point errors when the two
points are close together."""

  a = (np.sin(np.abs(d1 - d2)/2))**2
  b = np.cos(d1)*np.cos(d2)*np.sin(np.abs(r1 - r2)/2)**2
  d = np.degrees(2*np.arcsin(np.sqrt(a + b)))

  return d

if __name__ == '__main__':
  print(angular_dist(21.07, 0.1, 21.15, 8.2))
  print(angular_dist(10.3, -3, 24.3, -29))


#Import libraries
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression

"""
define function for linear regression modele
F(X) = theta * X

Theta: matrix which contains coefficient (a b c ... ) of polynomial F(X)
X : matrix which contains features
F(X) : target

"""
#Definition of modele function

def modele(X,theta):
    return X.dot(theta)

# Definition of cost function
def cost_function(X,y,theta):
    m=len(y)
    return 1/(2*m) * np.sum((modele(X,theta)-y)**2)

#Definition of grad function
def grad (X,y,theta):
    m=len(y)
    return 1/m * X.T.dot(modele(X,theta)-y)

 #Definition of Algorithm of Gradient Descent to find minimum (or best value of coefficient theta )
def gradient_descent(X,y,theta,learning_rate,n_iterration):
    cost_history=np.zeros(n_iterration)
    for i in range(0,n_iterration):
        theta=theta - learning_rate * grad(X,y,theta)
        cost_history[i]=cost_function(X,y,theta)
    return theta,cost_history

def coef_determination(y,prediction):
    u=((y-prediction)**2).sum()
    v=((y-y.mean())**2).sum()
    return 1- u/v

x, y = make_regression(n_samples=100,n_features=1, noise=10)
plt.scatter(x,y)
y=y.reshape(y.shape[0],1)
X=np.hstack((x,np.ones(x.shape)))
theta=np.random.randn(2,1)
cost_function(X,y,theta)
theta_final,cost_history= gradient_descent(X,y,theta,0.01,1000)
prediction=modele(X,theta_final)
plt.scatter(x,y)
plt.plot(x,prediction,c='r')
plt.plot(range(1000),cost_history)
coef_determination(y,prediction)


import numpy as np

# SQUARE MATRIX --> number of rows is equal to number of columns

matrix1 = np.random.randint(1,10,(4,4))

matrix1.shape

# SYMMETRIC MATRIX --> type of square matrix where top right triangle is same as bottom left triangle based on principle diagonal elements

# https://stackoverflow.com/questions/10806790/generating-symmetric-matrices-in-numpy/27331415
N = 4
b = np.random.random_integers(-2000,2000,size=(N,N))
symmetric_matrix = (b + b.T)/2

symmetric_matrix.shape

# TRIANGULAR MATRIX --> is a square matrix and top OR bottom elements are zero based on principle diagonal element

# top elements are zero
np.triu(matrix1)

# bottom elements are zero
np.tril(matrix1)

# DIAGONAL MATRIX --> is a square matrix and top AND bottom elements are zero based on principle diagonal  element

diagonal_matrix = np.array([[1,0,0],[0,2,0],[0,0,3]])

# IDENTITY MATRIX --> type of diagonal matrix, principle diagonal will be equal to one

np.eye(4)


'''
Author:    Michael Sherif Naguib
Date:      March 13, 2020
@:         University of Tulsa
Description: see README.md

( the coronavirus hit so we were sent home from college ... I had the idea to finally code this last week and finished the
first working code @ DFW ... I have touched it up now and put it on github)

'''

# Imports
import math
import random
import numpy as np
import matplotlib.pyplot as plt
import tqdm

def distSqr(x,y):
    '''
    :description: Calculates the distance squared from a point with coordinates x,y from the origin 0,0
    :param x: x coordinate
    :param y: y coordinate
    :return: distance squared
    '''
    return math.pow(x,2) + math.pow(y,2)

def calcPiEstimate(numInCircle,numInSquare):
    '''
    :description: Calculates an Estimate of Pi using the given parameters;
    NOTE! The calculation is based off of a circle inscribed in a square.
    Derivation
    where s is a scaling factor
    let r = s where r is the radius of the circle
    thus the side length of the square  = 2s = 2r
    1) As = Area Square = (2r)^2 = 4*r^2
    2) Ac = Area Circle = pi*r^2
    3) thus pi = Ac/r^2
    4) r^2 = Area Square/4           (from stmt 1)
    5) thus pi = Ac/ (As/4) = 4Ac/As

    :param numInCircle: a count of the number of points within the circle
    :param numInSquare: a count of the number of points within the square
    :return: an estimate of pi

    '''
    assert numInSquare !=0 # Catch Div by Zero
    return 4*numInCircle/numInSquare

def updateRunningAvg(curAvg,val,valCnt):
    '''
    :description: compute a running average
    :param curAvg: the current value of the average
    :param val: the new value to factor into the average
    :param valCnt: the number of values taken into account for the curAvg
    :return: the updated average

    (NOTE! this function does not change any of the parameters --> no side effect...)
    it is the prgmr's responsibility to update those vals
    '''
    assert valCnt != 0
    return (curAvg*valCnt + val)/(valCnt+1)

def monteCarloPiItr(radius=1,iterations=100_000_000):
    '''
    :description: A generator that generates successive estimates of pi
    :param radius: the Radius of the Circle inscribed in the Square (both centered at 0,0)
    :param iterations: the number points to compute and use to update the estimate
    :yeild: the next estimate of pi
    '''
    # Count how many are in the circle
    inCircleCount = 0
    # Store an estimate for Pi (will be updated as the simulation progresses)
    piEst = 0
    # Calculate the radius squared (once)
    radiusSquared = math.pow(radius, 2)

    # Begin iteration: i is the total up to that iteration .. (all points are in the square)
    for i in range(1, iterations + 1):
        # Pick random coords: the circle is centered @ 0,0 so shift over the range of the square by subtracting 0.5
        # before scaling by the sidelength of the square ( side length = 2*radius)
        x = (random.random() - 0.5) * radius * 2
        y = (random.random() - 0.5) * radius * 2

        # Check if the point is within the circle: increment if it is (use squared distance to be efficient)
        inCircleCount = inCircleCount + 1 if distSqr(x, y) <= radiusSquared else inCircleCount + 0

        # Update the pi estimate
        piEst = calcPiEstimate(inCircleCount,i)  # (all the points we select are in the square so our inSquareCount would be just i)

        # Yield the pi esitmate
        yield piEst

def plotSeries(series, x_name="x", y_name="y", title="Graph", x_key='x',y_key='y'):
    # Code adapted from my chaotic IFS project
    # Plotting Code: Passed a series list [series1,series2] where series {name:"",x:[],y:[]}
    # NOTE! CAN ONLY PLOT 3 colors before it starts using random values for colors
    # TAKEN from my PlotUtil Lib on github on 3/13/2020@10:48AM
    colors = [(70 / 255, 240 / 255, 240 / 255), (240 / 255, 50 / 255, 230 / 255), (210 / 255, 245 / 255, 60 / 255)]
    plt.title(title)
    plt.xlabel(x_name)
    plt.ylabel(y_name)
    idx = 0
    r = random.random
    for group in series:
        # Plot the points
        plt.scatter(group[x_key], group[y_key], c=[colors[idx] if idx < len(colors) else (r(), r(), r())],
                    s=np.pi * 3, alpha=0.5, label=group['name'])
        idx += 1
    plt.legend(loc='upper left')
    plt.show()

def reject_outliers(data, m=2):
    '''
    NOT MY CODE: i take no credit for this code...
    thanks to: https://stackoverflow.com/questions/11686720/is-there-a-numpy-builtin-to-reject-outliers-from-a-list
    :Description: this code rejects outliers ....
    :param data: array
    :param m:
    :return: data without outliers
    '''
    return data[abs(data - np.mean(data)) < m * np.std(data)]

# Main Simulation
if __name__ == "__main__":

    cnt=0
    for newEst in monteCarloPiItr():
        cnt+=1
        if cnt%10000==0:
            print(newEst)

    # Sample every k iterations:
    k = 10

    # Different Radius Values to Try:
    radii = [1,10,100]
    assert(len(radii)<=3) # a requirement of my plotting function ....
    # Point Quanty per Simulation]
    pointQuantity= 1000

    # Number of times to redo the simulation
    redos = 50
    allSeries=[]
    # Run the Simulation(s)
    for radius in radii:
        # Data structure to hold info about the series
        currentSeries = {
           "name": "radius = {0}".format(radius),
           "x":[],
           "y":[],
        }
        for rd in range(redos):
            # Use a counter to keep track of iterations
            cntr=0
            # Run the simulation getting the next estimate
            for newEstimate in tqdm.tqdm(monteCarloPiItr(radius=radius,iterations=pointQuantity)):
                if cntr%k==0:# Sample every k iters
                    if rd==0: # i.e it is the first time data is going in ... do nothing just add the data
                        currentSeries["x"].append(cntr)
                        currentSeries["y"].append(newEstimate)
                    else:
                        # Update the value as a running average

                        # Index the list of sampled data... since we log every k ... the index is cntr/k which is an int
                        indx = int(cntr/k)
                        assert float(indx) == cntr/k#check

                        currentSeries["y"][indx] = updateRunningAvg(currentSeries["y"][indx],newEstimate,rd+1)
                cntr+=1
            # Append the current series to all the series
        allSeries.append(currentSeries)

    # Graph the Data

    # Plot the convergence of the different simulations
    plotSeries(
        allSeries,
        x_name="Iterations  (sampled every {0}, redos={1})".format(k,redos),
        y_name="Estimate Value",
        title=" Estimate Value vs Iterations"
        )

    # Plot the histogram data for each radius
    bins = 100
    for i in range(len(radii)):
       cleaned_data= reject_outliers(np.array(allSeries[i]["y"]),m=2)
       plt.hist(cleaned_data,bins)
       plt.title("Estimates for radius={0} redos={1}".format(radii[i],redos))
       plt.xlabel("Estimate Values")
       plt.ylabel("Estimate Counts (outliers excluded)")
       plt.show()


"""
mean

The mean tool computes the arithmetic mean along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.mean(my_array, axis = 0)        #Output : [ 2.  3.]
print numpy.mean(my_array, axis = 1)        #Output : [ 1.5  3.5]
print numpy.mean(my_array, axis = None)     #Output : 2.5
print numpy.mean(my_array)                  #Output : 2.5

By default, the axis is None. Therefore, it computes the mean of the flattened array.

var

The var tool computes the arithmetic variance along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.var(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.var(my_array, axis = 1)         #Output : [ 0.25  0.25]
print numpy.var(my_array, axis = None)      #Output : 1.25
print numpy.var(my_array)                   #Output : 1.25

By default, the axis is None. Therefore, it computes the variance of the flattened array.

std

The std tool computes the arithmetic standard deviation along the specified axis.

import numpy

my_array = numpy.array([ [1, 2], [3, 4] ])

print numpy.std(my_array, axis = 0)         #Output : [ 1.  1.]
print numpy.std(my_array, axis = 1)         #Output : [ 0.5  0.5]
print numpy.std(my_array, axis = None)      #Output : 1.11803398875
print numpy.std(my_array)                   #Output : 1.11803398875

By default, the axis is None. Therefore, it computes the standard deviation of the flattened array.

Task

You are given a 2-D array of size
X

.
Your task is to find:

    The mean along axis

The var along axis
The std along axis

Input Format

The first line contains the space separated values of
and .
The next lines contains

space separated integers.

Output Format

First, print the mean.
Second, print the var.
Third, print the std.

Sample Input

2 2
1 2
3 4

Sample Output

[ 1.5  3.5]
[ 1.  1.]
1.11803398875
"""

import numpy as np

n,m = map(int, input().split())
b = []
for i in range(n):
    a = list(map(int, input().split()))
    b.append(a)

b = np.array(b)

np.set_printoptions(legacy='1.13')
print(np.mean(b, axis = 1))
print(np.var(b, axis = 0))
print(np.std(b))


"""
Andrea has a simple equation:
Y = a + b1f1 + b2f2 + ... + bmfm
for (m + 1) real constants (a, f1, f2, ..., fm). We can say that the value of Y depends on m features.
Andrea studies this equation for n different feature sets (f1, f2, ..., fm) and records each respective value of Y.
If she has q new feature sets, can you help Andrea find the value of Y for each of the sets?
"""

import numpy as np

def least_squares(x, y):
    a = np.append(x, np.ones((len(x), 1)), axis=-1)
    y = y[:, np.newaxis]

    result = np.linalg.inv(np.dot(a.T, a))
    result = np.dot(result, a.T)
    result = np.dot(result, y)

    return result[-1][0], result[:-1]

def main():

    m, n = list(map(int, str.split(input(), " ")))
    data = [list(float(x) for x in input().split()) for i in range(n)]

    x = np.array([[item[i] for i in range(m)] for item in data])
    y = np.array([item[-1] for item in data])

    a, b = least_squares(x, y)

    q = int(input())

    for i in range(q):
        data = list(map(float, input().split()))
        result = [b[j] * data[j] for j in range(m)]
        result = a + np.sum(result)
        print("{:.3f}".format(result))

if __name__ == "__main__":
    main()


'''
01 - Correlated data in nature

You are given an array grains giving the width and length of samples of grain.
You suspect that width and length will be correlated. To confirm this,  make a
scatter plot of width vs length and measure their Pearson correlation.

Instructions

- Import:
    - matplotlib.pyplot as plt.
    - pearsonr from scipy.stats.

- Assign column 0 of grains to width and column 1 of grains to length.
- Make a scatter plot with width on the x-axis and length on the y-axis.
- Use the pearsonr() function to calculate the Pearson correlation of width and
  length.
'''
# Perform the necessary imports
import matplotlib.pyplot as plt
from scipy.stats import pearsonr

# Assign the 0th column of grains: width
width = grains[:,0]

# Assign the 1st column of grains: length
length = grains[:,1]

# Scatter plot width vs length
plt.scatter(width, length)
plt.axis('equal')
plt.show()

# Calculate the Pearson correlation
correlation, pvalue = pearsonr(width, length)

# Display the correlation
print(correlation)

'''
<script.py> output:
    0.8604149377143466
'''

27_Unsupervised Learning in Python/03_decorrelating-your-data-and-dimension-reduction/01_correlated-data-in-nature.py


import numpy as np
import matplotlib.pyplot as plt

print("Polynomial interpolation \n")

def input_gen():
    x = [0.0,  0.1,  0.2,   0.3,   0.4,   0.5,   0.6,  0.7,   0.8,   0.9,   1.0]
    p = [0.0,  0.41, 0.79, 1.13,  1.46,  1.76,  2.04,  2.3,  2.55,  2.79,  3.01]

    # x = [0, 1, 2, 3]
    # p = [-2, -5, 0, -4]

    # x = [1, 3, 4]
    # p = [6, 24, 45]

    k = 7
    m = 3.5
    y = [(p[i] + ((-1) ** k) * m) for i in range(len(x))]
    dots = list(zip(x, y))

    # dots = [(0, 0), (1, 0)]
    # dots = [(-1, 0), (0, 1), (1, 0)]
    # dots = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 6)]
    # dots = [(1, 1), (2, 3), (3, 5), (4, 7), (5, 30000)] # 217341
    # dots = [(-1.5, np.tan(-1.5)), (-0.75, np.tan(-0.75)), (0, np.tan(0)), (0.75, np.tan(0.75)), (1.5, np.tan(1.5))]

    # dots = [(, ), ]

    # def f(x):
    #    return 1 / (1 + x**2)
    # SIZE = 11
    # dots = [(-5 + 10 * x / (SIZE - 1), f(-5 + 10 * x / (SIZE - 1))) for x in range(SIZE) ]

    return dots

dots = input_gen()
(x, y) = map(list, zip(*dots))
print("(x,y) =", dots, '\n')

def Lagrange(dots):
    n = len(dots)
    (x, y) = map(list, zip(*dots))
    polynom = np.poly1d([0])
    for i in range(n):
        p = np.poly1d([1])
        for j in range(n):
            if j != i:
                p *= np.poly1d([1, -x[j]]) / (x[i] - x[j])
        polynom += y[i] * p
    return polynom

lagr = Lagrange(dots)
print("Lagrange polynom =")
print(lagr, '\n')

def DividedDifferences(xs):
    n = len(xs)
    diffs = [[None for j in range(n - i)] for i in range(n)]
    for i in range(n):
        diffs[i][0] = y[i]
    for j in range(1, n):
        for i in range(n - j):
            diffs[i][j] = ((diffs[i][j - 1] - diffs[i + 1][j - 1]) / (xs[i] - xs[i + j]))
    return diffs

def Inaccuracy(xs, xdot):
    n = len(xs)
    diffs = DividedDifferences(xs)
    maxdiff = 0.0
    for i in range(len(diffs)):
        for j in range(len(diffs[i])):
            maxdiff = max(maxdiff, abs(diffs[i][j]))
    w = 1
    for i in range(n):
        w *= xdot - xs[i]
    f = 1
    for i in range(1, n + 1 + 1):
        f *= i
    R = maxdiff * w / f
    return R

def Newton(dots):
    n = len(dots)
    (x, y) = map(list, zip(*dots))

    diffs = DividedDifferences(x)

    polynom = np.poly1d([0])
    for i in range(n):
        p = np.poly1d([1])
        for j in range(i):
            p *= np.poly1d([1, -x[j]])
        polynom += p * diffs[0][i]

    return polynom

newt = Newton(dots)
print("Newton polynom =")
print(newt, '\n')

def Simple(dots):
    n = len(dots)
    (x, y) = map(list, zip(*dots))
    A = []
    for i in range(n):
        A.append([])
        for j in range(n):
            A[i].append(x[i] ** j)
    polynom = np.poly1d(np.linalg.solve(A, y)[::-1])
    return polynom

def Squares(dots, m=None):
    n = len(dots) - 1
    if m is None:
        m = n
    assert 0 <= m <= n
    if m == n:
        return Simple(dots)

    (x, y) = map(list, zip(*dots))

    b = []
    for k in range(m + 1):
        s = 0
        for i in range(n + 1):
            s += y[i] * (x[i] ** (m - k))
        b.append(s)

    A = []
    for k in range(m + 1):
        A.append([])
        for j in range(m + 1):
            s = 0
            for i in range(n + 1):
                s += x[i] ** (2 * m - k - j)
            A[k].append(s)

    polynom = np.poly1d(np.linalg.solve(A, b))
    return polynom

def LeastSquares(dots, m=None):
    n = len(dots) - 1
    if m is None:
        m = n
    assert 0 <= m <= n
    return np.poly1d(np.polyfit(*map(list, zip(*dots)), m))

sqrs = Squares(dots)
print("Squares polynom =")
print(sqrs, '\n')

last = LeastSquares(dots)
print("LeastSquares polynom =")
print(last, '\n')

M = 2
print(f"\n Let-s build least squares polymon with degree = {M} \n")
sqrs = Squares(dots, M)
print("Squares polynom =")
print(sqrs, '\n')
last = LeastSquares(dots, M)
print("LeastSquares polynom =")
print(last, '\n')

xdot = 0.47
print(f"Largange({xdot}) =", lagr(xdot))
print(f"Newton({xdot})   =", newt(xdot))
print(f"Squares({xdot}) =", "{:.4f}".format(sqrs(xdot)))
print(f"LeastSquares({xdot}) =", last(xdot))
print("|Largange - Newton| =", abs(lagr(xdot) - newt(xdot)))
print("|Squares - LeastSquares| =", abs(sqrs(xdot) - last(xdot)))
print("|Inteprolation - Squares|      =", "{:.4f}".format(abs((lagr(xdot) + newt(xdot)) / 2 - sqrs(xdot))))
print("|Inteprolation - LeastSquares| =", abs((lagr(xdot) + newt(xdot)) / 2 - last(xdot)))
print(f"Inaccuracy({xdot}) = ", Inaccuracy(x, xdot))


import numpy as np
import matplotlib.pyplot as plt

# y = ax + b

def calc_error(a,b,data):
	error = 0.0
	N = len(data)
	for i in range(N):
		x = data[i,0]
		y = data[i,1]
		error += (1 / N) * ((a * x + b) - y) ** 2
	return error

def step_gradient_descent(a,b,data,learning_rate):
	step_a = 0
	step_b = 0
	N = len(data)
	for i in range(N):
		x = data[i,0]
		y = data[i,1]
		step_a += (2/N) * (a * x + b - y) * x
		step_b += (2/N) * (a * x + b - y)
	new_a = a - learning_rate * step_a
	new_b = b - learning_rate * step_b
	return (new_a,new_b)

def gradient_descent_runner(init_a,init_b,data,learning_rate,num_iterations):
	a = init_a
	b = init_b
	for i in range(num_iterations):
		(a,b) = step_gradient_descent(a,b,data,learning_rate)
		print('step {}: a:{} b:{} error:{}'.format(i,a,b,calc_error(a,b,data)))
	return (a,b)

def main():
	data = np.genfromtxt("data.csv", delimiter=",")
	learning_rate = 0.00001
	init_a = 0
	init_b = 0
	num_iterations = 1000
	print('init a:{} b:{} error:{}'.format(init_a,init_b,calc_error(init_a,init_b,data)))
	(new_a,new_b) = gradient_descent_runner(init_a,init_b,data,learning_rate,num_iterations)
	plt.scatter(data[:,0],data[:,1])
	pt_x = np.arange(20,80,0.1)
	pt_y = new_a * pt_x + new_b
	plt.plot(pt_x,pt_y,color='red')
	plt.show()
	print('answer a:{} b:{} error:{}'.format(new_a,new_b,calc_error(new_a,new_b,data)))

if __name__ == '__main__':
	main()


# -*- encoding: utf-8 -*-

'''This Module List(s) out Various Functions for Calculating the Distance b/w Two Points'''

import numpy as np

def EuclideanDistance(startPoint : np.ndarray, targetPoint : np.ndarray) -> float:
	'''Calculates the Eulidean Distance b/w Two Points on an n-Dimensional Plane
	:param startPoint  : Start Point, with [x, y, ... z] Coordinates
	:param targetPoint : Start Point, with [x, y, ... z] Coordinates

	Returns the Distance b/w two Points (unitless Quantity)
	'''
	if (type(startPoint) != np.ndarray) or (type(targetPoint) != np.ndarray):
		startPoint  = np.array(startPoint)
		targetPoint = np.array(targetPoint)

	return np.sqrt(np.sum((startPoint - targetPoint) ** 2))

def ManhattanDistance(startPoint : np.ndarray, targetPoint : np.ndarray) -> float:
	'''Calculates the Manhattan Distance b/w Two Points on an n-Dimensional Plane
	:param startPoint  : Start Point, with [x, y, ... z] Coordinates
	:param targetPoint : Start Point, with [x, y, ... z] Coordinates

	Returns the Distance b/w two Points (unitless Quantity)
	'''
	if (type(startPoint) != np.ndarray) or (type(targetPoint) != np.ndarray):
		startPoint  = np.array(startPoint)
		targetPoint = np.array(targetPoint)

	return sum([abs(i) for i in (startPoint - targetPoint)])

def CircularEuclideanDistance(numElements : int or float, i : int or float, j : int or float) -> float:
	'''Calculates the Circular Euclidean Distance b/w Two-Integers (i, j) in a Circle of n-Elements'''
	manhattanDistance = abs(i - j) # 1-Dimensional Distance
	return min(manhattanDistance, numElements - manhattanDistance)


import numpy as np
import math

def euclidean_distances(X, Y):
    """Compute pairwise Euclidean distance between the rows of two matrices X (shape MxK)
    and Y (shape NxK). The output of this function is a matrix of shape MxN containing
    the Euclidean distance between two rows.

    Arguments:
        X {np.ndarray} -- First matrix, containing M examples with K features each.
        Y {np.ndarray} -- Second matrix, containing N examples with K features each.

    Returns:
        D {np.ndarray}: MxN matrix with Euclidean distances between rows of X and rows of Y.
    """
    all_distances = []
    for exampleX in X:
        one_row = []
        for exampleY in Y:
            one_row.append(euclidean_distance(exampleX, exampleY))
        all_distances.append(one_row)
    return np.array(all_distances)
    # raise NotImplementedError()

def euclidean_distance(point1, point2):
    sum = 0
    for dimension in range(0, len(point1)):
        sum += math.pow(point1[dimension] - point2[dimension], 2)
    return math.sqrt(sum)

def manhattan_distances(X, Y):
    """Compute pairwise Manhattan distance between the rows of two matrices X (shape MxK)
    and Y (shape NxK). The output of this function is a matrix of shape MxN containing
    the Manhattan distance between two rows.

    Arguments:
        X {np.ndarray} -- First matrix, containing M examples with K features each.
        Y {np.ndarray} -- Second matrix, containing N examples with K features each.

    Returns:
        D {np.ndarray}: MxN matrix with Manhattan distances between rows of X and rows of Y.
    """
    all_distances = []
    for exampleX in X:
        one_row = []
        for exampleY in Y:
            one_row.append(manhattan_distance(exampleX, exampleY))
        all_distances.append(one_row)
    return np.array(all_distances)
    # raise NotImplementedError()

def manhattan_distance(point1, point2):
    lat_dist = abs(point1[0] - point2[0])
    long_dist = abs(point1[1] - point2[1])
    true_long_dist = min(long_dist, 360 - long_dist)
    return lat_dist + true_long_dist

def cosine_distances(X, Y):
    """Compute Cosine distance between the rows of two matrices X (shape MxK)
    and Y (shape NxK). The output of this function is a matrix of shape MxN containing
    the Cosine distance between two rows.

    Arguments:
        X {np.ndarray} -- First matrix, containing M examples with K features each.
        Y {np.ndarray} -- Second matrix, containing N examples with K features each.

    Returns:
        D {np.ndarray}: MxN matrix with Cosine distances between rows of X and rows of Y.
    """
    all_distances = []
    for exampleX in X:
        one_row = []
        for exampleY in Y:
            one_row.append(cosine_distance(exampleX, exampleY))
        all_distances.append(one_row)
    return np.array(all_distances)
    # raise NotImplementedError()

def cosine_distance(point1, point2):
    u_dot_v = np.dot(point1, point2)
    u_mag = math.sqrt(np.sum(np.square(point1)))
    v_mag = math.sqrt(np.sum(np.square(point2)))
    cosine_similarity = u_dot_v / (u_mag * v_mag)
    return 1 - cosine_similarity


import numpy as np

# array to work with
np.random.seed(21)
random_integers = np.random.randint(1, high=500000, size=(20, 5))
print('#1', random_integers)

# average valeu of the second column
np.set_printoptions(precision=2)
print('#2', np.average(random_integers[:, 1]))

# average value of the first 5 rows of the third and fourth columns
print('#3', np.average(random_integers[0:4, 2:4]))

# expected result of first_matrix + second_matrix
# 1 2 3   +   1 2 3   =   2 4 6
# 4 5 6                   5 7 9
first_matrix = np.array([[1, 2, 3], [4, 5, 6]])
print('#4', first_matrix)
second_matrix = np.array([1, 2, 3])
print('#4', second_matrix)

# expected result of my_vector[selection]
# [2, 4, 6]
my_vector = np.array([1, 2, 3, 4, 5, 6])
selection = my_vector % 2 == 0
print('#5', my_vector)
print('#5', selection)

# check two previous results
print('#6 for #4', first_matrix + second_matrix)
print('#6 for #5', my_vector[selection])

# view vs copy
# view -> arrays appoints to the same data, but displays it differently
# copy -> arrays does not appoint to the same data
# get a view -> slice array array[1:2]
# get a copy -> fancy index array array[1,2]
# changes in view just will be propagated if you use simple index a[i] = n
my_array = np.array([1, 2, 3])
my_slice = my_array[1:3]
my_slice[0] = -1
print('#7: both will be changed', my_array, my_slice)
my_array = np.array([1, 2, 3])
my_slice = my_array[[1, 2]]
my_slice[0] = -1
print('#7: just slice will be changed', my_array, my_slice)
my_array = np.array([[1, 2, 3], [4, 5, 6]])
my_slice = my_array[:, 1:3]
print('#8', my_array, my_slice)
my_array[:, :] = my_array * 2
print('#9', my_array, my_slice)
my_array = my_array * 2
print('#10', my_array, my_slice)
my_array = np.array([[1, 2, 3], [4, 5, 6]])
print('#11', my_array)
my_slice = my_array[:, 1:3].copy()
print('#11', my_slice)
my_array[:, :] = my_array * 2
print('#11', my_array, my_slice)


import numpy as np
from numpy.linalg import inv
from functools import reduce
import argparse

'''
This is just for practice purpose. Scipy and numpy
have already provided enough tools.
'''
def x_hat(A, b):
    '''
    the x to project b onto A when b is not in col space of A
    '''
    aTa = A.T.dot(A)
    aTa_inv = inv(aTa)
    return aTa_inv.dot(A.T).dot(b)

def make_perm_mx(size, original_row, to_swap):
    P = np.identity(size)
    P[original_row, :] = P[to_swap, :] = 0
    P[original_row, to_swap] = P[to_swap, original_row] = 1
    return P

def make_subtract_mx(size, current_row, current_col, factor):
    I = np.identity(size)
    I[current_row, current_col] = factor
    return I

def LU(A):
    '''
    LU factorization by elimination.
    U = e1*p1*e2*...*A = E*A
    L = inv(E)
    A = L*U
    E as elimination matrix. p and e are permutation and subtraction mx at each step.
    Pivots located in U
    Note: this workflow is not optimized for performance. But efficiency is not considered here.
    '''
    nrows, ncols = A.shape
    U = A.copy()
    E = np.identity(nrows)
    npivots = nrows if nrows <= ncols else ncols # mx might not be square. number of pivots take the smaller value
    for col in range(npivots): # loop through columns
        row_w_max_val = A[:, col].argmax() # find the largest value as the pivot
        p = make_perm_mx(npivots, col, row_w_max_val)
        U = p.dot(U)
        E = p.dot(E)
        for row in range(col,npivots):
            if row != col: # omit the diagnal
                factor = U[row, col]/U[col, col]*-1
                e = make_subtract_mx(npivots, row, col, factor) # make subtraction mx for current pivot col
                E = e.dot(E)
                U = e.dot(U)
    L = inv(E)
    return L, U

def find_pivot(A):
    _, U = LU(A)
    return np.diagonal(U)

def find_det(A):
    pivots = find_pivot(A)
    det = reduce(lambda x, y: x*y, pivots)
    return det

def factorize(A=None):
    L, U = LU(A)
    pivots = find_det(A)
    return {'L': L, 'U': U, 'pivots': find_pivot(A), 'determines': find_det(A)}

def project(A=None, b=None):
    '''
    * is dot product
    p = P*b = a*inv(aT*a)*aT*b
    '''
    aTa = A.T.dot(A)
    aTa_inv = inv(aTa)
    x_bar = aTa_inv.dot(A.T)
    p = A.dot(x_bar).dot(b)
    return p, x_bar

### command parsing###
def input_to_mx(i):
    return np.array(eval(i))

def run_cmd(parsed_arg, cmd_name):
    '''
    gather arguments for each sub-commands
    and parse arguments into correct data type
    '''
    cmd = eval(getattr(parsed_arg, cmd_name))
    _, *keys= parsed_arg.__dict__.keys() # exclude the first 'command' argument
    _, *values = parsed_arg.__dict__.values()
    transformed_values = [input_to_mx(each) for each in values if each.strip().startswith('[')]
    args = dict(zip(keys, transformed_values))
    return cmd(**args)

def show(result):
    print(result)

def main():
    '''
    reflection is used to call function.
    So the names of subcommands need to be same as the names of the function.
    '''

    arg = argparse.ArgumentParser(description='Calculations in Linear Algebra')
    sub_parsers = arg.add_subparsers(
        title='calculation',
        description='enter the calculation to be done',
        help='calculations',
        dest='command',
        required=True
    )

    ###projection###
    projection_args = sub_parsers.add_parser('project', help='project help')
    projection_args.add_argument('-A', '--A', help='matrix to be projected on. example: [[1,2,3], [1,2,3]] is a 2x3 matrix.')
    projection_args.add_argument('-b', '--b', help='matrix to poject')

    factorization_args = sub_parsers.add_parser('factorize', help='LU factorization. return L, U, pivots and determines')
    factorization_args.add_argument('-A', '--A', help='matrix to be projected on. example: [[2,1,0],[1,2,1],[0,1,2]] is a 3x3 matrix.')

    args = arg.parse_args()

    result = run_cmd(args, 'command')
    show(result)

if __name__ == '__main__':
    # A = np.array([1,0,1,1,1,2]).reshape(3,2)
    # b = np.array([6,0,0])
    # print(x_hat(A, b))
    main()


"""
A manager wants to find the relationship between the number of hours
that a plant is operational in a week and weekly production.

Production Hours(x) : 34, 35, 39, 42, 43, 47
Production volume(y): 102, 109, 137, 148, 150, 158
"""

from scipy.stats import linregress

x = [34, 35, 39, 42, 43, 47]
y = [102, 109, 137, 148, 150, 158]

slope = round(linregress(x, y).slope, 1)
intercept = round(linregress(x,y ).intercept, 1)

#formula
print(f" y = {intercept} + {slope}x")

"""
if manager wants to produce 125 units per week, then he should run
x = -46.0 + 4.5x
125 = -46.0 + 4.5x
x = 38 hours per week
"""


'''
The standard deviation and the variance
100xp

As mentioned in the video, the standard deviation is the square root of the variance.
You will see this for yourself by computing the standard deviation using np.std() and
comparing it to what you get by computing the variance with np.var() and then computing
the square root.

Instructions
-Compute the variance of the data in the versicolor_petal_length array using np.var().
-Print the square root of this value.
-Compute the standard deviation of the data in the versicolor_petal_length array using
np.std() and print the result.
'''
import numpy as np

versicolor_petal_length = np.array([4.7,  4.5,  4.9,  4.,  4.6,  4.5,  4.7,  3.3,  4.6,  3.9,  3.5,
                                    4.2,  4.,  4.7,  3.6,  4.4,  4.5,  4.1,  4.5,  3.9,  4.8,  4.,
                                    4.9,  4.7,  4.3,  4.4,  4.8,  5.,  4.5,  3.5,  3.8,  3.7,  3.9,
                                    5.1,  4.5,  4.5,  4.7,  4.4,  4.1,  4.,  4.4,  4.6,  4.,  3.3,
                                    4.2,  4.2,  4.2,  4.3,  3.,  4.1])

# Compute the variance: variance
variance = np.var(versicolor_petal_length)

# Print the square root of the variance
print(np.sqrt(variance))

# Print the standard deviation
print(np.std(versicolor_petal_length))