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

# For all printing methods from Sympy
sym.init_printing()

def show_example_number(index):
    print("\n".join(("\n********", f"Example {index}", "********\n")))

# %%
#
# Ex 1 : dy/dt = -5 y(t) with y(0) = 1
# Plot solution from 0 to 20 with a step of 0.4
#
show_example_number(1)

# Variables
t = sym.symbols("t")
y = sym.Function("y")

# Initial conditions
ics = {y(0): 1}

# Build equation
left_hand = sym.Derivative(y(t), t)
right_hand = -5 * y(t)
eq = sym.Eq(left_hand, right_hand)
print("Diff equation :", sym.pretty(eq), sep="\n")

# Solve equation dsolve(eq, func)
sol = sym.dsolve(eq, y(t), ics=ics)
print("\nSolution :", sym.pretty(sol), sep="\n")

# Lambdify : Transform into function, from string to numpy function
fun_y = sym.lambdify(t, sol.rhs, modules=["numpy"])

a = 0
b = 20
h = 0.4
t = np.arange(a, b + h, h)
y = fun_y(t)

# Plot(y, t)
plt.plot(t, y, color="b")
plt.xlim(a, b)
plt.show()

# %%
#
# Ex 2 : f'(x) = x + (f(x) / 5) with f(0) = -3
#
show_example_number(2)

x = sym.var("x")
f = sym.Function("f")

# Equation, then solving
eq2 = sym.Eq(sym.Derivative(f(x), x), x + f(x) / 5)
ics = {f(0): -3}
sol2 = sym.dsolve(eq2, f(x), ics=ics).rhs
print("Solution :", sym.pretty(sym.simplify(sol2)), sep="\n")

# %%
#
# Ex 3 : f'(x) - f(x) = 0 with f(0) = A
#
show_example_number(3)

# Variables and initial condition
f_3 = sym.Function("f_3")
x_3 = sym.var("x_3")
A = sym.var("A")
ics = {f_3(0): A}

# Not using sym.Eq, simply using an expression, assumed to be equal to 0
sol3 = sym.dsolve(f_3(x_3).diff(x_3) - f_3(x_3), f_3(x_3), ics=ics)

print("Solution :", sym.pretty(sol3), sep="\n")


#Solutions to Policy Function Iteration Lab

import numpy as np
import scipy as sp
from scipy import sparse
from scipy.sparse import linalg
import math
from matplotlib import pyplot as plt
from scipy import linalg as la

def u(x):
    return np.sqrt(x).flatten()

def policyIter(beta, N, Wmax=1.):
    """
    Solve the infinite horizon cake eating problem using policy function iteration.
    Inputs:
        beta -- float, the discount factor
        N -- integer, size of discrete approximation of cake
        Wmax -- total amount of cake available
    Returns:
        values -- converged value function (Numpy array of length N)
        psi -- converged policy function (Numpy array of length N)
    """
    W = np.linspace(0,Wmax,N) #state space vector
    I = sparse.identity(N, format='csr')

    #precompute u(W-W') for all possible inputs
    actions = np.tile(W, N).reshape((N,N)).T
    actions = actions - actions.T
    actions[actions<0] = 0
    rewards = np.sqrt(actions)
    rewards[np.triu_indices(N, k=1)] = -1e10 #pre-computed reward function

    psi_ind = np.arange(N)
    rows = np.arange(0,N)
    tol = 1.
    while tol >= 1e-9:
        columns = psi_ind
        data = np.ones(N)
        Q = sparse.coo_matrix((data,(rows,columns)),shape=(N,N))
        Q = Q.tocsr()
        values = linalg.spsolve(I-beta*Q, u(W-W[psi_ind])).reshape(1,N)
        psi_ind1 = np.argmax(rewards + beta*values, axis=1)
        tol = math.sqrt(((W[psi_ind] - W[psi_ind1])**2).sum())
        psi_ind = psi_ind1
    return values.flatten(), W[psi_ind]

def modPolicyIter(beta, N, Wmax=1., m=15):
    """
    Solve the infinite horizon cake eating problem using modified policy function iteration.
    Inputs:
        beta -- float, the discount factor
        N -- integer, size of discrete approximation of cake
        Wmax -- total amount of cake available
    Returns:
        values -- converged value function (Numpy array of length N)
        psi -- converged policy function (Numpy array of length N)
    """
    W = np.linspace(0,Wmax,N) #state space vector

    #precompute u(W-W') for all possible inputs
    actions = np.tile(W, N).reshape((N,N)).T
    actions = actions - actions.T
    actions[actions<0] = 0
    rewards = np.sqrt(actions)
    rewards[np.triu_indices(N, k=1)] = -1e10 #pre-computed reward function

    psi_ind = np.arange(N)
    values = np.zeros(N)
    tol = 1.
    while tol >= 1e-9:
        for i in xrange(m):
            values = u(W - W[psi_ind]) + beta*values[psi_ind]
        psi_ind1 = np.argmax(rewards + beta*values.reshape(1,N), axis=1)
        tol = math.sqrt(((W[psi_ind] - W[psi_ind1])**2).sum())
        psi_ind = psi_ind1
    return values.flatten(), W[psi_ind]


import math
import numpy as np

from hybridimages.convolution import convolve

def createHybridImage(lowImage: np.ndarray, lowSigma: float, highImage: np.ndarray, highSigma:float) -> np.ndarray:
    """
    Create hybrid images by combining a low-pass and high-pass filtered pair.

    :param lowImage: the image to low-pass filter (either greyscale shape=(rows, cols) or colour shape=(rows,cols,channels))
    :type numpy.ndarray

    :param lowSigma: the standard deviation of the Gaussian used for low-pass filtering lowImage
    :type numpy.ndarray

    :param highSigma: the standard deviation of the Gaussian used for low-pass filtering highImage before subtraction to create the high-pass filtered image
    :type float

    :returns returns the hybrid image created
        by low-pass filtering lowImage with a Gaussian of s.d. lowSigma and combining it with a high-pass image created by subtracting highImage from highImage
        convolved with a Gaussian of s.d. highSigma. The resultant image has the same size as the input images.
    :rtype numpy.ndarray
    """
    print(f'Attempting to merge {lowImage.shape} low frequency image with {highImage.shape} high frequency image')

    low_sigma_kernel = makeGaussianKernel(lowSigma)
    print(f'Applying convolution...')
    low_pass_image = convolve(lowImage, low_sigma_kernel)
    print(f'Low-pass filter successfully applied')
    high_sigma_kernel = makeGaussianKernel(highSigma)
    print(f'Applying convolution...')
    low_pass_of_highimage = convolve(highImage, high_sigma_kernel)

    print(f'Acquiring High-pass filtered image...')
    high_pass_img = highImage - low_pass_of_highimage

    # visualise high_pass image by adding 128 as high freq image is 0 mean with negative values
    # show_image(high_pass_img + 128, grey=False)

    hybrid_img = low_pass_image + high_pass_img
    print(f'Hybrid image computed successfully')

    return hybrid_img

def makeGaussianKernel(sigma: float) -> np.ndarray:
    """
    Use this function to create a 2D Gaussian kernel with standard deviation sigma.
    The kernel values should sum to 1.0, and the size should be floor(8*sigma+1) or
    floor(8*sigma+1)+1 (whichever is odd).
    """
    print('Initiating Gaussian kernel with sigma:', sigma)
    size = np.floor(8 * sigma + 1).astype(int)

    # ensure size remains odd
    if size % 2 == 0:
        size += 1
    print(f'Gaussian kernel dimensions: {size} x {size}')

    # create range between -1 and +1
    ax = np.linspace( -(size-1) / 2, (size-1) / 2, size)
    # fill a size x size grid with above range
    x, y = np.meshgrid(ax,ax)

    # construct kernel using Gauss
    K = (1/(2 * math.pi * sigma**2)) * np.exp(-((x**2 + y**2)/(2 * sigma**2)))
    return K


# import linear matrix equation solving method or function
from numpy.linalg import solve
from numpy import array
from numpy import allclose
from numpy import dot

"""
    Solve(a, b): Take two arguments a = Co-efficient matrix
                                    b = dependent variable values

    Return: Solution of the system ax = b, returned shape is
            identical to b

    Raises: LinAlgError: if a is a singular or not square

    ** Broadcasting rules apply.
    ** a must be square and of full-rank, i.e., all rows (or, equivalently, columns) must be           linearly independent; if either is not true, use lstsq for the least-squares best “solution”    of the system/equation.
"""

# Solve the system of equations 3 * x0 + x1 = 9 and x0 + 2 * x1 = 8
a = array([[3, 1], [1, 2]])
b = array([9, 8])

x = solve(a, b)

print(x)

# Check the solution is okay or not!!!!!
flag = allclose(dot(a, x), b)

if flag:
    print("Solution is correct.")
else:
    print("Solution is not correct.")

# Ref: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.solve.html


'''
The sum of the squares of the first ten natural numbers is,
1**2 + 2**2 + ... + 10**2 == 385

The square of the sum of the first ten natural numbers is,
(1 + 2 + ... + 10)**2 == 3025

Hence the difference between the sum of the squares of the
first ten natural numbers and the square of the sum is:
3025 - 385 = 2640

Find the difference between the sum of the squares of the
first one hundred natural numbers and the square of the sum.
'''

import numpy as np

# brute force
def sum_square_difference(n):
  first_n = np.arange(1, n+1)
  sum_square = np.dot(first_n, first_n)
  square_sum = np.sum(first_n) ** 2
  return square_sum - sum_square

%timeit sum_square_difference(100)

# using euler and pyramidal numbers
def sum_square_difference_2(n):
  square_sum = ((n+1) * n / 2)**2
  sum_square = n * (n+1) * (2*n+1) / 6
  return square_sum - sum_square

%timeit sum_square_difference_2(100)


'''
Q: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
Find the sum of all the primes below two million.
'''

'''
final answer: 142913828922
'''

import numpy as np

# init constants
upper_bound = 2000000

def list_of_primes(n):
    # https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Pseudocode
    numbers = [True for i in range(n)]
    numbers[0] = numbers[1] = False

    for p in range(1, int(n ** 0.5) + 1):
        if (numbers[p] == True):
            for i in range(p ** 2, n, p):
                numbers[i] = False

    primes = []
    for p in range(len(numbers)):
        if numbers[p]:
            primes.append(p)

    return np.array(primes,
                    dtype=object)  # sum is too large so need to set as object (https://stackoverflow.com/a/22664510)

if __name__ == '__main__':
    print("The sum of all the primes below two million is " + str(list_of_primes(upper_bound).sum()))


# -*- coding: utf-8 -*-
"""HILL_CIPHER.ipynb

Automatically generated by Colaboratory.

Original file is located at
    https://colab.research.google.com/drive/14sYOs_tSma_mGwx3ulAB48srtODas8DD
"""

import numpy as np
import sympy
import string
import random

# Define variables
dimension = 3 # Your N
key = np.matrix([[17,17,5], [21,18,21], [2,2,19]]) # Your key
message = 'paymoremoney' # Your message

print("Plain Text: "+message)
print("Key Matrix: ")
print(key)
# Generate the alphabet
alphabet = string.ascii_lowercase

# Encrypted message
encryptedMessage = ""

# Group message in vectors and generate crypted message
for index, i in enumerate(message): #PAYMOREMONEY
    values = []
    # Make bloc of N values
    if index % dimension == 0:
        for j in range(0, dimension):
            if(index + j < len(message)):
                values.append([alphabet.index(message[index + j])])
                # print(f'if:{values}')
            else:
                values.append([random.randint(0,25)])
                # print(f'else:{values}')
        # Generate vectors and work with them
        vector = np.matrix(values)
        vector = key * vector
        vector %= 26
        for j in range(0, dimension):
            encryptedMessage += alphabet[vector.item(j)]

# Show the result
print("Encrypted message is: "+ encryptedMessage.upper())

#DECRYPTION

def modulo_multiplicative_inverse(A, M):

    # This will iterate from 0 to M-1
    for i in range(0, M):
        # If we have our multiplicative inverse then return it
        if (A*i) % M == 1:
            return i
    # If we didn't find the multiplicative inverse in the loop above
    # then it doesn't exist for A under M
    return -1

matrix= sympy.Matrix([[17,17,5], [21,18,21], [2,2,19]])
adj=(matrix.adjugate()%26) #TO FIND ADJOINT OF KEY MATRIX

mat=np.matrix([[17,17,5], [21,18,21], [2,2,19]])

det=(round(np.linalg.det(mat))%26) #TO FIND DETERMINENT

# print(det)
# print(adj)

mult_inverse=modulo_multiplicative_inverse(det, 26)

# print(mult_inverse)

inv_m=(mult_inverse*adj)%26
print("inverse of Key Matrix: ")
print(inv_m)

decryptedMessage=""

for index, i in enumerate(encryptedMessage):
    values = []
    if index % dimension == 0:
        for j in range(0, dimension):
            if(index + j < len(encryptedMessage)):
                values.append([alphabet.index(encryptedMessage[index + j])])
            else:
                values.append([random.randint(0,25)])
        vector = np.matrix(values)
        vector = inv_m * vector
        vector %= 26
        for j in range(0, dimension):
            decryptedMessage += alphabet[vector[j]]

# Show the result
print("Decrypted Message: "+ decryptedMessage)


# GRADED FUNCTION
import numpy as np
import numpy.linalg as la

