import numpy as np
import matplotlib.pyplot as plt

#The feature of activation function-Nonlinear function->if activation function is linear, then it means  just updating of weight(it's called by projection layer or linear layer)

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

x=np.arange(-5.0,5.0,0.1)
y=sigmoid(x)

plt.plot(x,y)
plt.plot([0,0],[1.0,0.0],':')
plt.title('Sigmoid Function')
plt.show()

#that's why we don't use Sigmoid. because of Vanishing Gradient. not update forward weight

#Hyperbolic tangent function
x=np.arange(-5.0,5.0,0.1)
y=np.tanh(x)

plt.plot(x,y)
plt.plot([0,0],[-1.0,1.0],':')
plt.axhline(y=0,color='orange',linestyle='--')
plt.title('Tanh Function')
plt.show()
#Hyperbolic tangent is well used than Sigmoid function because of low Vanishing Gradient than Sigmoid

#ReLU Function
def relu(x):
    return np.maximum(0,x)

x=np.arange(-5.0,5.0,0.1)
y=relu(x)

plt.plot(x,y)
plt.plot([0,0],[5.0,0.0],':')
plt.title('Relu Function')
plt.show()
#problem->if input is - then weight is 0 too. it calls dying ReLU

#Leaky ReLU
a=0.1#(Leaky rate)
def leaky_relu(x):
    return np.maximum(a*x,x)

x=np.arange(-5.0,5.0,0.1)
y=leaky_relu(x)

plt.plot(x,y)
plt.plot([0,0],[5.0,0.0],':')
plt.title('Leaky ReLU Function')
plt.show()

#Softmax Function
x=np.arange(-5.0,5.0,0.1)
y=np.exp(x)/np.sum(np.exp(x))

plt.plot(x,y)
plt.title('Softmax Function')
plt.show()
#Sigmoid Function is well used at Binary Classification, Softmax function is well used at MultiClass Classification

#Theory
#Binary Classification->Sigmoid_active F, nn.BCELoss()_cost F
#MultiClass Classificationi->Softmax_active F, nn.CrossEntropyLoss()_cost F (p.s)nn.CrossEntropyLoss has already Softmax Function
#Regressive->none, MSE_cost F

6. Aritificial Neural Network/6-6) Activation function.py


import numpy as np
import matplotlib.pyplot as plt

J_value = []

def normalization(X):
    shape = X.shape
    means = np.zeros((shape[1], 1))
    stds = np.zeros((shape[1], 1))
    for col in range(shape[1]):
        means[col] = np.mean(X[:, col])
        stds[col] = np.std(X[:, col])
        X[:, col] = (X[:, col] - means[col]) / stds[col]

    return X, means.T, stds.T

def cost_function(X, y, theta, m):
    a = np.dot(X, theta) # h(X)
    b = a - y[:][np.newaxis].T
    return np.dot(b.T, b)[0][0] / 2 / m

def gradient_descent(X, y, iteration=1500, alpha=0.01):
    shape = X.shape
    m = shape[0] # num of samples
    theta = np.zeros((1, shape[1]+1)).T # initiate theta

    X = np.concatenate((np.ones((m, 1)), X), axis=1) # add a colum of one to X

    J_value.append(cost_function(X, y, theta, m))
    print(shape[1] + 1)
    for i in range(iteration):
        theta_temp = theta
        a = np.dot(X, theta_temp) # h(X)
        b = a - y[:][np.newaxis].T

        for col in range(shape[1] + 1):
            theta[col] = theta_temp[col] - alpha * np.dot(b.T, X[:, col]) / m

        J_value.append(cost_function(X, y, theta, m))

    return theta.T[0]

def normal_equations(X,y):
    shape = X.shape
    m = shape[0] # num of samples

    X = np.concatenate((np.ones((m, 1)), X), axis=1) # add a colum of one to X

    a = np.dot(X.T, X)
    b = np.linalg.pinv(a)
    c = np.dot(b, X.T)

    return np.dot(c, y)

def main():
    data = np.loadtxt('./ex1/ex1/ex1data2.txt', delimiter=',')
    X = data[:, :-1]
    y = data[:, -1]

    theta_1 = normal_equations(X,y) # solve by normal equation

    X, means, stds = normalization(X) # normalize before gradient descent
    theta_2 = gradient_descent(X, y, iteration=500)

    print('Theta solved by normal equation is: ', theta_1)
    print('Theta solved by gradient descent is: ', theta_2)

    x = np.array([1., 1650, 3])
    print('Price of the 1650-square-foot house with 3 bedrooms is (normal equation): ', np.dot(x, theta_1.T))
    x[1:] = (x[1:] - means) / stds
    print('Price of the 1650-square-foot house with 3 bedrooms is (gradient descent): ', np.dot(x, theta_2.T))

    # plot J
    plt.plot(J_value)
    plt.xlabel('Number of iterations')
    plt.ylabel('Cost J')
    plt.show()

if __name__ == '__main__':
    main()


"""
Newton

A package to solve non-linear equations.

Copyright (c) 2020 Felipe Markson dos Santos Monteiro <fmarkson@gmail.com>
MIT License.
"""

from numpy.linalg import solve as default_solver
from numpy import ndarray
from typing import Tuple, Callable, Union

def solve(
    func: Callable[[ndarray], ndarray],
    jacobian: Callable[[ndarray], ndarray],
    x0: ndarray,
    *,
    tol: Union[int, float, ndarray] = 0.001,
    maxiter: int = 100,
    solver: Callable[[ndarray, ndarray], ndarray] = default_solver,
    verbose: bool = True,
) -> Tuple[bool, ndarray, ndarray]:
    """
    A Newton–Raphson method implementation for finding roots . See (https://en.wikipedia.org/wiki/Newton%27s_method)
        Ex:
            import newton
            import numpy as np

            (converged, error, solution) = newton.solve(
            lambda x: np.array([x ** 2]),
            lambda x: np.array([2 * x]),
            x0=np.array([1.2]),
            tol=0.001,
            maxiter=100,
            verbose=False,
            )

            print(solution)
            >>> [0.01875]
    Args:
        func : The func is a F, where F: ℝᵏ → ℝᵏ and k is the number of dimensions.
        jacobian : The Jacobian Matrix of func. See (https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant).
        x0 : The intial guess, where x0 ∈ ℝᵏ.
        tol : The tolerance. If ndarray, the tolerance for each dimension must be specified.
        maxiter : The maximum number of iterations.
        solver : A linear matrix equation solver. If not specified, the numpy.linalg.solve is used. See (https://numpy.org/doc/stable/reference/generated/numpy.linalg.solve.html)
        verbose :  If True, prints the iteration and the absolute maximum error.
    Returns:
        If converged, the mismatch vectors, and the current solution.

    """  # noqa

    Fx = func(x0)
    for indx in range(0, maxiter):
        Jx = jacobian(x0)
        deltaX = solver(-Jx, Fx)
        x0 = x0 + deltaX
        Fx = func(x0)
        Fxabs = abs(Fx)
        if verbose:
            print(f"## Iteration {indx}. Absolute maximum error: {Fxabs.max()}")

        if all(Fxabs <= tol):
            return (True, Fx, x0)

    return (False, Fx, x0)


import os
from scipy.special import comb

def bagging_proba(n, acc=0.8):
    ''' n independent estimators with accuracy 'acc', then what is estimated
    accuracy of bagging estimator with majority voting ?
    Note, 6 or more out of 10 is called a majority.
    '''
    if n == 1:
        return acc
    error = 0
    for i in range(n // 2 + 1):
        # only i estimator makes correct guess
        error += comb(n, i, exact=False) * \
            ((1 - acc) ** (n - i)) * ((acc) ** i)
    return 1 - error

for i in range(1, 10):
    n = i * 10
    print(n, bagging_proba(n))

'''
10 0.9672065024000001
20 0.997405172599326
30 0.9997687743883322
40 0.9999783081068737
50 0.9999979051451444
60 0.9999997938783113
70 0.999999979452253
80 0.999999997931789
90 0.9999999997902754
'''


from numpy import array, full, empty

# System of Equations
A = array([[4, 1, 2, -1],
           [3, 6, -1, 2],
           [2, -1, 5, -3],
           [4, 1, -3, -8]], float)
B = array([2, -1, 3, 2], float)

N = len(B)

X = full(N, 1.0, float) # Initial Gusses array([1.0, 1.0, 1.0, 1.0], float)]

Xnew = empty(N, float)  # Creates empty array of size n

for iterations in range(100):
    for i in range(N):

        s = 0
        for j in range(N):
            if j == i: continue
            s += A[i,j]*X[j]

        Xnew[i] = - 1/A[i,i] * (s - B[i])

    if (abs(Xnew - X) < 1e-6).all():
        break
    else:
        X = Xnew.copy() # X = Xnew would cause errors by binding memory address

else:
    raise OverflowError("The System does not have Diagonal Dominance")

print("The Solution of the System:")
for i in range(N):
    print('X[', i+1, '] = ', round(X[i], 6), sep='')
print('The Number of Iterations: %d' % (iterations+1))

'''
4x1 +  x2 + 2x3 -  x4 = 2
3x1 + 6x2 -  x3 + 2x4 = -1
2x1 -  x2 + 5x3 - 3x4 = 3
4x1 +  x2 - 3x3 - 8x4 = 2

x1 = - 1/4 ( x2 + 2x3 -  x4 - 2)
x2 = - 1/6 (3x1 -  x3 + 2x4 + 1)
x1 = - 1/5 (2x1 -  x2 - 3x4 - 3)
x2 =   1/8 (4x1 +  x2 - 3x3 - 2)

xnew[i] = - 1/aii (∑ aij*xj - bi)   where i = 1 to n, j = 1 to n and j ≠ i

'''

5. Systems of Linear Equations/7. Jacobi's (Iteration) Method.py


'''
Plotting the ECDF
100xp

You will now use your ecdf() function to compute the ECDF for the petal lengths of
Anderson's Iris versicolor flowers. You will then plot the ECDF. Recall that your ecdf()
function returns two arrays so you will need to unpack them. An example of such unpacking
is x, y = foo(data), for some function foo().

Instructions
-Use ecdf() to compute the ECDF of versicolor_petal_length. Unpack the output into x_vers and y_vers.
-Plot the ECDF as dots. Remember to include marker = '.' and linestyle = 'none' in addition to x_vers and y_vers as arguments inside plt.plot().
-Set the margins of the plot with plt.margins() so that no data points are cut off. Use a 2% margin.
-Label the axes. You can label the y-axis 'ECDF'.
-Show your plot.
'''
import numpy as np
import matplotlib.pyplot as plt

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])
def ecdf(data):
    """Compute ECDF for a one-dimensional array of measurements."""

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

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

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

    return x, y

# Compute ECDF for versicolor data: x_vers, y_vers
x_vers, y_vers = ecdf(versicolor_petal_length)

# Generate plot
_ = plt.plot(x_vers, y_vers, marker = '.', linestyle = 'none')

# Make the margins nice
plt.margins(0.02)

# Label the axes
_ = plt.xlabel('length')
_ = plt.ylabel('ECDF')

# Display the plot
plt.show()


# Question 05, Lab 04
# AB Satyaprakash - 180123062

# imports ----------------------------------------------------------------------------
from sympy.abc import t
from sympy import evalf, integrate
from math import ceil, sqrt
import numpy as np

# functions --------------------------------------------------------------------------

def f(x):
    return 1/(x+4)

def errorCompositeTrapezoidal(a, b):
    err = 10**(-5)
    d2fmax = 1/32
    val = (1/err)*(b-a)*(b-a)*(b-a)*(1/12)*d2fmax
    n = ceil(sqrt(val))
    print("Constraints :", "h <=", (b-a)/sqrt(val), "and n >=", n)
    return n

def errCompositeSimpson(a, b):
    err = 10**(-5)
    d4fmax = 24/(4**5)
    val = (1/err)*pow(b-a, 5)*(1/180)*d4fmax
    n = ceil(sqrt(sqrt(val)))
    if n % 2 == 0:
        print("Constraints :", "h <=", (b-a) /
              sqrt(sqrt(val)), "and n >=", n)
        return n
    else:
        print("Constraints :", "h <=", (b-a) /
              sqrt(sqrt(val)), "and n >=", n+1)
        return n+1

def errCompositeMidpoint(a, b):
    err = 10**(-5)
    d2fmax = 1/32
    val = (1/err)*(b-a)*(b-a)*(b-a)*(1/6)*d2fmax
    n = ceil(sqrt(val))
    if n % 2 == 0:
        print("Constraints :", "h <=", (b-a)/sqrt(val), "and n >=", n)
        return n
    else:
        print("Constraints :", "h <=", (b-a)/sqrt(val), "and n >=", n+1)
        return n+1

def compositeTrapezoidalRule(X):
    sum = 0
    a, b = X[0], X[-1]
    n = X.shape[0]
    h = (b-a)/(n-1)
    for i in range(n):
        x = X[i]
        if(i == 0 or i == n-1):
            sum += f(x)/2
        else:
            sum += f(x)
    return (h*sum)

def compositeSimpsonRule(X):
    sum = 0
    a, b = X[0], X[-1]
    n = X.shape[0]
    h = (b-a)/(n-1)
    for i in range(n):
        x = X[i]
        if(i == 0 or i == n-1):
            sum += f(x)
        else:
            if(i % 2 == 0):
                sum += 2*f(x)
            else:
                sum += 4*f(x)
    return (h*sum)/3

def compositeMidpointRule(X):
    a, b = X[0], X[-1]
    n = X.shape[0]-1
    h = (b-a)/(n)
    sum = 0
    for i in range(n):
        xi = a+i*h
        xi_nex = a+(i+1)*h
        pt = (xi+xi_nex)/2
        sum += f(pt)
    return h*sum

# program body
func = 1/(t+4)
a, b = 0, 2

I = integrate(func, (t, a, b)).evalf()
print('Actual value of integral is', I)

