### 3 Link Forward Kinematics ###

import numpy as np
from numpy import array
import sympy
from sympy import *
from sympy import symbols, cos, sin, pi, simplify, sqrt, atan2, pprint
from sympy.matrices import Matrix

def fk_3link(theta1,theta2,theta3):

    # Create symbols for DH param
    q1, q2, q3 = symbols('q1:4')                               # joint angles theta
    d1, d2, d3 = symbols('d1:4')                              # link offsets
    a0, a1, a2 = symbols('a0:3')                              # link lengths
    alpha0, alpha1, alpha2 = symbols('alpha0:3') # joint twist angles

    # DH Table
    dh = {alpha0:      0, a0:      0, d1:  0.75, q1:        q1,
          alpha1: -pi/2., a1:   0.35, d2:     0, q2: -pi/2.+q2,
          alpha2:      0, a2:   1.25, d3:     0, q3:        q3,}

    # Function to return homogeneous transform matrix

    def TF_Mat(alpha, a, d, q):
        TF = Matrix([[            cos(q),           -sin(q),           0,             a],
                     [ sin(q)*cos(alpha), cos(q)*cos(alpha), -sin(alpha), -sin(alpha)*d],
                     [ sin(q)*sin(alpha), cos(q)*sin(alpha),  cos(alpha),  cos(alpha)*d],
                     [                 0,                 0,           0,             1]])
        return TF

    ## Substiute DH_Table
    T0_1 = TF_Mat(alpha0, a0, d1, q1).subs(dh)
    T1_2 = TF_Mat(alpha1, a1, d2, q2).subs(dh)
    T2_3 = TF_Mat(alpha2, a2, d3, q3).subs(dh)

    #Homogeneous Transforms

    T0_2 = (T0_1 * T1_2) ## (Base) Link_0 to Link_2
    T0_3 = (T0_2 * T2_3) ## (Base) Link_0 to Link_3

    T0_3_value = N(T0_3.evalf(subs={q1: theta1, q2: theta2, q3: theta3}),2) #two digit output
    #print(T0_3 )
    #print('T0_3= \n    ',T0_3_value)
    #print("result:",np.array([T0_3_value]))

    #print("\nT_total Matrix : \n")
    #print("\n")

    return np.array([T0_3_value])

#fk_3link(pi/3,pi/3,pi/3)

#fk_3link(0,0,0)

import numpy,from numpy,import sympy,from sympy


#!/usr/bin/env python
# coding: utf-8
# ## Monte Carlo - Euler Discretization - Part I
# *Suggested Answers follow (usually there are multiple ways to solve a problem in Python).*
# Download the data for Microsoft (‘MSFT’) from Yahoo Finance for the period ‘2000-1-1’ until today.
# In[1]:
import numpy as np
import pandas as pd
from pandas_datareader import data as web
from scipy.stats import norm
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')
# In[2]:
ticker = 'MSFT'
data = pd.DataFrame()
data[ticker] = web.DataReader(ticker, data_source='yahoo', start='2007-1-1', end='2017-3-21')['Adj Close']
# Store the annual standard deviation of the log returns in a variable, called “stdev”.
# In[3]:
log_returns = np.log(1 + data.pct_change())
# In[4]:
log_returns.tail()
# In[5]:
data.plot(figsize=(10, 6));
# In[6]:
stdev = log_returns.std() * 250 ** 0.5
stdev
# Set the risk free rate, r, equal to 2.5% (0.025).
# In[7]:
r = 0.025
# To transform the object into an array, reassign stdev.values to stdev.
# In[8]:
type(stdev)
# In[9]:
stdev = stdev.values
stdev
# Set the time horizon, T, equal to 1 year, the number of time intervals equal to 250, the iterations equal to 10,000. Create a variable, delta_t, equal to the quotient of T divided by the number of time intervals.
# In[10]:
T = 1.0
t_intervals = 250
delta_t = T / t_intervals
iterations = 10000
# Let Z equal a random matrix with dimension (time intervals + 1) by the number of iterations.
# In[11]:
Z = np.random.standard_normal((t_intervals + 1, iterations))
# Use the .zeros_like() method to create another variable, S, with the same dimension as Z. S is the matrix to be filled with future stock price data.
# In[12]:
S = np.zeros_like(Z)
# Create a variable S0 equal to the last adjusted closing price of Microsoft. Use the “iloc” method.
# In[13]:
S0 = data.iloc[-1]
S[0] = S0
# Use the following formula to create a loop within the range (1, t_intervals + 1) that reassigns values to S in time t.
# $$
# S_t = S_{t-1} \cdot exp((r - 0.5 \cdot stdev^2) \cdot delta_t + stdev \cdot delta_t^2 \cdot Z_t)
# $$
# In[14]:
for t in range(1, t_intervals + 1):
    S[t] = S[t-1] * np.exp((r - 0.5 * stdev ** 2) * delta_t + stdev * delta_t ** 0.5 * Z[t])
# In[15]:
S
# In[16]:
S.shape
# Plot the first 10 of the 10,000 generated iterations on a graph.
# In[17]:
plt.figure(figsize=(10, 6))
plt.plot(S[:, :10]);


from statistics import mean
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from matplotlib import style
import random   # to make our data random

style.use('fivethirtyeight')

# xs = [1,2,3,4,5,6]
# ys = [5,4,6,5,6,7]

# plt.plot(xs,ys)
# plt.show()

# Since above are not np array they are just a python array, to change them which will give us
# more powerful way to iterate over then values in the array,

# xs = np.array(xs, dtype = np.float64)  # you can also specify the data-type into your array
# ys = np.array(ys, dtype = np.float64)

# Here we will add our dataset random generation for testing our model
def create_dataset(hm, variance, step = 2, correaltion = False):

    """
    Definition:
    hm          : number of data points that you want to create
    variance    : is how variables or scattered your data are
    step        : how far from the average of our data we want to be, default is (2)
    correlation : to show the data r correlated positively or negatively or none (boolean), default (False)
    this function will return a two lists one for xs and one for ys, you can set your variables in that case to the
    form like
    x1, y2 = create_dataset....etc

    """
    val =1 # this would be the first value in the ys
    ys = []
    for i in range(hm):
        y = val + random.randrange(-variance,variance) # return a value between these two numbers (-variance,variance)
        ys.append(y)  # this is the way to add value to your list above, I thought before ys[i] = y (which is wrong)
        if correaltion and correaltion == 'pos':
            val += step
        elif correaltion and correaltion == 'neg':
            val -= step
    xs = [i for i in range(len(ys))]
    return np.array(xs, dtype = np.float64), np.array(ys, dtype=np.float64)

# first we need a function to calculate the slop as

def best_fit_slop_and_intercept(xs,ys):

    m = ( ((mean(xs)*mean(ys)) - mean(xs*ys)) /
          (mean(xs)**2-mean(xs**2)))
    b = mean(ys)-m*mean(xs)

# remember the PEMDAS the order of the operations in computing
    return m,b

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

def coefficient_of_determination(ys_orig,ys_line):
    y_mean_line = [mean(ys_orig) for y in ys_orig]    # to make a vector with only one value which is mean(ys_orig)
    squared_error_regr = squared_error(ys_orig,ys_line)
    squared_error_y_mean = squared_error(ys_orig, y_mean_line)
    return 1- (squared_error_regr / squared_error_y_mean)

# create data from the random generation data that we               created
xs, ys = create_dataset(2000, 1700,2,'neg')

m,b = best_fit_slop_and_intercept(xs,ys)
print(m,b)

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

# Here we will add the piece of code to calculate the coefficient of determination,
r_squared = coefficient_of_determination(ys, regression_line)
print(r_squared)

# this is exact identical to the following format
# for x in xs:
#     regression_line.append((m*x)+b)

# now we will plot the data and the regression line

# what if you want to predict a specific value
predict_x = 8
predict_y = (m*predict_x)+b

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

Machine_Learning_Old_Files/[12] linear regression P.5.py


# https://docs.sympy.org/latest/tutorial/calculus.html

from sympy import *
from myprint import spprint

x, y, z = symbols('x y z')
init_printing(use_unicode=False)

# derivatives

spprint(diff(cos(x), x))
spprint(diff(exp(x**2), x))

# derivative 3 times

spprint(diff(x**4, x, x, x))
spprint(diff(x**4, x, 3))

# many variables?

expr = exp(x*y*z)

spprint(expr)

spprint(diff(expr, x, y, y, z, z, z, z))

spprint(diff(expr, x, y, 2, z, 4))

spprint(diff(expr, x, y, y, z, 4))

# diff method

spprint(expr.diff(x, y, y, z, 4))

# "unevaluated" derivative - interesting

deriv = Derivative(expr, x, y, y, z, 4)
spprint(deriv)

# evaluate with .doit - wonder how that works with other things

spprint(deriv.doit())

# not sure what this is - nth derivative with respect to x?

m, n, a, b = symbols('m n a b')
expr = (a*x + b)**m
spprint(expr.diff((x, n)))

#integrals

# indefinate

spprint(integrate(cos(x), x))

# definate

spprint(integrate(exp(-x), (x, 0, oo)))

# multiple

spprint(integrate(exp(-x**2 - y**2), (x, -oo, oo), (y, -oo, oo)))

# unable to compute

expr = integrate(x**x, x)
spprint(expr)
spprint(expr.doit())

# unevaluated

expr = Integral(log(x)**2, x)
spprint(expr)
spprint(expr.doit())

# more complex?

integ = Integral((x**4 + x**2*exp(x) - x**2 - 2*x*exp(x) - 2*x -
        exp(x))*exp(x)/((x - 1)**2*(x + 1)**2*(exp(x) + 1)), x)
spprint(integ)

spprint(integ.doit())

integ = Integral(sin(x**2), x)
spprint(integ)
spprint(integ.doit())

integ = Integral(x**y*exp(-x), (x, 0, oo))
spprint(integ)
spprint(integ.doit())

# limits

spprint(limit(sin(x)/x, x, 0))

# things that cannot be done by subs oo/oo = nan

expr = x**2/exp(x)
spprint(expr)
spprint(expr.subs(x, oo))
spprint(limit(expr, x, oo))

# unevaluated

expr = Limit((cos(x) - 1)/x, x, 0)
spprint(expr)
spprint(expr.doit())

# + or - side of limit
# I guess you do not know if x is
# positive or negative
# maybe can say that when defining symbol

spprint(limit(1/x, x, 0, '+'))
spprint(limit(1/x, x, 0, '-'))

# series - not sure about this
# not related to CS big O

expr = exp(sin(x))
spprint(expr.series(x, 0, 4))

spprint(x + x**3 + x**6 + O(x**4))

spprint(x*O(1))

# how do I enter formalas to print and do not get them
# immediately simplified?

spprint(expr.series(x, 0, 4).removeO())

# another thing I do not understand

f, g = symbols('f g', cls=Function)
spprint(differentiate_finite(f(x)*g(x)))

f = Function('f')
dfdx = f(x).diff(x)
spprint(dfdx.as_finite_difference())

f = Function('f')
spprint(f)
spprint(f(x))
d2fdx2 = f(x).diff(x, 2)
spprint(d2fdx2)
h = Symbol('h')
spprint(d2fdx2.as_finite_difference([-3*h,-h,2*h]))

spprint(finite_diff_weights(2, [-3, -1, 2], 0)[-1][-1])

x_list = [-3, 1, 2]
spprint(x_list)

y_list = symbols('a b c')
spprint(y_list)

spprint(apply_finite_diff(1, x_list, y_list, 0))

# way to get 2*x/x without evaluating
# there is no Div apparently

y = Mul(2*x,1/x, evaluate=False)

# unsimplified

spprint(y)

# simplified

spprint(y.doit())

# I guess there is Add, Mul, Pow


import numpy as np
# Setting a random seed, feel free to change it and see different solutions.
np.random.seed(42)

# TODO: Fill in code in the function below to implement a gradient descent
# step for linear regression, following a squared error rule. See the docstring
# for parameters and returned variables.
def MSEStep(X, y, W, b, learn_rate = 0.005):
    """
    This function implements the gradient descent step for squared error as a
    performance metric.

    Parameters
    X : array of predictor features
    y : array of outcome values
    W : predictor feature coefficients
    b : regression function intercept
    learn_rate : learning rate

    Returns
    W_new : predictor feature coefficients following gradient descent step
    b_new : intercept following gradient descent step
    """

    # Fill in code

    return W_new, b_new

# The parts of the script below will be run when you press the "Test Run"
# button. The gradient descent step will be performed multiple times on
# the provided dataset, and the returned list of regression coefficients
# will be plotted.
def miniBatchGD(X, y, batch_size = 20, learn_rate = 0.005, num_iter = 25):
    """
    This function performs mini-batch gradient descent on a given dataset.

    Parameters
    X : array of predictor features
    y : array of outcome values
    batch_size : how many data points will be sampled for each iteration
    learn_rate : learning rate
    num_iter : number of batches used

    Returns
    regression_coef : array of slopes and intercepts generated by gradient
      descent procedure
    """
    n_points = X.shape[0]
    W = np.zeros(X.shape[1]) # coefficients
    b = 0 # intercept

    # run iterations
    regression_coef = [np.hstack((W,b))]
    for _ in range(num_iter):
        batch = np.random.choice(range(n_points), batch_size)
        X_batch = X[batch,:]
        y_batch = y[batch]
        W, b = MSEStep(X_batch, y_batch, W, b, learn_rate)
        regression_coef.append(np.hstack((W,b)))

    return regression_coef