verySmallNumber = 1e-14 # That's 1×10⁻¹⁴ = 0.00000000000001

# Our first function will perform the Gram-Schmidt procedure for 4 basis vectors.
# We'll take this list of vectors as the columns of a matrix, A.
# We'll then go through the vectors one at a time and set them to be orthogonal
# to all the vectors that came before it. Before normalising.
# Follow the instructions inside the function at each comment.
# You will be told where to add code to complete the function.
def gsBasis4(A) :
    B = np.array(A, dtype=np.float_) # Make B as a copy of A, since we're going to alter it's values.
    # The zeroth column is easy, since it has no other vectors to make it normal to.
    # All that needs to be done is to normalise it. I.e. divide by its modulus, or norm.
    B[:, 0] = B[:, 0] / la.norm(B[:, 0])
    # For the first column, we need to subtract any overlap with our new zeroth vector.
    B[:, 1] = B[:, 1] - B[:, 1] @ B[:, 0] * B[:, 0]
    # If there's anything left after that subtraction, then B[:, 1] is linearly independant of B[:, 0]
    # If this is the case, we can normalise it. Otherwise we'll set that vector to zero.
    if la.norm(B[:, 1]) > verySmallNumber :
        B[:, 1] = B[:, 1] / la.norm(B[:, 1])
    else :
        B[:, 1] = np.zeros_like(B[:, 1])
    B[:,2] = B[:,2] - B[:, 2] @ B[:, 0] * B[:, 0]
    B[:,2] = B[:,2] - B[:, 2] @ B[:, 1] * B[:, 1]
    if la.norm(B[:,2]) > verySmallNumber :
        B[:, 2] = B[:, 2] / la.norm(B[:, 2])
    else :
        B[:, 2] = np.zeros_like(B[:, 2])
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 0] * B[:, 0]
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 1] * B[:, 1]
    B[:,3] = B[:,3] - B[:, 3] @ B[:, 2] * B[:, 2]
    if la.norm(B[:,3]) > verySmallNumber :
        B[:, 3] = B[:, 3] / la.norm(B[:, 3])
    else :
        B[:, 3] = np.zeros_like(B[:, 3])

    # Now we need to repeat the process for column 2.
    # Insert two lines of code, the first to subtract the overlap with the zeroth vector,
    # and the second to subtract the overlap with the first.

    # Again we'll need to normalise our new vector.
    # Copy and adapt the normalisation fragment from above to column 2.

    # Finally, column three:
    # Insert code to subtract the overlap with the first three vectors.

    # Now normalise if possible

    # Finally, we return the result:
    return B

# The second part of this exercise will generalise the procedure.
# Previously, we could only have four vectors, and there was a lot of repeating in the code.
# We'll use a for-loop here to iterate the process for each vector.
def gsBasis(A) :
    B = np.array(A, dtype=np.float_) # Make B as a copy of A, since we're going to alter it's values.
    # Loop over all vectors, starting with zero, label them with i
    for i in range(B.shape[1]) :
        # Inside that loop, loop over all previous vectors, j, to subtract.
        for j in range(i) :
            # Complete the code to subtract the overlap with previous vectors.
            # you'll need the current vector B[:, i] and a previous vector B[:, j]
            B[:, i] = B[:,i] - B[:,i] @ B[:,j] * B[:,j]
        # Next insert code to do the normalisation test for B[:, i]
        if la.norm(B[:, i]) > verySmallNumber :
            B[:, i] = B[:, i] / la.norm(B[:, i])
        else :
            B[:, i] = np.zeros_like(B[:, i])

    # Finally, we return the result:
    return B

# This function uses the Gram-schmidt process to calculate the dimension
# spanned by a list of vectors.
# Since each vector is normalised to one, or is zero,
# the sum of all the norms will be the dimension.
def dimensions(A) :
    return np.sum(la.norm(gsBasis(A), axis=0))


# Graphical Solutions
## Introduction to Linear Programming

#Import some required packages.
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

Graphical solution is limited to linear programming models containing only two decision variables (can be used with three variables but only with great difficulty).

Graphical methods provide a picture of how a solution for a linear programming problem is obtained.

## Product mix problem - Farmers Fields
Problem: How much of each brand to purchase to minimize total cost of fertilizer given following data ?

Product resource requirements and unit profit:
Two brands of fertilizer available – Super-gro, Crop-quick.
Field requires at least 16 pounds of nitrogen and 24 pounds of phosphate.
Super-gro costs $6 per bag, Crop-quick $3 per bag.

Decision Variables:

$x_{1}$ = number of bags of Super-gro

$x_{2}$ = number of bags of Crop-quick

Cost (Z) minimization

Z = 6$x_{1}$ + 3$x_{2}$

Nitrogen Constraint

2$x_{1}$ + 4$x_{2}$ >= 16

Phosphate Constraint Check

4$x_{1}$ + 3$x_{2}$ >= 24

Non-negativitiy Constraint

$x_{1}$ > 0

$x_{2}$ > 0

#Create an Array X2 from 0 to 60, and it should have a length of 61.
x2 = np.linspace(0, 14, 15)

#This is the same as starting your Excel Spreadsheet with incrementing X2
x2

#Nitrogen Constraint
# 2x1  + 4x2  >= 16
# x1= 8-2x2
c1 =  8 - 2*x2
c1

#Clay (Physicial Resource) Constraint Check
#4x1 + 3x2 <= 120
#4x1  + 3 x2  >= 24
c2  = (24 - 3*x2)/4
c2

#Calculate the maximim value of X1 you can make per the 2 different constraints.

ct = np.maximum(c1,c2)
ct

ct=ct[0:9] #removes negative values
ct

#Calculate the minimum cost from ct
#Z = 6 x1  + 3 x2
cost = 6*ct+3*x2[0:9]#Shape of array must be the same.
cost

# Make plot for the Nitrogen Constraint

plt.plot(c1, x2, label=r'2$x_{1}$ + 4$x_{2}$ >= 16')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(x2,c1, color='grey', alpha=0.5)

#Graph Phosphate Constraint Check

plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ >= 24')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(c2, x2, color='grey', alpha=0.5)

# Make plot for the combined constraints.
plt.plot(c1, x2, label=r'2$x_{1}$ + 4$x_{2}$ >= 16')
plt.plot(c2, x2, label=r'4$x_{1}$ + 3$x_{2}$ >= 24')
#plt.plot(ct, x2, label=r'min(x$x_{1}$)')
plt.xlim((0, 14))
plt.ylim((0, 14))
plt.xlabel(r'$x_{1}$') #Latex way of writing X subscript 1 (See Markdown)
plt.ylabel(r'$x_{2}$') #Latex way of writing X subscript 1 (See Markdown)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.fill_between(ct, x2,  color='grey', alpha=0.5)

Our solution must be in in lies somewhere in the grey feasible region in the graph above. However, according to the fundamental theorum of Linear programming we know it is at a vertex.

"In mathematical optimization, the fundamental theorem of linear programming states, in a weak formulation, that the maxima and minima of a linear function over a convex polygonal region occur at the region's corners. Further, if an extreme value occurs at two corners, then it must also occur everywhere on the line segment between them."