print("\nFor part a: (Trapezoidal Rule)")
n = errorCompositeTrapezoidal(a, b)
h = (b-a)/n
X = np.arange(a, b+h/2, h)
estimatedIntegral = compositeTrapezoidalRule(X)
print('Required tuple (n,h) with error < 0.00001 is ({}, {})'.format(n, h))
print('Estimated value of integral is', estimatedIntegral)
print('Error in this case is', abs(estimatedIntegral-I))

print("\nFor part b: (Simpson Rule)")
n = errCompositeSimpson(a, b)
h = (b-a)/n
X = np.arange(a, b+h/2, h)
estimatedIntegral = compositeSimpsonRule(X)
print('Required tuple (n,h) with error < 0.00001 is ({}, {})'.format(n, h))
print('Estimated value of integral is', estimatedIntegral)
print('Error in this case is', abs(estimatedIntegral-I))

print("\nFor part c: (Midpoint Rule)")
n = errCompositeMidpoint(a, b)
h = (b-a)/n
X = np.arange(a, b+h/2, h)
estimatedIntegral = compositeMidpointRule(X)
print('Required tuple (n,h) with error < 0.00001 is ({}, {})'.format(n, h))
print('Estimated value of integral is', estimatedIntegral)
print('Error in this case is', abs(estimatedIntegral-I))

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 4/Code/q5.py


import numpy as np
from typing import Union
from matplotlib.axes import Axes
from matplotlib.image import AxesImage
from matplotlib.colors import Colormap, LogNorm

def pascal_triangle(size: int) -> np.ndarray:
    """Creates an array representation of pascal's triangle (right-angled)

    Args:
        size: width and height of the triangle

    Returns:
        A 2D numpy array containing the triangle. The triangle is
        right-angled and the elements where the triangle is not defined
        are filled with zeros.

        Example: Triangle of size 5
        [[1, 0, 0, 0, 0],
         [1, 1, 0, 0, 0],
         [1, 2, 1, 0, 0],
         [1, 3, 3, 1, 0],
         [1, 4, 6, 4, 1]]
    """
    grid = np.zeros((size, size), dtype=np.uint64)
    grid[:, 0] = 1
    for i in range(1, size):
        for j in range(1, size):
            grid[i, j] = grid[i - 1, j - 1] + grid[i - 1, j]
    return grid

def draw_pascal_triangle(ax: Axes, size: int, fontsize: int = 10,
                         normalize: bool = True) -> None:
    """Plots pascal's triangle with the given size on ax

    Args:
        ax: matplotlib axes object
        size: width and height of the triangle
        fontsize: font size of the numbers
        normalize: whether or not to normalize the coordinates
            and the axes' limits to make the triangle equilateral
    """
    grid = pascal_triangle(size)
    hor = 1
    if normalize:
        ver = 3**0.5 / 2 * hor
    else:
        ver = hor

    for i in range(size):
        for j in range(i + 1):
            ax.text((size - i + 2 * j) * hor,
                    (2 * (size - 1 - i) + 1) * ver,
                    str(int(grid[i][j])), fontsize=fontsize,
                    ha="center", va="center")

    if normalize:
        ax.set_xlim(0, 2 * hor * (size - 1))
        ax.set_ylim(0, 2 * ver * (size - 1))

def draw_pascal_fractal(ax: Axes, size: int,
                        draw_numbers: bool = True, fontsize: float = 10,
                        cmap: Union[Colormap, str]="rainbow") -> AxesImage:
    """Draws the serpinski triangle inside pascal's triangle

    This is achieved by connecting the odd numbers in pascal's triangle

    Args:
        ax: matplotlib axes object
        size: width and height of the triangle
        draw_numbers: wheter or not to draw pascal's triangle
            on top of the fractal
        fontsize: font size of the numbers
        cmap: The colormap used for coloring the points. The points are
            colored according to the ratio of their number to the maximum
            number in the triangle. Logarithmic normalization is applied
            for better distinction.

    Returns:
        output of imshow
    """
    grid = pascal_fractal(size)

    image = ax.imshow(np.flip(grid, axis=0), origin="lower",
                      extent=(0, 2 * size, 0, 2 * size),
                      norm=LogNorm(), cmap=cmap)
    if draw_numbers:
        draw_pascal_triangle(ax, size, fontsize, False)

    ax.set_aspect('auto')
    return image

def pascal_fractal(size: int) -> np.ndarray:
    """Masked array representation of pascal's triangle serpinski fractal

    Suitable for use with imshow

    Args:
        size: width and height of the triangle

    Returns:
        Masked numpy array containing the serpinski triangle inside
        pascal's triangle. The numbers are repeated four times in squares
        and the squares are centered. Even numbers are masked.

        Example: Triangle of size 5
        [[- - - - 1 1 - - - -]
         [- - - - 1 1 - - - -]
         [- - - 1 1 1 1 - - -]
         [- - - 1 1 1 1 - - -]
         [- - 1 1 - - 1 1 - -]
         [- - 1 1 - - 1 1 - -]
         [- 1 1 3 3 3 3 1 1 -]
         [- 1 1 3 3 3 3 1 1 -]
         [1 1 - - - - - - 1 1]
         [1 1 - - - - - - 1 1]]
    """
    grid = _pascal_triangle_map(size)
    return np.ma.masked_where(grid % 2 == 0, grid)

def _pascal_triangle_map(size: int) -> np.ndarray:
    """Array representation of pascal's triangle, centered

    Args:
        size (int): width and height of the triangle

    Returns:
        Numpy array containing centered pascal's triangle. The numbers
        are repeated four times in squares and the squares are centered.
        Elements where the triangle is not defined are filled with zeros.

        Example: Triangle of size 5
        [[0 0 0 0 1 1 0 0 0 0]
         [0 0 0 0 1 1 0 0 0 0]
         [0 0 0 1 1 1 1 0 0 0]
         [0 0 0 1 1 1 1 0 0 0]
         [0 0 1 1 2 2 1 1 0 0]
         [0 0 1 1 2 2 1 1 0 0]
         [0 1 1 3 3 3 3 1 1 0]
         [0 1 1 3 3 3 3 1 1 0]
         [1 1 4 4 6 6 4 4 1 1]
         [1 1 4 4 6 6 4 4 1 1]]
    """
    grid = pascal_triangle(size)
    grid = grid.repeat(2, axis=0)
    grid = grid.repeat(2, axis=1)

    shifts = np.linspace(size - 1, 0, size).astype(int)
    shifts = shifts.repeat(2)
    return _apply_shifts(grid, shifts)

def _apply_shifts(array: np.ndarray, shifts: np.ndarray) -> np.ndarray:
    """Applies different shifts to diffrent rows of a numpy array

    Args:
        array: the numpy array
        shifts: shift amounts as a numpy array

    Returns:
        the numpy array with shifted rows
    """
    row, col = np.ogrid[:array.shape[0], :array.shape[1]]
    col = col - shifts[:, np.newaxis]
    return array[row, col]


# we are going to solve -u''(x) + 16*u(x)^p = 0
# intial condition of u(0) = 0 and u(1) = 2
# using Newton's method with an initial guess of 2x
# build a mesh on the domain of [0,1]

import numpy as np
import matplotlib.pyplot as plt

left_point = 0.0
right_endpoint = 1.0

p = 3.0  # the power on the u(x) term, the problem is nonlinear when p != 1
n = 80.0  # number of sub intervals
h = 1.0 / n  # sub interval length

x = np.arange(left_point, right_endpoint + h, h)

u = 2.0 * x
print "This is u", u
# when n = 4, then u = [ 0.   0.5  1.   1.5  2. ]

# now we need to create our Jacobian matrix.
# recall that Python is 0 based indexing and MATLAB has 1 based indexing!!!

# Jacobian has -1 on both sides of the main diagonal
diagonals = -1.0*np.ones(n-2)

print diagonals

# storage space
main_diagonal = np.zeros(n-1)

for iteration in np.arange(0, 100):

    for j in np.arange(n-1):
        # print x[i]
        main_diagonal[j] = 2.0 + 16.0 * p * np.power(h, 2.0) * np.power(u[j+1], p - 1.0)
        # print main_diagonal

    jacobian = np.diag(diagonals, -1) + np.diag(diagonals, 1) + np.diag(main_diagonal)

    # print jacobian

    F = np.zeros(n-1)

    for k in np.arange(1, n):
        F[k-1] = -u[k-1] + 2.0*u[k] - u[k+1] + 16.0 * np.power(h, 2.0) * np.power(u[k], p)

    # print "this is F", F

    # now we want to solve the system for delta u
    delta_u = np.linalg.solve(jacobian, -F)

    # print "this is u", u
    # print "this is delta u", delta_u

    print "Iteration: {} and delta u:{}:".format(iteration, np.linalg.norm(delta_u))

    for i in np.arange(1, n):
        u[i] += delta_u[i-1]

    # print "this is u", u

    if np.linalg.norm(delta_u) < 10e-15:
        print "It took this many iterations", iteration
        break

def hyperbolic_sin(x):
    return 2.0 * np.sinh(4.0*x) / np.sinh(4)

y = hyperbolic_sin(x)
# print "this is y", y
# print "this is y", y
plt.title("p={0}".format(p))
plt.xlabel("x")
plt.ylabel("y")
plt.plot(x, u)
# plt.plot(x, y)
plt.show()


'''
Introduction to NumPy Library.
This script introduces how to create NumPy arrays and how to operate with them.
For more details: https://numpy.org/devdocs/user/quickstart.html
'''

import numpy as npy

## 1. CREATING NumPy ARRAYS ##

# Casting lists to arrays:
list = [1,2,3,4,5]

array1 = npy.array(list)
print('The array1 is: ', array1)

# Using NumPy built-in functions:

# .zeros() creates an array of 0s with user-defined dimensions.
array2 = npy.zeros((1,5)) # the input is a tuple.
print('\n The array2 is: ', array2)

# .ones() creates an array of 1s with user-defined dimensions.
array3 = npy.ones((2,3))
print('\n The array3 is: \n', array3)

# .eye() creates an identity matrix with user-defined dimensions.
array4 = npy.eye(5)
print('\n The array4 is: \n', array4)

# .arange() creates an array of evenly spaced values within the user-specified interval.
array5 = npy.arange(1,11)
print('\n The array5 is: \n', array5)

array6 = npy.arange(1,100, 10) # Starts from 1 and iterates by adding 10 until it reaches to the upper limit 100.
print('\n The array6 is: \n', array6)

# .linspace() creates an array of evenly spaced values.
array7 = npy.linspace(1,100, 10) # divides the interval 1 to 100 into 10 equivalent pieces.
print('\n The array7 is: \n', array7)

# .random.rand() can create an array with random number entries with user-defined dimension
array8 = npy.random.rand(2,3) # by default, the generated numbers are between 0 to 1.
print('\n The array8 is: \n', array8)

# similarly:
array9 = npy.random.randn(2,3) # the entries of the array are coming from Standard Normal Distribution.
print('\n The array9 is: \n', array9)

array10 = npy.random.randint(1,10, 15) # generates random integers within a given interval.
print('\n The array10 is: \n', array10)

# Reshaping the array:
newArray10 = array10.reshape(3,5) # make sure d1xd2 = initial length. In this example 3x5 = 15 matches array10 length
print('\n The newArray10 is: \n', newArray10)

## 2. BUILT-IN ARRAY ATTRIBUTES ##

# Generate an array with random integer values between 1 and 100:
array = npy.random.randint(1,100,10)
print('\n The array is: \n', array)

# Finding the maximum entry of the array
arrMax = array.max()
print('\n The maximum entry of array is: ', arrMax, '\n')

# Finding the minimum entry of the array
arrMin = array.min()
print('\n The minimum entry of array is: ', arrMin, '\n')

# Fining the index of the maximum and minimum entries:
arrMinInd = array.argmin()
print('\n The index of the minimum entry of array is: ', arrMinInd, '\n') # Remember, indexing starts from 0!

arrMaxInd = array.argmax()
print('\n The index of the maximum entry of array is: ', arrMaxInd, '\n')

# Finding length the array
lenArray = len(array)
print('\n The length of the array is: ', lenArray, '\n')

# Finding dimensions of an array
dimArray = array8.shape
print('\n The dimensions of the array8 is: ', dimArray, '\n')

## 3. ARRAY INDEXING AND ENTRY SELECTION ##

# Create a new array:
array = npy.arange(1,21).reshape(4,5)
print('\n The new array is: \n', array)

# Picking a single entry from the array:
anEntry = array[1, 3] # retrieving entry 9
print('\n The picked entry is: ', anEntry, '\n')

# Picking an entire row of the array:
aRow = array[1, :] # retrieving the 2nd row
print('\n The picked row is: ', aRow, '\n')

# Picking an entire column of the array:
aColumn = array[:, 4] # retrieving the 5th column
print('\n The picked column is: ', aColumn, '\n')

# Picking multiple rows and columns of the array:
multiRowColumn = array[0:2, 2:4] # retrieving the 1st and 2nd row, and 3rd,4th columns
print('\n The picked multiRowColumn are: \n', multiRowColumn, '\n')

# Picking rows irregularly:
irregRows = array[[1,3]] # retrieving the 1st and 3rd rows
print('\n The picked irregRows are: \n', irregRows, '\n')

## 4. ARRAY OPERATIONS ##

# Adding arrays
arrAdd = array + array
print('\n The arrAdd is: \n', arrAdd, '\n')

# Substituting arrays
arrSubs = array - array
print('\n The arrSubs is: \n', arrSubs, '\n')

# Multiplying arrays
arrMult = array * array
print('\n The arrMult is: \n', arrMult, '\n')

# Dividing arrays
arrDiv = array / array
print('\n The arrDiv is: \n', arrDiv, '\n')

# Power of arrays
arrPow = array**4
print('\n The arrPow is: \n', arrPow, '\n')

# Square root of entries of array
arrSqrt = npy.sqrt(array)
print('\n The arrSqrt is: \n', arrSqrt, '\n')