if __name__ == "__main__":
    # perform gradient descent
    data = np.loadtxt('batch_graddesc_data.csv', delimiter = ',')
    X = data[:,:-1]
    y = data[:,-1]
    regression_coef = miniBatchGD(X, y)

    # plot the results
    import matplotlib.pyplot as plt

    plt.figure()
    X_min = X.min()
    X_max = X.max()
    counter = len(regression_coef)
    for W, b in regression_coef:
        counter -= 1
        color = [1 - 0.92 ** counter for _ in range(3)]
        plt.plot([X_min, X_max],[X_min * W + b, X_max * W + b], color = color)
    plt.scatter(X, y, zorder = 3)
    plt.show()

Supervised Learning/01 Linear Regression/batch_graddesc.py


import numpy as np
import matplotlib.pyplot as plt
from random import randint

def med(nums):
    '''
    Calcula a média aritmética de um comjunto de números
    :param nums: tupla, lista ou array
    :return: retorna a média
    '''
    soma = 0
    for i in nums:
        soma += i
    med = soma / len(nums)
    return med

def desvp(nums):
    '''
    Calcula o desvio padrão de um conjunto de números
    :param nums: tupla, lista ou array
    :return: retorna o desvio padrão
    '''
    soma = 0
    for i in nums:
        soma += i
    med = soma / len(nums)
    desvio = 0
    for i in nums:
        desvio += (i - med)**2
    desvp = (desvio/len(nums))**(1/2)
    return desvp

n = int(input('Quantidade de jogadas: '))
x1 = np.array(list([randint(1, 6) for i in range(n)]))
x2 = np.array(list([randint(1, 6) for j in range(n)]))
y = x1 + x2
media = med(y)
desviop = desvp(y)
print(f'{"-~"*25}\nA média da soma das jogadas é:')
print(f'Pela minha função = {media}')
print(f'Pela função do numpy = {np.mean(y)}')
print(f'{"-~"*25}\nO desvio padrão da soma das jogadas:')
print(f'Pela minha função = {desviop}')
print(f'Pela função do numpy = {np.std(y)}\n{"-~"*25}')
plt.hist(y, bins=11, range=(2, 13), align='left', rwidth=0.95)
plt.title(f'Histograma com {n} jogadas')
plt.show()
#plt.savefig('hist.png')


import numpy as np
# model = y = 2*x + 3
X = np.array([1,2,3,4],dtype=np.float32)
Y = [(2*x + 3) for x in X]
w,b = 0.0,0.0

def forward(x):
    return w*x+b

def loss(y,y_pred):
    return ((y_pred-y)**2).mean()

def gradient(x,y,y_pred):
    return np.dot(x,(y_pred-y))

print(f'print the prediction before training: f(5)={forward(5)}')

learning_rate,n_inters = (0.01,10000)

for epoch in range(n_inters):
    y_pred  = forward(X)
    l       = loss(Y,y_pred)
    dw      = gradient(X,Y,y_pred)
    w       = w - learning_rate*dw
    b       = b + learning_rate*l
    print(f'epoch {epoch+1}: w= {w:.3f},b= {b:.3f} loss = {l:.8f}, dw = {dw:.3f}')

print(f'print the prediction after training: f(5)={forward(5)}')


#T# divisibility can be checked with the modulo % operator, in a % b, if the result is 0 then a is divisible by b

#T# even numbers are divisible by 2, odd numbers are not, so in a % 2, if the result is 0 then a is an even number, if the result is 1 then a is an odd number
num1 = 5 % 2 # 1 #| 5 is an odd number
num1 = 8 % 2 # 0 #| 8 is an even number

#T# to check for divisibility in arrays element-wise, the numpy package is used
import numpy as np

#T# create an array to check divisibility on its elements
arr1 = np.arange(1, 8) # array([1, 2, 3, 4, 5, 6, 7])

#T# check divisibility by 2
arr2 = arr1 % 2 # array([1, 0, 1, 0, 1, 0, 1]) #| every second number is divisible by 2

#T# check divisibility by 3
arr2 = arr1 % 3 # array([1, 2, 0, 1, 2, 0, 1]) #| every third number is divisible by 3


'''
series_summation.py

Sum an arbitrary series
'''

from sympy import summation, sympify, Symbol, pprint
def find_sum(n_term, num_terms):
    n = Symbol('n')
    s = summation(n_term, (n, 1, num_terms))
    pprint(s)

if __name__ == '__main__':
    n_term = sympify(input('Enter the nth term: '))
    num_terms = int(input('Enter the number of terms: '))

    find_sum(n_term, num_terms)


import numpy as np
import pandas as pd

np.set_printoptions(precision=6)

def featureNormalize(X):
    """Normalizes the features in X

    featureNormalize(X) returns a normalized version of X where
    the mean value of each feature is 0 and the standard deviation
    is 1. This is often a good preprocessing step to do when
    working with learning algorithms.
    :param X:
    :return:
    """
    X_norm = pd.DataFrame()
    mu = []
    sigma = []
    for i in range(X.shape[1]):
        temp = np.array(X.iloc[:, i])
        mu.append(np.mean(temp))
        sigma.append(np.std(temp))
        X_norm[i] = (temp - mu[i]) / sigma[i]

    return X_norm, mu, sigma

def computeCostMulti(X, y, theta):
    """Compute cost for linear regression with multiple variables

    J = computeCostMulti(X, y, theta) computes the cost of using theta as the
    parameter for linear regression to fit the data points in X and y
    :param X:
    :param y:
    :param theta:
    :return:
    """
    # Initialize some useful values
    m = len(y)  # number of training examples
    diff = np.matmul(X, theta) - y
    J = 1 / (2 * m) * np.matmul(diff, diff)
    return J

def gradientDescentMulti(X, y, theta, alpha, num_iters):
    """Performs gradient descent to learn theta

    theta = gradientDescentMulti(x, y, theta, alpha, num_iters) updates theta by
    taking num_iters gradient steps with learning rate alpha
    :param X:
    :param y:
    :param theta:
    :param alpha:
    :param num_iters:
    :return:
    """
    # Initialize some useful values
    m = len(y)  # number of training examples
    J_history = []

    for i in range(num_iters):
        theta -= alpha / m * np.matmul(X.transpose(), np.matmul(X, theta) - y)
        # Save the cost J in every iteration
        J_history.append(computeCostMulti(X, y, theta))

    return theta, J_history

def normalEqn(X, y):
    """Computes the closed-form solution to linear regression

    normalEqn(X,y) computes the closed-form solution to linear
    regression using the normal equations.
    :param X:
    :param y:
    :return:
    """
    theta = np.matmul(np.matmul(np.linalg.inv(np.matmul(X.transpose(), X)), X.transpose()), y)
    return theta


import numpy as np

a = np.array([[1,2,3],
         [4,5,6]],
         dtype=np.int)

print(a.ndim)
print(a.shape)
print(a.dtype)

# 生成0矩阵
a = np.zeros((3,4), dtype=np.int16)
print(a)

# 生成随机数矩阵
a = np.empty((2,3))
print(a)

# 类似range
a = np.arange(12).reshape((3, 4))
print(a)

# 生成几段
a = np.linspace(1, 10, 6).reshape((2,3))
print(a)

a = np.array([10, 20, 30, 40])
b = np.arange(4)
print(a-b)
print(a+b)
print(a*b)

b = b ** 2
print(b)
print(b==0)

# 矩阵相乘
c = np.dot(a, b)
print(c)

# 生成0~1的数字
a = np.random.random((2,4))
print(a)

# axis=1 对行运算；axis=0 对列运算
print(np.sum(a, axis=1))
print(np.max(a))
print(np.min(a))

a = np.arange(2, 14).reshape((3,4))
print(np.argmax(a))
print(np.argmin(a))

# 平均值
print(np.mean(a))
print(a.mean())

# 中位数
print(np.median(a))

# 元素累加 输出一个向量
print(np.cumsum(a))

# 元素差
print(np.diff(a))

# 排序
print(np.sort(a))

# 转置
print(np.transpose(a))
print(a.T)
# 变化小于3大于5的数
print(np.clip(a, 3, 5))

a = np.arange(3, 15).reshape((3,4))
print(a)

# 打印出所有数
print(a[:1, :])

# flatten() 合并矩阵
print(a.flatten())
for item in a.flat:
    print(item)

# 合并矩阵
a = np.array([1, 1, 1])
b = np.array([2, 2, 2])

# 上下合并
c = np.vstack((a,b))
print(c)
print(c.shape)

# 左右合并
d = np.hstack((a,b))
print(d)
print(d.shape)

# 横向数列变为纵向数列
print(a.reshape(a.size,1))
print(a[:, np.newaxis])
a = a[:, np.newaxis]
b = b[:, np.newaxis]
print(a, b)

#
c = np.concatenate((a,b,b), axis=1)
print(c)

#numpy 的分割
a = np.arange(12).reshape((3,4))
print(a)

# 横向分割
c = np.split(a, 3, axis=0)
print(c)

# 纵向分割
c = np.split(a, 2, axis=1)
print(c)

# 不等分割
c = np.split(a, [1,1,2], axis=1)
print(c)

c = np.array_split(a, 3, axis=1)
print(c)

c = np.hsplit(a, [1,1,2])
print(c)

#from copy import deepcopy,copy

# numpy的赋值
a = np.array([0, 1, 2, 3])
b = a
c = a
d = a
a[0] = 4

print(a)
print(b)
print(c)
print(d)

# deepcopy
b = a.copy()
a[0] = 5
print(a)
print(b)


# MSDS 400 Module 6 Practice 1
import numpy as np
import matplotlib.pyplot as plt

'''
extrema() is a classification function used to evaluate a trio of points.
The function will evaluate the middle point of trio to determine if it
represents a relative maxima or minima for the trio. The result will be
a boolean value True or False which will be used later.  Note that if the
middle point is not an extrema, the value False will be returned.
'''

def extrema(a, b, c):
    x = max(a, b, c)
    z = min(a, b, c)
    epsilon = 0.0000001  # This is a safeguard against minor differences.
    result = False
    if abs(b - x) < epsilon:
        result = True
    if abs(b - z) < epsilon:
        result = True
    return result

# This is a user supplied function.  Example is Lial Figure 8 Section 13.1.

def f(x):
    y = (x ** 8) ** .333 - 16.0 * (x ** 2) ** .33
    return y

# The following extrema evaluation will be over a defined interval. Grid points
# will be defined and the function extreme() will compare trios of values.

# Define interval endpoints for a closed interval [xa,xb].
xa = -1.0
xb = +9.0

'''
n = number of grid points.  The interval [xa,xb] will be subdivided.
Adding delta to xb insures xb is included in the array generated.  For this
purpose, np.arange() will be used to create a numpy array of floating point
values to be used in subsequent calculations.
'''
n = 1000
delta = (xb - xa) / n
x = np.arange(xa, xb + delta, delta)
y = f(x)

value = [False]  # This defines the list value which will contain Boolean values.
value = value * len(x)  # This expands the list to the length of x.

'''
We are going to check each trio of points during the grid search.
If a local extrema is found, the boolean value will be set to True.
Otherwise it will remain False. The interval endpoints are always local
extrema so we define their boolean values first.
'''
L = len(x)
value[0] = True  # This will correspond to one endpoint.
value[L - 1] = True  # This corresponds to the other.

'''
The for loop will check each consecutive trios of f values with the function
extrema() to identify local extrema.  Only when an extrema is found will the
boolean value in the list value be changed to True.
'''

for x_index in range(L - 2):
    first_x = x[x_index]
    second_x = x[x_index + 1]
    third_x = x[x_index + 2]
    a = f(first_x)
    b = f(second_x)
    c = f(third_x)
    is_second_x_extrema = extrema(a, b, c)
    value[x_index + 1] = is_second_x_extrema

for k in range(L - 2):
    value[k + 1] = extrema(f(x[k]), f(x[k + 1]), f(x[k + 2]))

max_value = max(y)  # We check the list to find the global maxima.
min_value = min(y)  # We check the list to find the global minima.
'''
The following for loop checks the boolean value for each point. If the value
is True, that point will be plotted yellow.  The global maximum is plotted as
red and the minimum is plotted as green. We follow this up by plotting the
values of x and y.
'''
error = 0.0000001  # The error parameter guards against roundoff error.
# The code which follows assigns colors to maxima and minima and plots them.

plt.figure()
for k in range(L):
    if value[k] is True:
        plt.scatter(x[k], y[k], s=60, c='y')
        if abs(max_value - y[k]) < error:
            plt.scatter(x[k], y[k], s=60, c='r')
        if abs(min_value - y[k]) < error:
            plt.scatter(x[k], y[k], s=60, c='b')

plt.plot(x, y, c='k')  # This plots the line on the chart.
plt.xlabel('x-axis')
plt.ylabel('y-axis')
plt.title('Plot Showing Absolute and Relative Extrema')
plt.show()

'''
Exercise #1:  Refer to Lial Section 13.1 Example 2. Reproduce Figure 7.

Exercise #2: Refer to Lial Section 14.1 Example 3.  Evaluate over the
interval [0,10] and produce a plot showing maxima and minima.  Compare to
the answer sheet.
'''


import numpy as np

class RosenbrockProvider:
    """
    Rosenbrock function and it's gradient and hessian matrix
    """
    @staticmethod
    def f(x1, x2):
        return 100 * (x2 - x1 ** 2) ** 2 + (1 - x1) ** 2

    @staticmethod
    def grad(x1, x2):
        g1 = -400 * (x2 - x1 ** 2) * x1 + 2 * (x1 - 1)
        g2 = 200 * (x2 - x1 ** 2)
        return np.array([g1, g2])

    @staticmethod
    def hessian(x1, x2):
        return np.array([
            [400 * (3 * x1 ** 2 - x2) + 2, -400 * x1],
            [-400 * x1, 200]
        ])