- [Wikipedia](https://en.wikipedia.org/wiki/Fundamental_theorem_of_linear_programming)

#This returns the index position of the maximum value
min_value = np.argmin(cost)
min_value

#Calculate The min cost that is made.
profit_answer=cost[min_value]
profit_answer

# Verify all constraints are integers
x2_answer = x2[min_value]
x2_answer

# Verify all constraints are integers
ct_answer = ct[min_value]
ct_answer


from typing import Union, Dict, Callable, Tuple

import numpy as np

def sigmoid(X: Union[float, np.array]) -> Union[float, np.array]:
    """
    Sigmoid activation function σ(X) = 1 / /1 + e^(-x))
    :param X: A scalar or numpy array of any size.
    :return: σ(X) = 1 / (1 + e^(-x))
    """
    return 1 / (1 + np.exp(-X))

def sigmoid_prime(X: Union[float, np.array]):
    """
    Derivative of the sigmoid function σ'(X) = σ(X) * (1 - σ(X))
    :param X: A scalar or numpy array of any size
    :return: σ'(X) = σ(X) * (1 - σ(X))
    """
    s = sigmoid(X)
    sigma_prime = s * (1 - s)
    return sigma_prime

def relu(x: Union[float, np.array]) -> Union[float, np.array]:
    """
    ReLU activation function implementation relu(X) = max(0, X)
    :param x: A scalar or numpy array of any size
    :return: relu(X) = max(0, x)
    """
    return np.maximum(0, x)

def relu_prime(X: Union[float, np.array]):
    """
    Derivative of the relu function relu'(X) = 0 if x <= 0, 1 if x > 0
    :param X: A scalar or numpy array of any size
    :return: σ'(X) = σ(X) * (1 - σ(X))
    """
    relu_prime = X > 0
    return relu_prime

def tanh(X: Union[float, np.array]) -> Union[float, np.array]:
    """
    tanh activation function tanh(X) = (e^x - e^(-x)) / (e^x + e^(-x))
    :param X: A scalar or numpy array of any size
    :return: tanh(X) = (e^X - e^(-X)) / (e^X + e^(-X))
    """
    e_x = np.exp(X)
    e_minus_x = np.exp(-X)
    return (e_x - e_minus_x) / (e_x + e_minus_x)

def tanh_prime(X: Union[float, np.array]):
    """
    Derivative of the tanh function tanh'(X) = 1 - (tanh(X))^2
    :param X: A scalar or numpy array of any size
    :return: tanh'(X) = 1 - (tanh(X))^2
    """
    t = tanh(X)
    tanh_prime = 1 - t * t
    return tanh_prime

# The dictionary of activation functions, to be able to retrieve them by name
activation_functions: Dict[str, Tuple[Callable, Callable]] = {
    sigmoid.__name__: (sigmoid, sigmoid_prime),
    relu.__name__: (relu, relu_prime),
    tanh.__name__: (tanh, tanh_prime)
}

class UnknownActivationFunctionName(Exception):
    def __init__(self, activation_function_name: str):
        message = "Unknown activation function name {}".format(activation_function_name)
        super().__init__(message)

def get_activation_function(activation_function_name: str):
    """
    Given the name of an activation function, retrieve the corresponding function and its derivative
    :param cost_function_name: the name of the cost function
    :return: the corresponding activation function and its derivative
    """
    try:
        return activation_functions[activation_function_name]
    except KeyError:
        raise UnknownActivationFunctionName(activation_function_name)


# inner
# The inner tool returns the inner product of two arrays.

# import numpy
# A = numpy.array([0, 1])
# B = numpy.array([3, 4])
# print numpy.inner(A, B)     #Output : 4

# outer
# The outer tool returns the outer product of two arrays.

# import numpy
# A = numpy.array([0, 1])
# B = numpy.array([3, 4])
# print numpy.outer(A, B)     #Output : [[0 0]
#                             #          [3 4]]

# Task :
# You are given two arrays:
# and .
# Your task is to compute their inner and outer product.

import numpy as np

a = list(map(int, raw_input().split()))
b = list(map(int, raw_input().split()))
a = np.array(a)
b = np.array(b)

print(np.inner(a, b))
print(np.outer(a, b))

# Sample Input
# 0 1
# 2 3

# Sample Output
# 3
# [[0 0]
#  [2 3]]


from __future__ import annotations

from typing import Iterable, Union

import numpy as np

Vector = Union[Iterable[float], Iterable[int], np.ndarray]
VectorOut = Union[np.float64, int, float]

def euclidean_distance(vector_1: Vector, vector_2: Vector) -> VectorOut:
    """
    Calculate the distance between the two endpoints of two vectors.
    A vector is defined as a list, tuple, or numpy 1D array.
    >>> euclidean_distance((0, 0), (2, 2))
    2.8284271247461903
    >>> euclidean_distance(np.array([0, 0, 0]), np.array([2, 2, 2]))
    3.4641016151377544
    >>> euclidean_distance(np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8]))
    8.0
    >>> euclidean_distance([1, 2, 3, 4], [5, 6, 7, 8])
    8.0
    """
    return np.sqrt(np.sum((np.asarray(vector_1) - np.asarray(vector_2)) ** 2))

def euclidean_distance_no_np(vector_1: Vector, vector_2: Vector) -> VectorOut:
    """
    Calculate the distance between the two endpoints of two vectors without numpy.
    A vector is defined as a list, tuple, or numpy 1D array.
    >>> euclidean_distance_no_np((0, 0), (2, 2))
    2.8284271247461903
    >>> euclidean_distance_no_np([1, 2, 3, 4], [5, 6, 7, 8])
    8.0
    """
    return sum((v1 - v2) ** 2 for v1, v2 in zip(vector_1, vector_2)) ** (1 / 2)

if __name__ == "__main__":

    def benchmark() -> None:
        """
        Benchmarks
        """
        from timeit import timeit

        print("Without Numpy")
        print(
            timeit(
                "euclidean_distance_no_np([1, 2, 3], [4, 5, 6])",
                number=10000,
                globals=globals(),
            )
        )
        print("With Numpy")
        print(
            timeit(
                "euclidean_distance([1, 2, 3], [4, 5, 6])",
                number=10000,
                globals=globals(),
            )
        )

    benchmark()


import numpy as np

def pseudo(A):
    # Check if matrix A is injective.
    if np.linalg.det(A.T @ A) < 1e-9:
        print("the given matrix is not injective.")
        return

    # Return pseudo-inverse (See lecture notes page 155)
    print("the given matrix is injective.")
    return np.linalg.inv(A.T @ A) @ A.T

# A = np.array([[1, 2, 0], [2, 4, 0], [1, 0, 3]]) # not injective
A = np.array([[1, 2, 0], [1, 4, 2], [1, 0, 3]]) # injective
print(pseudo(A))

A = np.array([[1, 0], [2, 1], [0, 2]])
b = np.array([1, 0, 1]).reshape((3, 1))
x = pseudo(A) @ b
print("\nOwn implementation:")
print(x)

print("\nNumpy implementation:")
print(np.linalg.lstsq(a = A, b = b, rcond=None)[0])


import numpy as np
import sys

try:
    n=int(sys.argv[1])
except IndexError:
    n=100
except ValueError:
    try:
        n=int(sys.argv[2])
    except IndexError:
        n=100

silent="-s" in sys.argv

N=4

base=list(range(N))

def init():
    '''
    Function that initializes monty hall problem simulation.

    returns a list with randomly ordered door's content.
    '''
    l=[]
    for i in range(N-1):
        l.append("goat")
    l.append("car")
    return np.random.permutation(l)

def first_choice():
    '''
    Emulates the initial choice.
    '''
    return np.random.choice(base)

def reveal_goat(doors_list,initial_choice):
    '''
    In answer to the initial choice, the machine reveals a door (index of) where is a goat, randomly.
    '''
    possible_indexes=[]
    for i in base:
        if( i!=initial_choice and doors_list[i]=="goat" ):
            # It is not fair to reveal the first goat. Here are considered all scenarios.
            possible_indexes.append(i)
    return np.random.choice(possible_indexes)

def second_choice(change,initial_choice,goat_index):
    '''
    Emulates the second choice, according to the rules of the game.
    '''
    if(change):
        choices=[]
        for i in base:
            if( i!=goat_index and i!=initial_choice ):
                choices.append(i)
        return np.random.choice(choices)
    else:
        return initial_choice

def MontyHall(change_arg):
    '''
    Simulates a Monty Hall game, with the changing choice given by the boolean "change".

    Returns True if win the car, False if win a goat.
    '''
    doors=init()
    n1=first_choice()
    goat=reveal_goat(doors,n1)
    n2=second_choice(change_arg,n1,goat)
    if doors[n2]=="goat":
        return False
    if doors[n2]=="car":
        return True

# Set up of several simulations

# Change option, n games

CH_win=0
CH_lose=0

for i in range(n):
    result=MontyHall(True)
    if(result):
        CH_win+=1
    else:
        CH_lose+=1

# Non-change option, n games

N_win=0
N_lose=0

for i in range(n):
    result=MontyHall(False)
    if(result):
        N_win+=1
    else:
        N_lose+=1

# Print the results:

if silent:
    print(round(N_win/n*100,3),round(CH_win/n*100,3))
else:
    print("There was a ",round(N_win/n*100,3),"%  of probabilities to win if one chooses not to change the selected door")
    print("There was a ",round(CH_win/n*100,3),"%  of probabilities to win if one chooses to change the selected door")


# -*- coding: utf-8 -*-
"""
Created on Fri Sep 17 02:09:53 2021

@author: Mahfuz_Shazol
"""

import numpy as np
import torch as th

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

A_trace=np.trace(A)
print(A_trace)

# Tr(A)=Tr(A.T)
result1=np.trace(A)
print(result1)
result2=np.trace(A.T)
print(result2)
print('Tr(A)=Tr(A.T)   Ans:',result1==result2)

#Calculate Frobenius norm AF=(Tr(A A.T))**(1/2)
A_p=th.tensor([
    [-1,2],
    [3,-2],
    [5,7],
    ])

calculated_frobenius_norm=(th.trace(th.matmul(th.as_tensor(A),th.as_tensor(A.T))))**(1/2)
print('calculated_frobenius_norm  Ans:',calculated_frobenius_norm)

norm_result=np.linalg.norm(A)
print(norm_result)


#!/usr/bin/python

import os
import numpy as np
import math

print "This script solves the excercises propossed in the Linear Algebra & 2D Geometry Lectures"

# --------------------------------------------------------------------------------------------------
# Length and Normalized Vector
v = np.array([4, 8, -4])
len_v = math.sqrt(pow(v[0], 2) + pow(v[1], 2) + pow(v[2], 2))
print "Length of v=" + str(v) + " is = " + str(len_v)

v_norm = v / len_v

print "Normalized vector" + str(v_norm) + ",length of normalized v =" + str(np.linalg.norm(v_norm))

# --------------------------------------------------------------------------------------------------
# Scalar and cross product

x1 = np.array([2, -4, 1])
x2 = np.array([2, 1, -2])
dot_product = x1[0] * x2[0] + x1[1] * x2[1] + x1[2] * x2[2]
print "The scalar product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(dot_product)

dot_product = np.dot(x1, x2)
print "The scalar product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(dot_product)

cross_product = np.cross(x1, x2)
print "The cross product of x1= " + str(x1) + ",and x2= " + str(x2) + " ,is =  " + str(cross_product)

# --------------------------------------------------------------------------------------------------
# Matrix Algebra
M1 = np.matrix([[1, 2], [3, 4]])
M2 = np.matrix([[5, 4], [3, 2]])
print "M1= " + str(M1)
print " + M2= " + str(M2)
print "=" + str(M1 + M2)

M1 = np.matrix([[-1, 0], [0, 1]])
M2 = np.matrix([[-2, 1], [-1, 2]])
print "M1= " + str(M1)
print " - M2= " + str(M2)
print "=" + str(M1 - M2)

# --------------------------------------------------------------------------------------------------
# Scalar Matrix Multiplication
M1 = np.matrix([[1, -2], [2, -1]])
print "2.5 * M1= " + str(2.5 * M1)

# --------------------------------------------------------------------------------------------------
# Matrix Vector Multiplication
M1 = np.matrix([[1, 0], [0, 1]])
x1 = np.array([1, 2])
print "x1 * M1= " + str(M1.dot(x1))

M1 = np.matrix([[1, -1], [-1, 1]])
x1 = np.array([5, 6])
print "x1 * M1= " + str(M1.dot(x1))

# --------------------------------------------------------------------------------------------------
# Matrix Multiplication
M1 = np.matrix([[1, 0], [0, -1]])
M2 = np.matrix([[1, -2], [2, -1]])
print "M1 * M2= " + str(M1 * M2)

M1 = np.matrix([[1, 2], [3, 4]])
M2 = np.matrix([[4, 3], [2, 1]])
print "M1 * M2= " + str(M1 * M2)


import read_data as RD
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

X = RD.read_data()
print('X = ',X.shape)
X_mean = np.reshape(np.sum(X,1)/X.shape[1],[ X.shape[0],1])
X = X-X_mean
print('X_centerred = ',X.shape)
[U,S,V] = np.linalg.svd(X, full_matrices=False)
print('U = ',U.shape)
print('S = ',S.shape)
print('V = ',V.shape)

N = 12#number of eigen images
Eig_im = U[:,0:N]
plt.figure(figsize=(10,10))
for i in range(0,N):
	plt.subplot(int(np.sqrt(N)),int(np.ceil(N/int(np.sqrt(N)))),i+1)
	im = np.reshape(Eig_im[:,i],[64,64])
	plt.imshow(im,cmap=plt.cm.gray, interpolation='none')
	plt.title('Eigen Image = '+str(i+1))

plt.savefig('Eigen_Images.png')
plt.savefig('Eigen_Images.tif')

Y = np.matmul(np.transpose(U),X)
print('Y = ',Y.shape)
plt.figure(figsize=(10,10))
Np = 10#Number of projection coefficients to plot
Ni = 4#Number of images
images = ['a','b','c','d']
for i in range(0,Ni):
	plt.plot(np.arange(1,Np+1),Y[0:Np,i],label='Image = '+images[i])
plt.xlabel('Eigenvectors',fontsize=20)
plt.xticks(weight = 'bold',fontsize=15)
plt.ylabel('Magnitude of the projection coefficient',fontsize=20)
plt.yticks(weight = 'bold',fontsize=15)
plt.legend(fontsize=20)
plt.savefig('Projection_Coefficients.png')
plt.savefig('Projection_Coefficients.tif')

#Image synthesis
ind = 0#index of the image to synthesize
m = [1, 5, 10, 15, 20, 30]
plt.figure(figsize=(10,15))
for i in range(0,len(m)):
	X_hat = np.reshape(np.matmul(U[:,0:m[i]],Y[0:m[i],ind]),[X.shape[0],1])
	print(X_hat.shape)
	print(X_mean.shape)
	X_hat += X_mean
	plt.subplot(3,2,i+1)
	im = np.reshape(X_hat,[64,64])
	plt.imshow(im,cmap=plt.cm.gray, interpolation='none')
	plt.title('m = '+str(m[i]),fontsize=20)
	plt.xticks(weight = 'bold',fontsize=15)
	plt.yticks(weight = 'bold',fontsize=15)
	#img_out = Image.fromarray(im.astype(np.uint8))
	#img_out.save('Im_reconstruction_'+str(m[i])+'.tif')
plt.savefig('Im_reconstruction.png')
plt.savefig('Im_reconstruction.tif')


from __future__ import print_function, division

import numpy as np
import scipy.linalg as la

def simple_lu(A):
    R = A.copy()
    L = np.eye(A.shape[0])
    for i in range(A.shape[0] - 1):
        factors = R[i + 1:, i] / R[i, i]
        R[i+1:, :] -= factors[:, None] * R[i, :]
        L[i+1:, i] = factors
    return L, R

A = np.array([[1.1, 2.1, 3.1],
              [2.2, 3.2, 1.2],
              [3.3, 1.3, 2.3]])

print("Simple LR without pivotisierung")
L, R = simple_lu(A)
print("A:\n", A)
print("L:\n", L)
print("R:\n", R)
print("L*R:\n", L.dot(R))

def lu(A):
    R = A.copy()
    L = np.eye(A.shape[0])
    p_rows = np.arange(A.shape[0])
    for i in range(A.shape[0] - 1):
        # pivot wahl
        pivot_row = i + np.argmax(np.abs(R[i:, i]))
        p_rows[i], p_rows[pivot_row] = p_rows[pivot_row], p_rows[i]
        R[i, :], R[pivot_row, :] = R[pivot_row, :], R[i, :].copy()
        # eliminate
        factors = R[i + 1:, i] / R[i, i]
        R[i + 1:, :] -= factors[:, None] * R[i, :]
        L[i + 1:, i] = factors
    P = np.zeros(A.shape)
    for i, p in enumerate(p_rows):
        P[i, p] = 1
    return P, L, R

print("Simple LR with pivotisierung")
P, L, R = lu(A)
print("A:\n", A)
print("L:\n", L)
print("R:\n", R)
print("P:\n", P)
print("L*R - P*A:\n", L.dot(R) - P.dot(A))

def backsubstitution(upper_triangular_matrix, rhs):
    sol = np.zeros(rhs.shape[0])
    for i in range(rhs.shape[0] - 1, -1, -1):
        sol[i] = (rhs[i] - np.sum(upper_triangular_matrix[i, i + 1:]*sol[i + 1:]))/upper_triangular_matrix[i, i]
    return sol

def forwardsubstituition(lower_triangular_matrix, rhs):
    sol = np.zeros(rhs.shape[0])
    for i in range(rhs.shape[0]):
        sol[i] = (rhs[i] - np.sum(lower_triangular_matrix[i, :i]*sol[:i]))/lower_triangular_matrix[i, i]
    return sol

def lu_solve(P, L, R, b):
    # Ax = b
    # PA = LR
    # P^-1*L*Rx = b
    # LRx = Pb
    # y = Rx
    # Ly = Pb
    # Rx = y
    y = forwardsubstituition(L, P.dot(b))
    x = backsubstitution(R, y)
    return x

b = np.array([1,2,3])
x = lu_solve(P, L, R, b)
print("b:\n", b)
print("x with Ax = b:\n", x)
print("Ax - b:\n", A.dot(x) - b)


import numpy as np
from PIL import Image
import settings as globalv
import pca_utils as pca
from matplotlib import pyplot as plt
import glob
from matplotlib.image import imread
import warnings # Disable warning: https://stackoverflow.com/questions/41001533/how-to-ignore-python-warnings-for-complex-numbers
warnings.filterwarnings('ignore')
# from sklearn.preprocessing import normalize

globalv.init(False)

# globalv.N = 14
globalv.N = 982
N = globalv.N
# imgsize = 50
imgsize = 28
globalv.D = imgsize**2
D = globalv.D

img = np.zeros((imgsize,imgsize))
data = np.zeros((D, N))

plt.figure(1)

i = 0
for filename in glob.glob("four_dataset/*.jpg"):
    img = np.asarray(imread(filename))
    data[:, i] = np.ravel(img)
    i = i + 1

# for i in range(N):
# 	img = np.asarray(Image.open('faces/' + str(i+1) +'.jpg').convert('L'))
# 	data[:,i] = np.ravel(img)

print(" *** End reading image ..")

# The loaded images form a data vector of dimensions DxN, D=76^2, N = 6
# that is, 6 patterns of 76^2-dimensional data
# Because there are less patterns than dimensions, the maximum number of uncorrelated
# components we can keep is 6-1 = 5, because there are no more directions of data variability

# Do PCA:
centered_data, mean_vector = pca.centerData(data)
corr = pca.correlationMatrix(centered_data)
eigvals, eigvecs = pca.eigenDecomposition(corr)
# r = pca.readUserNumComponents('\nHow many components (0 <= int <= %d)?\n' %D)
n_comp = 4
# proj_matrix = pca.computeProjectionMatrix(eigvals, eigvecs, r)
proj_matrix = pca.computeProjectionMatrix(eigvals, eigvecs, n_comp)
princ_comps = pca.computePrincipalComponents(centered_data, proj_matrix)

reconstructed_data = pca.reconstructData(princ_comps, proj_matrix, mean_vector)

error = pca.computeRecError(data, reconstructed_data)

# Printing the graph
ori_data = data[:, 0].reshape(imgsize, imgsize)
rec_data = reconstructed_data[:, 0].reshape(imgsize, imgsize)

f, (ax1, ax2) = plt.subplots(1, 2)
f.suptitle('PCA comparison with #Dim = %s' % n_comp)
ax1.imshow(ori_data, cmap='gray', interpolation='none')
ax1.set_title('Original Image')
ax2.imshow(rec_data, cmap='gray', interpolation='none')
ax2.set_title('Compressed Image')
plt.show()

# n_max = 1
# for i in range(n_max):
#     rec_data = reconstructed_data[:, i].reshape(imgsize, imgsize)
#     plt.subplot(2, n_max, n_max+i+1)
#     plt.axis('off')
#     plt.imshow(rec_data, cmap='gray', interpolation='none')
#
# plt.show()


import numpy as cp

class Sigmoid():

    '''

    A Sigmoid function is a mathematical function which has a characteristic S-shaped curve.
    Normally used to refer specifically to the logistic function, also called the logistic
    sigmoid function.sigmoid functions have the property that they map the entire number line
    into a small range such as between 0 and 1,so one use of a sigmoid function is to convert
    a real value into one that can be interpreted as a probability.

    The logistic sigmoid function is defined as follows:

        S(x) =   _____1_______ where exp is exponential
                ( 1 + exp(-x))

     With the help of Sigmoid activation function, we are able to reduce the loss during
     the time of training because it eliminates the gradient problem in machine learning
     model while training.

    Parameters
    ----------
    x : cp.array
        Array of ouputs of deeplearning layer.

    Returns
    -------
    cp.array
        Array after sigmoid function applied .

    '''

    def __call__(self, x: cp.array) -> cp.array:

        return 1 / (1 + cp.exp(-x))

    def gradient(self, x: cp.array) -> cp.array:
        '''

        Parameters
        ----------
        x : cp.array
             Array of ouputs of deeplearning layer..

        Returns
        -------
        cp.array
            Array after derivatives of sigmoid applied .

        '''
        return self.__call__(x) * (1 - self.__call__(x))

tutorial/7. Deep Learning/Activation Function/Sigmoid.py


'''
    Vector length formula for three-dimensional vector
    --------------------------------------------------
    In the case of the spatial problem the length of the vector a = {ax ; ay ; az}
    can be found using the   following formula:

    |a| = √ax2 + ay2 + az2

    Examples of plane tasks
    Example 1. Find the length of the vector a = {2; 4}.
    Solution: |a| = √2^2 + 4^2 = √4 + 16 = √20 = 2√5.

    Example 2. Find the length of the vector a = {3; -4}.
    Solution: |a| = √3^2 + (-4)^2 = √9 + 16 = √25 = 5.

    Examples of spatial tasks
    Example 3. Find the length of the vector a = {2; 4; 4}.
    Solution: |a| = √2^2 + 4^2 + 4^2 = √4 + 16 + 16 = √36 = 6.

    Example 4. Find the length of the vector a = {-1; 0; -3}.
    Solution: |a| = √(-1)^2 + 0^2 + (-3)^2 = √1 + 0 + 9 = √10.
'''

import numpy as np

a = np.array([2,4,4]) # 3 dimension

total = 0

for i in range(len(a)):
    total += a[i] ** 2

print(np.sqrt(total))
# or
print(np.linalg.norm(a))

a = np.array([-1, 0, -3, -4, 10])

total = 0

for i in range(len(a)):
    total += a[i] ** 2

print(np.sqrt(total))
#or
print(np.linalg.norm(a))


# Least square method of univariate linear regression.
# Solution/weight coeffecients: y = mx + c -> m = sum((Mean-deviation of Xi) * (Mean-deviation of Yi)) / sum((Mean-deviation of Xi) ** 2)
#                                             c = Mean-Y - m * Mean-X
# https://www.amherst.edu/system/files/media/1287/SLR_Leastsquares.pdf

import numpy as np
import matplotlib.pyplot as plt

class LinearRegression:
    def __init__(self):
        self._X = None
        self._y = None
        self._beta0 = 0.
        self._beta1 = 0.
        self._correlation = 0.

    def _mean(self, arr):
        return np.mean(arr)

    def _diff(self, arr, num):
        return np.subtract(arr, num)

    def fit(self, X, y):
        self._X = X
        self._y = y
        self._compute_correlation()

        mean_x = self._mean(self._X)
        mean_y = self._mean(self._y)
        mean_deviation_x = self._diff(self._X, mean_x)
        mean_deviation_y = self._diff(self._y, mean_y)

        self._beta1 = np.sum(np.multiply(mean_deviation_x, mean_deviation_y)) / np.sum(mean_deviation_x ** 2)
        self._beta0 = mean_y - self._beta1 * mean_x

        train_pred = self.predict(X)
        return self._rmse_error(y, train_pred)

    def _rmse_error(self, y, y_pred):
        return np.sqrt(np.mean(np.subtract(y, y_pred) ** 2))

    def predict(self, input):
        return np.add(self._beta0, np.dot(self._beta1, input))

    def weight_coefficients(self, decimal=4):
        return round(self._beta0, decimal), round(self._beta1, decimal)

    def _compute_correlation(self):
        sum_x = np.sum(self._X)
        sum_y = np.sum(self._y)
        sum_xy = np.sum(np.multiply(self._X, self._y))
        sum_xsquare = np.sum(np.multiply(self._X, self._X))
        sum_ysquare = np.sum(np.multiply(self._y, self._y))
        n = len(self._X)
        self._correlation = (n * sum_xy - sum_x * sum_y) / np.sqrt(((n * sum_xsquare - sum_x ** 2) * (n * sum_ysquare - sum_y ** 2)))

    def correlation(self, decimal=4):
        return round(self._correlation, decimal)

    def plot_regression(self, X, y):
        plt.scatter(X, y, color="red", marker="x", s=20)

        y_pred = self._beta0 + np.multiply(self._beta1, X)

        plt.plot(X, y_pred, color="green")

        plt.xlabel('X')
        plt.ylabel('y')

        plt.show()

def main():
    linear_reg = LinearRegression()
    X = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    y = [6, 8, 9, 11, 13, 16, 17, 19, 20, 24]

    rmse_error = linear_reg.fit(X, y)
    weights = linear_reg.weight_coefficients()
    print 'Weight coefficients for y = mx + c: m = {}, c = {}\n'.format(weights[1], weights[0])
    print 'Correlation between X and y: {}\n'.format(linear_reg.correlation())
    print 'Root mean-squared error for training: {}\n'.format(rmse_error)

    test_y = [3, 5, 10]
    print 'Prediction of {}: {}'.format(test_y, (linear_reg.predict(test_y)))
    linear_reg.plot_regression(X, y)

if __name__ == '__main__':
    main()


# ***Question (1)***
#
# Part (a)
#
# Simultaneous equations for the loops:
# 80*i1-50*i2-30*i3 = 240
# 50*i1-100*i2-10*i3-25*i4 = 0
# -30*i1-10*i2+65*i3-20*i4 = 0
# -25*i2-20*i3-100*i4 = 0

# Part (b)

import scipy as sp
import numpy as np
import gaussElimin as gE

A = sp.array([[80.0,-50.0,-30.0,0.0],[-50.0,100.0,-10.0,-25.0],[-30.0,-10.0,65.0,-20.0],[0.0,-25.0,-20.0,100.0]]) # array of coefficients in simultaneous equations
b = sp.array([240.0,0.0,0.0,0.0]) # results for all the simultaneous equations

print "For the first array, plug in all of the coefficients of the simultaneous equations derived from the circuit diagrams. This array, A:"
print A

print "For the second array, plug in all the results of the simultaneous equations derived from the circuit diagram. This array, b:"
print b

x = gE.gaussElimin(A,b)

print "Using the Gaussian elimination module, we can solve:"
print x

# Part (c)

from scipy import linalg

P, L, U = linalg.lu(A) # computes LU factorisation explicitly where P is the permutation matrix, L is the lower triangular matrix, and U is the upper triangular matrix

print "We can use an LU (Lower-Upper) decomposition to decompose our resultant matrix into an upper part and a lower part. The lower part:"
print L

print "And the upper part:"
print U

# Part (d)

print "Next, use the LU decomposition to solve Ax = b"

y1 = gE.gaussElimin(L,b) # lower triangular matrix and b

x1 = gE.gaussElimin(U,y1) # upper triangular matrix and the gE of lower and b
print x1

print "Now we want to use our LU decomposition to prove that we can go the other direction and get an answer from it too."
print "Using a new array where the voltage across is 120 V instead of 240 V (as the range is 0 V to +120 V instead of -120 V to +120 V:"

c = sp.array([240.0,0.0,0.0,0.0]) # new b array where the -120 V is = 0 V

y2 = gE.gaussElimin(L,c) # same process as with y1 and x1

x2 = gE.gaussElimin(U,y2)

print x2

ExamPrep/Shit Comp/SciComp Exam 2016/Assignments 15-16/A1Q1 LU decomp Gauss Elim.py


# Load the libraries
import numpy as np

# Creating arrays
# Method 1 : From a list/tuple
arr_list = np.array([[[1,2,3,4],
                     [5,6,7,8],
                     [9,10,11,12]],
                    [[13,14,15,16],
                     [17,18,19,20],
                     [21,22,23,24]]],dtype="int32")

print(f'Numpy array from a list :\n{arr_list}')

# Method 2 : From Built-in routines

print(f"\n Using np.ones to create an array : \
\n{np.ones((3,2),dtype=float)}")

print(f"\n Using np.zeros to create an array : \
\n{np.zeros((4,3),dtype=int)}")

print(f"\n Using np.full to create an array : \
\n{np.full((3,4,5),0.34)}")

# Uses numerical range built-in functions

print(f'\n Using np.arange with step of 5 to create ndarray : \
\n{np.arange(25,40,5)}')

print(f'\n Using np.linspace with equally spaced elements to create ndarray : \
\n{np.linspace(25,40,4)}')

# Uses np.random
print(f'\n Using np.random.rand to create a uniform random array : \
\n{np.random.rand(2,4)}')

print(f'\n Using np.random.randn to create a normal random array : \
\n{np.random.randn(2,4)}')

print(f'\n Using np.random.normal to create a  \
random array of float in given interval : \
\n{np.random.normal(-15,15,(2,3,4))}')

print(f'\n Using np.random.randint to create a  \
random array of integers in given interval : \
\n{np.random.randint(25,75,(2,3,4))}')

# Uses np.eye
print(f'\n Using np.eye to create a matrix with kth diagonal \
elements set to 1 and others as 0: \
\n{np.eye(4,k=1)}')

# Loading files(txt,csv) to form numpy arrays

# 1. Using np.fromfile
np.random.rand(2,10000).tofile("/Users/pavitragajanana/development/5. InternalFiles/randomtext")
# Reading the file using fromfile
randtext = np.fromfile("/Users/pavitragajanana/development/5. InternalFiles/randomtext")

# 2. Using np.genfromtxt
datafile_genfromtxt = np.genfromtxt("/Users/pavitragajanana/development/2. Data Files/CrudeOil_Daily_Cushing_OK_WTI_Spot_Price_FOB.csv",
                                    usecols=[1],
                                    delimiter=",",
                                    skip_header=1)

# 3. Loading csv files into numpy array using np.loadtxt
datafile_arr = np.loadtxt("/Users/pavitragajanana/development/2. Data Files/CrudeOil_Daily_Cushing_OK_WTI_Spot_Price_FOB.csv",
                          delimiter=",",
                          skiprows=1, # Header
                          usecols=[1])

# 4. Using csv library
import csv
from datetime import datetime

with open("/Users/pavitragajanana/development/2. Data Files/CrudeOil_Daily_Cushing_OK_WTI_Spot_Price_FOB.csv", 'r') as f:
    datafile = list(csv.reader(f, delimiter=","))

# Converts the list to an ndarray
datafile = np.array(datafile)
# Converts into default datetime format
datearray = np.array([datetime.strptime(x, "%m/%d/%y").strftime('%Y-%m-%d') for x in datafile[1:,0]])
nominal = datafile[1:,1]

# Recreates original array
datafile = np.vstack([datearray,nominal]).T

# Array Attributes (size,shape,ndim,nbytes,itemsize,dtype)

# We create a 1D, 2D and a 3D array to explore the attributes
# One-Dimensional Array
one_dim_array = np.random.randint(12, size=7)

# Two-Dimensional Array
two_dim_array = np.array([["Cupcake","Donut"],
                   ["Eclair","Froyo"],
                   ["Gingerbread","Honeycomb"],
                   ["Ice Cream Sandwich","Jelly Bean"],
                   ["KitKat","Lollipop"],
                   ["Marshmallow","Nougat"],
                   ["Oreo","Pie"]])

# Three-Dimensional Array
three_dim_array = np.array([[["Civic","Accord","Pilot","FR-V"],
                       ["Odyssey","Jazz","CR-V","NSX"]],
                      [["Insight","Ridgeline","Legend","HR-V"],
                       ["Passport","S660","Clarity","Mobilio"]],
                      [["Airwave","Avancier","Beat","Shuttle"],
                       ["Concerto","Element","Logo","Stream"]]])

# Array Attributes for 1D array
print(f'Dimensions of 1D array : {one_dim_array.ndim}')
print(f'Size of 1D array : {one_dim_array.size}')
print(f'Shape of 1D array : {one_dim_array.shape}')
print(f'Total Bytes consumed by 1D array : {one_dim_array.nbytes}')
print(f'dtype of 1D array : {one_dim_array.dtype}')
print(f'Itemsize of 1D array : {one_dim_array.itemsize}')

# Array Attributes for 2D array
print(f'Dimensions of 2D array : {two_dim_array.ndim}')
print(f'Size of 2D array : {two_dim_array.size}')
print(f'Shape of 2D array : {two_dim_array.shape}')
print(f'Total Bytes consumed by 2D array : {two_dim_array.nbytes}')
print(f'dtype of 2D array : {two_dim_array.dtype}')
print(f'Itemsize of 2D array : {two_dim_array.itemsize}')

# Array Attributes for 3D array
print(f'Dimensions of 3D array : {three_dim_array.ndim}')
print(f'Size of 3D array : {three_dim_array.size}')
print(f'Shape of 3D array : {three_dim_array.shape}')
print(f'Total Bytes consumed by 3D array : {three_dim_array.nbytes}')
print(f'dtype of 3D array : {three_dim_array.dtype}')
print(f'Itemsize of 3D array : {three_dim_array.itemsize}')

pyFiles/NumPyCodeFiles_Array Creation and Attributes.py


"""
Created on Aug  30

@author: tsquire

Description: Solving the knapsack problem (see below) using dynamic programming

max 3x1 + x2 + 6x3
s.t 4x1 + 5x2 + 10x3 <= 20
"""

import numpy as np

c = np.array([3,1,6])
a= np.array([4,5,10])
d= 20
n= c.size

def KnapSackSolve(costs,weights,cap,varNum):
    valMat = np.zeros([varNum,cap+1])
    decMat = np.zeros([varNum,cap+1])
    for i in range(varNum-1,-1,-1):
        for j in range(cap+1):
            range2Check = int(j/weights[i])
            maxVal = 0
            maxPos = -1
            for k in range(range2Check+1):
                if i == varNum-1:
                    val = k*costs[i]
                else:
                    val = k*costs[i]+valMat[i+1,j-k*weights[i]]
                if val >= maxVal:
                    maxVal = val
                    maxPos = k
            valMat[i,j] = maxVal
            decMat[i,j] = maxPos
    return valMat,decMat

def EvalSolu(valMat,decMat,costs,weights,cap,varNum):
    x = np.zeros(varNum)
    currentCap = cap
    totalCost = 0
    for i in range(varNum):
        x[i] = decMat[i,currentCap]
        totalCost += x[i]*costs[i]
        currentCap -= int(x[i]*weights[i])
    return x,totalCost

valMat,decMat = KnapSackSolve(c,a,d,n)
solu,totalCost = EvalSolu(valMat,decMat,c,a,d,n)


"""Halton low discrepancy sequence.

This snippet implements the Halton sequence following the generalization of
a sequence of *Van der Corput* in n-dimensions.

---------------------------

MIT License

Copyright (c) 2017 Pamphile Tupui ROY

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np
import tensorflow as tf

def primes_from_2_to(n):

	sieve = np.ones(n // 3 + (n % 6 == 2), dtype=np.bool)
	for i in range(1, int(n ** 0.5) // 3 + 1):
		if sieve[i]:
			k = 3 * i + 1 | 1
			sieve[k * k // 3::2 * k] = False
			sieve[k * (k - 2 * (i & 1) + 4) // 3::2 * k] = False
	return np.r_[2, 3, ((3 * np.nonzero(sieve)[0][1:] + 1) | 1)]

def van_der_corput(n_sample, base=2):

	sequence = []
	for i in range(n_sample):
		n_th_number, denom = 0., 1.
		while i > 0:
			i, remainder = divmod(i, base)
			denom *= base
			n_th_number += remainder / denom
		sequence.append(n_th_number)

	return sequence

def halton(dim, n_sample):

	big_number = 10
	while 'Not enought primes':
		base = primes_from_2_to(big_number)[:dim]
		if len(base) == dim:
			break
		big_number += 1000

	sample = [van_der_corput(n_sample + 1, dim) for dim in base]
	sample = np.stack(sample, axis=-1)[1:]

	return sample

def halton_batch(batch_size, dim, n_sample, extent=1.5, z_offset=0.):

	if z_offset == 0:
		return tf.cast(tf.constant([halton(dim, n_sample) for _ in range(batch_size)]), tf.float32) * (extent / 0.5)
	else:
		arr = np.array([halton(dim, n_sample) for _ in range(batch_size)], dtype=np.float32) * (extent / 0.5)
		arr[:, :, 2] = z_offset
		arr[:, :, :2] = arr[:, :, :2] - np.mean(arr[:, :, :2], axis=1).reshape(arr.shape[0], -1, 2)
		return tf.constant(arr)


import numpy as np
import matplotlib.pyplot as plt
from tictoc import *
from plot import plot
#from RFA import secant

def secant(f,x0,x1,TOL,NMAX):
    #INPUT : f , x0 , x1 , TOL , NMAX
    # f : function / polynomial
    # x0 : first initial point/guess
    # x1 : second initial point/guess
    # TOL : tolerance value
    # NMAX : maximum number of iterations

    #Approximates root using Secant Method

    #Print Header
    print('--------------------------------------------------------------------------')
    print("iter \t\t\t x2 \t\t\t\t f(x2)")
    print('--------------------------------------------------------------------------')

    for i in range(NMAX):
        if f(x0) == f(x1):
            print('DIVIDE BY ZERO ENCOUNTERED!')
            exit()
        #improved guess
        x2 = x0 - (x1-x0)*f(x0)/(f(x1)-f(x0))
        N.append(1+i)
        f_x.append(f(x2))
        #print values after each iteration step
        print("%d \t\t %15.12f \t\t %15.12f" %(1+i,x2,f(x2)))
        x0 = x1
        x1 = x2

        #executing the required precision
        if abs(f(x2)) < TOL:
            print('--------------------------------------------------------------------------')
            print('Root Found : ',x2)
            break
    #limiting number of iterations
    if i == NMAX -1:
        print("MAX NUMBER OF ITERATIONS REACHED!")
        print('Approximaiton to the Root after max iterations is : '+str(c))
        exit()

f1 = lambda x: x**3 - 3*(x**2) - x + 9
f2 = lambda x: np.exp(x)*(x**3 - 3*(x**2) - x + 9)
x0 = -2
x1 = 5

N=[]
f_x=[]
tic()
secant(f1,x0,x1,1.e-10,1000)
toc()
plot(N,f_x,'b',1)

N=[]
f_x=[]
tic()
secant(f1,x0-0.5,x1+0.5,1.e-10,1000)
toc()
plot(N,f_x,'r',1)
plt.legend(['(x0,x1)=(-2,2)','(x0,x1)=(-2.5,5.5)'])
plt.show()

N=[]
f_x=[]
tic()
secant(f2,x0,x1,1.e-10,1000)
toc()
plot(N,f_x,'b',2)

N=[]
f_x=[]
tic()
secant(f2,x0-0.5,x1+0.5,1.e-10,1000)
toc()
plot(N,f_x,'r',2)
plt.legend(['(x0,x1)=(-2,2)','(x0,x1)=(-2.5,5.5)'])
plt.show()


# import packages
import numpy as np
import matplotlib.pyplot as plt
#from scipy.stats import linregress
from scipy.integrate import simps
#from matplotlib.patches import Polygon

# input data
x = np.array([1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5,9, 9.5, 10])
y = np.array([3.43, 4.94, 6.45, 9.22, 6.32, 6.11, 4.63, 8.95, 7.8, 8.35, 11.45, 14.71, 11.97, 12.46, 17.42, 17.0, 15.45, 19.15, 20.86])

# 1: fit linear
l_co = np.polyfit(x, y, 1)
l_fit = np.poly1d(l_co)

# 2: fit cubic
c_co = np.polyfit(x, y, 3)
c_fit = np.poly1d(c_co)

# 3: Find the area underneath the cubic curve
t = np.linspace(1, 10, 200)
area = simps(c_fit(t),t)
print("Area under cubic line = ",area)

# 4 & 5: Plot the data, the linear fit, and the cubic fit; Put the area on the plot

plt.scatter(x,y, label = 'Data')
plt.plot(x,l_fit(x),'r', label = 'Linear Fit')
plt.plot(t,c_fit(t),'g', label = 'Cubic Fit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Fitting the data')
plt.legend()
plt.text(0.8,16,'Area under cubic line = '+str(area))
plt.savefig('ylu_data_fitting.pdf')

# 6: Justify preferable model (linear or cubic) by BIC
def BIC(y, yhat, k, weight = 1):
    err = y - yhat
    sigma = np.std(np.real(err))
    n = len(y)
    B = n*np.log(sigma**2) + weight*k*np.log(n)
    return B
linear = BIC(y,l_fit(x),len(l_co)) # k = number of coefficients
cubic = BIC(y,c_fit(x),len(c_co))
print('BIC(linear):',linear)
print('BIC(cubic):',cubic)
if linear <= cubic:
    print('linear model is preferable')
else:
    print('cubic model is preferable')


#!/usr/bin/env python
# coding: utf-8
# # CAPM - Capital Asset Pricing Model
# Watch the video for the full overview.
# Portfolio Returns:
# ## $r_p(t) = \sum\limits_{i}^{n}w_i r_i(t)$
# Market Weights:
# ## $ w_i = \frac{MarketCap_i}{\sum_{j}^{n}{MarketCap_j}} $
# ### CAPM of a portfolio
# ## $ r_p(t) = \beta_pr_m(t) + \sum\limits_{i}^{n}w_i \alpha_i(t)$
# Model CAPM as a simple linear regression
from scipy import stats
import pandas as pd
import pandas_datareader as web
import matplotlib.pyplot as plt
import numpy as np

help(stats.linregress)
spy_etf = web.DataReader('SPY', 'yahoo')
# In[21]:
spy_etf.info()
# In[22]:
spy_etf.head()
# In[18]:
start = pd.to_datetime('2010-01-04')
end = pd.to_datetime('2017-07-18')
aapl = web.DataReader('AAPL', 'yahoo', start, end)
aapl.head()
# In[28]:
#get_ipython().run_line_magic('matplotlib', 'inline')
aapl['Close'].plot(label='AAPL', figsize=(10, 8))
spy_etf['Close'].plot(label='SPY Index')
plt.legend()
# ## Compare Cumulative Return
aapl['Cumulative'] = aapl['Close'] / aapl['Close'].iloc[0]
spy_etf['Cumulative'] = spy_etf['Close'] / spy_etf['Close'].iloc[0]
aapl['Cumulative'].plot(label='AAPL', figsize=(10, 8))
spy_etf['Cumulative'].plot(label='SPY Index')
plt.legend()
plt.title('Cumulative Return')
# ## Get Daily Return
aapl['Daily Return'] = aapl['Close'].pct_change(1)
spy_etf['Daily Return'] = spy_etf['Close'].pct_change(1)
plt.scatter(aapl['Daily Return'], spy_etf['Daily Return'], alpha=0.3)
# In[46]:
aapl['Daily Return'].hist_df(bins=100)
spy_etf['Daily Return'].hist_df(bins=100)
beta, alpha, r_value, p_value, std_err = stats.linregress(aapl['Daily Return'].iloc[1:],
                                                          spy_etf['Daily Return'].iloc[1:])
beta
# In[39]:
alpha
# In[40]:
r_value
# ## What if our stock was completely related to SP500?
spy_etf['Daily Return'].head()
noise = np.random.normal(0, 0.001, len(spy_etf['Daily Return'].iloc[1:]))
noise
# In[65]:
spy_etf['Daily Return'].iloc[1:] + noise
# In[66]:
beta, alpha, r_value, p_value, std_err = stats.linregress(spy_etf['Daily Return'].iloc[1:] + noise,
                                                          spy_etf['Daily Return'].iloc[1:])
beta
# In[68]:
alpha
# Looks like our understanding is correct!


import numpy as np
from numpy.linalg import svd
from scipy.io import loadmat
import matplotlib.pyplot as plt

def featureNormalize(X):
    """
    Normalize the dataset X

    :param X:
    :return:
    """

    mu = np.mean(X, axis=0)
    sigma = np.std(X, axis=0)

    X_normalized = (X - mu) / sigma

    return X_normalized, mu, sigma

def pca(X):
    """
    Compute eigenvectors of the covariance matrix X

    :param X:
    :return:
    """

    number_of_examples = X.shape[0]
    sigma = (1/number_of_examples) * np.dot(X.T, X)
    U, S, V = svd(sigma)

    return U, S, V

def projectData(X, U, K):
    """
    Computes the reduced data representation when projecting only onto
    the top K eigenvectors

    :param X: Dataset
    :param U: Principal components
    :param K: The desired number of dimensions to reduce
    :return:
    """

    number_of_examples = X.shape[0]
    U_reduced = U[:, :K]

    Reduced_representation = np.zeros((number_of_examples, K))

    for i in range(number_of_examples):
        for j in range(K):
            Reduced_representation[i, j] = np.dot(X[i, :], U_reduced[:, j])

    return Reduced_representation

def recoverData(Z, U, K):
    """
    Recovers an approximation of the original data when using the projected data

    :param Z: Reduced representation
    :param U: Principal components
    :param K: The desired number of dimensions to reduce
    :return:
    """

    number_of_examples = Z.shape[0]
    number_of_features = U.shape[0]

    X_recovered = np.zeros((number_of_examples, number_of_features))
    U_reduced = U[:, :K]

    for i in range(number_of_examples):
        X_recovered[i, :] = np.dot(Z[i, :], U_reduced.T)

    return X_recovered

'''
Step 0: Load the dataset.
'''

dataset = loadmat("./data/lab9data2.mat")
print(dataset.keys(), '\n')

X = dataset["X"]

'''
Step 1: Normalize the dataset X
'''

X_normalized, mu, std = featureNormalize(X)
print("Values of the first 3 normalized dataset X: \n{}\n".format(X_normalized[:3]))

'''
Step 2: Compute the principal components and the diagonal matrix
'''

U, S, _ = pca(X_normalized)

print("The principal components: \n{}\n".format(U))
print("The diagonal matrix: \n{}\n".format(S))

'''
Step 3: Project the data onto 1 dimension
'''

reduced_representation = projectData(X_normalized, U, 1)
print("Projection of the first example: {}\n".format(reduced_representation[0][0]))

'''
Step 4: Reconstruct an approximation of the data (which was reduced to 1 dimension)
'''

X_recovered = recoverData(reduced_representation, U, 1)
print("Approximation of the first example: {}\n".format(X_recovered[0, :]))

'''
Step 5: Visualizing the projections
'''

plt.scatter(X_normalized[:, 0], X_normalized[:, 1], marker="o", label="Original", facecolors="none", edgecolors="b", s=15)
plt.scatter(X_recovered[:, 0], X_recovered[:, 1], marker="o", label="Approximation", facecolors="none", edgecolors="r", s=15)
plt.title("The Normalized and Projected Data after PCA")
plt.legend()


import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
from numpy import linalg as LA

fig = plt.figure()
ax = fig.add_subplot(321, aspect='equal')
# Parallelogram
xy = np.array([[0,0], [7,2], [12,11],[5,9]])
mean = np.mean(xy, axis=0)
print('Mean', mean)
cov = np.cov(xy.T)
print('Cov::')
print(cov)
eigenvalues, eigenvectors = LA.eig(cov)
print('Eigen values', eigenvalues)
print('Eigen vectors', eigenvectors)
ax.add_patch(patches.Polygon(xy, fill=False))
plt.quiver(mean[0],mean[1],eigenvectors[0,0],eigenvectors[0,1], color=['r'],scale=5, label="eigen vector[0]")
plt.quiver(mean[0],mean[1],eigenvectors[1,0],eigenvectors[1,1], color=['g'],scale=5, label="eigen vector[1]")

ax1 = fig.add_subplot(322, aspect='equal')
# Parallelogram : Square
xy1 = np.array([[0,0], [0,3], [3,3],[3,0]])
mean1 = np.mean(xy1, axis=0)
print('Mean', mean1)
cov1 = np.cov(xy1.T)
print('Cov::')
print(cov1)
eigenvalues1, eigenvectors1 = LA.eig(cov1)
print('Eigen values', eigenvalues1)
print('Eigen vectors', eigenvectors1)
ax1.add_patch(patches.Polygon(xy1, fill=False))
ax1.quiver(mean1[0],mean1[1],eigenvectors1[0,0],eigenvectors1[0,1], color=['r'],scale=5, label="eigen vector[0]")
ax1.quiver(mean1[0],mean1[1],eigenvectors1[1,0],eigenvectors1[1,1], color=['g'],scale=5, label="eigen vector[1]")

ax2 = fig.add_subplot(323, aspect='equal')
# Parallelogram : Rectangle
xy2 = np.array([[0,0], [0,2], [4,2],[4,0]])
mean2 = np.mean(xy2, axis=0)
print('Mean', mean2)
cov2 = np.cov(xy2.T)
print('Cov::')
print(cov2)
eigenvalues2, eigenvectors2 = LA.eig(cov2)
print('Eigen values', eigenvalues2)
print('Eigen vectors', eigenvectors2)
ax2.add_patch(patches.Polygon(xy2, fill=False))
ax2.quiver(mean2[0],mean2[1],eigenvectors2[0,0],eigenvectors2[0,1], color=['r'],scale=5, label="eigen vector[0]")
ax2.quiver(mean2[0],mean2[1],eigenvectors2[1,0],eigenvectors2[1,1], color=['g'],scale=5, label="eigen vector[1]")

ax3 = fig.add_subplot(324, aspect='equal')
# Parallelogram : Rectangle
xy3 = np.array([[0,0], [-1,3], [4,3],[5,0]])
mean3 = np.mean(xy3, axis=0)
print('Mean', mean3)
cov3 = np.cov(xy3.T)
print('Cov::')
print(cov3)
eigenvalues3, eigenvectors3 = LA.eig(cov3)
print('Eigen values', eigenvalues3)
print('Eigen vectors', eigenvectors3)
ax3.add_patch(patches.Polygon(xy3, fill=False))
ax3.quiver(mean3[0],mean3[1],eigenvectors3[0,0],eigenvectors3[0,1], color=['r'],scale=5, label="eigen vector[0]")
ax3.quiver(mean3[0],mean3[1],eigenvectors3[1,0],eigenvectors3[1,1], color=['g'],scale=5, label="eigen vector[1]")

ax4 = fig.add_subplot(325, aspect='equal')
# Parallelogram
xy4 = np.array([[0,0], [1,3], [5,3],[4,0]])
mean4 = np.mean(xy4, axis=0)
print('Mean', mean4)
cov4 = np.cov(xy4.T)
print('Cov::')
print(cov4)
eigenvalues4, eigenvectors4 = LA.eig(cov4)
print('Eigen values', eigenvalues4)
print('Eigen vectors', eigenvectors4)
ax4.add_patch(patches.Polygon(xy4, fill=False))
ax4.quiver(mean4[0],mean4[1],eigenvectors4[0,0],eigenvectors4[0,1], color=['r'],scale=5, label="eigen vector[0]")
ax4.quiver(mean4[0],mean4[1],eigenvectors4[1,0],eigenvectors4[1,1], color=['g'],scale=5, label="eigen vector[1]")
plt.show()


'''
The NumPy ndarray: A Multidimensional Array Object

One of the key features of NumPy is its N-dimensional array object, or ndarray,
which is a fast, flexible container for large datasets in Python.
Arrays enable you to perform mathematical operations on whole blocks of data using similar syntax to the
equivalent operations between scalar elements.
'''

'''
Problem 2)
Generating random data using ndarray
'''

import numpy as np

data1 = np.random.randn(3) # returns a random sample
print(data1) # run the program in python terminal to see the result --- row-vector with 3 elements is produced

data2 = np.random.randn(2,4) # returns a random array with 2 rows and 4 columns each
print(data2) # run the program in python terminal to see the result --- array with 2 rows and 4 columns is produced

# performing mathematical operations on the ndarray
data3 = data2 * 5 # multiply each element by 5
print(data3)

# produces a new array wherein each "cell" of data2 is added to each other
data4 = data2 + data2
print(data4)

'''
Problem 3)
To find size of each dimension of data4 produced above and also finding the data type of the array
'''
# to get the size of each dimension of data4 we use the shape method that returns the tuple
print(data4.shape) # prints a tuple (2, 4) indicating that there are 2 dimensions with size 4 each

# to get the data type of the array we use the object dtype
print(data4.dtype) # prints float64 indicating each cell value is a floating-point value occupying 64 bits in memory


# strassens_algorithm.py

# A demonstration of Strassen's subcubic runtime matrix multiplication
# algorithm on square matrices using the divide and conquer model.

import numpy as np

def main():
    m1 = np.array([[1,2],[3,4]])
    m2 = np.array([[5,6],[7,8]])

    print("The first square matrix is: ")
    print(m1)
    print("The second square matrix is: ")
    print(m2)

    output_matrix = strassens_algorithm(m1, m2)
    expected_matrix = np.matmul(m1, m2)

    print("When taking the dot product of m1 and m2 we expected an \
output matrix of:")
    print(expected_matrix)
    print("Using Strassen's subcubic runtime matrix multiplication \
algorithm we got:")
    print(output_matrix)

def strassens_algorithm(m1, m2):
    # Base case: A matrix of shape 1x1 is solved by default
    if np.shape(m1) == (1, 1):
        return np.asscalar(m1 * m2)

    # Pre-calculate the n/2 value. We'll be using it a lot and it's the same
    # for matrix 1 and matrix 2 since they have equivalent square dimensions.
    n_over_2 = len(m1)//2

    # Divide
    # Create submatrix blocks from quadrants of m1
    # |A B|
    # |C D|
    A = m1[0:n_over_2, 0:n_over_2]
    B = m1[0:n_over_2, n_over_2:]
    C = m1[n_over_2:, 0:n_over_2]
    D = m1[n_over_2:, n_over_2:]

    # Create submatrix blocks from quadrants of m2
    # |E F|
    # |G H|
    E = m2[0:n_over_2, 0:n_over_2]
    F = m2[0:n_over_2, n_over_2:]
    G = m2[n_over_2:, 0:n_over_2]
    H = m2[n_over_2:, n_over_2:]

    # Conquer
    # Calculate the 7 products: (elements matrix 1) * (elements matrix 2)
    p1 = strassens_algorithm(A, F-H)        # p1 = A * (F - H)
    p2 = strassens_algorithm(A+B, H)        # p2 = (A + B) * H
    p3 = strassens_algorithm(C+D, E)        # p3 = (C + D) * E
    p4 = strassens_algorithm(D, G-E)        # p4 = D * (G - E)
    p5 = strassens_algorithm(A+D, E+H)      # p5 = (A + D) * (E + H)
    p6 = strassens_algorithm(B-D, G+H)      # p6 = (B - D) * (G + H)
    p7 = strassens_algorithm(A-C, E+F)      # p7 = (A - C) * (E + F)

    # Combine
    return np.array([[p5+p4-p2+p6, p1+p2],[p3+p4, p1+p5-p3-p7]])

if __name__ == "__main__":
    main()


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

## jupyter console

import numpy as np

##
data = [[1,2,3,4],[5,6,7,8]]
arr1 = np.array(data)
type(arr1)
arr1.ndim
arr1.shape
np.zeros((3,2))
np.empty((3,2,2))
np.arange(10)

##
arr1 * arr1
arr1 * 0
arr1 + 1

##
arr2 = np.arange(10)
arr2[2]
arr3 = np.array([[1,2,3],[4,5,6]])
arr3[0]
arr3[0][1]

##
arr2[:3]
arr2[:-3]
arr3[1:, :2]

##
arr3 < 5
arr3[arr3 < 5]

##
arr4 = np.empty((8,4))
for i in range(8):
    arr4[i] = i
arr4

##
arr5 = np.arange(32).reshape((8,4))
arr5

##
arr5.shape
arr6 = arr5.T
arr6.shape

##
arr7 = np.random.randn(6, 3)
arr7
np.dot(arr7.T, arr7)

##
arr8 = np.arange(16).reshape((2,2,4))
arr8
arr8.transpose((2, 1, 0))

##
arr9 = np.arange(1, 17)
arr9
np.sqrt(arr9)
np.exp(arr9)

## 用数组表达式代替循环: 矢量化
arr10 = np.arange(-5, 5, 0.01)
xs, ys = np.meshgrid(arr10, arr10)
ys
z = np.sqrt(xs ** 2 + ys ** 2)
z

## broadcast
arr11 = np.arange(12).reshape((3,4))
arr11
arr11[0]
arr11 - arr11[0]


import numpy as np
import pandas as pd
import numpy as np
from scipy.constants import G
import random

random.seed(0)

def newton_equation(m1=1, m2=1, r=1, G=G):
    """
    Newton´s Equation
    :param m1: Mass first body in kg
    :param m2: Mass second body in kg
    :param r: Distance between m1 and m2 in meters
    :param G: Gravitational constant
    :return: Newtons equation in the Internation System of Units
    """
    return G * m1 * m2 * (1 / r ** 2)

def movement_equation(x0=1, v=1, a=1, t=1):
    return x0 + v * t + 0.5 * a * t * t

def make_newton(
    samples=100,
    m1_min=0.001,
    m1_max=1,
    m2_min=0.001,
    m2_max=1,
    r_min=1,
    r_max=10,
    G=G,
    dataframe=True,
):
    """
    Creates a sample of data of the Newton Equation
    :param samples: Number of samples
    :param m1_min: Minimum value in the range of the mass of the first body
    :param m1_max: Maximum value in the range of the mass of the first body
    :param m2_min: Minimum value in the range of the mass of the second body
    :param m2_max: Maximum value in the range of the mass of the second body
    :param r_min: Minimum value of the distance between the bodies
    :param r_max: Maximum value of the distance between the bodies
    :param dataframe: wether it returns a dataframe or an array
    :return: data sample
    """
    data = []
    for n in range(samples):
        m1 = (m1_max - m1_min) * np.random.random() + m1_min
        m2 = (m2_max - m2_min) * np.random.random() + m2_min
        r = (r_max - r_min) * np.random.random() + r_min

        data.append([m1, m2, r, newton_equation(m1=m1, m2=m2, r=r, G=G)])
    if dataframe:
        return pd.DataFrame(data=data, columns=["m1", "m2", "r", "f"])
    else:
        return data

def make_movement_data(
    samples=100,
    x0_min=1,
    x0_max=2,
    v_min=1,
    v_max=2,
    a_min=1,
    a_max=2,
    t_min=1,
    t_max=2,
    dataframe=True,
):
    data = []
    for n in range(samples):
        x0 = (x0_max - x0_min) * np.random.random() + x0_min
        v = (v_max - v_min) * np.random.random() + v_min
        a = (a_max - a_min) * np.random.random() + a_min
        t = (t_max - t_min) * np.random.random() + t_min

        data.append([x0, v, a, t, movement_equation(x0=x0, v=v, a=a, t=t)])
    if dataframe:
        return pd.DataFrame(data=data, columns=["x0", "v", "a", "t", "pos"])
    else:
        return data


import numpy as np

'''
    This python code calculates the Min-Max Normalization

    Min-max normalization is one of the most common ways to normalize data.
    For every feature, the minimum value of that feature gets transformed into a 0,
    the maximum value gets transformed into a 1, and every other value
    gets transformed into a decimal between 0 and 1.

    norm = [(x - X.min)/(X.max - X.min)]*(new_max - new_min) + new_min

'''

def minmax_norm(X, npmin, diff, nmax, nmin):
    return ( (X - npmin) / (diff) ) * (nmax-nmin) + nmin

# Input the data array
data = [ 13, 15, 16, 16, 19, 20, 20, 21, 22, 22,
         25, 25, 25, 25, 30, 33, 33, 35, 35, 35,
         35, 36, 40, 45, 46, 52, 70]

# Setting the new min and new max
nmin = 0
nmax = 1

# Putting the data in new numpy array
nparray = np.array(data)

#------------- Normalizing the data --------------------------#
# Difference between max nparray value and min nparray value
diff = nparray.max() - nparray.min()
npmin = nparray.min()

ndata = minmax_norm(nparray, npmin, diff, nmax, nmin)

print(ndata)

# Getting the norm of 35
nvalue = minmax_norm(35, npmin, diff, nmax, nmin)
print(np.round(nvalue, 2))


import numpy as np
from operator import itemgetter

def cosine_sim(param_list, meta):
    """
    Caculate the cosine similarity score of two given vectors
    Param 1 - First vector
    Param 2 - Second vector

    Return - float between 0 and 1
    """
    vec_a = param_list[0]
    vec_b = param_list[1]

    result = np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) *
                                     np.linalg.norm(vec_b))
    return result.astype(float)

def dot_product(param_list, meta):
    """
    Caculate the dot product of two given vectors
    Param 1 - First vector
    Param 2 - Second vector

    Return - float between 0 and 1
    """

    return np.inner(param_list[0], param_list[1])

def get_centroid(param_list, meta):
    """
    Calculate the centroid of the given list of vectors
    Param 1 - List of vectors

    Return - (centroid vector, cluster tightness)
    """
    vec_list = param_list[0]
    centroid = np.mean(vec_list, axis=0)
    tightness = np.mean([cosine_sim([vec, centroid], meta)
                         for vec in vec_list]).astype(float)
    return [centroid, tightness]

def softmax(param_list, meta):
    """
    Calculate the centroid of the given list of vectors
    Param 1 - List of vectors

    Return - (centroid vector, cluster tightness)
    """
    vec_list = param_list[0]
    e_x = np.exp(vec_list - np.max(vec_list))
    return list(e_x / e_x.sum())

def sort_by_key(param_list, meta):  # TODO: Should be in std lib
    """
    Sort the given list. Optionally by specific key
    Param 1 - List of items
    Param 2 - if Reverse
    Param 2 (Optional) - Index of the key to be used for sorting
    if param 1 is a list of tuples.
    """
    data = param_list[0]
    if_reverse = param_list[1]

    if (len(param_list) > 2):
        key_pos = param_list[2]
        return sorted(data, key=itemgetter(key_pos), reverse=if_reverse)
    else:
        return sorted(data, reverse=if_reverse)


# -*- coding: utf-8 -*-

'''
Un-documented code (booo!) for trial documentation-sprint.
'''

import numpy as np

def quadratic_formula(a,b,c):
        """Calculates the real roots for ax^2+bx+c=0

        :param a: quadratic coefficient
        :type a: float, optional
        :param b: linear coefficient
        :type b: float, optional
        :param c: linear coefficient
        :type c: float, optional

        :raises: raises ValueError if the discriminant is less than zero
        :return: List of quadratic polynomial roots
        :rtype: list
        """
        d = b**2-4*a*c # discriminant

        if d < 0:
            raise ValueError("This equation has no real solution")
        elif d == 0:
            x = (-b+np.sqrt(b**2-4*a*c))/2*a
            sol = [x]
            return sol
        else:
            x1 = (-b+np.sqrt(b**2-4*a*c))/2*a
            x2 = (-b-np.sqrt(b**2-4*a*c))/2*a
            sol = [x1, x2]
            return sol

# simple numerical integration of f(x) over an interval x: [x_lower, x_upper] using composite trapezoid rule.

def simple_integrate(f, x):
    """Computes the definite integral of a function from the lower to the upper limit using composite trapezoid rule.

    :param f: function to be integrated.
    :param x:  [x_lower, x_upper] lower limit of integration upper limit of integration
    :raises: raises ValueError if the discriminant is less than zero
    :return: integral value
    :rtype: float
    """
    int_sum = 0

    for i in range(x.size-1):
        int_sum += 0.5*(x[i+1] - x[i])*(f[i] + f[i+1])

    return int_sum

# function for nth Fibonacci number

def fibonacci(n):
    """Computes the nth fibonacci number, starting at 0.

    :param n: the index of the Fibonacci number to be computed
    :raises: raises ValueError if n is less than 1
    :return: nth fibonacci number
    :rtype: int
    """
    if n<=0:
        raise ValueError("Input cannot be less than 1")
    elif n==1:
        return 0
    elif n==2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)

# convert time in seconds to hours, minutes and seconds

def get_hms(t_sec):
    """Converts time in seconds to hours, minutes, and seconds.

    :param t_sec: time in seconds
    :return: time in hours, minutes, and seconds
    :rtype: list
    """

    h = t_sec//3600

    m = (t_sec - h*3600)//60

    s = t_sec%60

    return h,m,s #question: does it return a list? Or maybe not?


import math
import unittest
import numpy as np
from parameterized import parameterized
from lab2.src.methods.newton_method import newton_method

newton_method_testcases = [
    (
        lambda x: x[0] ** 2 + x[1] ** 2,
        lambda x: np.array([2 * x[0], 2 * x[1]]),
        lambda x: np.array([[2, 0], [0, 2]]),
        [3, 2],
        [0, 0]
    ),
    (
        lambda x: 7 * (x[0] - 1) ** 2 + x[1] ** 2,
        lambda x: np.array([14 * (x[0] - 1), 2 * x[1]]),
        lambda x: np.array([[14, 0], [0, 2]]),
        [400, -300],
        [1, 0]
    ),
    (
        lambda x: (x[0] - 2) ** 2 + (x[1] + 3) ** 2,
        lambda x: np.array([2 * (x[0] - 2), 2 * (x[1] + 3)]),
        lambda x: np.array([[2, 0], [0, 2]]),
        [6, 7],
        [2, -3]
    ),
    (
        lambda x: x[0] ** 2 + x[1] ** 2,
        lambda x: np.array([2 * x[0], 2 * x[1]]),
        lambda x: np.array([[2, 0], [0, 2]]),
        [-0.1, 0.2],
        [0, 0]
    ),
    (
        lambda x: 7 * (x[0] - 1) ** 2 + x[1] ** 2,
        lambda x: np.array([14 * (x[0] - 1), 2 * x[1]]),
        lambda x: np.array([[14, 0], [0, 2]]),
        [413, -1210],
        [1, 0]
    ),
    (
        lambda x: (x[0] - 2) ** 2 + (x[1] + 3) ** 2,
        lambda x: np.array([2 * (x[0] - 2), 2 * (x[1] + 3)]),
        lambda x: np.array([[2, 0], [0, 2]]),
        [100, -3232],
        [2, -3]
    )
]

DELTA = 1e-3

class TestNewtonMethod(unittest.TestCase):

    @parameterized.expand(newton_method_testcases)
    def test_newton_method(self, f, f_grad, f_hess, start, expected_result):
        result, _ = newton_method(f=f,
                                  f_grad=f_grad,
                                  f_hess=f_hess,
                                  start=start)
        self.check_result(result, expected_result)

    def check_result(self, result, expected_result):
        dist = 0.0
        for result_x_k, expected_result_x_k in list(zip(result, expected_result)):
            dist += (result_x_k - expected_result_x_k) ** 2
        dist = math.sqrt(dist)
        self.assertAlmostEqual(dist, 0, delta=DELTA)


import numpy as np

def R(theta):
    """
        Returns the rotation matrix for rotating an object
        centered around the origin with a given angle

        Arguments:
            theta: angle in degrees

        Returns:
            R: 2x2 np.ndarray with rotation matrix
    """
    theta = np.radians(theta)
    return np.array(
        [[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]
    )

def M(axis="x"):
    """
        Returns a matrix to mirror an object against a given axis

        Arguments:
            axis: str. 'x', 'y', 'origin' or 'xy'

        Returns:
            M: mirror matrix
    """
    if axis == "x":
        return np.array([[1, 0], [0, -1]])
    elif axis == "y":
        return np.array([[-1, 0], [0, 1]])
    elif axis == "origin":
        return np.array([[-1, 0], [0, -1]])
    elif axis == "xy":
        return np.array([[0, 1], [1, 0]])
    else:
        raise NotImplementedError(
            f"Could not recognize axis of mirroring: {axis}"
        )

def cart2pol(x, y):
    """
        Cartesian to polar coordinates

        angles in degrees
    """
    rho = np.hypot(x, y)
    phi = np.degrees(np.arctan2(y, x))
    return rho, phi

def pol2cart(rho, phi):
    """
        Polar to cartesian coordinates

        angles in degrees
    """
    x = rho * np.cos(np.radians(phi))
    y = rho * np.sin(np.radians(phi))
    return x, y


"""
author: Fabian Schaipp
"""

import numpy as np

class f_rosenbrock:
    """
    Nonsmooth Rosenbrock function (see 5.1 in Curtis, Overton "SQP FOR NONSMOOTH CONSTRAINED OPTIMIZATION")

    x -> w|x_1^2 − x_2| + (1 − x_1)^2
    """
    def __init__(self, w = 8):
        self.name = 'rosenbrock'
        self.dim = 2
        self.dimOut = 1
        self.w = w

    def eval(self, x):
        return self.w*np.abs(x[0]**2-x[1]) + (1-x[0])**2

    def differentiable(self, x):
        return np.abs(x[0]**2 - x[1]) > 1e-10

    def grad(self, x):
        a = np.array([-2+x[0], 0])
        sign = np.sign(x[0]**2 -x[1])

        if sign == 1:
            b = np.array([2*x[0], -1])
        elif sign == -1:
            b = np.array([-2*x[0], 1])
        else:
            b = np.array([-2*x[0], 1])

        #b = np.sign(x[0]**2 -x[1]) * np.array([2*x[0], -1])
        return a + b

class g_max:
    """
    maximum function (see 5.1 in Curtis, Overton "SQP FOR NONSMOOTH CONSTRAINED OPTIMIZATION")

    x -> max(c1*x_1, c2*x_2) - 1
    """
    def __init__(self, c1 = np.sqrt(2), c2 = 2.):
        self.name = 'max'
        self.c1 = c1
        self.c2 = c2
        self.dimOut = 1
        return

    def eval(self, x):
        return np.maximum(self.c1*x[0], self.c2*x[1]) - 1

    def differentiable(self, x):
        return np.abs(self.c1*x[0] -self.c2*x[1]) > 1e-10

    def grad(self, x):

        sign = np.sign(self.c1*x[0] - self.c2*x[1])
        if sign == 1:
            g = np.array([self.c1, 0])
        elif sign == -1:
            g = np.array([0, self.c2])
        else:
            g = np.array([0, self.c2])
        return g

class g_linear:
    """
    linear constraint:

    x -> Ax - b
    """
    def __init__(self, A, b):
        self.name = 'linear'
        self.A = A
        self.b = b
        self.dim = A.shape[0]
        self.dimOut = A.shape[1]
        return

    def eval(self, x):
        return self.A @ x - self.b

    def differentiable(self, x):
        return True

    def grad(self, x):
        return self.A


import numpy as np
def LM(f,a,b,n):
    # Carries out the numerical integration using the Left Hand Rule
    # INPUT: f , a , b , n
    # f : function which is the integrand
    # a : lower limit of the interval
    # b : upper limit of the interval
    # n : number of sub-intervals

    # makes sure the input format is correct
    if (b < a) :
        print('Input Error: Lower limit(a) must be < Upper limit(b)')
        exit()
    # limits the maximum number of iterations to 1 million
    if (n > 1000000) :
        print('Input Error: No more than 1 million divisions are allowed!')
        exit()
    # calculate the step size or length of each sub-interval
    h = (b - a)/n
    #step.append(h)
    # calculates the numerical integration using Left Hand Rule
    area = 0
    for i in range(n):
        x = a + float(i*h)
        area = area + f(x)*h
    # calculates error
    err = 1 - area
    rel_err = np.abs(err)*100
    #abs_err.append(np.abs(err))
    print('---------------------------------------------------')
    print('NUMERICAL QUADRATURE RESULTS : LEFT-ENDPOINT METHOD')
    print('---------------------------------------------------')
    print('Integrating from %8.2f    to %8.2f   in %5d steps' %(a,b,n))
    print('Area           = %10.4f units\u00b2'%(area))
    print('Error          = %10.4f'%err)
    print('Absolute Error = %10.4f '%(np.abs(err)))
    print('Relative Error = %10.4f'%rel_err)
    print('---------------------------------------------------')
    return h,np.abs(err)
def RM(f,a,b,n):
    # Carries out the numerical integration using the Right Hand Rule
    # INPUT: f , a , b , n
    # f : function which is the integrand
    # a : lower limit of the interval
    # b : upper limit of the interval
    # n : number of sub-intervals

    # makes sure the input format is correct
    if (b < a) :
        print('Input Error: Lower limit(a) must be < Upper limit(b)')
        exit()
    # limits the maximum number of iterations to 1 million
    if (n > 1000000) :
        print('Input Error: No more than 1 million divisions are allowed!')
        exit()
    # calculate the step size or length of each sub-interval
    h = (b - a)/n
    #step.append(h)
    # calculates the numerical integration using Right Hand Rule
    area = 0
    for i in range(1,n+1):
        x = a + float(i*h)
        area = area + f(x)*h
    # calculates error
    err = 1 - area
    rel_err = np.abs(err)*100
    #abs_err.append(np.abs(err))
    print('---------------------------------------------------')
    print('NUMERICAL QUADRATURE RESULTS : RIGHT-ENDPOINT METHOD')
    print('---------------------------------------------------')
    print('Integrating from %8.2f    to %8.2f   in %5d steps' %(a,b,n))
    print('Area           = %10.4f units\u00b2'%(area))
    print('Error          = %10.4f'%err)
    print('Absolute Error = %10.4f '%(np.abs(err)))
    print('Relative Error = %10.4f'%rel_err)
    print('---------------------------------------------------')
    return h,np.abs(err)
def MpM(f,a,b,n):
    # Carries out the numerical integration using the Midpoint Rule
    # INPUT: f , a , b , n
    # f : function which is the integrand
    # a : lower limit of the interval
    # b : upper limit of the interval
    # n : number of sub-intervals

    # makes sure the input format is correct
    if (b < a) :
        print('Input Error: Lower limit(a) must be < Upper limit(b)')
        exit()
    # limits the maximum number of iterations to 1 million
    if (n > 1000000) :
        print('Input Error: No more than 1 million divisions are allowed!')
        exit()
    # calculate the step size or length of each sub-interval
    h = (b - a)/n
    #step.append(h)
    # calculates the numerical integration using Midpoint Rule
    area = 0
    for i in range(n):
        x = a + float((i+0.5)*h)
        area = area + f(x)*h
    # calculates error
    err = 1 - area
    rel_err = np.abs(err)*100
    #abs_err.append(np.abs(err))
    print('---------------------------------------------------')
    print('NUMERICAL QUADRATURE RESULTS : MIDPOINT METHOD')
    print('---------------------------------------------------')
    print('Integrating from %8.2f    to %8.2f   in %5d steps' %(a,b,n))
    print('Area           = %10.4f units\u00b2'%(area))
    print('Error          = %10.4f'%err)
    print('Absolute Error = %10.4f '%(np.abs(err)))
    print('Relative Error = %10.4f'%rel_err)
    print('---------------------------------------------------')
    return h,np.abs(err)
def TM(f,a,b,n):
    # Carries out the numerical integration using the Trapezoidal Rule
    # INPUT: f , a , b , n
    # f : function which is the integrand
    # a : lower limit of the interval
    # b : upper limit of the interval
    # n : number of sub-intervals

    # makes sure the input format is correct
    if (b < a) :
        print('Input Error: Lower limit(a) must be < Upper limit(b)')
        exit()
    # limits the maximum number of iterations to 1 million
    if (n > 1000000) :
        print('Input Error: No more than 1 million divisions are allowed!')
        exit()
    # calculate the step size or length of each sub-interval
    h = (b - a)/n
    #step.append(h)
    # calculates the numerical integration using Trapezoidal Rule
    area = f(float(a))*h/2
    for i in range(1,n):
        x = a + float(i*h)
        area = area + f(x)*h
    area = area + f(float(b))*h/2
    # calculates error
    err = 1 - area
    rel_err = np.abs(err)*100
    #abs_err.append(np.abs(err))
    print('---------------------------------------------------')
    print('NUMERICAL QUADRATURE RESULTS : TRAPEZOID METHOD')
    print('---------------------------------------------------')
    print('Integrating from %8.2f    to %8.2f   in %5d steps' %(a,b,n))
    print('Area           = %10.4f units\u00b2'%(area))
    print('Error          = %10.4f'%err)
    print('Absolute Error = %10.4f '%(np.abs(err)))
    print('Relative Error = %10.4f'%rel_err)
    print('---------------------------------------------------')
    return h,np.abs(err)


from sympy import *

x, y, a, b = symbols('x y a b')
all_symbols = [a,b]

target_value = y
model_function = a*x + b
obs_eq = Matrix([target_value - model_function]).vec()
obs_eq_jacobian = obs_eq.jacobian(all_symbols)

print(model_function)
print(obs_eq)
print(obs_eq_jacobian)
print(latex(model_function))
print(latex(obs_eq_jacobian))

with open("example_func_ax_plus_b_eq_y_jacobian.h",'w') as f_cpp:
    f_cpp.write("inline void example_func_ax_plus_b(double &y, double x, double a, double b)\n")
    f_cpp.write("{")
    f_cpp.write("y = %s;\n"%(ccode(model_function)))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_example_func_ax_plus_b_eq_y(double &delta, double x, double y, double a, double b)\n")
    f_cpp.write("{")
    f_cpp.write("delta = %s;\n"%(ccode(obs_eq[0,0])))
    f_cpp.write("}")
    f_cpp.write("\n")
    f_cpp.write("inline void observation_equation_example_func_ax_plus_b_eq_y_jacobian(Eigen::Matrix<double, 1, 2> &j, double x, double y, double a, double b)\n")
    f_cpp.write("{")
    for i in range (1):
        for j in range (2):
            f_cpp.write("j.coeffRef(%d,%d) = %s;\n"%(i,j, ccode(obs_eq_jacobian[i,j])))
    f_cpp.write("}")


# solutions.py
"""Volume I: Monte Carlo Integration
Solutions file. Written by Tanner Christensen, Jan 2016
Edited by Jessica Morrise, May 2016
"""

import numpy as np
import scipy.stats as stats
from matplotlib import pyplot as plt

def prob1(N=10000):
    """Return an estimate of the volume of the unit sphere using Monte
    Carlo Integration.

    Input:
        N (int, optional) - The number of points to sample. Defaults
            to 10000.

    """
    points = np.random.rand(3, N)
    points = points*2 - 1
    radii = np.linalg.norm(points,axis=0)
    numInSphere = np.count_nonzero(radii <= 1)
    return 8.*numInSphere/N

def prob2(f, a, b, N=10000):
    """Use Monte-Carlo integration to approximate the integral of
    1-D function f on the interval [a,b].

    Inputs:
        f (function) - Function to integrate. Should take scalar input.
        a (float) - Left-hand side of interval.
        b (float) - Right-hand side of interval.
        N (int, optional) - The number of points to sample in
            the Monte-Carlo method. Defaults to 10000.

    Returns:
        estimate (float) - The result of the Monte-Carlo algorithm.

    Example:
        >>> f = lambda x: x**2
        >>> # Integral from 0 to 1. True value is 1/3.
        >>> prob2(f, 0, 1)
        0.3333057231764805
    """
    points = np.random.rand(1,N)
    V = b-a
    points = V*points + a
    fPoints = np.apply_along_axis(f,0,points)
    return V*np.sum(fPoints)/float(N)

def prob3(f, mins, maxs, N=10000):
    """Use Monte-Carlo integration to approximate the integral of f
    on the box defined by mins and maxs.

    Inputs:
        f (function) - The function to integrate. This function should
            accept a 1-D NumPy array as input.
        mins (1-D np.ndarray) - Minimum bounds on integration.
        maxs (1-D np.ndarray) - Maximum bounds on integration.
        N (int, optional) - The number of points to sample in
            the Monte-Carlo method. Defaults to 10000.

    Returns:
        estimate (float) - The result of the Monte-Carlo algorithm.

    Example:
        >>> f = lambda x: np.hypot(x[0], x[1]) <= 1
        >>> # Integral over the square [-1,1] x [-1,1]. True value is pi.
        >>> mc_int(f, np.array([-1,-1]), np.array([1,1]))
        3.1290400000000007
    """
    if len(mins) != len(maxs):
        raise ValueError("Dimension of mins and maxs must be the same")

    # create points
    dim = len(mins)
    side_lengths = maxs-mins
    points = np.random.rand(N,dim)
    points = side_lengths*points + mins

    # calculate Volume
    V = 1
    for i in xrange(dim):
        V *= maxs[i] - mins[i]

    # apply the function f along axis=1 and sum all the results
    estimate = V*np.sum(np.apply_along_axis(f,1,points))/float(N)

    return estimate

def prob4():

    #define the joint normal distribution
    joint_normal = lambda x: 1/(2*np.pi)**(len(x)/2.)*np.exp(-x.T.dot(x)/2.)

    mins = np.array([-1.5,0,0,0])
    maxs = np.array([0.75,1,0.5,1])
    means = np.zeros(4)
    covs = np.eye(4)

    my_value = prob3(joint_normal,mins,maxs,50000)
    scipy_value, inform = stats.mvn.mvnun(mins, maxs, means, covs)
    err = np.abs(my_value - scipy_value)/abs(scipy_value) # relative error
    return my_value, scipy_value, err

def prob5(numEstimates=50):
    """Plot the error of Monte Carlo Integration.
    """

    # actual volume of the unit sphere
    actual = 4.1887902047863905

    # construct an array of values of N
    # use closer-spaced values near 50
    N = [i*1000 for i in xrange(1,51)]
    N = [50,100,500] + N
    errors = []

    for n in N:
        meanErr = 0.
        for i in xrange(numEstimates):
            I = prob1(n) # estimate the integral
            err = np.abs(I - actual)/actual # compute the relative error
            meanErr += err
        errors.append(meanErr/float(numEstimates))

    # create the plot
    plt.plot(N,errors,label='Error')
    plt.plot(N,[1./n**0.5 for n in N],'r--',label=r'$1/\sqrt{N}$')
    plt.ylim([0,max(errors)])
    plt.xlim([0,max(N)])
    plt.xlabel(r'$N$')
    plt.ylabel('Relative error')
    plt.title('Sphere volume error vs. number of points used')
    plt.legend()
    plt.show()

if __name__ == "__main__":
    print prob1()


import numpy as np
a = np.array([0, 0.5, 1.0, 1.5, 2.0])
type(a)
a[:2]  # Slicing works as for lists

# Built in methods
a.sum()
a.std()
a.cumsum()
a.max()
a.argmax()

# Careful with np.max!
np.max(2, 0)
np.max(-2, 0)  # silent fail :) second argument is the axis
np.max(0, 2)  # fail
np.maximum(-2, 0)

# Vectorized operations: operations are applied to each element
a*2
a**2
np.sqrt(a)
np.log2(a+1)

b = np.array([a, a*2])
b

b.sum(axis=0)  # sum along axis 0 ==> columns
b.sum()
b.sum(axis=1)

eye = np.identity(4)
eye

np.ones_like(eye)
np.ones((2,3))

zeros = np.zeros((2,3,4))
zeros.shape
zeros[1]

# Optimized for speed!
import time
start = time.time()
acc = 0
for i in range(1000):
    for j in range(1000):
        acc += np.random.standard_normal()
end = time.time()
print("It took (ms): ", (end-start)*1000)

# Numpy outsources the loops to underlying C code for performance
# %timeit test = np.random.standard_normal((1000,1000)).sum()

# CODE VECTORIZATION
r = np.random.standard_normal((4,3))
s = np.random.standard_normal((4,3))
r+s

# Broadcasting
2*r+3 # same as 2*r+3*np.ones_like(r)

########################################
########################################
######## MOVE TO BROWSER HERE
########################################
########################################

# Functions are applied element-wise.
def f(x):
    return 3*x+5

f(3)
f(r)

import math
math.sin(math.pi)
math.sin(r)  # Error: this function only takes real numbers!
np.sin(r)
type(np.sin)  # ufunc: universal function (works with arrays too)