# Sine of entries of array
arrSin = npy.sin(array)
print('\n The arrSin is: \n', arrSin, '\n')

# Exponention of entries of array
arrExp = npy.exp(array)
print('\n The arrExp is: \n', arrExp, '\n')

# Logarithm of entries of array
arrLog = npy.log(array)
print('\n The arrLog is: \n', arrLog, '\n')


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

"""
rotation.py
Rotation is a module designed to organize the three basic
rotation matrices in three dimensions, according to
https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations.
It is based on numpy arrays.
Context examples:
The unit vector in the direction of the x axis
of a three dimensional Cartesian coordinate system
is written as np.array([1, 0, 0]).
The identity amtrix of size 3 is written as
np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
Rotation example:
# TODO
Rotate hat(i) to get hat(k).
"""

import numpy as np

# Basic rotation

def R_x(angle):
    """Rotation matrix by given angle in radians about x-axis."""
    cos = np.cos(angle)
    sin = np.sin(angle)
    R_x_angle = np.array(
        [
            [1, 0, 0],
            [0, cos, -sin],
            [0, sin, cos]
        ]
    )
    return R_x_angle

def R_y(angle):
    """Rotation matrix by given angle in radians about y-axis."""
    cos = np.cos(angle)
    sin = np.sin(angle)
    R_y_angle = np.array(
        [
            [cos, 0, sin],
            [0, 1, 0],
            [-sin, 0, cos]
        ]
    )
    return R_y_angle

def R_z(angle):
    """Rotation matrix by given angle in radians about z-axis."""
    cos = np.cos(angle)
    sin = np.sin(angle)
    R_z_angle = np.array(
        [
            [cos, -sin, 0],
            [sin, cos, 0],
            [0, 0, 1]
        ]
    )
    return R_z_angle

# General rotation

def cartesian_rotation(vector, x_angle, y_angle, z_angle):
    """
    Rotate a given vector in a three dimensional Cartesian coordinate system
    by given angles in radians about each axis.
    """
    R_vector = np.matmul(
        R_z(z_angle), np.matmul(
            R_y(y_angle), np.matmul(
                R_x(x_angle), vector
            )
        )
    )
    return R_vector

def normalize(vector):
    norm = np.linalg.norm(vector, ord = 2)
    return vector / norm

def axis_rotation(vector, angle, axis):
    """
    Rotate a given vector by given angle in radians about given axis.
    The axis is defined by a unit vector.
    """

    unit_vector = normalize(axis)

    cos = np.cos(angle)
    sin = np.sin(angle)

    ux,uy,uz = unit_vector

    R = np.array(
        [
            [cos+(1-cos)*ux**2, ux*uy*(1-cos)-uz*sin, ux*uz*(1-cos)+uy*sin],
            [uy*ux*(1-cos)+uz*sin, cos+(1-cos)*uy**2, uy*uz*(1-cos)-ux*sin],
            [uz*ux*(1-cos)-uy*sin, uz*uy*(1-cos)+ux*sin, cos+(1-cos)*uz**2]
        ]
    )

    R_vector = np.matmul(
        R, vector
    )
    return R_vector

def orthographic_projection(vector3D, z):
    """
    Project 3D vector into 2D vector
    It is perspective_projection with FOV = np.pi/2
    """
    projection_matrix = np.array(
        [
            [z, 0, 0],
            [0, z, 0]
        ]
    )
    vector2D = np.matmul(
        projection_matrix, vector3D
    )
    return vector2D

def perspective_projection(vector, near, far, FOV = 2*np.pi/3, AR = 1):
    """
    Project 3D into 2D with perspective.

    Parameters
    ----------
    vector : array_like
        Input vector array.
    near : float
        Distance to the near clipping plane along the z-axis.
    far : float
        Distance to the far clipping plane along the z-axis.
    FOV : float
        Field of view.
        The angle between the upper and lower sides of the viewing frustum.
    AR : float
        The aspect ratio of the viewing window (width/height).
    Returns
    -------
    projected_vector : array_like
        Output vector array.
    """

    # perspective_matrix = np.array(
    #     [
    #         [1, 0, 0, 0],
    #         [0, 1, 0, 0],
    #         [0, 0, 1, 0],
    #         [0, 0, -1, 0]
    #     ]
    # )

    perspective_matrix = np.array(
        [
            [1/(AR*np.tan(FOV/2)), 0, 0, 0],
            [0, 1/np.tan(FOV/2), 0, 0],
            [0, 0, (near+far)/(near-far), 2*(near+far)/(near-far)],
            [0, 0, -1, 0]
        ]
    )

    aux_vector = np.matmul(
        perspective_matrix, vector
    )

    w = aux_vector[2] / near

    projected_vector = aux_vector / w

    projected_vector = projected_vector[:2]

    return projected_vector


from typing import Tuple

import numpy as np
from numpy.linalg import norm, eig

def eigenvalue(A, v):
    return np.dot(v, np.dot(A, v)) / np.dot(v, v)

def eigendecomp(A: np.ndarray,
                eps: float = 0.01) \
        -> Tuple[np.ndarray, np.ndarray]:
    """
    Calculates the eigendecomposition of matrix A using power method.

    :param A: matrix of shape (n, n)
    :param eps: precision of iterations
    :return: tuple vector, matrix - (eigenvalues, eigenvectors)
    """
    n = A.shape[0]

    eigvals = np.zeros(n)
    eigvecs = np.zeros(A.shape)

    for i in range(n):
        eig_vec = np.random.rand(n)
        eig_val = eigenvalue(A, eig_vec)

        while True:
            Av = A.dot(eig_vec)

            eig_vec_new = Av / np.linalg.norm(Av)
            eig_val_new = eigenvalue(A, eig_vec_new)

            if np.abs(eig_val - eig_val_new) < eps:
                break

            eig_vec = eig_vec_new
            eig_val = eig_val_new

        eigvals[i] = eig_val_new
        eigvecs[i] = eig_vec_new

        A = A - eig_val_new * eig_vec_new * eig_vec_new[:, np.newaxis]

    return eigvals, eigvecs.T

if __name__ == '__main__':
    A = np.array([[2, -1, 0],
                  [-1, 2, -1],
                  [0, -1, 2]])

    eig_vals, eig_vecs = eig(A)
    print("Library:")
    print(eig_vals)
    print(eig_vecs)
    print()

    eig_vals, eig_vecs = eigendecomp(A)
    print("My:")
    print(eig_vals)
    print(eig_vecs)
    print()


"""
Normalization also called as norms have three types
    - L2 Norms   (also called as Euclidean Norm)
    - L1 Norms
    - Frobenius Norms (used for matrix normalization)

Normalization is nothing but calculating the magnitude of the given vector/matrix

"""
import numpy as np

# define a array
A = np.arange(9) - 3

# reshape array into 3x3 matrix
B = A.reshape((3, 3))

# Euclidean (L2) Norm - Default
print(np.linalg.norm(A))
print(np.linalg.norm(B))
"""
Output:
8.306623862918075
8.306623862918075
"""

# The Frobenius norm is the L2 norm for a Matrix
print(np.linalg.norm(B, 'fro'))
"""
Output:
8.306623862918075
"""

# the L1 norm
print(np.linalg.norm(A, 1))
print(np.linalg.norm(B, 1))
"""
Output:
21.0
8.0
"""

# the max norm (P = infinity)
print(np.linalg.norm(A, np.inf))
print(np.linalg.norm(B, np.inf))
"""
Output:
5.0
12.0
"""

"""
Vector Normalization
"""

# normalization to produce unit vector
norm = np.linalg.norm(A, 2)
A_unit = A / norm
"""
Output:
[-0.36115756 -0.24077171 -0.12038585  0.          0.12038585  0.24077171
  0.36115756  0.48154341  0.60192927]
"""

# the magnitude of a unit vector is equal to 1
print(np.linalg.norm(A_unit))
"""
Output:
1.0
"""


import numpy as np
import math

# Compute the intersection distance between histograms x and y
# Return 1 - hist_intersection, so smaller values correspond to more similar histograms
# Check that the distance range in [0,1]

def dist_intersect(x,y):

    zipped = list(map(min, list(zip(x,y))))
    dist = 1 - (0.5*((np.sum(zipped)/np.sum(x)) + (np.sum(zipped)/np.sum(y))))
    assert 0 <= dist <= 1, "not in [0,1]"
    return dist

# Compute the L2 distance between x and y histograms
# Check that the distance range in [0,sqrt(2)]

def dist_l2(x,y):

    dist = np.sum((x - y)**2)
    assert 0 <= dist <= np.sqrt(2), "not in [0, sqrt(2)]"
    return dist

# Compute chi2 distance between x and y
# Check that the distance range in [0,Inf]
# Add a minimum score to each cell of the histograms (e.g. 1) to avoid division by 0

def dist_chi2(x,y):

    x += 0.5
    y += 0.5
    dist = np.sum(((x - y)**2)/((x) + (y)))
    assert 0 <= dist <= np.inf, "not in [0, inf]"
    return dist

def get_dist_by_name(x, y, dist_name):
  if dist_name == 'chi2':
    return dist_chi2(x,y)
  elif dist_name == 'intersect':
    return dist_intersect(x,y)
  elif dist_name == 'l2':
    return dist_l2(x,y)
  else:
    assert False, 'unknown distance: %s'%dist_name


# This code shows how a linear regression analysis can be applied to a 1-dimensional data
# Implementation here is based on the theory described in the jupyter notebook.

# Code Flow:
    # 1. Import all relevant libraries.
    # 2. Generate sample data & save it as a csv file (Stored as a csv file just to use pandas).
    # 3. Load the dataset using pandas (X - input/feature, Y - output/target).
    # 4. Plot the generated data understand the trend.
    # 5. Calculate weights (parameters - a & b) using the equation from the theory lecture.
    # 6. Calculate Yhat from the weights above. Yhat = a*X + b.
    # 7. Plot actual vs. predicted to visualize the fit.
    # 8. Calculate R-squared using the equation from the theory lecture to validate the model.
    # 9. How is the fit? Good? Bad? Ok?

# 1.Imports:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# 2.Generate sample data:
N = 100
with open('data_1d.csv', 'w') as f:
    X = np.random.uniform(low=0, high=100, size=N)
    Y = 2*X + 1 + np.random.normal(scale=5, size=N)
    for i in range(N):
        f.write("%s,%s\n" % (X[i], Y[i]))

# 3.Load the data:
df = pd.read_csv('data_1D.csv',header = None)
X = df[0].values
Y = df[1].values

# 4.Plot the data:
plt.figure(1)
plt.scatter(X,Y,c = 'red')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Sample Data Scatter Plot')

# 5.Model: Y = a*X + B
# Apply the equations from the jupyter notebook to calculate a & b:
# Denominator is same for both a & b
denominator = X.dot(X) - X.mean() * X.sum()
a = (X.dot(Y) - Y.mean()*X.sum())/denominator
b = (Y.mean()*X.dot(X) - X.mean() * X.dot(Y))/denominator

# 6.Predict Y:
Yhat = a*X + b

# 7.Plot predicted vs actual:
plt.figure(2)
plt.scatter(X,Y,c = 'red',label = 'Actual Data')
plt.plot(X,Yhat,c = 'black',label = 'Predicted Data')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Actual vs. Model')
plt.legend()

# 8 & 9.R-squared:
d1 = Y - Yhat
d2 = Y - Y.mean()
r2 = 1 - d1.dot(d1)/d2.dot(d2)
print('the r-squared is {} & hence the model is good'.format(r2))

1.Linear Regression/1.Code - Using Theory/0.1D - Regression.py


import numpy as np
import matplotlib.pyplot as plt
from numba import njit
import streamlit as st

@njit
def fourier(x, coeffs):
    a = coeffs[0:-1:2]
    b = coeffs[1:-1:2]
    y = a[0]/2*np.ones_like(x)
    for i, (ai, bi) in enumerate(zip(a, b)):
        y += ai*np.cos(2*np.pi*(i+1)*x) + bi*np.sin(2*np.pi*(i+1)*x)
    return y

@njit
def integral(coeffs):
    a = coeffs[0:-1:2]
    b = coeffs[1:-1:2]
    y = a[0] / 2
    for i, (ai, bi) in enumerate(zip(a, b)):
        k = np.pi*(i+1)
        y += np.sin(k)/k * (ai*np.cos(k)+bi*np.sin(k))
    return y

st.title("Random Fourier Functions")
st.markdown("This application creates random functions of the following type:")
st.latex(
    r'''f_{coeffs}(x) = a_0/2 +
    \sum_{i=1}^N {a_i}\, \cos(2\pi \, i \, x)+b_i\, \sin(2\pi \, i \, x)''')
st.markdown("The coefficients $a_i$ and $b_i$ are of order $O(i^{-smoothness})$. ")
st.markdown("More precisely, $a_i = r_i \cdot i^{-smoothness}$ for a random number $r_i$.")

smoothness = st.sidebar.slider('Smoothness', 0.0, 10.0, 2.0, 0.1)
npoints = st.sidebar.slider('Number of points in plot', 100, 10000, 1000, 100)
fixorder = st.sidebar.checkbox('Fix expansion order?', 10)
fixordervalue = st.sidebar.slider('Expansion order value', 1, 1000, 100, 1)
nsamples = st.sidebar.slider("Number of samples", 1, 200, 3, 1)
sampling = st.sidebar.selectbox(
    'Which random nunber generator?',
    ('rand', 'randn'))
fixrandomseed = st.sidebar.checkbox('Fix random seed?', True)
randomseed = st.sidebar.text_input("Random seed value", 1)
normalizezero = st.sidebar.checkbox('Fix integral to be zero?', True)

if fixorder:
    order = fixordervalue
else:
    order = int((10**-8)**(-1/smoothness))

if fixrandomseed:
    rng = np.random.RandomState(int(randomseed))
else:
    rng = np.random.RandomState(np.random.randint(2**32-1))