class LeastSquares:
    """
    Least Squares function and it's gradient and hessian matrix
    """
    def __init__(self, A, b):
        A_shape = np.shape(A)
        b_shape = np.shape(b)
        print(A_shape, b_shape)
        assert len(b_shape) == 1 and len(A_shape) == 2 and b_shape[0] == A_shape[0] == A_shape[1]
        self.A = A
        self.b = b

    def f(self, *x):
        return sum((np.dot(self.A, x) - self.b)**2)

    def grad(self, *x):
        ax_b = np.dot(self.A, x) - self.b
        return 2 * np.dot(self.A.T, ax_b)

    def hessian(self, *x):
        return 2 * np.dot(self.A.T, self.A)


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

# define a problem to solve
def f(x, a, b, c):
    '''
    evaluates a quadratic polynomial at location x
    expects x,a,b to be column vectors and c to be a scalar constant
    '''
    return (x.T**2 @ a) + (x.T @ b) + c

def fprime(x, a, b):
    '''
    evaluates the gradient of quadratic polynomial
    '''
    return ((2 * x) * a) + b

def linear_ineq_constraint(x, y, m, b, less_eq=True):
    '''
    implement a linear inequality constraint, return True if constraint
    is satisfied, False otherwise
    '''
    return  m * x + b <= y if less_eq else m * x + b >= y

# define gradient descent procedure
def grad_descent(x_init, nsteps, eta, fprime):
    dim = x_init.shape[0]
    trajectory = np.zeros((nsteps + 1, dim))
    trajectory[0,:] = x_init.squeeze()
    x = x_init
    for i in range(nsteps):
        x -= eta * fprime(x)
        trajectory[i+1,:] = x.squeeze()
    return x, trajectory

def constrained_grad_descent(x_init, nsteps, eta, fprime, constraints, bounce_dirs):
    dim = x_init.shape[0]
    trajectory = np.zeros((nsteps + 1, dim))
    trajectory[0,:] = x_init.squeeze()
    x = x_init
    for i in range(nsteps):
        # first check for constraints
        constraintViolated = False

        for j, constraint in enumerate(constraints):
            if not constraint(*x.squeeze()):
                constraintViolated = True
                break

        # if constraint is violated bounce
        if constraintViolated:
            x += bounce_dirs[j][:, None]

        # else take regular optimization step
        else:
            if i < nsteps - 1:  # exclude last step to avoid
                                # jumping into the constrained
                                # area on the last step with no
                                # chance to bounce back
                x -= eta * fprime(x)

        trajectory[i+1,:] = x.squeeze()

    return x, trajectory

###########################################
# set up constrained optimization problem #
###########################################

x1 = np.linspace(-10, 10, 100)
x2 = np.linspace(-10, 10, 100)

X1, X2 = np.meshgrid(x1, x2)

x = np.array([X1.flatten(), X2.flatten()])
a = np.array([[3.], [4.]])
b = np.array([[1.],[1.]])
c = 0

# evaluate function at each location
y = f(x, a, b, c)
Y = np.reshape(y, X1.shape)

# add some constraints
# first constraint
m1 = 1
b1 = 3

constraint1 = [1 if linear_ineq_constraint(a, b, m1, b1) else 0 for a, b in x.T]
constraint1 = np.reshape(constraint1, X1.shape)

# second constraint
m2 = -1
b2 = 3

constraint2 = [1 if linear_ineq_constraint(a, b, m2, b2, less_eq=False) else 0 for a, b in x.T]
constraint2 = np.reshape(constraint2, X1.shape)

x_init = np.array([[-10.],[-5.]])

bounce_size = 0.03
constraints = [lambda x, y: linear_ineq_constraint(x, y, m1, b1), lambda x, y: linear_ineq_constraint(x, y, m2, b2, less_eq=False)]
bounce_dirs = np.array([[-1, 1], [-1, -1]]) * bounce_size

x_min, constrained_trajectory = constrained_grad_descent(x_init, 3000, 0.01, lambda x: fprime(x, a, b), constraints, bounce_dirs)
print(x_min, constrained_trajectory)
# plot
constraint_cmap = matplotlib.colors.ListedColormap([[186/255, 0, 0, 1], [1, 1, 1, 0]])  # transparent for 0, filled in for 1

plt.figure()
plt.contourf(X1, X2, Y, cmap='binary')
plt.plot(x1, m1 * x1 + b1, color='red')
plt.plot(x1, m2 * x1 + b2, color='red')
plt.contourf(X1, X2, constraint1, cmap=constraint_cmap, alpha=.3)
plt.contourf(X1, X2, constraint2, cmap=constraint_cmap, alpha=.3)
plt.plot(constrained_trajectory[:,0], constrained_trajectory[:,1], color='black', marker='.')
plt.xlim([-10, 10])
plt.ylim([-10, 10])
plt.show()

# # define a range for evaluation and plotting
# x1 = np.linspace(-10, 10, 100)
# x2 = np.linspace(-10, 10, 100)

# X1, X2 = np.meshgrid(x1, x2)

# x = np.array([X1.flatten(), X2.flatten()])
# a = np.array([[3.], [4.]])
# b = np.array([[1.],[1.]])
# c = 0

# # evaluate function at each location
# y = f(x, a, b, c)
# Y = np.reshape(y, X1.shape)

# define some constraints

# plot surface
# plt.figure()
# ax = plt.axes(projection='3d')
# ax.plot_surface(X1, X2, Y, cmap='viridis')
# plt.show()

# # solve unconstrained problem with gradient descent and plot
# x_init = np.array([[10.],[9.]])

# x_min, trajectory = grad_descent(x_init, 3000, 0.01, lambda x: fprime(x, a, b))

# plt.figure()
# plt.contourf(X1, X2, Y)
# plt.plot(trajectory[:,0], trajectory[:,1], color='black', marker='.')
# plt.show()


# weighted mean tutorial - real time example
# https://www.hackerrank.com/challenges/s10-weighted-mean/tutorial

import numpy as np

def weighted_mean(values: list, costs: list) -> float:
    total_weight = sum([val * cost for val, cost in zip(values, costs)])
    weighted_avg = total_weight / sum(costs)
    return '%.1f' % weighted_avg

if __name__ == '__main__':
    data = [10, 40, 30, 50, 20]
    weights = [1, 2, 3, 4, 5]
    print(weighted_mean(data, weights))

    # using numpy
    avg = np.average(np.array(data), weights=np.array(weights))
    print(avg)


import numpy as np

def median_bins(values, B):
    mean = np.mean(values)
    std_dev = np.std(values)
    min_val = mean - std_dev
    max_val = mean + std_dev

    left_bin = 0
    bins = np.zeros(B)
    bin_width = 2 * std_dev / B

    for value in values:
        if value < min_val:
            left_bin += 1
        elif value < max_val:
            current_bin = int((value - min_val) / bin_width)
            bins[current_bin] += 1

    return mean, std_dev, left_bin, bins

def median_approx(values, B):
    mean, std_dev, left_bin, bins = median_bins(values, B)

    n = len(values)
    mid = (n + 1) / 2

    count = left_bin
    for b, bin_count in enumerate(bins):
        count += bin_count
        if count >= mid:
            break

    bin_width = 2 * std_dev / B
    median = mean - std_dev + bin_width * (b + 0.5)

    return median

if __name__ == '__main__':
    # Test Case 1
    print(median_bins([1, 1, 3, 2, 2, 6], 3))
    print(median_approx([1, 1, 3, 2, 2, 6], 3))

    # Test Case 2
    print(median_bins([1, 5, 7, 7, 3, 6, 1, 1], 4))
    print(median_approx([1, 5, 7, 7, 3, 6, 1, 1], 4))

    # Test Case 3
    print(median_bins([0, 1], 5))
    print(median_approx([0, 1], 5))


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

HELIXLIB - A SET OF FUNCTIONS TO CONSTRUCT AND MANIPULATE HELICAL OBJECTS