n = 2*order+1
x = np.linspace(0, 1, npoints)

plt.style.use("https://raw.githubusercontent.com/camminady/kitstyle/master/kitishnotex.mplstyle")
fig, axs = plt.subplots(2, 1, figsize=(8, 6))

datastorage = {}
for sampleid in range(nsamples):
    if sampling == "rand":
        coeffs = rng.uniform(-1, 1, n)
    else:
        coeffs = rng.normal(0, 1, n)
    for i in range(1, n, 2):
        coeffs[i] *= i**(-smoothness)
        coeffs[i+1] *= i**(-smoothness)
    if normalizezero:
        y = integral(coeffs)
        coeffs[0] -= 2*y  # integral now has value 0
    y = fourier(x, coeffs)
    axs[0].plot(x, y, lw=1, alpha=0.7)
    if order > 1:
        axs[1].loglog(np.arange(order), np.abs(coeffs[0:-1:2]), '.', markersize=3, alpha=0.7)

    infos = {"order": order,
             "x": list(x),
             "y": list(y),
             "coeffs": list(coeffs),
             }
    datastorage[f"sample_{sampleid}"] = infos

axs[0].set_ylim([-2, 2])
axs[0].set_xlabel("$x$")
axs[0].set_ylabel("$f_{coeffs}(x)$", rotation=90, labelpad=0)
axs[1].set_ylim([10**-8, 10**1])
axs[1].set_ylabel("$|coeffs|$", rotation=90, labelpad=0)
axs[1].set_xlabel("$n$")

st.pyplot(fig)

toshow = st.checkbox("Show function values and coefficients in JSON format?", False)
st.markdown("(Maybe only choose few samples and few points in plot and recreate the function values with the script below.)")
st.markdown("""
```
def fourier(x, coeffs):
    a = coeffs[0:-1:2]
    b = coeffs[1:-1:2]
    y = a[0]/2*np.ones_like(x)
    for i, (ai, bi) in enumerate(zip(a, b)):
        y += ai*np.cos(2*np.pi*(i+1)*x) + bi*np.sin(2*np.pi*(i+1)*x)
    return y
```
""")
if toshow:
    st.json(datastorage)


""" Fourier Series --> Approximate periodic functions using an infinite sum of cosines and sine waves
    Gibbs Phenomenon happens while computing Fourier Series of a Discontinuous Function:
     Top-hat function discontinuous at edges of unit step """

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = [8, 8]
plt.rcParams.update({'font.size': 18})

dx = 0.01                                                                       # 0.001
L = 2 * np.pi
x = np.arange(0, L+dx, dx)
n = len(x)
nquart = int(np.floor(n/4))

f = np.zeros_like(x)
f[nquart:3*nquart] = 1

A0 = np.sum(f * np.ones_like(x)) * dx * 2 / L
fFs = A0/2 * np.ones_like(f)

for k in range(1, 101):                                                         # 1001
    Ak = np.sum(f * np.cos(2 * np.pi * k * x / L)) * dx * 2 / L
    Bk = np.sum(f * np.sin(2 * np.pi * k * x / L)) * dx * 2 / L
    fFs = fFs + Ak * np.cos(2 * k * np.pi * x / L ) + Bk * np.sin(2 * k * np.pi * x / L)

plt.plot(x, f, color='k', LineWidth=2)
plt.plot(x, fFs, '-', color='r', LineWidth=1.5)
plt.show()

""" Fourier Series aprroximation with first 100 sines and cosines Gibbs Phenomenon at the corners where discontinuous
    Ringing behavior at the points of discontinuity
    !! Reminder !! = If we added up all infinitely many sines and cosines of all frequencies we would have
                     perfect approximation of discontinuous top-hat function

    !!! we have discontinuous function but sines and cosines are still continuous no sharp corners or jumps
        these corners and sharps requires all fourier frequences to be able to approximate

    Gibbs Phenomenon: happens while approximating discontinuous function using a finite truncated Fourier Series

    If we use the Fourier Series to approximate the derivative of triangular hat function - which is a top hat function
     then the derivative is gonna have Gibbs Phenomenon """


# Question 2, Lab 6
# AB Satyaprakash, 180123062

# imports
from sympy.abc import t,y
import numpy as np
import sympy as sp

# functions
def getEulerApproximation(f,X,Y,h):
    for i in range(1,Y.shape[0]):
        Y[i]=Y[i-1] + f.subs({t:X[i-1], y:Y[i-1]})*h

def getActualValues(g,X):
    for i in range(X.shape[0]):
        Z[i]=g.subs(t,X[i])

# program body
# Case (A):
# t belongs to [0,1] and y(0)=1, with h = 0.5
a, b, h = 0, 1, 0.5
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Z = np.zeros(X.shape[0])
Y[0] = 1

f = sp.exp(t-y) # from question
g = sp.log(sp.exp(t)+sp.exp(1)-1)

getEulerApproximation(f,X,Y,h)
getActualValues(g,X)
print('(a)')
print('Approx Sol: {}'.format(Y))
print('Exact Sol: {}'.format(Z))
for i in range(X.shape[0]):
    print('Error for y({}) = {}'.format(X[i],abs(Y[i]-Z[i])))

# Case (B):
# t belongs to [1,2] and y(1)=2, with h = 0.5
a, b, h = 1, 2, 0.5
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Z = np.zeros(X.shape[0])
Y[0] = 2

f = (1+t)/(1+y) # from question
g = sp.sqrt(t*t + 2*t +6)-1

getEulerApproximation(f,X,Y,h)
getActualValues(g,X)

print('(b)')
print('Approx Sol: {}'.format(Y))
print('Exact Sol: {}'.format(Z))
for i in range(X.shape[0]):
    print('Error for y({}) = {}'.format(X[i],abs(Y[i]-Z[i])))

# Case (C):
# t belongs to [2,3] and y(2)=2, with h = 0.25
a, b, h = 2, 3, 0.25
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Z = np.zeros(X.shape[0])
Y[0] = 2

f = -y+(t*sp.sqrt(y)) # from question
g = (t-2+(sp.sqrt(2)*sp.exp(1)*sp.exp(-t/2)))**2

getEulerApproximation(f,X,Y,h)
getActualValues(g,X)

print('(c)')
print('Approx Sol: {}'.format(Y))
print('Exact Sol: {}'.format(Z))
for i in range(X.shape[0]):
    print('Error for y({}) = {}'.format(X[i],abs(Y[i]-Z[i])))

# Case (D):
# t belongs to [1,2] and y(1)=2, with h = 0.25
a, b, h = 1, 2, 0.25
X = np.arange(a,b+h/2,h)
Y = np.zeros(X.shape[0])
Z = np.zeros(X.shape[0])
Y[0] = 2

f = (sp.sin(2*t)-(2*t*y))/(t*t) # from question
g = (4+sp.cos(2)-sp.cos(2*t))/(2*(t**2))

getEulerApproximation(f,X,Y,h)
getActualValues(g,X)

print('(d)')
print('Approx Sol: {}'.format(Y))
print('Exact Sol: {}'.format(Z))
for i in range(X.shape[0]):
    print('Error for y({}) = {}'.format(X[i],abs(Y[i]-Z[i])))

Semester 6/MA 322 (Scientific Computing Theory)/Labs/Lab 6/Code/q2.py


import numpy as np
import scipy as sp
import sys
import os
from sklearn.decomposition import PCA

def analyze(m):
    #d=np.linalg.det(m)
    r=np.linalg.matrix_rank(m)
    c=np.linalg.cond(m)
    #cd=np.linalg.cholesky(m)
    svd=np.linalg.svd(m)
    print('--------------------')
    print(m)
    print('--------------------')
    print('rank=',r,'condition number=',c)
    #print(cd)
    print('--------------------')
    print('singular value decomposition')
    u = svd[0]
    s = np.zeros(m.shape)#np.diag(svd[1])
    s[0,0]=svd[1][0]
    s[1,1]=svd[1][1]
    v = svd[2]
    print('u')
    print(u)
    print('sigma')
    print(s)
    print('v transpose')
    print(v)
    print('--------------------')
    print('reconstruct m from svd m = U*S*V.T')
    m1 = np.dot(np.dot(u, s), v)
    print(m1)
    print('--------------------')
    print('show u is orthognoal matrix')
    u0 = u[:,0]
    u1 = u[:,1]
    print("u0=",u0)
    print("u1=",u1)
    print('dot product of u0 dot u1 = 0')
    print(np.dot(u0,u1))
    print('u*u.T is the idenity')
    print(np.dot(u,u.T))
    print('--------------------')
    print('show v is orthognoal matrix')
    v0T = v[0,:]
    v1T = v[1,:]
    print("v0.T=",v0T)
    print("v1.T=",v1T)
    print(np.dot(v0T,v1T))
    print('u*u.T is the idenity')
    print(np.dot(u,u.T))
    print('v*v.T is the idenity')
    print(np.dot(v,v.T))
    print('--------------------')
    print('recreate A matrix from indivudual components')
    print('a1 = s1 * u1 * v1.T')
    ur,uc = u.shape
    vr,vc = v.shape
    a1=s[0,0]*np.dot(np.reshape(u0,(ur,1)),np.reshape(v0T,(1,vc)))
    print(a1)
    print('a2 = s2 * u2 * v2.T')
    a2=s[1,1]*np.dot(np.reshape(u1,(ur,1)),np.reshape(v1T,(1,vc)))
    print(a2)
    print('A = a1 + a2')
    m2 = a1 + a2
    print(m2)
    print('--------------------')
    print('norms')
    A=m
    print('------------------------')
    print('norms(A)')
    print('A l1',np.linalg.norm(A, 1))
    print('A l2',np.linalg.norm(A, 2))
    print('A fro',np.linalg.norm(A, 'fro'))
    print('A inf',np.linalg.norm(A, np.inf))

    print('------------------------')
    print('norm(a1)')
    print('a1 rank', np.linalg.matrix_rank(a1))
    print('l1',np.linalg.norm(a1, 1))
    print('l2',np.linalg.norm(a1, 2))
    print('fro',np.linalg.norm(a1, 'fro'))
    print('inf',np.linalg.norm(a1, np.inf))

    dx = m-a1
    print('------------------------')
    print('norm(A-a1)')
    print('||A-a1|| l1',np.linalg.norm(dx, 1))
    print('||A-a1|| l2',np.linalg.norm(dx, 2))
    print('||A-a1|| fro',np.linalg.norm(dx, 'fro'))
    print('||A-a1|| inf',np.linalg.norm(dx, np.inf))

    # pca
    print('------------------------')
    print('pca')
    pca = PCA(n_components=2)
    pca.fit(m)
    print(pca.explained_variance_ratio_)
    print(pca.singular_values_)
    z = pca.fit_transform(m)
    print(z)
    iv = pca.inverse_transform(z)
    print(iv)
    print('------------------------')
    print('recreate pca from svd')
    print('s0=',s[0,0])
    print('s1=',s[1,1])
    # step 1 center the data around means
    mu = np.mean(m, axis=0)
    print('mu-',mu)
    A0 = A - mu
    print(A0)
    # step 2 compute covar matrix
    cov = np.dot(A0.T, A0) / (len(mu)-1)
    print('covar matrix')
    print(cov)
    sv_cov = np.linalg.svd(cov)
    print("svd u", sv_cov[0])
    print("svd s", sv_cov[1])
    print("svd v", sv_cov[2])
    eig=np.linalg.eig(cov)
    tot = sum(sv_cov[1])
    print(sv_cov[1][0]/tot)
    print(np.sqrt(sv_cov[1]))
    print(np.sqrt(sv_cov[1])/sum(np.sqrt(sv_cov[1])))
    print(np.dot(A0, sv_cov[2]))

def pca_test(data):
    Mean = np.mean(data, axis = 0)
    print(Mean)
    V = data - Mean
    print(V)
    C = np.dot(V.T, V)/(len(data) - 1)
    Csv = np.linalg.svd(C)
    Diag = Csv[1]
    U = Csv[0]
    print(U)
    print(Diag/np.sum(Diag))
    Proj = np.dot(V, U[0]).reshape((len(data),1))
    print(Proj) #
    #Back = np.dot(U[0],Proj)
    #print(Back)

def main():
    print("start")
    m = np.array([[3, 1], [1, 2], [4, 1], [5,2],[11,3],[18,4]])
    #analyze(m)
    pca_test(m)

if __name__ == '__main__' :
    main()


import math
import numpy as np
import numpy.linalg as la

def point_from_angle(x, y, angle, length):
    """return the endpoint of a line starting in x,y using the given angle and length"""
    x = x + length * math.cos(angle)
    y = y + length * math.sin(angle)
    return x, y

def distance(point_1, point_2):
    """Distance between the 2 points"""
    return math.sqrt((point_1[0] - point_2[0]) ** 2 + (point_1[1] - point_2[1]) ** 2)

def get_line_angle(point_1, point_2):
    """Returns the angle of a line that goes from point_1 to point_2"""
    return ((math.atan2((point_1[1] - point_2[1]), (point_1[0] - point_2[0])) * 180.0 / math.pi) + 360) % 360

def py_ang(v1, v2):
    """ Returns the angle in radians between vectors 'v1' and 'v2'    """
    cosang = np.dot(v1, v2)
    sinang = la.norm(np.cross(v1, v2))
    return np.arctan2(sinang, cosang)

def check_periodicity(angle):
    # Reset degrees after 2 PI radians
    if angle >= 2 * math.pi:
        angle = angle - 2 * math.pi
    elif angle <= -2 * math.pi:
        angle = angle + 2 * math.pi
    return angle

def get_point_from_angle(point, angle, lenght):
    """
    Return a point given a starting point, the angle that it has to form with respect to X-axis and how far from that
    point it has to be.
    :param point: Initial point
    :param angle: angle with respect to X-axis
    :param lenght: How far from the initial point the final point must be
    :return: array with [x, y]
    """
    return [point[0] + lenght * math.cos(angle), point[1] + lenght * math.sin(angle)]