"""

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

import csv
import logging as log

def getUnitVector(vi, vj, vk):
    v = np.array([vi, vj, vk])
    mag = np.linalg.norm(v)
    unitVector = v/mag
    return unitVector

def calculateHelixPoints(R, startDepth, thetaStart, thetaEnd, numOfPoints, parameterB, isOuterProfile):
    x = np.array([])
    y = np.array([])
    z = np.array([])
    ni = np.array([])
    nj = np.array([])
    nk = np.array([])
    inc = (thetaEnd-thetaStart)/(numOfPoints-1)
    for i in range(numOfPoints):
        theta = thetaStart + inc * i
        x = np.append(x, R * np.cos(theta))
        y = np.append(y, R * np.sin(theta))
        z = np.append(z, parameterB * theta)
        if isOuterProfile:
            normalUnitVector = getUnitVector(R*(R**2+parameterB**2)*np.cos(theta), R*(R**2+parameterB**2)*np.sin(theta), 0)
            ni = np.append(ni, normalUnitVector[0])
            nj = np.append(nj, normalUnitVector[1])
            nk = np.append(nk, normalUnitVector[2])
        else:
            normalUnitVector = getUnitVector(-R*(R**2+parameterB**2)*np.cos(theta), -R*(R**2+parameterB**2)*np.sin(theta), 0)
            ni = np.append(ni, normalUnitVector[0])
            nj = np.append(nj, normalUnitVector[1])
            nk = np.append(nk, normalUnitVector[2])

    if z[0] < 0:
        z = z + np.abs(z[0]) + startDepth
    else:
        z = z - z[0] + startDepth
    return [x, y, z, ni, nj, nk]

def storeHelixData(helixDataFilePath, helix):
    with open(helixDataFilePath, 'w', newline='') as csvfile:
     helixwriter = csv.writer(csvfile, delimiter=',',
                             quotechar='|', quoting=csv.QUOTE_MINIMAL)
     helixwriter.writerow(['x'] + ['y'] + ['z'] + ['Ni'] + ['Nj'] + ['Nk'])
     x = helix[0]
     y = helix[1]
     z = helix[2]
     ni = helix[3]
     nj = helix[4]
     nk = helix[5]
     for i in range(len(x)):
         helixwriter.writerow([x[i] , y[i] , z[i], ni[i], nj[i], nk[i]])

def plotHelix(helix):
    fig = plt.figure()
    ax1 = fig.add_subplot(111, projection='3d')
    x = helix[0]
    y = helix[1]
    z = helix[2]
    ax1.plot( x, y, z, c='b', label='Helix')
    plt.legend(loc='upper left');
    plt.axis('equal')
    plt.show()

def degreeToRadians(thetaDeg):
    return thetaDeg * np.pi/180.0

def buildHelix(R, startDepth, thetaStart, numberOfRevolutions, numOfPointsPerRev, pitch, isOuterProfile, helixFileFullName):
    numOfPoints = int(numberOfRevolutions * numOfPointsPerRev)
    thetaStart = degreeToRadians(thetaStart)
    thetaEnd = degreeToRadians(numberOfRevolutions * 360)
    parameterB = pitch/(2*np.pi)
    helix = calculateHelixPoints(R, startDepth, thetaStart, thetaEnd, numOfPoints, parameterB, isOuterProfile)
    storeHelixData(helixFileFullName, helix)
    plotHelix(helix)


import numpy as np

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

# General Romberg Method
def Romberg(a, b, tol):
    R = np.zeros((50, 50))
    h = b - a

    # We compute the first column of Romberg array with Trapezoid Method
    R[1][1] = 0.5*h*(f(a) + f(b))

    for i in range(2, 50+1):
        R[i][1] = 0.5*R[i-1][1]

        for k in range(1, int(pow(2, i-2)+1)):
            R[i][1] += 0.5 * h * f(a + (k-0.5)*h)

        # There we start computing elements from the other columns
        for j in range(2, i+1):
            R[i][j] = R[i][j-1] + (1./(pow(4, j-1)-1))*(R[i][j-1] - R[i-1][j-1])

        d = np.abs((R[i][i]-R[i-1][i-1])/R[i][i])

        if(d < tol):
            break

        h = 0.5 * h

    I = R[i][j]
    dI = d * I

    return I, dI, d

if __name__ == "__main__":
    print('\nComputated with General Romberg Method:')
    print(f"\tI ± δI = {Romberg(-5, 5, 0.001)[0]:.8f} ± {Romberg(-5, 5, 0.001)[1]:.8f}")
    print(f'\tThe relative error is η = {Romberg(-5, 5, 0.001)[2]:.5f} < 0.1 %')


"""
Singular value decomposition and expansion.
"""

from linear_algebra import gramian
from power_method import dominant_eigen_system
import math
import numpy as np

def find_u_from_v(matrix, v, singular_value):
    """
    Finds the u column vector of the U matrix in the SVD UΣV^T.

    Parameters
    ----------
    matrix : numpy.ndarray
        Matrix for which the SVD is calculated

    v : numpy.ndarray
        A column vector of V matrix, it is the eigenvector of the Gramian of `matrix`.

    singular_value : float
        A singular value of `matrix` corresponding to the `v` vector.

    Returns
    -------
    numpy.ndarray
        u column vector of the U matrix in the SVD.
    """

    return matrix @ v / singular_value

def svd(matrix, max_eigenvalues, iterations):
    """
    Performs reduced singular value decomposition of the matrix.

    Parameters
    ----------
    matrix : numpy.ndarray
        A matrix.

    max_eigenvalues : int
        Maximum number of non-zero eigenvalues to calculate.

    iterations : int
        The number of iterations of the power method.

    Returns
    -------
    list of tuples (numpy.ndarray, float, numpy.ndarray)
        List of tuples (u, sigma, v), where
            `u` is a column vector of U matrix,
            `sigma` is the corresponding singular value of `matrix`,
                which are the diagonal entries of Σ matrix,
            `v` is a column vector of V matrix
        in `matrix = U Σ V^T` svd.
    """

    svd_items = []

    for iteration in range(max_eigenvalues):
        matrix_gramian = gramian(matrix)
        eigenvalue, v = dominant_eigen_system(matrix_gramian, iterations=iterations)

        if eigenvalue == 0:
            break

        singular_value = math.sqrt(eigenvalue)
        u = find_u_from_v(matrix, v=v, singular_value=singular_value)
        svd_items.append((u, singular_value, v))

        if iteration == (max_eigenvalues - 1):
            break

        # Calculate the first dominant term of the singular value expansion
        dominant = u @ np.transpose(v) * singular_value

        # Subtract the dominant term
        matrix = matrix - dominant

    return svd_items

def singular_value_expansion(data):
    """
    Performs singular value expansion by reconstructing the original `matrix`
    from its SVD UΣV^T.

    Parameters
    ----------
    data : list of tuples
        List of tuples produced by `svd` function: (u, sigma, v), where
            `u`: numpy.ndarray is a column vector of U matrix,
            `sigma`: float is the corresponding singular value of `matrix`,
                which are the diagonal entries of Σ matrix,
            `v`: numpy.ndarray is a column vector of V matrix
        in `matrix = U Σ V^T` svd.

    Returns
    -------
    numpy.ndarray
        Matrix reconstructed form its SVD UΣV^T.
    """

    if len(data) == 0:
        return

    col_number = len(data[0][0])
    row_number = len(data[0][2])

    matrix = np.zeros([col_number, row_number])

    for data_item in data:
        u = data_item[0]
        singular_value = data_item[1]
        v = data_item[2]

        product = singular_value * (u @ np.transpose(v))
        matrix = matrix + product

    return matrix


import numpy as np

#X is matrix of m*d where m is no. of samples and d is no. of features
#n is an integer >= 1 indiacting no. of components or n is a decimal between 0 and 1 indicating variance
#returns reduced_x matrix of k*m where k is no. of reduced features and m no of samples

class PCA:
    def __init__(self, n):
        if n <1:
            self.variance = n
            self.choice=False
        else:
            self.n_components = n
            self.choice = True
        self.components = None
        self.mean = None
        self.std = None

    def fit(self, X):
        # Mean centering
        self.mean = np.mean(X, axis=0)
        self.std = np.std(X, axis=0)+0.00001 #adding af small value to prevent dividing by zero

        X = (X - self.mean)/self.std

        # covariance, function needs samples as columns
        cov = np.cov(X.T)
        # eigenvalues, eigenvectors
        eigenvalues, eigenvectors = np.linalg.eig(cov)
        # -> eigenvector v = [:,i] column vector, transpose for easier calculations
        # sort eigenvectors
        eigenvectors = eigenvectors.T
        idxs = np.argsort(eigenvalues)[::-1]
        eigenvalues = eigenvalues[idxs]
        eigenvectors = eigenvectors[idxs]

        if self.choice == True:
            # store first n eigenvectors
            self.components = eigenvectors[0:self.n_components]
        else:
            # Identifying components that explain at least variance equals n*100
            variance_vector= []
            for i in eigenvalues:
                variance_vector.append((i / sum(eigenvalues)) * 100)
            accumlated_variance = np.cumsum(variance_vector)
            self.n_components = len(  accumlated_variance[  accumlated_variance< (self.variance * 100)])
            self.components = eigenvectors[0:self.n_components]

    def transform(self, X):
        # project data
        #X = (X - self.mean)/self.std
        reduced_x=(np.dot(X, self.components.T)).T
        return reduced_x

#testing using iris dataset
'''import pandas as pd
import matplotlib.pyplot as plt
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data" # Get the IRIS dataset
data = pd.read_csv(url, names=['sepal length', 'sepal width', 'petal length', 'petal width', 'target'])
# prepare the data
x = data.iloc[:, 0:4]
# prepare the target
target = data.iloc[:, 4]

# Applying it to PCA function(how to use PCA class)

pca=PCA(2)  #using no. components
#pca=PCA(0.98)  #using variance
pca.fit(x)
print(x.shape)
mat_reduced = (pca.transform(x)).T
print(mat_reduced.shape)
# Creating a Pandas DataFrame of reduced Dataset
principal_df = pd.DataFrame(mat_reduced, columns=['PC1', 'PC2'])
# Concat it with target variable to create a complete Dataset
finalDf = pd.concat([principal_df, pd.DataFrame(target)], axis=1)

fig = plt.figure(figsize = (8,8))
ax = fig.add_subplot(1,1,1)
ax.set_xlabel('Principal Component 1', fontsize = 15)
ax.set_ylabel('Principal Component 2', fontsize = 15)
ax.set_title('2 component PCA', fontsize = 20)
targets = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
colors = ['r', 'g', 'b']
for target, color in zip(targets,colors):
    indicesToKeep = finalDf['target'] == target
    ax.scatter(finalDf.loc[indicesToKeep, 'PC1']
               , finalDf.loc[indicesToKeep, 'PC2']
               , c = color
               , s = 50)
ax.legend(targets)
ax.grid()
plt.show()'''


import sympy as sy
from sympy import *

"""
2x+8y=−40
4x+16y=−79
"""

x, y, z = sy.symbols('x, y, z')
eq1 = sy.Eq(-7*x - 3*y + 3*z, 5)
eq2 = sy.Eq(-2*x + 5*y - 4*z, 5)
eq3 = sy.Eq(-27*x - 35*y + 31*z, 5)

eq4 = sy.Eq(10*x + 4*y - 6*z, -8)
eq5 = sy.Eq(20*x + 8*y - 12*z, -16)
eq6 = sy.Eq(35*x + 14*y + 21*z, -28)

eq7 = sy.Eq(5*x + 10*y + 19*z, 6)
eq8 = sy.Eq(-2*x - 3*y - 7*z, 1)
eq9 = sy.Eq(3*x + 6*y + 12*z, 9)

eq10 = sy.Eq(-7*x - 3*y + 3*z, 5)
eq11 = sy.Eq(-2*x + 5*y - 4*z, 5)
eq12 = sy.Eq(-27*x - 35*y + 31*z, 4)

ans1 = sy.solve((eq1, eq2, eq3), (x, y, z))
print(ans1)
ans2 = sy.solve((eq4, eq5, eq6), (x, y, z))
print(ans2)
ans3 = sy.solve((eq7, eq8, eq9), (x, y, z))
print("ans3:",ans3)
ans4 = sy.solve((eq10, eq11, eq12), (x, y, z))
print("ans4:",ans4)

eq13 = sy.Eq(x - 4*y - 4*z, -3)
eq14 = sy.Eq(x - 6*y - 8*z, 1)
eq15 = sy.Eq(-2*x + 11*y + 14*z, 0)
ans5 = sy.solve((eq13, eq14, eq15), (x, y, z))
print("ans5:",ans5)

M = Matrix([[1, -4, -4, -3], [1, -6, -8, 1], [-2, 11, 14, 0]])

# Use sympy.rref() method
M_rref = M.rref(pivots=True)

print("The Row echelon form of matrix M and the pivot columns : {}".format(M_rref))

h , k = sy.symbols('h, k')
eq16 = sy.Eq(-7*x + 7*y + 7*z, -6)
eq17 = sy.Eq(-2*x - 9*y - 5*z, 3)
eq18 = sy.Eq(11*x + 11*y + h*z, k)
ans6 = sy.solve((eq16, eq17, eq18), (x, y, z))
print("ans6:",ans6)


from sympy import *
import numpy as np
import array as arr

def TongQuat(X, Y):
    list ( zip(X , Y ) )
    x = symbols('x')
    m = len(X)
    A = [[X[i] ** j for j in range (m) ] for i in range (m) ]
    kq = np.linalg.solve(A,Y)
    hamSo = ''
    for i in range (len(kq)):
        hamSo += '+%d*(x ** %d)' %(kq[i], i)
    P = lambda x: eval(hamSo )
    f1 = str(P(x))
    f1 = eval(f1)
    f1 = latex(f1)
    return f1, A

def Newton(X, Y, pp):
    X = [0.0,0.5,1.0,1.5,2.0] #mốc nội suy
    Y = [-1.0,0.125,1.0,2.375,5.0]
    n = len(X)
    h = X[1]-X[0]
    x , t = symbols ('x t')
    sp = [ [d(k, i, Y) for i in range(n-k)] for k in range (n)]
    if pp == 'Newton':
        P = Y[0]
        for k in range(1, n): # k chạy từ 1 tới n-1
            prod = d(k, 0,Y)/factorial(k)
            for i in range(k):
                prod *= t - i
            P += prod
        P = P . subs (t , ( x - X [0]) / h) . expand()
    if pp == 'Newton Lùi':
        m = n-1
        P = Y[m]
        for k in range(1, n):
            prod = d(k, m-k, Y)/factorial(k)
            for i in range(k):
                prod *= t + i
            P += prod
        P = P.subs(t, (x - X[m]) / h).expand()
    print(P)
    f1 = latex(P)
    return f1, sp

def d (k , i, Y ) :
    if k == 0:
        return Y[i]
    return d (k -1,i +1, Y ) - d (k -1 , i, Y )

def checkCondition(X, Y):
    n = len(X)
    h = X[1]-X[0]
    if(len(X) != len(Y)):
        return False
    for i in range(0,n-1):
        if(X[i+1] - X[i] != h):
            return False
    return True

def Lagrange(X,Y):
    n = len(X)
    x = symbols('x')
    P = 0
    for i in range (n) :
        P += Y [i ] * L (i , x, n , X )
    P = P.expand()
    f1 = latex(P)
    print(f1)
    s = []
    s1 = []
    for i in range(n):
        a, b = L(i, x, n, X), L(i, x, n , X).expand()
        s.append( latex(a))
        s1.append( latex(b))
    return f1, s, s1
def L (i , x, n, X ) :
    prod = 1
    for j in range (n) :
        if j != i :
            prod *= ( x - X[ j ]) / ( X [ i ] - X [ j ])
    return prod


'''Purpose of the module is to describe different matrix types in code'''

import numpy as np

# Setting default random generator
rng = np.random.default_rng(12345)

def get_square_matrix(n, random=False, integer=1):
    '''Return a square matrix with integer scalaras. Whether random or specified'''

    if random:
        return rng.integers(50, size=(n, n))
    else:
        return np.ones((n, n), dtype=int) * integer

def get_symmetrical_matrix(n):
    '''Return a matrix that's symetrical'''
    # Creating permutations of rows
    current_integer_list = [i for i in range(1, n+1)]
    permutations = []
    while(current_integer_list[-1] != 1):
        permutations.append(current_integer_list[:])
        current_integer_list.pop()
        current_integer_list.insert(0, current_integer_list[0] + 1)
    # To add the last row
    permutations.append(current_integer_list[:])
    return np.array(permutations)

def get_triangular_matrix(matrix, triangular_type):
    '''Return a triangular matrix'''
    if triangular_type == "triup":
        return np.triu(matrix)
    elif triangular_type == "trile":
        return np.tril(matrix)
    else:
        raise ValueError("'tringular_type' is either wrong or empty.")

def get_diagonal_matrix(diagonal_vector, extra_row=0, fill_value=0):
    '''Return a diagonal matrix'''
    rows = []
    for index, value in enumerate(diagonal_vector):
        row = [0]*len(diagonal_vector)
        row[index] = value
        rows.append(row)
    counter = 0
    while(counter < extra_row):
        rows.append([fill_value]*len(diagonal_vector))
        counter += 1
    return np.array(rows)

def format_message_str(string):
    '''Return re-formatted string for output messages.

    It's expected a docstring with 4 spaces that delimits sentences
    with some new lines at each end.
    '''
    string = string.strip()
    return "\n".join([line.strip() for line in string.split(" "*4)])

if __name__ == "__main__":
    # Square matrix
    m = """
    Square matrices are a type of matrices where column and row are equal.
    (since it's a square Duh). These types of matrices allow to be used in
    matrix arithmetic with ease due to equal sizes."""
    print(format_message_str(m))
    print('4 x 4 square matrix')
    print(get_square_matrix(4, 4, True))
    print("Let's do some square matrix arithmetic")
    A = get_square_matrix(5, 5, integer=2)
    B = get_square_matrix(5, 5, integer=5)
    print('A:\n', A)
    print('B:\n', B)
    print("Let's do some multiplication and division in the form of A * B and A / B")
    print("Multiplication:\n", A * B)
    print("Division:\n", A / B)
    print("Keep in mind that it multiplies across elements, not dot products")
    # Symmetrical matrix
    m = """
    Symmetrical matrix is a square matrix that has values mirrored along a diagonal line.
    The diagonal line consists of just '1' scalars and then each scalar get's incremented to left and right
    sides of the diagonal line.
    For example, a matrix with 16 dimension size would look like this:
    """
    print(format_message_str(m))
    print("\n", get_symmetrical_matrix(16))
    print("It has to be a square such that the values can be mirrored")

    # Triangular matrix
    m = """
    Triangular matrix is a square matrix where a part of matrix
    (in a shape of a 'square') has values on one side of diagonal
    line and the other one has zeros.

    Triangular matrix can be characterised by either 'triangular up' (scalar values above
    diagonal line) and 'triangular down' (scalar values below diagonal line).

    For example a 5x5 trinagular matrix would like this:
    """
    print(format_message_str(m))
    m = get_square_matrix(5, random=True)
    print(get_triangular_matrix(m, "trile"))
    print("or this:")
    print(get_triangular_matrix(m, "triup"))
    m = """
    Functions that can perform the formatting of these matrices are `numpy.tril`
    and `numpy.triu`.
    """
    print(format_message_str(m))
    # Diagonal matrix
    m = """
    Diagonal matrix is a type of matrix that has values alongside a diagonal line.
    This diagonal line is usually called a 'diagonal vector'. Usually scalar values
    are line up diagonally and the rest tend to be 0's. For example:
    """
    print(format_message_str(m))
    print(get_diagonal_matrix([3, 5, 68, 79, 666]))
    m = """
    Keep in mind that the matrix doesn't have to be a square matrix since the diagonal vector's
    last value needs to reach at the last column of the column. For example.
    """
    print(format_message_str(m))
    print(get_diagonal_matrix([3, 5, 68, 79, 666], 1))
    m = """
    There is a helper function called `numpy.diag`:
    - if it takes a matrix, it will return a diagonal vector.
    - if it takes a vector, it will return a matrix containing the provided diagonal vector.
    For example:
    """
    print(format_message_str(m))
    m = get_diagonal_matrix([v**2 for v in range(1, 4)])
    print(f"Diagonal matrix as an example.\n{m}")
    diagonal_vector = np.diag(m)
    print(f"Using numpy.diag function will produce a vector of {diagonal_vector}")
    m = np.diag(diagonal_vector)
    print(f"And providing the vector to the same function produces a matrix like this:\n{m}")

    # Identity matrix
    m = """
    Identity matrix is a type of a square matrix where a diagonal line consists of 1's and the rest of vectors are 0's.
    Special property of it is "a vector doesn't change when multiplied by it".
    To produce an identity matrix, the following functions can be used:
    1. numpy.matlib.identity
    2. numpy.identity

    For example, the following matrix can be produced using numpy.identity:
    """
    print(format_message_str(m))
    m = np.identity(3)
    print(m)
    print("Then produce a different normal square matrix, like this:")
    sq_m = get_square_matrix(3, True)
    print(f"{sq_m}")
    print(f"If you multiply the identity matrix with the square one, you get:\n{m * sq_m}")
    print("As for seeing that a vector is not affected by multiplication with the identitiy vector")
    vector = np.array([2, 3, 4, 5, 6])
    print(f"You have vector of {vector}")
    i = np.identity(5)
    print(f"And then an identity matrix like below:\n{i}")
    print(f"So if we do 'vector * i', then it's:\n {vector * i}")
    print(f"So if we do 'vector @ i', then it's: {vector @ i}")
    # Orthogonal matrix


from scipy.fftpack import fft
import numpy as np

"""
A3-Part-2: Optimal zero-padding

Given a sinusoid, write a function that computes the DFT of the sinusoid after zero-padding and returns
the positive half of the magnitude spectrum (in dB). Zero-padding needs to be done such that one of
the bin frequencies of the DFT coincides with the frequency of the sinusoid. Choose the minimum
zero-padding length for which this condition is satisfied.

The input arguments are the sinusoid x of length M, sampling frequency fs and the frequency of the
sinusoid f. The output is the positive half of the magnitude spectrum mX computed using the N point
DFT (N >= M) of x after zero-padding x to length N appropriately as required.

To get the positive half of the spectrum, first compute the N point DFT of the zero-padded input signal
(for this you can use the fft function of scipy.fftpack, which is already imported in this script).
Consider only the first (N/2)+1 samples of the DFT and compute the magnitude spectrum of the positive
half (in dB) as mX = 20*log10(abs(X[:(N/2)+1])), where X is the N point DFT of the zero-padded input.

For this exercise, you can assume that the frequency of the sinusoid f is a positive integer and a
factor of the sampling rate fs. The input parameters will be given in such a way that N will be even.
Note that the numerical value of f is an integer but the data type is float, for example 1.0, 2.0,
55.0 etc. This is to avoid issues in python related with division by a integer.

Due to the precision of the FFT computation, the zero values of the DFT are not zero but very small
values < 1e-12 (or -240 dB) in magnitude. For practical purposes, all values with absolute value less
than 1e-6 (or -120 dB) can be considered to be zero.

HINT: One of the DFT bin frequencies coincides with the frequency f of a sinusoid when the DFT size
(N in this question) contains exactly an integer number of periods of the sinusoid. For example,
if f = 100 Hz and fs = 1000 Hz, one period of the sinusoid has 10 samples. Then given a signal of length
M = 25 samples, there are 2.5 periods in it. The minimum zero-padding length here would be 5 samples
(0.5 period), so that the DFT size N = 30 corresponds to 3 periods of a sinusoid of frequency f=100 Hz.

Test case 1: For a sinusoid x with f = 100 Hz, M = 25 samples and fs = 1000 Hz, you will need to
zero-pad by 5 samples and compute an N = 30 point DFT. In the magnitude spectrum, you can see a
maximum value at bin index 3 corresponding to the frequency of 100 Hz. The output mX you return is
16 samples in length.

Test case 2: For a sinusoid x with f = 250 Hz, M = 210 samples and fs = 10000 Hz, you will need to
zero-pad by 30 samples and compute an N = 240 point DFT. In the magnitude spectrum, you can see a
maximum value at bin index 6 corresponding to the frequency of 250 Hz. The output mX you return is
121 samples in length.

"""
def optimalZeropad(x, fs, f):
    """
    Inputs:
        x (numpy array) = input signal of length M
        fs (float) = sampling frequency in Hz
        f (float) = frequency of the sinusoid in Hz
    Output:
        The function should return
        mX (numpy array) = The positive half of the DFT spectrum of the N point DFT after zero-padding
                        x appropriately (zero-padding length to be computed). mX is (N/2)+1 samples long
    """
    ## Your code here
    M = x.shape[-1]
    per_samp = fs / f
    N = per_samp
    while N<M:
        N += per_samp
    N = int(N)
    fftbuffer = np.zeros(N)
    fftbuffer[:M] = x
    X = fft(fftbuffer)
    mX = 20 * np.log10(abs(X[:(N//2)+1]))
    return mX


from numpy import array, zeros, sqrt, linalg

def symmetric(A):
    return (A == A.T).all()
def positiveDefinite(A):            # for i in range(len(matrix)):
    E = linalg.eigvals(A)           #     if E[i] < 0: return False
    return (E >= 0).all()           # return True

def decomposition(A):
    N = len(A)
    L = zeros((N,N))                # L = zeros_like(A)

    for j in range(N):                                      # j = 0 to N-1
        for i in range(j, N):                               # i = j to N-1
            if i == j:
                L[i,j] = sqrt(A[i,j] - sum(L[i,:j]**2))     # k = 0 to j-1
            else:
                L[i,j] = (A[i,j] - sum(L[i,:j]*L[j,:j])) / L[j,j]

    print('Cholesky Decomposition:')
    print('[L] =\n', L, sep='', end='\n\n')
    return L

def solveCholesky(A, B):
    L = decomposition(A)
    U = L.T
    N = len(L)

    X = zeros(N)
    Y = zeros(N)

    # Forward Substitution
    for i in range(N):                                      # i = 0 to N-1
        Y[i] = (B[i] - sum(L[i,:i] * Y[:i])) / L[i,i]       # j = 0 to i-1

        # sumj = 0
        # for j in range(i):
        #     sumj += L[i,j] * Y[j]
        # Y[i] = (B[i] - sumj) / L[i,i]

    # Backward Substitution
    for i in range(N-1, -1, -1):                            # i = N-1 to 0
        X[i] = (Y[i] - sum(U[i,i+1:] * X[i+1:])) / U[i,i]   # j = i+1 to N-1

        # sumj = 0
        # for j in range(i+1, N):
        #     sumj += U[i,j] * X[j]
        # X[i] = (Y[i] - sumj) / U[i,i]

    return X

# System of Equations

A = array([[ 6,  15,  55],
           [15,  55, 225],
           [55, 225, 979]], float)
B = array([-19, -100, -474], float)
'''
A = array([[8,    3.22,  0.8, 0,     4.10],
           [3.22, 7.76, 2.33, 1.91, -1.03],
           [0.8,  2.33, 5.25, 1,     3.02],
           [0,    1.91, 1,    7.5,   1.03],
           [4.1, -1.03, 3.02, 1.03,  6.44]], float)
B = array([9.45, -12.20, 7.78, -8.1, 10.0], float)
'''
N = len(A)

if symmetric(A) and positiveDefinite(A):
    X = solveCholesky(A, B)

    print("The Solution of the System:")
    for i in range(N):
        print('X[', i+1, '] = ', round(X[i], 6), sep='')
else:
    print("The System cannot be Solved by Cholesky Decomposition")

'''
Decomposition: [A] = [L][U]   # [L][U] ≠ [U][L]    Not commutative

│a11 a12 a13 ... a1n│   │L11  0   0  ...  0 ││U11 U12 U13 ... U1n│
│a21 a22 a23 ... a2n│   │L21 L22  0  ...  0 ││ 0  U22 U23 ... U2n│
│a31 a32 a33 ... a3n│ = │L31 L32 L33 ...  0 ││ 0   0  U33 ... U3n│
│... ... ... ... ...│   │... ... ... ... ...││... ... ... ... ...│
│an1 an2 an3 ... ann│   │Ln1 Ln2 Ln3 ... Lnn││ 0   0   0  ... Unn│

Conditions of matrix A for Cholesky Decomposition:
1. Symmetric:        A == A.T for all elements  # aij=aji   i,j = 1 to n
2. Positive Definite: All eigenvalues positive  # linalg.eigvals(A) >= 0

For Cholesky: [A] = [L][L]^T  # [U] = [L]^T       Symmetric matrix

│a11 a12 a13 ... a1n│   │L11  0   0  ...  0 ││L11 L12 L13 ... L1n│
│a21 a22 a23 ... a2n│   │L21 L22  0  ...  0 ││ 0  L22 L23 ... L2n│
│a31 a32 a33 ... a3n│ = │L31 L32 L33 ...  0 ││ 0   0  L33 ... L3n│
│... ... ... ... ...│   │... ... ... ... ...││... ... ... ... ...│
│an1 an2 an3 ... ann│   │Ln1 Ln2 Ln3 ... Lnn││ 0   0   0  ... Lnn│

│a11 a21 a31 ... an1│   │L11  0   0  ...  0 ││L11 L21 L31 ... Ln1│
│a21 a22 a32 ... an2│   │L21 L22  0  ...  0 ││ 0  L22 L32 ... Ln2│
│a31 a32 a33 ... an3│ = │L31 L32 L33 ...  0 ││ 0   0  L33 ... Ln3│
│... ... ... ... ...│   │... ... ... ... ...││... ... ... ... ...│
│an1 an2 an3 ... ann│   │Ln1 Ln2 Ln3 ... Lnn││ 0   0   0  ... Lnn│

│a11       symmetric│
│a21 a22            │
│a31 a32 a33        │ =
│... ... ... ...    │
│an1 an2 an3 ... ann│

│L11^2                                                           symmetric│
│L11L21  L21^2+L22^2                                                      │
│L11L21 L31L21+L32L22  L31^2+L32^2+L33^2                                  │
│ ...        ...             ...           ...                            │
│L11Ln1 Ln1L21+Ln2L22 Ln1L31+Ln2L32+Ln3L33 ... Ln1^2+Ln2^2+Ln3^2+...+Lnn^2│

L11 = √(a11)
L21 = a21/L11   L22 = √(a22-L21^2)
L31 = a31/L11   L32 = (a32-L31L21)/L22   L32 = √(a33-L31^2-L32^2)
    ...             ...                     ...
Ln1 = an1/L11   Ln2 = (an2-Ln1L21)/L22   Ln3 = (an3 - Ln1L31 - Ln2L32) / L33
                                         Lnn = √(ann - Ln1^2 - Ln2^2 - Ln3^2)

Lij = √(aij - ∑ Lik^2)         , i = j, j = 1 to n, i = j to n, k = 1 to j-1
Lij =  (aij - ∑ Lik*Ljk) / Ljj , i ≠ j, j = 1 to n, i = j to n, k = 1 to j-1

For Li1 there is no ∑ and is implimented by k = 1 to 1-1, not entering k loop

Substitution: [A]{X}={B}    => [L][U]{X}={B}    => [U]{X}={y} and [L]{y}={B}

Forward Substitution: [L]{y}={B}

│L11  0   0  ...  0 ││y1│   │b1│
│L21 L22  0  ...  0 ││y2│   │b2│
│L31 L32 L33 ...  0 ││y3│ = │b3│
│... ... ... ... ...││……│   │……│
│Ln1 Ln2 Ln3 ... Lnn││yn│   │bn│

y1 =  b1 / L11
y2 = (b2 - L21y1) / L22
y3 = (b3 - L31y1 - L32y2) / L33
yn = (bn - Ln1y1 - Ln2y2 - ... - L[n,n-1]y[n-1]) / Lnn

yi = (bi - ∑ Lij*yj) / Lii , i = 1 to n, j = 1 to i-1

For y1 there is no ∑ and is implimented by j = 1 to 1-1, not entering j loop

Backward Substitution: [U]{X}={y}

│U11 U12 U13 ... U1n││x1│   │y1│
│ 0  U22 U23 ... U2n││x2│   │y2│
│ 0   0  U33 ... U3n││x3│ = │y3│
│... ... ... ... ...││……│   │……│
│ 0   0   0  ... Unn││xn│   │yn│

xn =  yn / Unn
x3 = (y3                   - U34*x4) / U33
x2 = (y2          - U23*x3 - U24*x4) / U22
x1 = (y1 - U12*x2 - U13*x3 - U14*x4) / U11
xi = (yi - Uij*xj - Uij*xj - Uij*xj) / Uii

xi = (yi - ∑ Uij*xj) / Uii , i = n to 1, j = i+1 to n

For xn there is no ∑ and is implimented by j = n+1 to n, not entering j loop

'''

5. Systems of Linear Equations/6. Cholesky's (Factorization or Decomposition) Method.py


import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits import mplot3d
import scipy.optimize

# Task 1 - Find the vector x in R^3 that minimizes ||Ax-b||
def task_1():
    A = np.array([[1, 1, 2],
                [1, 2, 1],
                [2, 1, 1],
                [2, 2, 1]])

    b = np.array([1, -1, 1, -1]) # Numpy interprets this as a column matrix.

    def min_x(A, b):
        '''
        (1.1) Solves normal equation A_t . A . x = A_t . b for x by:
            * Taking transpose of matrix A.
            * Taking the dot products A_t . A and A_t . b, and assign them new names B and C.
            * Returning linalg.solve(B,C) to find the solution for Bx = C.
        '''
        A_t = np.transpose(A)
        B = np.dot(A_t, A)
        C = np.dot(A_t, b)
        return np.linalg.solve(B, C)

    def min_x_norm(A, b, x_guess = [0, 0, 0]):
        '''
        (1.2) Finds the solution for ||Ax-b|| by:
            * Creating a lambda function f, which represents ||Ax-b|| in a programatical way.
            * Finding a suitable 'guess' for the algorithm, which is based on the result of min_x().
            * Use scipy.optimize.fmin() with the guess for x to solve for x
        '''

        f = lambda x: np.linalg.norm((np.dot(A, x) - b))
        return scipy.optimize.fmin(f, x_guess,disp=False)

    min_x_normal = min_x(A, b)
    min_x_scipy = min_x_norm(A, b)

    print(f"Task 1.1 - Using the normal equation A_t . A . x = A_t . b and solving for x, we obtain:\n{min_x_normal}\n")
    print(f"Task 1.2 - With scipy.optimize.fmin(), the x for which ||Ax-b|| is minimized is:\n{min_x_scipy}\n")
    print(f"Task 1.2 - The difference between the two is:\n{min_x_normal - min_x_scipy}\n")

    def r(a):
        '''
        (1.3) Creates a function representing the residual ||Ax(a) - b(a)|| by:
            * Using min_x() from 1.1 to minimize the new solution x dependent on input a.
            * Returning ||Ax(a)-b(a)||.
        '''
        b = [1, a, 1, a] # Numpy interprets this as a column matrix.
        x = min_x(A, b)
        return np.linalg.norm(np.dot(A, x) - b)

    x_axis = np.linspace(0,100,1000)
    y_axis = [r(a) for a in x_axis] # For every value a, take ||Ax(a)-b(a)|| using the r(a) fn.

    # Show the result of task 1.3.
    plt.plot(x_axis, y_axis)
    plt.xlabel("a")
    plt.ylabel("r(a)")
    plt.grid(color='black', linestyle='-', alpha=0.2)
    plt.title("Residual r(a) = ||Ax(a)-b(a)||")
    plt.show()

def task_2():
    A = np.array([[ 1,3,2],
                [-3,4,3],
                [ 2,3,1]])

    z_0 = np.array([8,3,12])

    def z_closed(n):
        '''
        (2.1) Returns z_n based on derived closed form (to be shown in presentation).
        '''
        e_1 = np.array([1,12,-19])
        e_2 = np.array([1,0,1])
        e_3 = np.array([3,1,3])
        return (-1)**(n+1) * 1/5 * e_1 - 3**n * 8 * e_2 + 4**n * 27/5 * e_3

    def v_n(n):
        '''
        (2.2) Function representation of v_n := z_n / ||z_n||
        '''
        z_n = z_closed(n)
        return z_n/np.linalg.norm(z_n)

    def q_n(n):
        '''
        (2.4) Returns q_n for every normalized iterate of v_n by:
            * Computing v_n for nth iterate.
            * Returning q_n where q_n = v_n^T A v_n
        '''
        v = v_n(n)
        return np.transpose(v).dot(A.dot(v))

    def iterates(a_n,a,epsilon):
        '''
        (2.6) Determines iterates necessary to satisfy an epsilon difference by:
            * Taking the norm ||v_n - v||.
            * Returning amount of iterates when the norm is less than epsilon.
        '''
        n = 0
        while True:
            if np.linalg.norm(a_n(n)-a) < epsilon:
                return n
            n += 1

    # (2.1) Check if z_n converges as n -> 'infinity' and print last result.
    z_vals = [ z_closed(n) for n in range(0, 200) ]
    print(f"Task 2.1 - As n → ∞, we have after 200 iterations that:\nz_n = {z_vals[-1]}")

    plt.plot(np.array(range(0,200)), [ np.linalg.norm(z) for z in z_vals ])
    plt.title("Graph of Z_n as n → ∞")
    plt.show()

    # (2.2) Determining numerically the value that v_n converges to.
    v_vals = [ v_n(n) for n in range(0,200) ]
    v = v_vals[-1]
    print(f"Task 2.2 - As n → ∞, v_n converges to {v}")

    # (2.2) Plot iterates of v_n
    X = [ v[0] for v in v_vals ]
    Y = [ v[1] for v in v_vals ]
    Z = [ v[2] for v in v_vals ]
    fig = plt.figure()
    ax = fig.add_subplot(projection='3d')
    ax.scatter(X,Y,Z)
    ax.plot(X,Y,Z)
    plt.title(f"Iteration of v_n := z_n / ||z_n|| for n from 0 to {len(v_vals)}")
    plt.show()
    fig.clf() # clear figure

    # (2.4) Check limit of q_n as n -> 'infinity', and print last result.
    q_vals = [ q_n(n) for n in range(0,200) ]
    q = q_vals[-1]
    print(f"Task 2.4 - the limit of q as n -> inf is approximately {round(q,2)}.")

    # (2.6) Define epsilon from task and print result
    epsilon = 10**-8
    print(f"Task 2.6 - {iterates(v_n,v,epsilon)} iterations needed for ||v_n - v|| < ε")

    # (2.7) Set a range of epsilons between 10^-1 and 10^-14, and compute number of
    # iterates required for the result to be less than some epsilon in the range.
    epsilon_vals = 10**((-1)*np.linspace(1,14,1000))
    v_iterates = [ iterates(v_n,v,eps) for eps in epsilon_vals ]
    q_iterates = [ iterates(q_n,q,eps) for eps in epsilon_vals ]

    # (2.7) Plot the number of iterates of ||v_n-v|| and ||q_n-n|| against epsilon.
    plt.gca().invert_xaxis()
    plt.semilogx(epsilon_vals,v_iterates,label="||v_n - v|| < ε")
    plt.semilogx(epsilon_vals,q_iterates,label="||q_n - q|| < ε")
    plt.legend()
    plt.show()

def task_3():
    def f(x_1,x_2):
        '''
        (3b) Determines solution of x_3 for a given x_1 and x_2 by:
            * Defining g as the given function, but making RHS equal to 0 (for fsolve()).
            * Defining two reasonable initial values for the fsolve().
            * Return the result.
        '''
        g = lambda x_3 : 2*x_1**2 - x_2**2 + 2*x_3**2 - 10*x_1*x_2 - 4*x_1*x_3 + 10*x_2*x_3 - 1
        result_1 = scipy.optimize.fsolve(g,-10)
        result_2 = scipy.optimize.fsolve(g,10)
        return [result_1[0],result_2[0]]

    # (3.1) Setting some parameters for the 3D plot of the function.
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    x = np.linspace(-1,1,20)
    y = np.linspace(-1,1,20)
    X,Y = np.meshgrid(x,y)

    Z_1 = []
    Z_2 = []

    # Below, we calculate z for all x-y combinations by looping over every y_coord
    # and then generating a list of z values for every x_coord in x.
    # This covers all possible x-y combinations of the plot.
    # zip(*z_coord_list) separates the two different z values of each coordinate into their own lists.

    z_coord_list = []
    for y_coord in y:
       z = [f(x_coord, y_coord) for x_coord in x]
       z_coord_list.append(zip(*z))
    Z_1, Z_2 = zip(*z_coord_list)

    # (3.1) Plot the surfaces
    Z_1 = np.array(Z_1)
    Z_2 = np.array(Z_2)

    ax.plot_surface(X,Y,Z_1)
    ax.plot_surface(X,Y,Z_2)

    plt.show()
    fig.clf()

# Main function to run all the tasks.
def main():
    task_list = [task_1, task_2, task_3]
    while True:
        try:
            i = int(input("Select the task to run! (0 to quit): "))
            # If task nr is valid, execute it and finish by clearing matplotlib figure.
            if i in range(1,4):
                task_list[i-1]()
            elif i == 0:
                break # Exits while loop & closes program.
            else:
                raise ValueError
        except:
            print("Invalid input!")

if __name__ == "__main__":
    main()


import numpy as np

def make_cubic(n_samples, x_min, x_max, a=1, b=0, c=0, d=0, noise=0.0, random_state=None):
    np.random.seed(random_state)
    x = np.linspace(x_min, x_max, n_samples)
    y = a*x**3 + b*x**2 + c*x + d + (2*noise*np.random.random(n_samples) - noise)
    return x.reshape(-1,1), y.reshape(-1,1)

def make_exp(n_samples, x_min, x_max, noise=0.0, random_state=None):
    np.random.seed(random_state)
    x = np.linspace(x_min, x_max, n_samples)
    y = np.exp(x) + 2*noise*np.random.random(n_samples) - noise
    return x.reshape(-1,1), y.reshape(-1,1)

def make_log10(n_samples, x_min, x_max, noise=0.0, random_state=None):
    np.random.seed(random_state)
    x = np.logspace(np.log10(x_min), np.log10(x_max), n_samples)
    y = np.log10(x) + 2*noise*np.random.random(n_samples) - noise
    return x.reshape(-1,1), y.reshape(-1,1)

def make_spiral(n_samples, n_class=2, radius=1, laps=1.0, noise=0.0, random_state=None):
    np.random.seed(random_state)
    x = np.zeros((n_samples * n_class, 2))
    y = np.zeros((n_samples * n_class))

    pi_2 = 2 * np.math.pi
    points = np.linspace(0, 1, n_samples)
    r = points * radius
    t = points * pi_2 * laps
    for label, delta_t in zip(range(n_class), np.arange(0, pi_2, pi_2/n_class)):
        random_noise = (2 * np.random.rand(n_samples) - 1) * noise
        index = np.arange(label*n_samples, (label+1)*n_samples)
        x[index] = np.c_[r * np.sin(t + delta_t) + random_noise,
                         r * np.cos(t + delta_t) + random_noise]
        y[index] = label
    return x, y.reshape(-1, 1)

def make_square(n_samples, x_min, x_max, a=1, b=0, c=0, noise=0.0, random_state=None):
    np.random.seed(random_state)
    x = np.linspace(x_min, x_max, n_samples)
    y = a*x**2 + b*x + c + (2*noise*np.random.random(n_samples) - noise)
    return x.reshape(-1,1), y.reshape(-1,1)


"""
basic operations II
"""

import numpy as np

arr = np.array([[1, 5, 6],
                [4, 7, 2],
                [3, 1, 9]])

# maximum element of array
print("Largest element is:", arr.max())

# minimum element of array
print("Smallest element is:", arr.min())

# maximum element per row
print("Row-wise maximum elements:",
      arr.max(axis=1))

# minimum element per col
print("Column-wise minimum elements:",
      arr.min(axis=0))

# sum of array elements
print("Sum of all array elements:",
      arr.sum())

# cumulative sum along each row
print("Cumulative sum along each row:\n",
      arr.cumsum(axis=1))

c = np.array([[1, 2],
              [3, 4]])
d = np.array([[4, 3],
              [2, 1]])

# add arrays
print("Array sum:\n", c + d)

# multiply arrays (elementwise multiplication)
print("Array multiplication:\n", c * d)


import numpy as np

class Gaussian:
    """Summary of class here.

    Attributes:
        mean: μ (平均值)
        std: standard deviation, σ (標準差)
        variance: σ^2 (變異數)
    """
    def __init__(self, mean=0, std=None, variance=None) -> None:
        self._mean = mean

        if std:
            self._std = std
            self._variance = self._std ** 2
        elif variance:
            self._variance = variance
            self._std = np.sqrt(self._variance)
        else:
            print('Initial error, missing parameters "std" or "variance", please at least select one.')
            raise AttributeError

    @property
    def mean(self):
        return self._mean

    @mean.setter
    def mean(self, val):
        self._mean = val

    @property
    def std(self):
        return self._std

    @std.setter
    def std(self, val):
        self._std = val
        self._variance = self._std ** 2

    @property
    def variance(self):
        return self._variance

    @variance.setter
    def variance(self, val):
        self._variance = val
        self._std = np.sqrt(self._variance)

# Measurement Update (Correction)
def measurement_update(gauss1: Gaussian, gauss2: Gaussian) -> Gaussian:
    mean1 = gauss1.mean
    std1 = gauss1.std
    mean2 = gauss2.mean
    std2 = gauss2.std

    new_mean = (np.power(std1, 2) * mean2 + np.power(std2, 2) * mean1) / (std1**2 + std2**2)
    new_std = np.sqrt(1 / ((1 / std1**2) + (1 / std2**2)))

    new_gauss = Gaussian(new_mean, new_std)
    return new_gauss

# Prediction (Motion Update)
def prediction(gauss1: Gaussian, gauss2: Gaussian) -> Gaussian:
    mean1 = gauss1.mean
    variance1 = gauss1.variance
    mean2 = gauss2.mean
    variance2 = gauss2.variance

    new_mean = mean1 + mean2
    new_variance = variance1 + variance2

    new_gauss = Gaussian(new_mean, variance=new_variance)
    return new_gauss

if __name__ == "__main__":
    # Measurement Update
    #   Gaussian(mean, std, variance)
    print(">>> Measurement Update <<<")

    gauss1 = Gaussian(10, 2)
    gauss2 = Gaussian(12, 2)
    new_gauss = measurement_update(gauss1, gauss2)
    print("New mean: {}, new std: {}, new variance: {}".format(
        new_gauss.mean, new_gauss.std, new_gauss.variance))

    gauss1 = Gaussian(10, np.sqrt(8))
    gauss2 = Gaussian(13, np.sqrt(2))
    new_gauss = measurement_update(gauss1, gauss2)
    print("New mean: {}, new std: {}, new variance: {}".format(
        new_gauss.mean, new_gauss.std, new_gauss.variance))

    # Prediction (Motion Update)
    print(">>> Prediction <<<")

    gauss1 = Gaussian(8, variance=4)
    gauss2 = Gaussian(10, variance=6)
    new_gauss = prediction(gauss1, gauss2)
    print("New mean: {}, new std: {}, new variance: {}".format(
        new_gauss.mean, new_gauss.std, new_gauss.variance))

    gauss1 = Gaussian(10, variance=4)
    gauss2 = Gaussian(12, variance=4)
    new_gauss = prediction(gauss1, gauss2)
    print("New mean: {}, new std: {}, new variance: {}".format(
        new_gauss.mean, new_gauss.std, new_gauss.variance))

Sensor Fusion and Tracking/Kalman Filters/kalman_concept.py


from typing import Tuple

import numpy as np

def computeD1D2(current: float, volatility: float, ttm: float, strike: float,
                rf: float) -> Tuple[float, float]:
    """Helper function to compute the risk-adjusted priors of exercising the
    option contract, and keeping the underlying asset. This is used in the
    computation of both the Call and Put options in the
    Black-Scholes-Merton framework.

    Arguments:
        current {float} -- Current price of the underlying asset.
        volatility {float} -- Volatility of the underlying asset price.
        ttm {float} -- Time to expiration (in years).
        strike {float} -- Strike price of the option contract.
        rf {float} -- Risk-free rate (annual).

    Returns:
        Tuple[float, float] -- Tuple with d1, and d2 respectively.
    """

    d1 = (np.log(current / strike) + (rf + ((volatility ** 2) / 2)) * ttm) \
        / (volatility * np.sqrt(ttm))
    d2 = d1 - (volatility * np.sqrt(ttm))

    return (d1, d2)


import numpy as np
from numpy import linalg as la

# This file is also used in the voronoi lab.

def prob_1():
	#problem 1
	#part 1
	A = np.array([[.75, .5], [.25, .5]])
	print A.dot(A)[0,0]
	#part 2
	print la.matrix_power(A, 20)[0,0]

def prob_2():
	#problem 2
	#part 1
	A = np.array([[1./4, 1./3, 1./2], [1./4, 1./3, 1./3], [1./2, 1./3, 1./6]])
	print A
	#part 2
	print A.dot(A)[0,1]
	#part 3
	#it is fine if they just raised the matrix to a few powers and did the comparison themselves
	Anew = A.dot(A)
	prev = A.copy()
	tol = .0000001
	iters = 1
	maxiters = 100
	while la.norm(prev-Anew) > tol:
		prev[:] = Anew
		Anew[:] = A.dot(Anew)
		if iters > maxiters:
			print "exceeded ", maxiters, " iterations."
			break
		iters += 1
	if iters is not maxiters+1:
		print "reached steady state after ", iters, " iterations."
	print Anew

def prob_3:
	#problem 3
	A = np.array([[0, 0, 1, 0, 1, 0, 1],
				  [1, 0, 0, 0, 0, 1, 0],
				  [0, 0, 0, 0, 0, 1, 0],
				  [1, 0, 0, 0, 1, 0, 0],
				  [0, 0, 0, 1, 0, 0, 0],
				  [0, 0, 1, 0, 0, 0, 1],
				  [0, 1, 0, 0, 0, 0, 0]], dtype=np.int64)
	A5 = la.matrix_power(A,5)
	coords = np.where(A5==np.max(A5))
	#note: indexing from 0
	print "maximum of 5 step connections at: ", zip(coords[0], coords[1])
	A7 = la.matrix_power(A,7)
	coords = np.where(A7==0)
	print "no 7 step connection for: ", zip(coords[0], coords[1])

#problem 4
def findpath(a, b, A):
	Anew = A.copy()
	arrs = [Anew]
	num = 0
	while Anew[a,b] == False:
		num += 1
		Anew = Anew.dot(A)
		arrs.append(Anew)
		if num > A.shape[0]-1:
			raise ValueError("Nodes are not connected")
			break
	current = a
	path = [current]
	for arr in reversed(arrs[:-1]):
		#iterating over steps
		for i in xrange(A.shape[0]):
			#iterating over possible points
			if A[i,current] == True:
				#if it links to the current node
				if arr[b,i] == True:
					#if it links to b at this step
					current = i
					path.append(current)
					break
	path.append(b)
	return path

def prob_4():
	A = np.load("maze.npy")
	print findpath(0, 224, A)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 10 15:18:57 2019

@author: alankar
"""

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,2,1000)
f = lambda x:np.sin(1/(x*(2-x)))**2

plt.figure(figsize=(13,10))
plt.plot(x,f(x))
plt.grid()
plt.xlabel(r'$x$',size=18)
plt.ylabel(r'$f(x)$',size=20)
plt.title(r'Integrand', size=21)
plt.tick_params(axis='both', which='major', labelsize=15)
plt.tick_params(axis='both', which='minor', labelsize=12)
plt.savefig('4.png')
plt.show()

N = int(1e4)
x = np.random.uniform(low=0,high=2.0,size=N)
y = np.random.uniform(low=0.,high=1.0,size=N)
A = 2.*1.
I = (np.count_nonzero(np.array(y<=f(x),dtype=np.int32))/N)*A
error = np.sqrt(I*(A-I)/N)
print('Monte Carlo Hit-Miss Algorithm')
print('Integral: %f'%I)
print('Error: %e'%error)

print()
I = ((2.-0.)/N)*np.sum(f(x))
varf = (1/N)*np.sum(f(x)**2)-(I/(2.-0.))**2
error = (2.-0.)*np.sqrt(varf/N)
print('Monte Carlo Mean Value Algorithm')
print('Integral: %f'%I)
print('Error: %e'%error)

"""
Output:

Monte Carlo Hit-Miss Algorithm
Integral: 1.456000
Error: 8.899798e-03

Monte Carlo Mean Value Algorithm
Integral: 1.453037
Error: 5.308131e-03
"""