import numpy as np
from sympy import *

"""

The transfer function is defined as

Y(s)/U(s) = G(s)

The state space equations is defined as
x' = Ax + Bu
y  = Cx + du

Take the laplace transform of the state space equation and get
sX(s) - x(0) = AX(s) + BU(s)
Y(s)         = CX(s) + DU(s)

The Laplace Transform assumes the initial conditions to be 0 so x(0) = 0
sX(s) - AX(s) = BU(s)

Factor X(s) out
(sI - A)X(s) = B*U(s)

Solve for X(s)
X(s) = (sI - A)^-1BU(s)

Substitute X(s)
Y(s) = CX(s) + DU(s)
Y(s) = (C(sI - A)^-1B + D)U(s)

We can see that the equation
G(s) = C(sI - A)^-1B + D

"""

def st2tf(A, B, C, D):
    s = Symbol('s')
    I = eye(A.shape[0])
    G = C*(s*I - A)**-1*B + D
    G = simplify(G)

    print('Converting State Space Matrix to Transfer Function')
    print('\nA\n\n')
    pprint(A)
    print('\nB\n\n')
    pprint(B)
    print('\nC\n\n')
    pprint(C)
    print('\nD\n\n')
    pprint(D)
    print('\nG\n\n')
    pprint(G)
    print('\n\n')

b = Symbol('b')
k = Symbol('k')
m = Symbol('m')
Kp = Symbol('kp')
taup = Symbol('taup')
tau = Symbol('tau')
zeta = Symbol('zeta')

A = Matrix([[0, 1], [-k/m, -b/m]])
B = Matrix([[0], [1/m]])
C = Matrix([[1, 0]])
D = Matrix([0])
st2tf(A, B, C, D)

A = Matrix([[-14, -56, -160], [1, 0, 0], [0, 1, 0]])
B = Matrix([[1], [0], [0]])
C = Matrix([[0, 1, 0]])
D = Matrix([0])
st2tf(A, B, C, D)

A = Matrix([-1/taup])
B = Matrix([Kp/taup])
C = Matrix([1])
D = Matrix([0])
st2tf(A, B, C, D)

A = Matrix([[0,1],[-1/tau**2,-2*zeta/tau]])
B = Matrix([[0],[Kp/tau**2]])
C = Matrix([[1, 0]])
D = Matrix([0])
st2tf(A, B, C, D)

A = Matrix([[0, 1], [0, -10/100]])
B = Matrix([[0], [1/100]])
C = Matrix([[1, 0]])
D = Matrix([0])
K = Matrix([[30, 70]])
st2tf(A, B, C, D)
st2tf(A - np.dot(B, K), B, C, D)


import numpy as np
from numpy.linalg import inv

'''
Standard matrix formula for solving a system of equations. A is an nxn matrix.

Ax = b
Ai A x = Ai b
I x = Ai b
x = Ai b
'''

# Test matrix. An underdetermined example is commented out to test exception handling.
A = np.array([[1.0, 2.0, 3.0],[10.0, 7.0, 3.0],[3.0, 1.0, 11.0]])
# A = np.array([[1.0, 2.0, 3.0],[1.0, 2.0, 3.0],[3.0, 1.0, 11.0]])

# Get the inverse of A.
try:
    Ai = inv(A)
except np.linalg.LinAlgError:
    print("Matrix is singular")
    quit()

print("\nStandard method for solving Ax = b using an inverse")
print("A")
print(A)
print("Matrix A rank: ", np.linalg.matrix_rank(A))
print("Ai")
print(Ai)
print("Ai A = I")
print(np.dot(Ai, A))

# Calculate b from some test x values.
xtest = np.array([10.0, 11.0, 12.0])
b = np.dot(A, xtest)
print("b")
print(b)

# Now solve for x and show that they match.
x = np.dot(Ai, b)
print("xtest and x calculated")
print (xtest, x)

'''
SVD way - this is a least squares fit for use when there is error in the system.
https://en.wikipedia.org/wiki/Singular_value_decomposition
https://austingwalters.com/using-svd-to-obtain-regression-lines/
See PDFs in this repo, too.

For:

Ax = b

A can be decomposed by SVD as follows:

A = U S Vt

U and V are orthogonal matrices for n x n systems, so transpose == inverse. Transpose == inverse
for these matrices on m x n systems (m > n), too. S is diagonal matrix containing the singular values.
If A is overdetermined (more rows than columns), S must have zeroed rows added in order to calc x.
The svd() function just returns the vector of values in S as the s vector.

U S Vt x = b

x = V Si Ut b

Si = is a pseudo-inverse in which the non-zero elements on the diagonal are inverted.
'''

# Reusing A, b, and xtest from above.

# SVD
# Note: catch LinAlgError to see if the calculation doesn't converge. I test this above.
U, s, Vt = np.linalg.svd(A)

# Create a diagonal matrix for S and it's pseudo-inverse
S = np.diag(s)
si = 1/s
Si = np.diag(si)

print("\nSVD method of solving Ax = b")
print("A = U S Vt")
print("U ", U.shape)
print(U)
print("S", S.shape)
print(S)
print("s ", s.shape)
print(s)
print("Si", Si.shape)
print(Si)
print("si ", si.shape)
print(si)
print("Vt ", Vt.shape)
print(Vt)

print("\nNow let's check the decomposition")
print("A")
print(A)

# element-wise multiply works for s because s is a 1D vector (??)
# Acheck = np.dot(U * s, Vt)
# print("A = U s Vt")
# print(Acheck)

# Check the decomposition.
Acheck2 = np.dot(U, np.dot(S, Vt))
print("A = U S Vt")
print(Acheck2)

print("\nNow let's solve for x")

# Solve for x. This just uses the si vector instad of the Si matrix.
xnew = np.dot(Vt.T * si, np.dot(U.T, b))
print("x = V si Ut b")
print(xnew)

# Solve for x again using the Si matrix. I prefer this notation better.
# Note: there are lots of ways to write this matrix multiplication. multi_dot is the clearest to me.
# Also note that technically this should be Si.T as below. For a square matrix, it doesn't matter.
# xnew = np.dot(np.dot(Vt.T, Si), np.dot(U.T, b))
# xnew = Vt.T.dot(Si).dot(U.T).dot(b)
xnew = np.linalg.multi_dot([Vt.T, Si, U.T, b])
print("x = V Si Ut b")
print(xnew)
print("\n")

'''
SVD again, but with an overdetermined system of equations, i.e. more rows than columns.

Also adding some error into the system to show an "almost correct" answer.
'''

print("\n SVD again with a non-square matrix B")

# Test matrix
B = np.array([[1.0, 2.0, 3.0],[10.0, 7.0, 3.0],[3.0, 1.0, 11.0],[15.0, 9.0, 5.0]])
print("B")
print(B)
print("Matrix B rank: ", np.linalg.matrix_rank(B))

# Put in some x values to calculate b
xtest2 = np.array([10.0, 11.0, 12.0])
b = np.dot(B, xtest2)
print("xtest")
print(xtest2)
print("b")
print(b)

# Add some error to B to show least-squares convergence.
print("\nLet's add some error to B")
error = np.random.rand(4,3)
error /= 100.0
print(error)
B += error
print("\nB with error")
print(B)

# SVD
U, s, Vt = np.linalg.svd(B)

# Create a diagonal matrix for S and it's pseudo-inverse
S = np.diag(s)
si = 1/s
Si = np.diag(si)

# Addd a row to S since we're over-determined by one row. (TODO: fix to work with any m x n.)
S = np.append(S, [[0, 0, 0]], axis=0)
Si = np.append(Si, [[0, 0, 0]], axis=0)

print("\nSVD method of solving Bx = b")
print("B = U S Vt")
print("U ", U.shape)
print(U)
print("S", S.shape)
print(S)
print("s ", s.shape)
print(s)
print("Si", Si.shape)
print(Si)
print("Si.T", Si.T.shape)
print(Si.T)
print("si ", si.shape)
print(si)
print("Vt ", Vt.shape)
print(Vt)

print("\nNow let's check the decomposition")
print("B")
print(B)

# Check the decomposition
Bcheck2 = np.linalg.multi_dot([U, S, Vt])
print("B = U S Vt")
print(Bcheck2)

# Solve for x
# Note that Si has to be transposed to make it 3x4. The diagonal values don't change on the transpose.
# The zero row just becomes a zero column.
xnew2 = np.linalg.multi_dot([Vt.T, Si.T, U.T, b])
print("x = V Si Ut b")
print(xnew2)
print("\n")


#!/usr/bin/env python3

import numpy as np
import operator as op
from functools import reduce

#Maximum path sum II
#Problem 67
#By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.

#3
#7 4
#2 4 6
#8 5 9 3

#That is, 3 + 7 + 4 + 9 = 23.

#Find the maximum total from top to bottom in triangle.txt (right click and 'Save Link/Target As...'), a 15K text file containing a triangle with one-hundred rows.

#NOTE: This is a much more difficult version of Problem 18. It is not possible to try every route to solve this problem, as there are 299 altogether! If you could check one trillion (1012) routes every second it would take over twenty billion years to check them all. There is an efficient algorithm to solve it. ;o)

filename = 'p067_triangle.txt'
rawfile = open(filename).read()

# split by line
lines = rawfile.splitlines()

# create matrix to hold data
grid_n = np.zeros(shape=(len(lines),len(lines)))

# convert to integers
for i in range(len(lines)):
  num_str = lines[i]
  nums = np.asarray(num_str.split(' '))
  grid_n[i,:len(nums)] = nums

print(grid_n[0:3,:])
#print(grid_n[0:3,:])

def walk_sum(grid,row,col):
  if row==0: return grid[0,0]
  if (row>0):
    if(col>0):
      temp1=grid[row-1,col]
      temp2=grid[row-1,col-1]
      if temp1>temp2:
        return(grid[row,col]+walk_sum(grid,row-1,col))
      return(grid[row,col]+walk_sum(grid,row-1,col-1))
    return(grid[row,col]+walk_sum(grid,row-1,col))

count = 0
#print(grid_n.shape)
#for i in range(grid_n.shape[1]):
#  temp = walk_sum(grid_n,grid_n.shape[0]-1,i)
#  if temp > count: count = temp

#print(count)

# idea 2 - optimal substructure!
# start from top, and calculate max sum for each element
# at the bottom, take the max

def max_sum(grid):
  grid_sum = np.zeros(shape=(grid.shape[0],grid.shape[1]))

  for row in range(0,grid.shape[0]):
    for col in range(0,row+1):
      #print(row,col, grid[row,col])
      grid_sum[row,col] = grid[row,col]
      if row==0:
        if col==0: grid_sum[0,0] = grid[0,0]
        else: grid_sum[row,col] += grid_sum[row-1,col]
        #print("row 0,", row,grid[row-1,col],grid_sum[row,col])
      elif col==row:
        grid_sum[row,col] += grid_sum[row-1,col-1]
        #print("col=row,", row,grid[row-1,col-1],grid_sum[row,col])
      else:
        grid_sum[row,col] += max([grid_sum[row-1,col-1],grid_sum[row-1,col]])
  return(grid_sum)

temp = max_sum(grid_n)
print(temp[-1,:],max(temp[-1,:]))

# 7273 YAY


#!/usr/bin/env python
"""
    Copyright 2019 by Michael Wild (alohawild)

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

============================================================================================================
This is a pi calculation process using Monte Carlo or simple random process.

Imagine a circle in a box. The edges just touch the box. Lets make it a circle of radius 1.

Thus any point on or within the cirlce are 1 unit or less from the center.

It should be possible to randomly select points in the box and determine if the point is in the circle or not.
The ratio of points in the box over the number of randomly selected should be 1/4*Pi

We are using the numpy library to get some more exact results. Also numpy is not slow.ArithmeticError

We also added the mpmath library to use 50 digits of floating point accuracy. The calls to mpf force the results into
50 digits of floating point accuracy.

"""
__author__ = 'michaelwild'
__copyright__ = "Copyright (C) 2018 Michael Wild"
__license__ = "Apache License, Version 2.0"
__version__ = "0.0.1"
__credits__ = ["Michael Wild"]
__maintainer__ = "Michael Wild"
__email__ = "alohawild@mac.com"
__status__ = "Initial"

from mpmath import mp,mpf  # This is the floating point accuracy set to 50 for this example
mp.dps = 50

import sys
import numpy as np
import math

from time import process_time

def howFar(x,y):

# The distance is always from the center at 0,0 and thus we can dispense with the subtractions of points.

    distance = mpf((x * x) + (y * y))  # Orginally I used an exponent as this more appealing to me, but it was less accurate!
    return mpf(np.sqrt(distance))  # From what I have read this is more accurate and faster version

def runtime(start):

# I use this a lot so I have a routine.

    return process_time() - start

# =============================================================

program = "Pi Calc"

piLoop = 10000
inCircle = 0

begin_time = process_time()

# =============================================================
# Main program begins here

print(program)
print("Version ", __version__, " ", __copyright__, " ", __license__)
print("Running on ", sys.version)

for i in range(1, piLoop):
    x = mpf(np.random.uniform()* 2) -1
    y = mpf(np.random.uniform() * 2) -1

    if (howFar(x,y)<1.0) :
        inCircle = inCircle + 1
piGuess = mpf(4.0* mpf(inCircle / piLoop))

piError = math.pi - piGuess

print("Loops:", piLoop)
print("Calculated value: ",piGuess, "Error: ", piError)

finish = runtime(begin_time)
print("Run time:", finish)


import numpy as np
import scipy as sc