import numpy as np

def lu_decomposition_with_pp(matrix):
    # Get the number of rows
    n = matrix.shape[0]

    tmp_L = np.eye(n, dtype=np.double)
    tmp_U = matrix.copy()
    for i in range(n):
        result = np.where(np.abs(tmp_U[i:, i]) == np.amax(np.abs(tmp_U[i:, i])))
        swap_i = result[0][0]
        tmp_U[[swap_i + i, i], i:n] = tmp_U[[i, swap_i + i], i:n]

        factors = tmp_U[i + 1:, i] / tmp_U[i, i]
        tmp_L[i + 1:, i] = factors
        tmp_U[i + 1:] -= factors[:, np.newaxis] * tmp_U[i]
    return tmp_L, tmp_U

def forward_substitution(tmp_l, tmp_b):
    # get size of L matrix
    n = tmp_l.shape[0]

    # initialize empty array with size b
    tmp_y = np.zeros_like(tmp_b, dtype=np.double)

    # computing tmp_y values
    tmp_y[0] = tmp_b[0] / tmp_l[0, 0]
    for i in range(1, n):
        tmp_y[i] = (tmp_b[i] - np.dot(tmp_l[i, :i], tmp_y[:i])) / tmp_l[i, i]
    return tmp_y

def backward_substitution(tmp_u, tmp_y):
    # get size of U matrix
    n = tmp_u.shape[0]

    # initialize emtpy array with size y
    tmp_x = np.zeros_like(tmp_y, dtype=np.double)

    # computing tmp_x values
    tmp_x[-1] = tmp_y[-1] / tmp_u[-1, -1]
    for i in range(n - 2, -1, -1):
        tmp_x[i] = (tmp_y[i] - np.dot(tmp_u[i, i + 1:], tmp_x[i + 1:])) / tmp_u[i, i]
    return tmp_x

def solve_lin_system_with_lu(mat, x):
    TMP_L, TMP_U = lu_decomposition_with_pp(mat)
    y = forward_substitution(TMP_L, x)
    return backward_substitution(TMP_U, y)

def iterative_ref(A, b, tolerance=1e-10, iterations=1000):
    x = np.ones_like(b, dtype=np.double)
    for i in range(iterations):
        x0 = x.copy()
        r = b - np.dot(A, x0)
        c = np.linalg.solve(A, r)
        x = np.add(x0, c)
        if np.linalg.norm(x - x0, 2) / np.linalg.norm(x0, 2) < tolerance:
            break
    return x

#  correct answer is given by gepp
gepp = np.array([[-5.93025381, 0.10179665, -5.63806883],
                 [5.71248256, -5.33907931, 0.04770739],
                 [5.09042443, 4.20346378, -7.43808245]])
gepp_b = np.array([3.00764463,
                   3.39289929,
                   -3.35276915])

print("correct answer is given by gepp method")
print("built in function")
print(np.linalg.solve(gepp, gepp_b))
print("iterative refinement method")
print(iterative_ref(gepp, gepp_b))
print("GEPP method")
print(solve_lin_system_with_lu(gepp, gepp_b), "\n\n\n")

#  gepp fails and works only with iterative refinement
gepp = np.array([[-5e-3, 1.0, 2.0],
                 [-2.0, -1, 1.0],
                 [-5.0, 5.0, 1]])
gepp_b = np.array([6.0,
                   -9.0,
                   2.0])
print("gepp fails and gives accurate results only with iterative refinement")
print("built in function")
print(np.linalg.solve(gepp, gepp_b))
print("iterative refinement method")
print(iterative_ref(gepp, gepp_b))
print("GEPP method")
print(solve_lin_system_with_lu(gepp, gepp_b), "\n\n\n")


from math import ceil
import numpy as np

def bisection(f, a, b, eps=5e-6):
    """
        Function that finds a root using Bisection method for a given function f(x).
        The function finds the root of f(x) with a predefined absolute accuracy epsilon.
        The function excepts an interval [a,b] in which is known that the function f has a root.
        If the function f has multiple roots in this interval then Bisection method converges randomly to one of them.
        If in the interval [a,b] f(x) doesn't change sign (Bolzano theorem can not be applied) the function returns nan
        as root and -1 as the number of iterations.Also the function checks if either a or b is a root of f(x), if both
        are then the function returns the value of a.

        Parameters
        ----------
        f : callable
            The function to find a root of.
        a : float
            The start of the initial interval in which the function will find the root of f(x).
        b : float
            The end of the initial interval in which the function will find the root of f(x).
        eps : float
            The target accuracy.
            The iteration stops when the length of the current interval divided by 2 to the power of n+1 is below eps.
            Default value is 5e-6.

        Returns
        -------
        root : float
            The estimated value for the root.
        iterations_num : int
            The number of iterations.
    """

    # check if Bolzano theorem can not be applied or a is larger than b
    if f(a) * f(b) > 0 or a > b:
        return np.nan, -1
    elif f(a) == 0:  # check if a is root of f
        return a, 0
    elif f(b) == 0:  # or b is root of f
        return b, 0

    # find how many iterations are needed for achieving error less than eps
    iterations_num = ceil((np.log(b - a) - np.log(eps)) / np.log(2))

    # Bisection algorithm
    for i in range(0, iterations_num):
        current_root = (a + b) / 2  # each iteration root approximation is the middle of the current interval
        if f(current_root) == 0:
            return current_root, i+1
        elif f(a) * f(
                current_root) < 0:  # find out where Bolzano theorem still can be applied and update the interval [a,b]
            b = current_root
        else:
            a = current_root

    return current_root, iterations_num


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import timeit

plt.style.use('seaborn')
sns.set_style("darkgrid")

# So    :   initial stock price
# dt    :   time increment (a day)
# T     :   length of the prediction time horizon
# N     :   number of time points in the prediction time horizon -> T/dt
# t     :   array for time points in the prediction time horizon [1, 2, 3, .. , N]
# mu    :   mean of historical daily returns
# sigma :   standard deviation of historical daily returns
# b     :   array for brownian increments
# W     :   array for brownian path
# sims  :   number of simulations

sims = 10000

s0 = 10
T = 100
dt = 1
N = T / dt
t = np.arange(1, int(N) + 1)
mu = 0
sigma = 0.009

# Dictionary Implementation
np.random.seed(123)

start = timeit.default_timer()

b = {str(i): np.random.normal(0, np.sqrt(dt), int(N)) for i in range(sims)}
W = {str(i): np.cumsum(b[str(i)]) for i in range(sims)}
drift = (mu - 0.5 * sigma**2) * t
diffusion = {str(i): sigma * W[str(i)] for i in range(sims)}

S = np.array([s0 * np.exp(drift + diffusion[str(i)]) for i in range(sims)])
S = np.hstack((np.array([[s0] for i in range(sims)]), S))