def Normalize(adjmat, tele_const = 0.2):
    """
    This method will try and normalize the adjacency matrix, so that it will be suitable for the PageRank algorithm. Using the teleporting constant it will remove the effect of deadends in the PageRank algorithm.

    Parameters
    ----------
    adjmat: numpy array
            a square adjacency matrix
    tele_const: float
                teleporting constant for the PageRank algorithm (P' = (1-alpha)*P + alpha*v)

    Returns
    -------
    mat: numpy array
         an square matrix of size equal to the adjmat matrix and normalized
    """

    mat = np.zeros(adjmat.shape)
    cols = adjmat.shape[0]
    deadend_const = 1.0 / cols
    for i in range(cols):
        s = np.sum(adjmat[i,:])
        if s == 0:
            mat[i,:] = deadend_const
        else:
            mat[i,:] = adjmat[i,:] / s
        mat[i,:] = mat[i,:] * (1 - tele_const) + deadend_const * tele_const
    return mat

def PageRankScores(norm_mat):
    """
    Calculates the PageRank score vector for norm_mat normalized adjacency matrix

    Parameters
    ----------
    norm_mat: numpy array
              the normalized adjacency matrix, such that each row sums up to one.

    Returns
    -------
    sv: numpy array
        the PageRank score vector
    """
    sw, sv = sc.sparse.linalg.eigs(norm_mat.T, k=1, which='LR')
    sv = sv.T[0]
    sv = np.abs(sv)
    sv /= np.sum(sv)
    return sv


import numpy as np # for math

# Collection of activation functions
# References:
# https://en.wikipedia.org/wiki/Activation_function
# https://miro.medium.com/proxy/1*RD0lIYqB5L2LrI2VTIZqGw.png
# https://deepai.org/machine-learning-glossary-and-terms/sigmoid-function
# σ(x) = 1 / 1+e(−xb)
# x = cosh(a) = e(a)+e(−a)/2   &    y = sinh(a) = e(a)−e(−a)/2
# https://brilliant.org/wiki/hyperbolic-trigonometric-functions/

class Sigmoid():
    def __call__(self, x, bias=0):
        return 1 / (1 + np.exp(-x + bias))

    def gradient(self, x, bias=0, out=None):
        if out is None:
            out = self.__call__(x, bias)
        return out * (1 - out)

class TanH():
    def __call__(self, x):
        return 2 / (1 + np.exp(-2 * x)) - 1

    def gradient(self, x):
        return 1 - (self.__call__(x) ** 2)

class ReLU():
    def __call__(self, x):
        return np.where(x >= 0, x, 0)

    def gradient(self, x):
        return np.where(x >= 0, 1, 0)

class SoftPlus():
    def __call__(self, x):
        return np.log(1 + np.exp(x))

    def gradient(self, x):
        return 1 / (1 + np.exp(-x))

class LeakyReLU():
    def __init__(self, alpha=0.2):
        self.alpha = alpha

    def __call__(self, x):
        return np.where(x >= 0, x, self.alpha * x)

    def gradient(self, x):
        return np.where(x >= 0, 1, self.alpha)

class ELU():
    def __init__(self, alpha=0.2):
        self.alpha = alpha

    def __call__(self, x):
        return np.where(x >= 0.0, x, self.alpha * (np.exp(x) - 1))

    def gradient(self, x):
        return np.where(x >= 0.0, 1, self.__call__(x) + self.alpha)

class SELU():
    def __init__(self):
        self.alpha = 1.6732632423543772848170429916717
        self.scale = 1.0507009873554804934193349852946

    def __call__(self, x):
        return self.scale * np.where(x >= 0.0, x, self.alpha*(np.exp(x) - 1))

    def gradient(self, x):
        return self.scale * np.where(x >= 0.0, 1, self.alpha * np.exp(x))

class Softmax:
    def __call__(self, x):
        e_x = np.exp(x - np.max(x))
        return e_x / np.sum(e_x)

    def gradient(self, x):
        p = self.__call__(x)
        return p * (1 - p)

class Softmax_V2():
    def __call__(self, x):
        exponential = np.exp(x - np.max(x, axis=-1, keepdims=True))
        return exponential / np.sum(exponential , axis=-1, keepdims=True)

    def gradient(self, x):
        p = self.__call__(x)
        return p * (1 - p)

act_functions = {
    "sigmoid"   : Sigmoid,
    "tanh"      : TanH,
    "relu"      : ReLU,
    "softplus"  : SoftPlus,
    "leakyrelu" : LeakyReLU,
    "elu"       : ELU,
    "selu"      : SELU,
    "softmax"   : Softmax,
    "softmax_v2"   : Softmax_V2
}


import numpy as np

from utils import get_input, ints

def main():
    """
    Goal: develop an analytical solution for the problem.
    First, describe the recurrence relation. Note that:

    a0,n = a1,n-1
    a1,n = a2,n-1
    ...
    a6,n = a7,n-1 + a0,n-1
    a7,n = a8,n-1
    a8,n = a0,n-1

    Where ai,j := the number of fish at time i, with lifetime j
    Then, define yn := a0,n
    The above system can then be reduced down to the following single recurrence relation:

    yn = yn-7 + yn-9

    The solution to this is the equation:

    yn = c1r1^n + c2r2^n + ... + c9r9^n
    where c1, ... c9 are some arbitrary coefficients
          r1, ... rn are the roots of the polynomial x^9 - x^2 - 1 = 0

    These roots are then computed numerically, and then we need to solve for the coefficients.
    In order to do that, we need the first y1, ... y9 values, which can be inferred from the input data
    Then we solve the matrix equation:

    [ r1^1 ... r9^1 ] [ c1 ]   [ y1 ]
    [ ...      ...  ] [ .. ] = [ .. ]
    [ r1^n ... r9^n ] [ c9 ]   [ y9 ]

    This gives us the values of c1, ... c9.
    Now, with an explicit formula for yn, we can define Kn = the sum of a0,n + ... + a8,n
    Using the definition of yn, this becomes:

    Kn = yn+5 + ... + yn + 2*yn-1 + yn-2 + yn-3

    The solution is then rounded to the nearest complex integer, and the real part is now our answer.
    """

    values = ints(get_input())

    ys = np.array([values.count(n % 7) for n in range(9)])
    rs = np.roots((1, 0, 0, 0, 0, 0, 0, -1, 0, -1))  # x^9 - x^2 - 1 = 0
    A = np.array([[pow(r, n) for r in rs] for n in range(9)])
    cs = np.linalg.solve(A, ys)  # Ax = b

    # Note that both rs is constant (across all inputs), and cs is constant (across any specific input).
    # So solving any value of n can be done just with those precomputed values.

    print('Part 1:', solve(80, rs, cs))
    print('Part 2:', solve(256, rs, cs))

def solve(n: int, rs, cs) -> int:
    return round(sum((2 if j == -1 else 1) * sum(c * pow(r, n + j) for r, c in zip(rs, cs)) for j in range(-3, 6)).real)

if __name__ == '__main__':
    main()


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

# Number of observations
t = np.arange(0, 1, 0.001)
print("Number of observations: ", t.shape[0])

# Sine
mysine = np.sin(2 * np.pi * t)

# Sine * 4
mysine4 = np.sin(2 * np.pi * t * 4)

# Cosine * 4
mycos4 = np.cos(2 * np.pi * t * 4)

# Cosine * 18
mycos18 = np.cos(2 * np.pi * t * 18)

# Combining all the functions
myfun = mysine + mysine4 + mycos4 + mycos18
plt.figure(figsize=(10, 4))
plt.plot(t, myfun)
plt.savefig('myfun.png')

# Match sines and cosines with 4Hz (part of the signal)
Omega = 4
match_sin_4 = myfun * np.sin(Omega * (2 * np.pi) * t)
match_cos_4 = myfun * np.cos(Omega * (2 * np.pi) * t)
print('Sum of matching sines at 4Hz = %.2f' % np.sum(match_sin_4))
print('Sum of matching cosines at 4Hz = %.2f' % np.sum(match_cos_4))

# Match sines and cosines with 3Hz (not part of the signal)
Omega = 3
match_sin_3 = myfun * np.sin(Omega * (2 * np.pi) * t)
match_cos_3 = myfun * np.cos(Omega * (2 * np.pi) * t)
print('Sum of matching sines at 3Hz = %.4f' % np.sum(match_sin_3))
print('Sum of matching cosines at 3Hz = %.4f' % np.sum(match_cos_3))

# This procedure is able to detect that our function has 4Hz patterns while 3Hz
# is absent

# Plot the point-wise multiplication to observe this effect

plt.figure(figsize=(10, 4))
plt.plot(t, myfun, '-k')
plt.plot(t, match_sin_3, '--r')
plt.savefig('match_sin_3.png')

plt.figure(figsize=(10, 4))
plt.plot(t, myfun, '-k')
plt.plot(t, match_sin_4, '--r')
plt.savefig('match_sin_4.png')

def match_freqs(f, t, maxfreq):
  print("Coefficients of sine and cosine matching")
  for Omega in np.arange(0, maxfreq):
    match_sin = np.sum(f * np.sin(Omega * 2 * np.pi * t))
    match_cos = np.sum(f * np.cos(Omega * 2 * np.pi * t))
    print("%d\t%.1f\t%.1f" % (Omega, match_sin, match_cos))

match_freqs(myfun, t, 22)


import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn import decomposition
from scipy import linalg as la

def kmeans(data,n_clusters,init='random',max_iter=300,normalize=False):
    n,m = data.shape
    norms = np.zeros((n,n_clusters))
    if init=='random':
        means = data.mean(axis=0)+np.random.randn(n_clusters,m)*data.std()
    else:
        means=init
    if normalize:
        means /= np.linalg.norm(means,axis=1)[:,np.newaxis]
    for i in xrange(n_clusters):
        norms[:,i] = ((data-means[i,:])**2).sum(axis=1)
    labels = np.argmin(norms,axis=1)
    it =0
    for j in xrange(max_iter):
        it += 1
        means = np.array([data[labels==i].mean(axis=0) for i in xrange(n_clusters)])
        if normalize:
            means /= np.linalg.norm(means,axis=1)[:,np.newaxis]
        for i in xrange(n_clusters):
            norms[:,i] = ((data-means[i,:])**2).sum(axis=1)
        new_labels = np.argmin(norms,axis=1)
        if not np.allclose(labels,new_labels):
            labels=new_labels
        else:
            break

    inertia = 0
    for i in xrange(n_clusters):
        inertia += ((data[labels==i]-means[i,:])**2).sum()
    return means,labels,inertia

def clusterIris():
    iris = load_iris()

    X = iris.data
    # pre-process
    Y = X - X.mean(axis=0)
    # get SVD
    U,S,VT = la.svd(Y,full_matrices=False)
    # project onto the first two principal components
    Yhat = U[:,:2].dot(np.diag(S[:2]))

    # cluster 10 times, retaining the best
    means = None
    labs = None
    inertia=np.inf

    for j in xrange(10):
        m,l,i = kmeans(Yhat,3)
        if i < inertia:
            inertia=i
            means=m
            labs=l

    setosa = iris.target==0
    versicolor = iris.target==1
    virginica = iris.target==2
    p1, p2 = Yhat[:,0], Yhat[:,1]
    mrkr = []
    for flower, m,n in zip([setosa,versicolor,virginica],['*','.','^'],['Setosa','Versicolor','Virginica']):
        mr = plt.scatter([],[],color='k',marker=m,label=n)
        mrkr.append(mr)
        for i,c in enumerate(['cyan','red','green']):
            msk = np.where(labs[flower]==i)[0]
            if msk.any():
                plt.scatter(p1[flower][msk],p2[flower][msk], marker=m, color=c)
    mrkr.append(plt.scatter(means[:,0],means[:,1],marker='+',s=100,linewidths=2,label="Means"))

    plt.legend(handles=mrkr, loc=2)
    plt.ylim([-4,5])
    plt.xlim([-4,4])
    plt.xlabel("First Principal Component")
    plt.ylabel("Second Principal Component")
    plt.show()

def loadEarthquakes(path):
    latitudes = []
    longitudes = []
    for i in xrange(1,7):
        with open(path.format(i),'r') as f:
            for line in f:
                la = float(line[20:25])/1000
                s = line[25]=='S'
                lo = float(line[26:32])/1000
                w = line[32]=='W'
                latitudes.append(la*(-1)**s)
                longitudes.append(lo*(-1)**w)
    latitudes=np.array(latitudes)
    longitudes = np.array(longitudes)
    return latitudes,longitudes

def sphericalToEuclidean(r, theta, phi):
    """
    theta and phi must be in radians!!
    """
    eucl = np.zeros((len(theta),3))
    eucl[:,0] = np.sin(phi)*np.cos(theta)
    eucl[:,1] = np.sin(phi)*np.sin(theta)
    eucl[:,2] = np.cos(phi)
    return r*eucl
def euclideanToSpherical(pts):
    """
    returns answers in radians.
    """
    x = pts[:,0]
    y = pts[:,1]
    z = pts[:,2]
    phi=np.arccos(z)
    theta=np.arctan2(y,x)
    return phi, theta

def clusterEarthquakes(path):
    # load in the data
    latitudes, longitudes = loadEarthquakes(path)
    # convert to euclidean coordinates
    eucl = sphericalToEuclidean(1.,longitudes*np.pi/180,(90-latitudes)*np.pi/180)
    # cluster 10 times, retaining best
    best_i = np.inf
    best_m = None
    best_l = None
    for j in xrange(10):
        m,l,i = kmeans(eucl,15,normalize=True)
        if i < best_i:
            best_i=i
            best_m = m
            best_l = l
    # get means back into latitude and longitude coordinates
    m_phi, m_longitude = euclideanToSpherical(best_m)
    m_latitude = np.pi/2-m_phi
    return latitudes, longitudes, m_latitude, m_longitude, best_l

def plotClusters(lats, longs, m_lats, m_longs, labs):
    for i in xrange(15):
        msk=labs==i
        c=np.random.rand(3)
        plt.scatter(longs[msk],lats[msk],marker='.',
                    color=c)
    plt.scatter(m_longs*180/np.pi,m_lats*180/np.pi,marker='+',s=100,linewidths=2,color='k')
    plt.show()


import numpy as np
from numpy import linalg as LA
from scipy import optimize