end = timeit.default_timer()
print("Time {:,.3f} seconds".format(end - start))

fig, ax = plt.subplots(figsize=(8,5))
for i in range(sims):
    ax.plot(S[i, :], label="_nolabel_")
ax.plot(S[0, :], color="k", label="Actual Path")
ax.set(title="Geometric Brownian Motion", xlabel="days", ylabel="Stock Price, £")
plt.legend()
plt.show()

# Array Implementation
np.random.seed(123)

start = timeit.default_timer()

b = np.random.normal(0, np.sqrt(dt), (sims, int(N)))
W = np.cumsum(b, axis=1)
drift = (mu - 0.5 * sigma**2) * t
diffusion = sigma * W

S = np.array(s0 * np.exp(drift + diffusion))
S = np.vstack((np.array([[s0] * sims]), S.T))

end = timeit.default_timer()
print("Time {:,.3f} seconds".format(end - start))

fig, ax = plt.subplots(figsize=(8,5))
ax.plot(S, alpha=0.5, label="_nolabel_")
ax.plot(S[:, 0], color="k", label="Actual Path")
ax.set(title="Geometric Brownian Motion", xlabel="days", ylabel="Stock Price, £")
plt.legend()
plt.show()

fig, ax = plt.subplots(figsize=(8,5))
ax.hist(S[-1, :], bins = 20)
ax.set(title="Geometric Brownian Motion", xlabel="days", ylabel="Stock Price, £")
plt.show()

# Drift

def brownianMotion(sims, T, dt, seed):

    '''
    int sims    : number of simulations
    int T       : number of time points to predict
    float dt    : time increment
    return b, W : b - Brownian increment, W - Brownian path
    '''

    np.random.seed(seed)
    N = int(T / dt)
    b = np.random.normal(0, 1, (sims, N))*np.sqrt(dt)
    W = np.cumsum(b, axis=1)

    return b, W

b, W = brownianMotion(1, 252, 1, 123)

fig, ax = plt.subplots(figsize=(16,5), ncols=2, nrows=1)
ax[0].plot(b.T)
ax[0].set(title="Brownian Increment", xlabel="days", ylabel="Random Variate")
ax[1].plot(W.T)
ax[1].set(title="Brownian Path", xlabel="days", ylabel="Random Variate")
plt.show()

def GBM(sims, T, dt, s0, mu, sigma, seed):

    '''
    int sims    : number of simulations
    int T       : number of time points to predict
    float dt    : time increment
    float s0    : initial stock price
    float mu    : drift coefficient
    float sigma : diffusion coefficient
    return S    : stock price simulations
    '''

    # Calculate the simulation range
    N = int(T / dt)
    t = np.arange(1, N + 1)

    # Calculate Brownian random paths
    b, W = brownianMotion(sims, T, dt, seed)

    # Calculate drift and diffusion
    drift = (mu - 0.5 * sigma**2) * t
    diffusion = sigma * W

    # Simulate stock price
    S = np.array(s0 * np.exp(drift + diffusion))
    S = np.vstack((np.array([[s0] * sims]), S.T))

    return S

S = GBM(sims=1, T=252, dt=1, s0=10, mu=0, sigma=0.01, seed=10)

fig, ax = plt.subplots(figsize=(16,5), ncols=2, nrows=1)
for mu in [-0.001, 0, 0.001]:
    S = GBM(sims=1, T=252, dt=1, s0=10, mu=mu, sigma=0.01, seed=10)
    ax[0].plot(S, label=f"$\mu = ${mu}")
    ax[0].set(title="Drift Coefficient Sensitivity", xlabel="days", ylabel="Stock Price, £")
ax[0].legend()
for sigma in [0.01, 0.05, 0.1]:
    S = GBM(sims=1, T=252, dt=1, s0=10, mu=0, sigma=sigma, seed=10)
    ax[1].plot(S, label=f"$\sigma = ${sigma}")
ax[1].set(title="Diffusion Coefficient Sensitivity", xlabel="days", ylabel="Stock Price, £")
plt.legend()
plt.show()


# generate interactive demo session running the script
# below as input to scitools file2interactive
import sympy as sym
import sys

# Integration associated with sine expansion for -u''=2
i, j = sym.symbols('i j', integer=True)
x, L = sym.symbols('x L')
# Cannot do this one unless the arguments are i*x and j*x
#A_ij = sym.integrate(sym.sin((i+1)*sym.pi*x/L)*sym.sin((j+1)*sym.pi*x/L),
#                    (x, 0, L))
A_ii = sym.integrate(sym.sin((i+1)*sym.pi*x/L)**2, (x, 0, L))
print(A_ii)
f = 2
a = 2*L/(sym.pi**2*(i+1)**2)
c_i = a*sym.integrate(f*sym.sin((i+1)*sym.pi*x/L), (x, 0, L))
c_i = sym.simplify(c_i)
print(c_i)
print(sym.latex(c_i, mode='plain'))
#sys.exit(0)

x, x_m, h, X = sym.symbols('x x_m h X')

from fe_approx1D_numint import *
c = approximate(sym.sin(x), symbolic=True, d=1, N_e=4, numint='Trapezoidal',
                Omega=[0,sym.pi])
print(c)
c = approximate(sym.sin(x), symbolic=True, d=1, N_e=4, numint='Simpson',
                Omega=[0,sym.pi])
print(c)
#sys.exit(0)
from fe_approx1D import *

# "Hand"-integration of element matrix and vector
A_00 = sym.integrate(h/8*(1-X)**2, (X, -1, 1))
print(A_00)
print(sym.latex(A_00, mode='plain'))
A_10 = sym.integrate(h/8*(1+X)*(1-X), (X, -1, 1))
print(A_10)
print(sym.latex(A_10, mode='plain'))
A_11 = sym.integrate(h/8*(1+X)**2, (X, -1, 1))
print(A_11)
print(sym.latex(A_11, mode='plain'))
x = x_m + h/2*X
b_0 = sym.integrate(h/4*x*(1-x)*(1-X), (X, -1, 1))
b_1 = sym.integrate(h/4*x*(1-x)*(1+X), (X, -1, 1))
print(b_0)
print(b_1)
print(sym.latex(b_0, mode='plain'))
print(sym.latex(b_1, mode='plain'))

phi = basis(d=1)
phi
element_matrix(phi, Omega_e=[0.1, 0.2], symbolic=True)
element_matrix(phi, Omega_e=[0.1, 0.2], symbolic=False)

h, x = sym.symbols('h x')
nodes = [0, h, 2*h]
elements = [[0, 1], [1, 2]]
phi = basis(d=1)
f = x*(1-x)
A, b = assemble(nodes, elements, phi, f, symbolic=True)
A
b
c = A.LUsolve(b)
c
fn = sym.lambdify([x], f)
[fn(xc) for xc in nodes]

# The corresponding numerical computations, as done by sympy and
# still based on symbolic integration, goes as follows:

nodes = [0, 0.5, 1]
elements = [[0, 1], [1, 2]]
phi = basis(d=1)
x = sym.Symbol('x')
f = x*(1-x)
A, b = assemble(nodes, elements, phi, f, symbolic=False)
A
b
c = A.LUsolve(b)
c

d=1; N_e=8; Omega=[0,1]  # 8 linear elements on [0,1]
phi = basis(d)
f = x*(1-x)
nodes, elements = mesh_symbolic(N_e, d, Omega)
A, b = assemble(nodes, elements, phi, f, symbolic=True)
A

from fe_approx1D_numint import *
c = approximate(sym.sin(x), symbolic=True, d=1, N_e=4, numint='Trapezoidal',
                Omega=[0,sym.pi])
print(c)
c = approximate(sym.sin(x), symbolic=True, d=1, N_e=4, numint='Simpson',
                Omega=[0,sym.pi])
print(c)

# The integration does not work with sin(pi*x), but works fine with
# sin(x) on [0,pi] instead.
#approximate(sym.sin(sym.pi*x), symbolic=True, d=1, N_e=3, numint=None,
#            Omega=[0,1])
c = approximate(sym.sin(x), symbolic=True, d=1, N_e=2, numint=None,
                Omega=[0,sym.pi])
print(sym.simplify(c[1,0].subs('h', sym.pi/2)))


# %%
import numpy as np
# standard exponential function to each element
# and normalizes these values by dividing by the sum
# of all these exponentials. This normalization ensures
# that the sum of the components of the output vector
# is 1.

# Write a function that takes as input
# a list of numbers, and returns the list
# of values given by the softmax function.

# Returns a list
""" def softmax(L):
    expL = np.exp(L)
    sumExpL = sum(expL)
    result = []
    for i in expL:
        result.append(i/sumExpL)
    return result """

# Alternatively...
""" def softmax(L):
    exp = np.exp(L)
    return np.divide(exp, exp.sum()) """

# Returns an array
def softmax(L):
    exponentials = np.exp(L)
    sum_exponentials = sum(exponentials)
    result = exponentials/sum_exponentials
    return result

softmax([1,2,3])

# [0.09003057317038046, 0.24472847105479767, 0.6652409557748219]

# %%
# Quiz
# Based on the above video, let's define the combination
# of two new perceptrons as w1*0.4 + w2*0.6 + b.
# Which of the following values for the weights and the
# bias would result in the final probability of the point
# to be 0.88?

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

# Output (prediction) formula
def output_formula(features, weights, bias):
    return sigmoid(np.dot(features, weights) + bias)

inputs = [.4,.6]
w1 = [2,6]
b1 = -2
w2 = [3,5]
b2 = -2.2
w3 = [5,4]
b3 = -3

print(output_formula(inputs,w2,b2))

# %%


from numpy import array
from numpy import mean
from numpy import cov
from numpy.linalg import eig
import pandas as pd

# define a matrix
A = array([[1, 2], [2, 3], [3, 2], [4, 4], [5, 4], [6, 7], [7, 6], [9, 7]])

print("We have the data:")
print(A)
# calculate the mean of each column
M = mean(A.T, axis=1)
print("Step1: Let's compute the mean vector:")
print(M)
# center columns by subtracting column means
C = A - M
print("Step2: Subtract the mean from data:")
print(C)
# calculate covariance matrix of centered matrix
V = cov(C.T)
print("Step3: Let's calculate the covariance matrix:")
print(V)
# eigendecomposition of covariance matrix
values, vectors = eig(V)
print("Step4: Let's calculate the eigen vectors and eigen values.")
print("Eigen vectors:")
print(vectors)
print("Eigen values:")
print(values)

# project data
P = vectors.T.dot(C.T)
print("Step5: Projected Data:")
print(P.T)


import numpy as np

def pascalindex2d(k):
    """
    The purpose of this function is to prepare a set of combinations of m and n for the creation of the Jacobi polynomials that satisfy (m+n)<=k.
    k: int, max order of the Jacobi polynomial desired

    Returns: array of (ncomb x 2), where each row corresponds to a tuple of (m, n) values for the Jacobi polynomials. Take, for example, the monomial basis: {1, x1, x2, x1^2, x1x2, x2^2, ...}. The order of the polynomials in the variables x1 and x2 are (0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), which is exactly what this function outputs.
    Remember the formula nplocal = (k+1)(k+2)/2 for 2D? This is just a formula for the number of elements up to that order (row) of the pascal triangle. Assuming each entry has equal area weighting, this formula basically calculates the "area" of the elements in pascal's triangule using A=(base1*base2)/2*(1/3)h - volume of a square pyramid is (1/3)bh.
    """

    if k==0:
        # Base case - top element in pascal triangle has index (0, 0)
        return np.array([0, 0])[None, :]

    # Builds the index pairs of the kth row of the triangle

    nth_row = np.zeros((k+1, 2))
    nth_row[:, 0] = np.arange(k+1)
    nth_row[:, 1] = k - nth_row[:, 0]

    nth_row = np.fliplr(nth_row)    # So it matches the 16.930 matlab code
    # And then stacks it with the index pairs of the elements in the rows of the pyramid above it
    prev_rows = pascalindex2d(k-1)
    pindx = np.concatenate((prev_rows, nth_row), axis=0).astype(int)

    return pindx

def pascalindex3d(k):
    """
    The purpose of this function is to prepare a set of combinations of m, n, and l for the creation of the Jacobi polynomials that satisfy (m+n+l)<=k.
    k: int, max order of the Jacobi polynomial desired

    Returns: array of (ncomb x 3), where each row corresponds to a tuple of (m, n, l) values for the Jacobi polynomials.
    Remember the formula nplocal = (k+1)(k+2)(k+3)/6 for 3D? This is just a formula for the number of elements up to that order (row) of the pascal pyramid. Assuming each entry has equal volume weighting, this formula basically calculates the "volume" of the elements in pascal's triangule using A=bh/2*(altitude)/3
    Recursively
    """

    if k==0:
        # Base case - top element in pascal pyramid has index (0, 0, 0)
        return np.array([0, 0, 0])[None,:]

    # Builds the index pairs of the kth level of the pyramid
    level2d_indices = pascalindex2d(k)  # 2D pascal indices on a level
    nth_level = np.zeros((level2d_indices.shape[0], 3))
    nth_level[:,1:] = level2d_indices
    nth_level[:, 0] = k-np.sum(nth_level[:,1:], axis=1)   # Adds the third column/dimension so that the rows add to k

    # nth_level = np.fliplr(nth_level)    # So it matches the 16.930 matlab code
    # And then stacks it with the index pairs of the elements in the levels of the pyramid above it
    prev_levels = pascalindex3d(k-1)
    pindx = np.concatenate((prev_levels, nth_level), axis=0).astype(int)

    return pindx

if __name__ == '__main__':
    print(pascalindex2d(3))
    # print(pascalindex3d(3))