a = np.array([[1, 0], [0, 2]])
b = np.array([1, -1])

def f(x):
    return (lambda x : np.matmul(np.matmul(np.dot(0.5, x.transpose()), a), x) - np.matmul(x.transpose(), b) + 7)(x)

def g(x):
    return (lambda x : np.matmul(a, x) - b)(x)

# only pass np values - no internal checking
def quad_alpha(x, d):
    return np.matmul(np.dot(-1, g(x)), d) / np.matmul(np.matmul(d.transpose(), a), d)

# rank one correction formula
def rank_one(x0, max, tol):
    # step 1
    k = 0
    x0 = np.array(x0)
    H = np.array([[1, 0], [0, 1]])

    while k < max:
        # step 2
        print(LA.norm(g(x0)))
        if LA.norm(g(x0)) < tol:
            return x0
        else:
            d = np.matmul(np.dot(-1, H), g(x0))

        # step 3
        alpha = quad_alpha(x0, d)
        delta_x = np.dot(alpha, d)
        x1 = x0 + delta_x

        # step 4
        delta_x = np.dot(alpha, d)
        delta_g = g(x1) - g(x0)

        num = np.matmul((delta_x - np.matmul(H, delta_g)), (delta_x - np.matmul(H, delta_g)).transpose())
        denom = np.matmul(delta_g.transpose(), (delta_x - np.matmul(H, delta_g)))

        H1 = H + (num / denom)
        # loop back
        H = H1
        x0 = x1
        k = k + 1

# # SciPy offers BFGS as part of its library
# print(optimize.fmin_bfgs(f, np.array([0, 0])), g)

# # NOTE: x must be entered as a 2-D array
# # there is a ZeroDivisionError increasing the error range
# print(rank_one([0, 0], 10, 0.5))


import numpy as np

# Funciton
def f(x):
    y = 1 / np.sqrt(2 * np.pi) * np.exp(-x ** 2 / 2)
    return y

# Adaptable Simpson Method
def adSimpson(a, b, tol):

    # Simpson Rule for 2 intervals
    def S(n, m):
        value = (m-n)/6 * (f(n) + f(m) + 4*f(0.5*(n + m)))
        return value

    # Compute Integral with Simpson Rule in left half and right hald interval
    Il, Ir = S(a, 0.5*(a+b)), S(0.5*(a+b), b)
    # Compute Integral wth Simpson Rule
    I = S(a, b)

    # Integral Error
    dI = np.abs((I - (Il+Ir)))/15

    # Check If We Get the Desired Tolerance
    if dI < tol:
        I = Il + Ir
    # If we Don't get it Reiterate
    else:
        Il = adSimpson(a, 0.5*(a+b), 0.5*tol)
        Ir = adSimpson(0.5*(a+b), b, 0.5*tol)

    I = Il + Ir

    return I

if __name__ == "__main__":
    print('\nComputed with Adaptable Simpson Method:')
    print(f'\tI = {adSimpson(-5, 5, 0.001):.8f}')


"""
Course: ME/MF F342 Computer Aided Design
Author: Bhavya Bhatia

Topic: Bsplines

Description:
-------------
The Bspline functions can take the degree and control vectors to return a set of coordinates which plot the actual bspline curve.
"""

import numpy as np
import scipy.interpolate as si

cv = np.array([[ 50.,  25.],
   [ 59.,  12.],
   [ 50.,  10.],
   [ 57.,   2.],
   [ 40.,   4.],
   [ 40.,   14.]])

def bspline(cv, n=100, degree=3, periodic=False):
    """
    Parameters :
    ------------
    cv : Array ov control vertices
    n  : Number of samples to return
    degree: Curve degree
    periodic: True - Curve is closed
              False - Curve is open
    Returns :
    ---------
    Returns array of x,y coordinates of the spline, which can be used to plot bspline graphs usin Matplotlib

    """

    # If periodic, extend the point array by count+degree+1
    cv = np.asarray(cv)
    count = len(cv)

    if periodic:
        factor, fraction = divmod(count+degree+1, count)
        cv = np.concatenate((cv,) * factor + (cv[:fraction],))
        count = len(cv)
        degree = np.clip(degree,1,degree)

    # If opened, prevent degree from exceeding count-1
    else:
        degree = np.clip(degree,1,count-1)

    # Calculate knot vector
    kv = None
    if periodic:
        kv = np.arange(0-degree,count+degree+degree-1,dtype='int')
    else:
        kv = np.concatenate(([0]*degree, np.arange(count-degree+1), [count-degree]*degree))

    # Calculate query range
    u = np.linspace(periodic,(count-degree),n)

    #print(u)

    # Calculate result
    return np.array(si.splev(u, (kv,cv.T,degree))).T

'''
Example :
---------

from gbot.bsplines import bspline
import matplotlib.pyplot as plt
import numpy as np

colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k')

cv = np.array([[ 50.,  25.],
   [ 59.,  12.],
   [ 50.,  10.],
   [ 57.,   2.],
   [ 40.,   4.],
   [ 40.,   14.]])

plt.plot(cv[:,0],cv[:,1], 'o-', label='Control Points')

d = 4
p = bspline(cv,n=100,degree=4,periodic=False)
x,y = p.T
plt.plot(x,y,'k-',label='Degree %s'%d,color=colors[d%len(colors)])

plt.minorticks_on()
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(35, 70)
plt.ylim(0, 30)
plt.gca().set_aspect('equal', adjustable='box')
plt.show()

Out : Matplotlib figure
'''

########################################################################
########################################################################
# End of File


# Behnam Asadi
# http://ros-developer.com
# To see the function that we are working on visit:
# http://ros-developer.com/2017/05/07/gradient-descent-method-for-finding-the-minimum/
# or simply put the following latex code in a latex doc:
# $$ z= -( 4 \times e^{- ( (x-4)^2 +(y-4)^2 ) }+ 2 \times e^{- ( (x-2)^2 +(y-2)^2 ) } )$$

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

def objective_function(x,y):
    z=-( 4*np.exp(-(x-4)**2 - (y-4)**2)+2*np.exp(-(x-2)**2 - (y-2)**2) )
    return z

def f_prim(x,y):
    f_x=-( (-2)*(x-4)*4*np.exp(-(x-4)**2 - (y-4)**2)    +   (-2)*(x-2)*2*np.exp(-(x-2)**2 - (y-2)**2) )
    f_y=-( (-2)*(y-4)*4*np.exp(-(x-4)**2 - (y-4)**2)    +   (-2)*(y-2)*2*np.exp(-(x-2)**2 - (y-2)**2) )
    return [f_x,f_y]

x = np.linspace(-2,10,200)
y = np.linspace(-2,10,200)

X, Y = np.meshgrid(x,y)

Z=objective_function(X,Y)

#Make a 3D plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, Z,linewidth=0,cmap='coolwarm')

ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

X_old=-2
Y_old=0

# The starts point for the algorithm:
X_new=4
Y_new=2.2

# step size
epsilon=0.1

# stop criteria
precision = 0.00001

x_path_to_max=[]
y_path_to_max=[]
z_path_to_max=[]

while np.sqrt( (X_new-X_old)**2 + (Y_new-Y_old)**2 ) > precision:
    X_old=X_new
    Y_old=Y_new

    #[X_new,Y_new]=f_prim(X_new,Y_new)
    #print f_prim(X_new,Y_new)
    x_path_to_max.append(X_new )
    y_path_to_max.append(Y_new )
    z=objective_function(X_new,Y_new)
    z_path_to_max.append(z)

    ret_val=f_prim(X_old,Y_old)
    X_new=X_old-epsilon*ret_val[0]
    Y_new=Y_old-epsilon*ret_val[1]
#    print X_new
#    print Y_new

line1=plt.plot(x_path_to_max,y_path_to_max,z_path_to_max)
plt.setp(line1,color='g',linewidth=0.5)

#print X_new
#print Y_new
plt.show()


import math
import sympy as sp
from sympy.abc import x

def lagrange_interpolation(t, y):
    """ Lagrange Interpolation Polinomial

    Args:
        t: list/ndarray, the interpolation points
        y: list/ndarray, the values of the function at every t

    Returns:
        L: symbol object, Lagrange interpolation Polinomial
    """
    # the number of the points
    n = len(t)

    # Lagrange interpolation polinomial
    L = 0
    for i in range(n):
        # the base function of Lagrange interpolation polinomial
        l = y[i]
        for j in range(n):
            if i == j:
                continue
            else:
                l *= (x - t[j])/(t[i] - t[j])
        L += l

    return sp.simplify(L)

if __name__ == '__main__':
    # the interpolation points(degrees)
    t = [11, 12, 13]
    # the interpolation points(radians)
    t = list(map(math.radians, t))
    y = [0.190809, 0.207912, 0.224951]
    L = lagrange_interpolation(t, y)
    print(f"The Lagrange interpolation polinomial is L = {L}")
    # calculate the value of sin11.5 by Lagrange interpolation
    x0_deg = 11.5
    x0_rad = math.radians(x0_deg)
    true_value = math.sin(x0_rad)
    pre_value = L.subs(x, x0_rad)
    print(f"The predict value of sin{x0_deg} is {pre_value:.7f}")
    print(f"The true value of sin{x0_deg} is {true_value:.7f}")
    print(f"The error is {math.fabs(true_value - pre_value):.7f}")


import numpy as np
import matplotlib.pyplot as plt

def logistic_map(r, x_0, n):
    final_x = x_0

    for step in range(n):
        final_x = (4 * r) * final_x * (1 - final_x)

    return final_x

def bifurcation_diagram(r_start, r_end, r_samples, sample_numbers, max_n):
    r_s = np.linspace(r_start, r_end, r_samples)
    x_0_s = np.random.rand(sample_numbers)
    x_r_s = np.zeros((r_samples, sample_numbers))

    for r_index, r in enumerate(r_s):
        x_r_s[r_index] = logistic_map(r, x_0_s, max_n)

    data = {
        'r_start': r_start,
        'r_end': r_end,
        'r_samples': r_samples,
        'sample_numbers': sample_numbers,
        'max_n': max_n,
        'x_r_s': x_r_s
    }
    file_name = f'data/q4_{r_start}_{r_end}_{r_samples}_{sample_numbers}_{max_n}.npy'
    np.save(file_name, data)

    return file_name

def show(file_name, fig_width = 10, fig_height=5, r_lim=None, x_lim=None, show_x_m=False, save=True):
    data = np.load('data/' + file_name, allow_pickle=True).tolist()
    r_start = data['r_start']
    r_end = data['r_end']
    r_samples = data['r_samples']
    sample_numbers = data['sample_numbers']
    max_n = data['max_n']
    x_r_s = data['x_r_s']

    r_lim_start = r_start if r_lim is None else r_lim[0]
    r_lim_end = r_end if r_lim is None else r_lim[1]
    r_lim_ratio = (r_lim_end - r_lim_start) / 1

    r_s = np.linspace(r_start, r_end, r_samples)
    ones = 4 * np.ones(sample_numbers)

    plt.figure(figsize=(fig_width, fig_height))
    for r_index, r in enumerate(r_s):
        plt.plot(r * ones, x_r_s[r_index], linestyle='', marker='.', color='black',
                 markersize=133 * fig_width / r_samples * r_lim_ratio)

    if show_x_m:
        plt.axhline(0.5)
    if r_lim is not None:
        plt.xlim(r_lim)
    if x_lim is not None:
        plt.ylim(x_lim)
    plt.xlabel(r'$r$')
    plt.ylabel(r'$x$')
    if save:
        plt.savefig(f'images/q4_{r_lim_start}_{r_lim_end}_{r_samples}_{sample_numbers}_{max_n}.jpg')
    plt.show()

if __name__ == "__main__":
    file_name = bifurcation_diagram(0, 1, 10000, 100, 10000)
    show(file_name=file_name, fig_height=5, fig_width=10)


import numpy as np
import matplotlib.pyplot as plt

# Primera parte

# Soluciones a los ejercicios de la seccion 6.2.5
# del libro A Survey of Computational Physics Introductory Computational Science
# de Landau, Paez, Bordeianu (Python Multimodal eTextBook Beta4.0)

#1. Write a double-precision program to integrate an arbitrary function numerically
# using the trapezoid rule, the Simpson rule, and Gaussian quadrature.
def integra(f, a, b, n_points=10, metodo="trapecio"):
    # Genera siempre un numero impar de puntos
    if n_points%2 == 0:
        n_points = n_points + 1

    if metodo=="trapecio":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points) * h
        w[0] = h/2
        w[-1] = h/2
    elif metodo=="simpson":
        x = np.linspace(a, b, n_points)
        h = x[1] - x[0]
        w = np.ones(n_points)
        ii = np.arange(n_points)
        w[ii%2!=0] = 4.0*h/3.0
        w[ii%2==0] = 2.0*h/3.0
        w[0] = h/3
        w[-1] = h/3
    elif metodo=="cuadratura":
        y, wprime = np.polynomial.legendre.leggauss(n_points)
        x = 0.5*(b+a) + 0.5*(b-a)*y
        w = 0.5*(b-a)*wprime
    else:
        print('metodo no implementado')
        x = np.zeros(n_points)
        y = np.zeros(n_points)

    return np.sum(f(x)*w)

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

def error(x):
    return np.abs(2-x)/2

# 2 Compute the relative error (epsilon=abs(numerical-exact)/exact) in each case.
# Present your data in tabular form for N=2,10,20,40,80,160

N = [2,10,20,40,80,160]
print("Primera Parte")
out = open("tabla_resultados.dat", "w")
print("# N\t e_T\t e_S \t e_G")
for n_points in N:
    a = integra(func, 0, np.pi, n_points=n_points, metodo="trapecio")
    b = integra(func, 0, np.pi, n_points=n_points, metodo="simpson")
    c = integra(func, 0, np.pi, n_points=n_points, metodo="cuadratura")
    print("{:d}\t {:.1e} {:.1e} {:.1e}".format(n_points, error(a), error(b), error(c)))
    out.write("{:d}\t {:.1e} {:.1e} {:.1e}\n".format(n_points, error(a), error(b), error(c)))
out.close()
print("")

# 3 Make a log-log plot of relative error versus N
data = np.loadtxt("tabla_resultados.dat")
plt.figure()
plt.plot(data[:,0], data[:,1], label="Trapecio")
plt.plot(data[:,0], data[:,2], label="Simpson")
plt.plot(data[:,0], data[:,3], label="Cuadratura")

plt.xlabel('N')
plt.ylabel('|error|')
plt.loglog()
plt.legend()
plt.savefig("loglogplot.png")

# 4. Use your plot or table to estimate the power-law dependence of the error on N and
# to determine the nuber of decimal places of precision.

for i,m in zip([1,2,3],["Trapecio", "Simpson", "Cuadratura"]):
    power_law = (np.log(data[2,i]) - np.log(data[0,i]))/(np.log(data[2,0]) - np.log(data[0,0]))
    decimal_places = -np.log10(data[-1,i])
    print("Metodo {}".format(m))
    print("\t Power Law: {:.1f}".format(power_law))
    print("\t Decimal Places: {:d}".format(int(decimal_places)))

print("")

# Segunda parte.
# Calcule la integral de la función Gamma (https://en.wikipedia.org/wiki/Gamma_function) para z>1. Imprima los resultados Gamma(2), Gamma(3) y Gamma(4).

def gamma(z, n_points=20):
    def fun(z, x):
        return x**(z-1) * np.exp(-x)
    # Usando la formula de transformacion (6.36) del libro.
    y, wprime = np.polynomial.legendre.leggauss(n_points)
    x = (1+y)/(1-y)
    w = wprime * 2/(1-y)**2
    return np.sum(fun(z,x)*w)
print("Segunda Parte")
print("Gamma(2): {}\nGamma(3): {}\nGamma(4): {}\t".format(gamma(2), gamma(3), gamma(4)))

def gamma2(z, n_points=20):
    # Usando el cambio de variable u = exp(-x)
    def fun(z,u):
        return (-np.log(u))**(z-1)    # diverge para u=0
    a = 0.0
    b = 1.0
    y, wprime = np.polynomial.legendre.leggauss(n_points)
    x = 0.5*(b+a) + 0.5*(b-a)*y
    w = 0.5*(b-a)*wprime
    return np.sum(fun(z,x)*w)
print("Segunda Parte")
print("Gamma(2): {}\nGamma(3): {}\nGamma(4): {}\t".format(gamma2(2), gamma2(3), gamma2(4)))


#!/usr/bin/env python3
"""
Clustering Module
"""
import numpy as np
kmeans = __import__('1-kmeans').kmeans
variance = __import__('2-variance').variance

def optimum_k(X, kmin=1, kmax=None, iterations=1000):
    """
    Tests for the optimum number of clusters by variance:

    X is a numpy.ndarray of shape (n, d) containing the data set
    kmin is a positive integer containing the minimum number of clusters to
    check for (inclusive)
    kmax is a positive integer containing the maximum number of clusters to
    check for (inclusive)
    iterations is a positive integer containing the maximum number of
    iterations for K-means
    This function should analyze at least 2 different cluster sizes
    You may use at most 2 loops

    Returns: results, d_vars, or None, None on failure
        results is a list containing the outputs of K-means for each cluster
        size
        d_vars is a list containing the difference in variance from the
        smallest cluster size for each cluster size
    """
    if type(X) is not np.ndarray or len(X.shape) != 2:
        return (None, None)
    if type(kmin) is not int:
        return (None, None)
    if type(iterations) is not int:
        return (None, None)
    if kmax is not None and type(kmax) is not int:
        return (None, None)
    n, _ = X.shape
    if kmax is None:
        kmax = n
    if kmin <= 0 or kmax <= 0 or iterations <= 0:
        return (None, None)
    if kmin >= kmax:
        return (None, None)

    d_vars = []
    results = []
    for i in range(kmin, kmax + 1):
        center, klss = kmeans(X, i, iterations)
        results.append((center, klss))
        if i == kmin:
            kmin_var = variance(X, center)
        cvar = variance(X, center)
        d_vars.append(kmin_var - cvar)
    return (results, d_vars)


from fractions import Fraction
import numpy as np

# These functions are written based on the exercises in
# Xanadu Quantum Codebook nodes S.3 and S.4
# https://codebook.xanadu.ai

def fractional_binary_to_float(sample):
    """Convert an n-bit sample [k1, k2, ..., kn] to a floating point
    value using fractional binary representation,

        k = (k1 / 2) + (k2 / 2 ** 2) + ... + (kn / 2 ** n)

    Args:
        sample (list[int] or array[int]): A list or array of bits, e.g.,
            the sample output of quantum circuit.

    Returns:
        float: The floating point value corresponding computed from the
        fractional binary representation.
    """
    return np.sum(
        [int(sample[bit]) / 2 ** (bit + 1) for bit in range(len(sample))]
    )

def phase_to_order(phase, max_denominator):
    """Estimating which integer values divide to produce a float.

    Given some floating-point phase, estimate integers s, r such
    that s / r = phase, where r is no greater than some specified value.

    Args:
        phase (float): Some fractional value (here, will be the output
            of running QPE).
        max_denominator (int): The largest r to be considered when looking
            for s, r such that s / r = phase.

    Returns:
        int: The estimated value of r.
    """
    s_over_r = Fraction(phase)
    return s_over_r.limit_denominator(max_denominator).denominator

def get_U_Na(N, a):
    """Computes the unitary matrix U_(N, a) used in the order-finding
    portion of Shor's algorithm.

    U_(N, a) multiples a computational basis state by a modulo N, i.e.,
        U_(N, a) |k> = |ak mod N>

    In Shor's algorithm, we try to find its order, i.e., the smallest
    m such that
        U_(N, a)^m |k> = |k mod N> = |k>

    Args:
        N (int): The modulus. In Shor's algorithm, this is the number
            we are trying to find the prime factors of.
        a (int): The candidate value a which we are testing to try and
            find a non-trivial square root (which will then allow us to
            recover the prime factors of N).

    Returns:
        array[int]: The matrix representation U_(N, a).
    """
    # Compute size of the matrix; we need at least log2(N) qubits
    # because we are looking at computational basis states modulo N
    n_qubits = int(np.ceil(np.log2(N)))

    U_Na = np.zeros([2 ** n_qubits, 2 ** n_qubits])

    # U_Na is a permutation matrix; for each k < N, need to compute
    # |l> = |a k mod N>, and then set the value of U_Na[l, k] = 1
    for k in range(N):
        U_Na[(k * a) % N, k] = 1

    # We might have more basis states than we need, if N < 2 ** n_qubits
    # so we set the remaining rows to identity rows
    for extra in range(N, 2 ** n_qubits):
        U_Na[extra, extra] = 1

    return U_Na


# -*- coding: UTF-8 -*-
from __future__ import division

import numpy as np

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

print(x + y)  # elementwise addition     [1 2 3] + [4 5 6] = [5  7  9]
print(x - y)  # elementwise subtraction  [1 2 3] - [4 5 6] = [-3 -3 -3]
print(x * y)  # elementwise multiplication  [1 2 3] * [4 5 6] = [4  10  18]
print(x / y)  # elementwise divison         [1 2 3] / [4 5 6] = [0.25  0.4  0.5]
print(x ** 2)  # elementwise power  [1 2 3] ^2 =  [1 4 9]

#  Dot Product
print(x.dot(y))  # dot product  1*4 + 2*5 + 3*6 = 32

# T
z = np.array([y, y ** 2])
print(len(z))  # number of rows of array # 2
print(z)
# [[ 4  5  6]
#  [16 25 36]]
print(z.shape)  # (2L, 3L)
print(z.T)
# [[ 4 16]
#  [ 5 25]
#  [ 6 36]]
print(z.T.shape)  # (3L, 2L)

# dtype
print(z.dtype)  # int32
# convert data type to float
z = z.astype('f')
print(z.dtype)  # float32


import random

import numpy as np
import matplotlib.pyplot as plt

phi = lambda vec: np.sin(np.product(vec))
h = lambda x: np.sqrt(1 + np.sin(np.product(x)) ** 2)

# Section 1.1.5 - Analytical Evaluation

# SubSection 1
def gradient_phi(vec: np.matrix):
    grad_vec = np.matrix(np.array([[vec[1] * vec[2]], [vec[0] * vec[2]], [vec[0] * vec[1]]]), dtype=np.float128)
    grad_vec = grad_vec.transpose()
    return np.cos(np.product(vec)) * grad_vec

def hessian_phi(mat: np.matrix, vec: np.matrix):
    nabla = np.zeros((3, 3))
    nabla[0, 0] = (-((vec[1] * vec[2]) ** 2) * np.sin(np.product(vec)))
    nabla[0, 1] = vec[2] * np.cos(np.product(vec)) - np.product(vec) * vec[2] * np.sin(np.product(vec))
    nabla[0, 2] = vec[1] * np.cos(np.product(vec)) - np.product(vec) * vec[1] * np.sin(np.product(vec))
    nabla[1, 0] = vec[2] * np.cos(np.product(vec)) - np.product(vec) * vec[2] * np.sin(np.product(vec))
    nabla[1, 1] = (-((vec[0] * vec[2]) ** 2) * np.sin(np.product(vec)))
    nabla[1, 2] = vec[0] * np.cos(np.product(vec)) - np.product(vec) * vec[0] * np.sin(np.product(vec))
    nabla[2, 0] = vec[1] * np.cos(np.product(vec)) - np.product(vec) * vec[1] * np.sin(np.product(vec))
    nabla[2, 1] = vec[0] * np.cos(np.product(vec)) - np.product(vec) * vec[0] * np.sin(np.product(vec))
    nabla[2, 2] = (-((vec[0] * vec[1]) ** 2) * np.sin(np.product(vec)))
    return mat.transpose() * nabla * mat

# SubSection 2
def gradient_h(vec: np.matrix):
    return 0.5 * phi(vec) / ((1 + (phi(vec) ** 2)) ** 0.5) \
           * gradient_phi(vec)

def hessian_h(vec: np.matrix):
    # Calculating the 2nd derivative of h of phi
    derivative = (np.cos(np.product(vec)) ** 2 -
                  (1 + np.sin(np.product(vec)) ** 2) * np.sin(np.product(vec)) ** 2) / \
                 (1 + np.sin(np.product(vec)) ** 2) ** 1.5

    return derivative * hessian_phi(np.matrix(np.identity(3)), vec)

# Section 1.2.1 - Numerical Differentiation

def numerical_diff_gradient(func, vector: np.matrix, epsilon):
    vec_len = vector.shape[0]
    assert vector.shape[1] == 1
    assert vec_len > 0
    gradient = np.matrix(np.zeros((vec_len, 1)), dtype=np.float128)
    for i in range(vec_len):
        base_vector = np.matrix(np.zeros((vec_len, 1)), dtype=np.float128)
        base_vector[i, 0] = 1
        func_plus = func(vector + (epsilon * base_vector))
        func_minus = func(vector - (epsilon * base_vector))
        gradient[i, 0] = ((func_plus - func_minus) / (2 * epsilon))
    return gradient

def numerical_diff_hessian(vec: np.matrix, epsilon, hess_phi=False, hess_h=False,):
    vec_len = vec.shape[0]
    assert vec.shape[1] == 1
    assert vec_len > 0
    hessian = np.matrix(np.zeros((vec_len, vec_len)), dtype=np.float128)
    for i in range(vec_len):
        base_vector = np.matrix(np.zeros((vec_len, 1)))
        base_vector[i, 0] = 1
        if hess_phi is True:
            v1 = gradient_phi(vec + epsilon * base_vector)
            v2 = gradient_phi(vec - epsilon * base_vector)
        elif hess_h is True:
            v1 = gradient_h(vec + epsilon * base_vector)
            v2 = gradient_h(vec - epsilon * base_vector)
        else:
            return None
        value = (v1 - v2)
        hessian[0:vec_len, i] = value / (2 * epsilon)
    return hessian

# Section 1.3 - Comparison plot
def compare_grad():
    vec = np.matrix(np.random.rand(3), dtype=np.float128).transpose()
    A = np.matrix(np.random.rand(3, 3), dtype=np.float128)
    epsilon = []
    values = []
    for i in range(61):
        epsilon.append(2 ** -i)

    # Comparison for f1 gradient
    analytical_grad = gradient_phi(A * vec)
    for i in range(61):
        numerical_grad = numerical_diff_gradient(phi, A * vec, epsilon[i])
        x = np.abs(analytical_grad - numerical_grad)
        values.append(np.linalg.norm(x, np.inf))
    show_plot("f1 gradient", values)

    # Comparison for f1 hessian
    analytical_hessian = hessian_phi(A, vec)
    values = []
    for i in range(61):
        numerical_hessian = numerical_diff_hessian(A * vec, epsilon[i], hess_phi=True)
        x = np.abs(analytical_hessian - numerical_hessian)
        values.append(np.linalg.norm(x, np.inf))
    show_plot("f1 hessian", values)

    # Comparison for f2
    analytical_grad = gradient_h(vec)
    values = []
    for i in range(61):
        numerical_grad = numerical_diff_gradient(h, vec, epsilon[i])
        x = np.abs(analytical_grad - numerical_grad)
        y = max(x).item()
        values.append(y)
    show_plot("f2 gradient", values)

def show_plot(str, y_axis):
    fig = plt.figure()
    ax = fig.add_subplot()
    fig.subplots_adjust(top=0.85)
    fig.suptitle(str, fontsize=14, fontweight='bold')
    ax.set_xlabel('epsilon')
    ax.set_ylabel('differentiation')
    plt.plot(list(range(61)), y_axis)
    plt.xscale("linear")
    plt.yscale("log")
    plt.show()

compare_grad()