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

# Compute pdf of normal distribution
def normal_pdf(x, mu, sigma):
	return (1 / (np.sqrt(2 * np.pi * np.square(sigma)))) * (np.power(np.e, -(np.square((x - mu)) / (2 * np.square(sigma)))))

# Part A
variance = 9
a, b2 = 0, 1
with open('q2_sigma3.dat') as file:
	for line in file.readlines():
		sample = [float(i) for i in line.strip().split(', ')]
		n = len(sample)
		se2 = variance / n
		sample_mean = sum(sample) / n
		x = ((b2 * sample_mean) + (se2 * a)) / (b2 + se2)
		y2 = (b2 * se2) / (b2 + se2)
		a, b2 = x, y2
		print('{}\t{}'.format(a, b2))
		sigma = math.sqrt(b2)
		x_points = np.linspace(a - 3*sigma, a + 3*sigma, 100)
		plt.plot(x_points, normal_pdf(x_points, a, sigma), label='\u03BC={:.2f} \u03C3={:.2f}'.format(a, sigma))

plt.title("q2_sigma3.dat")
plt.xlabel("x")
plt.ylabel("pdf(x)")
plt.legend(loc='upper left')
plt.show()

# Part B
variance = 10000
a, b2 = 0, 1
with open('q2_sigma100.dat') as file:
	for line in file.readlines():
		sample = [float(i) for i in line.strip().split(', ')]
		n = len(sample)
		se2 = variance / n
		sample_mean = sum(sample) / n
		x = ((b2 * sample_mean) + (se2 * a)) / (b2 + se2)
		y2 = (b2 * se2) / (b2 + se2)
		a, b2 = x, y2
		print('{}\t{}'.format(a, b2))
		sigma = math.sqrt(b2)
		x_points = np.linspace(a - 3*sigma, a + 3*sigma, 100)
		plt.plot(x_points, normal_pdf(x_points, a, sigma), label='\u03BC={:.2f} \u03C3={:.2f}'.format(a, sigma))

plt.title("q2_sigma100.dat")
plt.xlabel("x")
plt.ylabel("pdf(x)")
plt.legend(loc='upper left')
plt.show()


# -*- coding:utf-8 -*-
"""
Project   : numpy
File Name : 5_to_6_eigen
Author    : Focus
Date      : 8/21/2021 5:32 PM
Keywords  :  eigenvalue, scalar, np.linalg.eigenvals
Abstract  :
Param     :
Usage     : py 5_to_6_eigen
Reference :
"""
import numpy as np
A = np.mat("3 -2; 1 0")
print("A \n", A)
print("Eigenvalues", np.linalg.eigvals(A))
eigenvalues, eigenvectors = np.linalg.eig(A)
print("First tuple of eigen: ", eigenvalues)
print("Second tuple of eigen: ", eigenvectors)

print("Checking:" + "*"*100)

for i in range(len(eigenvalues)):
    x_left = np.dot(A, eigenvectors[:,i])
    x_right = np.dot(eigenvalues[i], eigenvectors[:,i])
    print("left : \n", x_left)
    print("right: \n", x_right)
    print("error: \n", x_left - x_right)


from scipy import sign
from scipy.optimize import bisect

def bisection(f, a, b, e=10**(-12)):
    while (b - a) > e:
        m = a + (b - a) / 2
        if sign(f(a)) == sign(f(m)):
            a = m
        else:
            b = m
    m = a + (b - a) / 2
    return m

if __name__ == "__main__":
    f = lambda x: x**2 - 10
    a = 0
    b = 10
    print('Bisección Propio:', bisection(f, a, b))
    print('Bisección Scipy:', bisect(f, a, b))
    print('Raíz Exacta:', 10**(1/2))


"""
斐波拉契数列 的通项式
"""
import numpy as np
# X_n=A`n-1 x_1
# 向量 x_1
x1 = np.array([1, 0])
# 矩阵A
A = np.array([[1, 1], [1, 0]])
# 求矩阵a的特征值和特征向量
ks, vs = np.linalg.eig(A)
k_1 = ks[0]  # 特征值1
k_2 = ks[1]  # 特征值2
v_1 = vs[0]  # 特征向量1
v_2 = vs[1]  # 特征向量2
print(f"求矩阵A的特征值为k_1={k_1},k_2={k_2}")
print(f"求矩阵A的特征向量为v_1={v_1},v_2={v_2}")
# 求解c1 和 c2,用v1和v2 的线性组合替换x_1, c1*v_1+c2*v_2=x1
c1, c2 = np.linalg.solve(vs, x1)
print(f"求解得c1={c1},c2={c2}")
# 带入元公式 xn=A`(n-1)x1=A~(n-1) (c1v_1+c2v_2)=c1A`v_1+c2Av_2=c1k_1`v_1+c2k_2`v_2
# x_n的第一个分项就是a_n
def fblq(n): return c1*k_1**(n-1)*v_1[0]+c2*k_2**(n-1)+v_2[0]

# 测试求前50项
print([fblq(i) for i in range(1, 50)])
# 直接迭代求法

def fb(n: int) -> int:
    if n == 0 or n == 1:
        return n
    else:
        return fb(n-1)+fb(n-2)

# 求1～9 项
# print([fb(i) for i in range(1,30)])


from sympy import S, zeros, Matrix, nsimplify, symbols, Function, ones
from ..T5 import polinomio_newton
from ..genericas import matriz_inversa

import numpy as np

def derivacion_polinomio_newton(f, x0, var=symbols('x'), grado=1, h=0.01):
    """Aplica el método de Newton para calcular la derivada de una función en un punto, creando una matriz con x, x+h, x+2h, etc.

    Args:
        f (funcion): Función a derivar
        x0 (float): Punto de evaluación
        var (variable, optional): Variable de f. Defaults to symbols('x').
        grado (int, optional): Grado del polinomio de aproximación. Defaults to 1.
        h (float, optional): Valor h de diferencia. Defaults to 0.01.

    Returns:
        p_diff (float): Valor de la derivada.
        p (polinomio): Polinomio de Newton.
        m (matriz): Matriz de diferencias del algoritmo.
    """
    x_vals = [x0 + i * h for i in range(grado + 1)]
    y_vals = [f.subs(var, (x0 + i * h)) for i in range(grado + 1)]

    # Con esto creamos el polinomio
    p, m = polinomio_newton(x_vals, y_vals, var)

    # Ahora lo diferenciamos
    p_diff = p
    for i in range(grado):
        p_diff = p_diff.diff(var)

    return p_diff, p, m

def polinomio_lagrange_derivada(f, x_vals=None, var=symbols('x'), grado=1, I=[0, 1]):
    """Genera el polinomio de Lagrange para la derivada de f.

    Args:
        f (funcion): Función a derivar
        x_vals (list): Lista de nodos dentro del intervalo donde se evaluará el polinomio. Si None, se distribuyen equidistantemente por el intervalo.
        var (variable, optional): Variable de f. Defaults to symbols('x').
        grado (int, optional): Grado del polinomio de aproximación. Defaults to 1. Se cumple que el grado es igual al número de valores - 1
        I (list): Intervalo de aproximación

    Returns:
        p (polinomio): función con los coeficientes de aproximación
        val (float): evaluación del polinomio en los nodos correspondientes
    """

    ff = Function('f')

    if x_vals is None:
        x_vals = np.linspace(I[0], I[1], grado + 1)
    assert len(x_vals) == grado + 1

    G = ones(len(x_vals), len(x_vals))
    for row in range(1, len(x_vals)):
        for col in range(len(x_vals)):
            G[row, col] = x_vals[col] ** row

    x_mat = Matrix([var ** i for i in range(grado + 1)])
    for _ in range(grado):
        x_mat = x_mat.diff(var)

    # Con esto creamos el vector de valores de A
    a = matriz_inversa(G) * x_mat

    p, val = S(0), S(0)
    for i in range(grado + 1):
        p += a[i] * ff(x_vals[i])
        val += a[i] * f.subs(var, x_vals[i])

    return p, val

def derivada_richardson(f, x0=0, r=0.5, h=0.5, x_var=symbols('x'), h_var=symbols('h'), Nf=None, m=None, grado=3):
    """Aplica el método de Richardson para el cálculo de la derivada de una función.

    Args:
        f (funcion): Función a derivar
        x0 (float): Punto de evaluación
        r (float, optional): Valor de r de aproximación (ideal cercano a 0.5). Defaults to 0.5.
        h (float, optional): Valor h de diferencia. Defaults to 0.5.
        x_var (variable, optional): Variable de f. Defaults to symbols('x').
        h_var (variable, optional): Variable h. Defaults to symbols('h').
        Nf (funcion, optional): Función de derivación. Por defecto es f(x+h) - f(x-h) / 2h.
        m (int, optional): Grado de error inicial en base a Nf. Para el caso por defecto, m = 2.
        grado (int, optional): Número de pasos del algoritmo. Defaults to 3.

    Returns:
        val (float): valor de la derivada
        matriz_r (matriz): matriz con los pasos del algoritmo
    """
    if Nf is None:
        Nf = (f.subs(x_var, x0 + h_var) - f.subs(x_var, x0 - h_var)) / (2 * h_var)
        m = 2

    matriz_r = zeros(grado, grado)

    # primero llenamos la matriz con los valores N(rih)
    for row in range(grado):
        rih = r ** row * h
        matriz_r[row, 0] = nsimplify(Nf.subs(h_var, rih), tolerance=1e-10, rational=True)

    for col in range(1, grado):
        for row in range(grado - col):
            r_exp = m + col - 1
            matriz_r[row, col] = nsimplify((r**r_exp * matriz_r[row, col - 1] - matriz_r[row + 1, col - 1]) / (r**r_exp - 1), rational=True)

    val = matriz_r[0, -1]

    return val, matriz_r


'''
    Iterative Method
    Purpose:
        Computes approximate solution of Ax = b by the classic iterative method
    Including methods:
        - Jacobi Method
        - Gauss-Seidel Method
        - Successive Over-Relaxation (SOR) Method
        - Conjugate Gradient (CG) Method
    Input:
        A: the n x n matrix
        b: the right hand vector
        x0: initial guess
        tol: tolerance error
    Output:
        x: the approximate solution
        iters: iteration number

Last edited by Xiaozhou Li, September 23, 2020
'''

import numpy as np

def Jacobi_tol(A, b, x0, tol):
    x_old = np.copy(x0)
    x_new = np.zeros(np.size(x0))
    for i in range(np.size(x0)):
        x_new[i] = (b[i] - np.dot(A[i,:i],x_old[:i]) - np.dot(A[i,i+1:],x_old[i+1:]))/A[i,i]
    iters = 1
    while ((np.linalg.norm(x_new-x_old,np.inf)) > tol):
        x_old = np.copy(x_new)
        for i in range(np.size(x0)):
            x_new[i] = (b[i] - np.dot(A[i,:i],x_old[:i]) - np.dot(A[i,i+1:],x_old[i+1:]))/A[i,i]
        iters += 1
    return x_new, iters

def GS_tol(A, b, x0, tol):
    x_old = np.copy(x0)
    x = np.copy(x0)
    for i in range(np.size(x0)):
        x[i] = (b[i] - np.dot(A[i,:i],x[:i]) - np.dot(A[i,i+1:],x[i+1:]))/A[i,i]
    iters = 1
    while ((np.linalg.norm(x-x_old,np.inf)) > tol):
        x_old = np.copy(x)
        for i in range(np.size(x0)):
            x[i] = (b[i] - np.dot(A[i,:i],x[:i]) - np.dot(A[i,i+1:],x[i+1:]))/A[i,i]
        iters += 1
    return x, iters

def SOR_tol(A, b, x0, omega, tol):
    x_old = np.copy(x0)
    x = np.copy(x0)
    for i in range(np.size(x0)):
            x[i] = x[i] + omega*(b[i] - np.dot(A[i,:i],x[:i]) - np.dot(A[i,i:],x[i:]))/A[i,i]
    iters = 1
    while ((np.linalg.norm(x-x_old,np.inf)) > tol):
        x_old = np.copy(x)
        for i in range(np.size(x0)):
            x[i] = x[i] + omega*(b[i] - np.dot(A[i,:i],x[:i]) - np.dot(A[i,i:],x[i:]))/A[i,i]
        iters += 1
    return x, iters

def CG_tol(A, b, x0, x_star, tol):
    r_new = b - np.dot(A, x0)
    r_old = np.copy(np.size(x0))
    d_old = np.zeros(np.size(x0))
    x = np.copy(x0)
    iters = 0
    while ((np.linalg.norm(x-x_star,np.inf)) > tol):
        if (iters == 0):
            d_new = np.copy(r_new)
        else:
            beta = np.dot(r_new,r_new)/np.dot(r_old,r_old)
            d_new = r_new + beta*d_old
        Ad = np.dot(A, d_new)
        alpha = np.dot(r_new,r_new)/np.dot(d_new,Ad)
        x += alpha*d_new
        d_old = d_new
        r_old = r_new
        r_new = r_old - alpha*Ad
        iters += 1
    return x, iters

#########################################################
# Example
def Iterative_solver(n, tol=1.e-8):
    # Create matrix A with size n x n
    A = 3*np.eye(n) - np.diag(np.ones(n-1),-1) - np.diag(np.ones(n-1),+1)
    for i in range(n):
        if (abs(n-1 - 2*i) > 1):
            A[i, n-1-i] = - 1/2

    if (n <= 10):
        print ("The Matrix A: \n", A)
    # Exact solution v = [1, 1, ..., 1]^T
    v = np.ones(n)
    b = np.dot(A, v)

    # initial guess
    v0 = np.zeros(np.size(b))

    v_J, iters = Jacobi_tol(A, b, v0, tol)
    print ("Jacobi Method:  %4d    %7.2e" %(iters, np.linalg.norm(v - v_J, np.inf)))

    v_GS, iters = GS_tol(A, b, v0, tol)
    print ("Gauss Seidel :  %4d    %7.2e" %(iters, np.linalg.norm(v - v_GS, np.inf)))

    omega = 1.25
    v_SOR, iters = SOR_tol(A, b, v0, omega, tol)
    print ("SOR Method   :  %4d    %7.2e" %(iters, np.linalg.norm(v - v_SOR, np.inf)))

    #v_CG, iters = CG_tol(A, b, v0, v, tol)
    #print ("CG Method    :  %4d    %7.2e" %(iters, np.linalg.norm(v - v_CG, np.inf)))

Iterative_solver(10, 1.e-8)


# Given matrices A, C, and vectors b, d representing the polytope
# Ax <= b
# Cx = d, create a derived representation A'x' <= b' where any feasible
# point x' can be translated into an x so that Ax <= b, Cx = d.

# Example problem:

# A = |-1  0  0  0 | b = |0|  C = |1 1 1 0| d = | 50|
#     | 0 -1  0  0 |     |0|      |2 3 4 0|     |158|
#     | 0  0 -1  0 |     |0|      |4 3 2 0|     |142|
#     | 0  0  0 -1 |     |0|
#     | 0  0  0  1 |     |1|

import numpy as np
from scipy.linalg import null_space, lstsq

def incorporate_equality(A, b, C, d):
	# Now use the following strategy: Any solution to Cx=d is given by
	# Fz + x_0, where x_0 is some solution to Cx=d, and F is the null
	# space of C.
	# Much easier than mucking about with backsubstitution.

	F = null_space(C)

	# This makes it easier to read the transformed matrices.
	F = F / F.max(axis = 0)

	# Any solution to Cx=d will do, but this is the only that scipy
	# natively supports.
	x_0 = lstsq(C, d)[0]

	# The reduced problem is AFz <= b - Ax0.
	# To turn z to x, x = Fz + x_0.
	Ax0 = np.dot(A, x_0)
	return np.dot(A, F), b - Ax0, F, x_0

if __name__ == "__main__":

	A = np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0],
					[0,0,0,-1], [0, 0, 0, 1]])
	b = np.array([0, 0, 0, 0, 1])
	C = np.array([[1, 1, 1, 0], [2, 3, 4, 0], [4, 3, 2, 0]])
	d = np.array([50, 158, 142])

	AF, bn, F, x_0 = incorporate_equality(A, b, C, d)

	# For verification purposes, consider the linear program

	# max cTx s.t. Ax <= b, Cx=d
	# with c being [0, 1, 0, 0]
	# The optimal solution is x = [0, 42, 8, 0].

	# The modified version is
	# max cTFz s.t. AFz <= b - Ax0

	# The test consists of running an LP against both the unmodified problem
	# and the problem with equality constraints incorporated.

	from scipy.optimize import linprog

	c = np.array([0, -1, 0, 0])
	x_opt = linprog(c, A, b, C, d)["x"] # (0, 42, 8, 0)

	# We're no longer constrained to nonnegative solutions, so explicitly
	# state that we're not because otherwise the test fails.
	cF = np.dot(c, F)
	z_opt = linprog(cF, AF, bn, bounds=((None, None), (None, None)))["x"]

	x_sec_opt = np.dot(F, z_opt) + x_0

	if np.all(np.abs(x_sec_opt - x_opt) < 1e-12):
		print "Passed test."
	else:
		print "Failed test."


import numpy as np

class NNActivator(object):
    def __init__(self):
        pass

    def __sigmoid(self, data):
        """

        Sigmoid Function : 1/(1 + exp(-x))

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """

        result = []

        for element in data:
            result.append( 1/(1+np.exp(-element)))
        return result

    def __tanh(self, data):
        """

        tanh Function : \\frac{exp(x) - exp(-x)}{exp(x) + exp(-x)}

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """

        result = []

        for element in data:
            pos_e = np.exp(element)
            neg_e = np.exp(-element)
            result.append((pos_e - neg_e)/(pos_e + neg_e))

        return result

    def __relu(self, data):
        """

        ReLU Function : f(x) = max(0, x)

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """

        result = np.maximum(0, data)
        return result

    def __leaky_relu(self, data):
        """

        Leaky ReLU Function : f(x) = max(0.01x, x)

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """

        result = np.maximum(0.01 * data, data)
        return result

    def __exponential_linear_units(self, data, alpha = 1.0):
        """

        ELU Function : f(x) = x (if x>0) or f(x) = alpha*(exp(x)-1) (otherwise)

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """

        result = []

        for element in data:
            if element > 0:
                result.append(element)
            else:
                result.append(alpha * (np.exp(element)-1) )

        return result

    def __softmax(self, data):
        """

        Softmax Function : f(x) = exp(i)/ (\\sum_j(exp(j)))

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """
        exp_data = np.exp(data)
        exp_data_sum = np.sum(exp_data)
        result = exp_data/exp_data_sum

        return result

    def fit(self, data, function_name="relu", alpha = 1.0):
        """

        activate data

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        function_name :
            1. relu
            2. sigmoid
            3. tanh
            4. leaky_relu
            5. elu

        alpha : if function is elu, use this variable(default: 1.0)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """
        data = np.array(data)
        if function_name == "relu":
            result = self.__relu(data=data)
        elif function_name == "sigmoid":
            result = self.__sigmoid(data=data)
        elif function_name == "tanh":
            result = self.__tanh(data=data)
        elif function_name == "leaky_relu":
            result = self.__leaky_relu(data=data)
        elif function_name == "elu":
            result = self.__exponential_linear_units(data=data, alpha=alpha)
        else:
            pass

        return result

    def softmax_fit(self, data):
        """

        softmax data

        Parameters
        ----------
        data : {array-like, sparse matrix} of shape(1_samples, n_features)

        Returns
        -------
        result : {array-like, python_list } of shape(1_samples, n_activate_result)

        """
        data = np.array(data)
        result = self.__softmax(data)

        return result


import numpy as np
from math import isnan, isinf
from scipy.interpolate import splev
from matplotlib import pyplot as plt

# Recursive De Boor algorithm problem.
def N(x, i, k, t):
    """ Computes the i'th basis function of order 'k'
    for the spline with knot vector 't' at the parameter value 'x'."""
    pass

# Circle approximation problem,
# 'splev' version.
def circle_interp(m, k, res=401):
    """ Plots an interpolating spline of degree 'k'
    with parameters ranging from 0 to 'm'
    that approximates the unit circle.
    Uses scipy.integrate.splev."""
    pass

# version using your own code
def my_circle_interp(m, k, res=401):
    """ Plots an interpolating spline of degree 'k'
    with parameters ranging from 0 to 'm'
    that approximates the unit circle.
    Uses the function 'N' defined above."""
    pass

# What the script does if it is run from command line.
if __name__=="__main__":
    circle_interp(20, 4)
    my_circle_interp(20, 4)


import numpy as np

# global variables
# win_prob = 0.25
# win_prob = 0.55
win_prob = 0.4
gamma = 1

def initialize():
	array_state_value = np.zeros(101) # with two dummy state 0 and 100
	# array_state_value[100] = 0

	return array_state_value

def optimal_value_choose(state, array_state_value):
	upper_bound = min(state, 100-state)
	lower_bound = 0

	action_list = list(range(0, upper_bound+1))

	maximum_value = 0.0
	optimal_action = 101
	for action in action_list:
		s_next_loss = state - action
		s_next_win = state + action

		# define the reward
		reward_loss = 0
		reward_win = 0
		if s_next_win == 100:
			reward_win = 1

		value = (win_prob*(reward_win+(gamma*array_state_value[s_next_win]))) + ((1-win_prob)*(reward_loss+gamma*(array_state_value[s_next_loss])))
		# value = (win_prob*array_state_value[s_next_win]) + ((1-win_prob)*array_state_value[s_next_loss])
		if value >= maximum_value:
			maximum_value = value
			optimal_action = action

	return (maximum_value, optimal_action)

def save_result_estimate(array_state_value, sweep_time):
	tem = np.delete(array_state_value, 0)
	array_state_value = np.delete(tem, len(tem)-1)

	file_name = "estimate_sweep_" + str(sweep_time) + ".dat"

	# save result (for value estimate)
	with open(file_name, "w") as data_file:
		for i in range(len(array_state_value)):
			data_file.write("{0}\n".format(array_state_value[i]))

def save_result_final_policy(final_optimal):
	tem = np.delete(final_optimal, 0)
	final_optimal = np.delete(tem, len(tem)-1)

	# save result (for final policy)
	with open("final_out.dat", "w") as data_file:
		for i in range(len(final_optimal)):
			data_file.write("{0}\n".format(int(final_optimal[i])))

def value_iteration():
	array_state_value = initialize()
	# initialize the final optimal
	final_optimal = [0]*101
	sweep_time = 0

	theta = 1e-15
	while(True):
		delta = 0

		sweep_time += 1

		for state in range(1,100):
			tem_value = array_state_value[state]
			(array_state_value[state], final_optimal[state]) = optimal_value_choose(state, array_state_value)
			delta = max(delta, np.absolute(tem_value - array_state_value[state]))

		# if(sweep_time == 1 or sweep_time == 2 or sweep_time == 3 or sweep_time == 23):
		if(sweep_time == 1 or sweep_time == 2 or sweep_time == 3 or sweep_time == 27):
			save_result_estimate(array_state_value, sweep_time)

		if delta < theta:
			break

	print("Total sweep time is " + str(sweep_time))
	save_result_final_policy(final_optimal)

if __name__ == "__main__":
	value_iteration()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Mar 30 00:25:14 2019

@author: Abdulkadir Kahraman, Süleyman Tarsuslu
"""

import numpy as np

"""
% Program 1.1 Bisection Method
% Computes approximate solution of f(x)=0
% Input: function handle f; a,b such that f(a)*f(b)<0,
%        and tolerance tol defaultly tol=10e-6
% Output: Approximate solution c
"""
def bisection(f,a,b,tol=10**-5):
    c=0
    itr=0
    while abs(b-a)/2 > tol:
        c=(a+b)/2
        #print (itr,a,c,b,(b*c<0))
        if f(c)*f(a)<0:
            b=c
        else:
            a=c
        itr+=1
    #print (itr,a,c,b)
    return itr,c

# Returns step number to reach the desired accuracy
def step(a,b,ac):
    er=(b-a)/(0.5*10**(-1*ac))
    return np.ceil(np.log2(er)-1)

# Sample functions for test
#f=lambda x: x**3+x-1
#f=lambda x: cos(x)-x
#f=lambda x: x**3-9
#f=lambda x: x**3-2*x**2+4*x/3-8/27
f=lambda x: np.cos(x)-np.sin(x)

# Test the bisection method
a=0
b=1
st=step(a,b,3)
itr,c=bisection(f,a,b,10**-3)


import numpy as np

def remap(value, input_min, input_max, output_min=0., output_max=1.):
    ''' Remaps the input value from input range to output range
	:param value: input value or numpy array
	:param input_min: minimum value of the input range
	:param input_max: maximum value of the input range
	:param output_min: minimum value of the output range
	:param output_max: maximum value of the output range
	:returns: input value or numpy array remapped to the output range
	'''
    # Convert the input to 0-1 range
    valueScaled = np.divide(value - input_min, (input_max - input_min))
    # Convert the 0-1 range into a value in the right range.
    return output_min + (np.multiply(valueScaled, (output_max - output_min)))

def sigmoid(x):
	''' Sigmoid function
    :param x: x value
    :returns: y = f(x)
	'''
	return 1.0 / (1.0 + np.exp(-x))

def gaussian(x,sigma=1.0):
	''' Gaussian distribution function
	:param x: Value on the x axis of the distribution
	:param sigma: sigma defines the distribution width
	:returns: y value, the probability of x occuring
	'''
	return np.exp(-np.power(sigma*x,2.0))

def gaussian_2d(x,y , sigma_x=1.0, sigma_y=1.0):
	''' Two dimentional Gaussian distribution function
	:param x: Value on the x axis of the distribution
	:param y: Value on the y axis of the distribution
	:param sigma_x: distribution width in the x axis
	:param sigma_y: distribution width in the y axis
	:returns: Probability of f(x,y)
	'''
	return 1/(np.e*np.pi*sigma_x*sigma_y) \
            * np.exp(-np.power(x,2.0) /(2*np.power(sigma_x,2.0))) \
            * np.exp(-np.power(y,2.0) /(2*np.power(sigma_y,2.0)))

def trig(angle):
	''' Converts angle to a vector on the unit circle
	:param angle: Angle in radians
	:returns: x, y of the unit-vector
	'''
	return np.cos(angle), np.sin(angle)

def transform_matrix(rotation, translation):
	''' Returns homogeneous affine transformation matrix for a translation and rotation vector
	:param rotation: Roll, Pitch, Yaw as a 1d numpy.array or list
	:param translation: X, Y, Z as 1d numpy.array or list
	:returns: 3D Homogenous transform matrix
	'''
	xC, xS = trig(rotation[0])
	yC, yS = trig(rotation[1])
	zC, zS = trig(rotation[2])
	dX = translation[0]
	dY = translation[1]
	dZ = translation[2]
	Translate_matrix =np.array([[1, 0, 0, dX],
								[0, 1, 0, dY],
								[0, 0, 1, dZ],
								[0, 0, 0, 1]])
	Rotate_X_matrix = np.array([[1, 0, 0, 0],
								[0, xC, -xS, 0],
								[0, xS, xC, 0],
								[0, 0, 0, 1]])
	Rotate_Y_matrix = np.array([[yC, 0, yS, 0],
								[0, 1, 0, 0],
								[-yS, 0, yC, 0],
								[0, 0, 0, 1]])
	Rotate_Z_matrix = np.array([[zC, -zS, 0, 0],
								[zS, zC, 0, 0],
								[0, 0, 1, 0],
								[0, 0, 0, 1]])
	return np.matrix(np.dot(Rotate_Z_matrix,np.dot(Rotate_Y_matrix,np.dot(Rotate_X_matrix,Translate_matrix))))


import math

import matplotlib.pyplot as plt
import numpy as np

from numerical_analysis.dependencies import Polynomial

class GeometricalPlace:

    def __init__(self):
        pass

    def x_t(self, t):
        return

    def y_t(self, t):
        return

    def point_t(self, t):
        return [self.x_t(t), self.y_t(t)]

    def graph(self, dt):
        graph = [[], []]
        for t in np.arange(0., 1. + dt, dt):
            for k in range(2):
                graph[k].append(self.point_t(t)[k])
        return graph

    def plot(self, dt):
        graph = self.graph(dt)
        plt.plot(graph[0], graph[1])
        plt.show()

class StraightLine(GeometricalPlace):

    def __init__(self, points):
        # points given in following format: [[t0, [x0, y0]], [t1, [x1, y1]]]
        self.points = points
        self.c = self.coefficients()
        super().__init__()

    def coefficients(self):
        t0 = self.points[0][0]
        t1 = self.points[1][0]

        x0 = self.points[0][1][0]
        x1 = self.points[1][1][0]

        y0 = self.points[0][1][1]
        y1 = self.points[1][1][1]

        a = (x1 - x0) / (t1 - t0)
        b = (x0 * t1 - x1 * t0) / (t1 - t0)
        c = (y1 - y0) / (t1 - t0)
        d = (y0 * t1 - y1 * t0) / (t1 - t0)

        return np.array([[b, a], [d, c]])

    def x_t(self, t):
        return Polynomial(self.c[0]).value(t)

    def y_t(self, t):
        return Polynomial(self.c[1]).value(t)

    def slope_rad(self):
        if self.x_t(1.) != self.x_t(0.):
            phi = math.atan((self.y_t(1.) - self.y_t(0.)) / (self.x_t(1.) - self.x_t(0.)))
            if self.x_t(1.) > self.x_t(0.):
                return phi
            else:
                return phi + math.pi
        elif self.y_t(1.) > self.y_t(0.):
            return math.pi / 2
        else:
            return 3 * math.pi / 2

    # noinspection PyMethodOverriding
    def graph(self, ta, tb):
        def invert_table(table):
            return [[table[j][i] for j in range(len(table))] for i in range(len(table[0]))]
        return invert_table([self.point_t(ta), self.point_t(tb)])

    def modify_points(self, points):
        self.points = points
        self.c = self.coefficients()

class Circle(GeometricalPlace):

    def __init__(self, center, radius):
        self.C = center
        self.R = radius
        super().__init__()

    def x_t(self, angle_rad):
        return self.R * math.cos(angle_rad) + self.C[0]

    def y_t(self, angle_rad):
        return self.R * math.sin(angle_rad) + self.C[1]


import numpy as np
import pandas as pd
import logging
from tqdm import tqdm

# No need to define the Logging Configuration here

class Perceptron:
  def __init__(self,eta,epochs):  #initialise object
    self.weights = np.random.randn(3) *1e-4 # Returns an array of length 3 with random values from standard normal distribution
    #self.weights = np.reshape(self.weights, (3,1))  # 3 rows,1 column
    logging.info(f"Shape of Weights {self.weights.shape}")
    # Example : array([ 3.83262836e-05,  8.66627761e-05, -2.10441382e-04]) ,  multiply by 0.0001 to get even smaller values

    logging.info(f"initial Weights before Training \n{self.weights}")
    self.eta = eta
    self.epochs = epochs

  def activation_fn(self,inputs,weights):
    z = np.dot(inputs ,weights) # summation of wi * xi ,here we use function args and not the object variables
    logging.info(f"Dot Product Of Input * Weights \n{z}")
    step_fn = np.where(z>0,1,0)
    logging.info(f"Apply Step function \n{step_fn}")
    return step_fn

  def fit(self,X,y):
    self.X = X
    self.y = y

    X_with_bias = np.c_[self.X,-np.ones((len(self.X) ,1 ))]  # Append x0 to x1 and x2
    ''' #Note: 2 brackets necessary for (Row,Column) interpretation
    > -np.ones((4,1))  #4 rows,1 col
     array([[-1.],
       [-1.],
       [-1.],
       [-1.]]) '''

    for epoch in tqdm( range(self.epochs), total= self.epochs , desc= "training model epochs"):
      logging.info("\n")
      logging.info("-"*20)
      logging.info(f"Epoch {epoch}")

      # Forward Propagation
      y_hat = self.activation_fn(X_with_bias , self.weights) # Calculate Y= activation_fn(summation of wixi)
      logging.info(f"\nPredicted Value : \n{y_hat}")

      self.error = self.y - y_hat  # Actual Y - Predicted Y
      logging.info(f"\nError: \n{self.error}")

      # Backward Propagation
      self.weights = self.weights + self.eta * (np.dot(X_with_bias.T,self.error)) #x_with_bias transpose * error

      logging.info(f"\nEpoch {epoch}/{self.epochs} \nUpdated Weights : {self.weights}")
      logging.info("-"*20)

  def predict(self,X):
      X_with_bias = np.c_[X,-np.ones((len(X),1))]
      return self.activation_fn(X_with_bias , self.weights)  #This objects's activation fn

  def total_loss(self):
      loss = np.sum(self.error)  #self.error => This object's Error
      logging.info(f"Total Error : {loss}")
      return loss


import numpy as np
import scipy.sparse as sps

__all__ = ['column_norm',
           'row_norm',
           'scale_factor_matrices']

def column_norm(X, by_norm='2'):
    """
    Compute the norms of each column of a given matrix.

    Parameters
    ----------
    X : numpy.array or scipy.sparce matrix
        Array to be normalized.

    by_norm : string
        - '1' for l1-norm
        - '2' for l2-norm

    Returns
    -------
    norm_vec: numpy.array
        Array with normalization factors for each column.
    """
    if sps.issparse(X):
        if by_norm == '2':
            norm_vec = np.sqrt(X.multiply(X).sum(axis=0))
        elif by_norm == '1':
            norm_vec = X.sum(axis=0)
        return np.asarray(norm_vec)[0]
    else:
        if by_norm == '2':
            norm_vec = np.sqrt(np.sum(X * X, axis=0))
        elif by_norm == '1':
            norm_vec = np.sum(X, axis=0)
        return norm_vec

def row_norm(X, by_norm='2'):
    """
    Compute the norms of each row of a given matrix.

    Parameters
    ----------
    X : numpy.array or scipy.sparce matrix
        Array of data to be normalized.
    by_norm : string
        - '1' for l1-norm
        - '2' for l2-norm

    Returns
    -------
    norm_vec : numpy.array
        Array with normalization factors for each row.
    """
    if sps.issparse(X):
        if by_norm == '2':
            norm_vec = np.sqrt(X.multiply(X).sum(axis=1))
        elif by_norm == '1':
            norm_vec = X.sum(axis=1)
        return np.asarray(norm_vec)[0]
    else:
        if by_norm == '2':
            norm_vec = np.sqrt(np.sum(X * X, axis=1))
        elif by_norm == '1':
            norm_vec = np.sum(X, axis=1)
        return norm_vec

def scale_factor_matrices(W, H, by_norm):
    """
    Column normalization of factor matrices.

    Scale the columns of W and H so that the columns of W
    have unit norms and the product W.dot(H) remains
    the same.

    Parameters
    ----------
    W : numpy.array, shape (n, r)
    H : numpy.array, shape (r, m)
    by_norm : string
        - '1' for l1-normalization
        - '2' for l2-normalization

    Returns
    -------
    W, H : normalized matrix pair
    """
    norm_vec = column_norm(W, by_norm=by_norm)
    W = W / norm_vec[None, :]
    H = H * norm_vec[:, None]

    return W, H


# -*- coding: utf-8 -*-
"""
Created on Thu May  9 15:47:34 2019

@author: hindesa

Lotka-Volterra specific case
Following SciPy Tutorial:
    https://scipy-cookbook.readthedocs.io/items/LoktaVolterraTutorial.html
"""

import numpy as np
import pylab as p
from scipy import integrate
#----------------------------------------
# Lotka Volterra parameters
a = 2. # Prey growth rate
b = 0.2 #Background prey mortality rate
c = 1.3 #Bockground predator mortality rate
d = 0.65 #Predation conversion efficiency

# X[0] = prey, x[1] = predator
#----------------------------------------

# Lotka-Volterra equations and Jacobian
#----------------------------------------
def dX_dt(A, t=0):
    """ Return the growth rate of prey and predator population """
    x = A[0]
    y = A[1]
    matrix = np.array([a*x - b*x*y,
                    -c*y +d*b*x*y])
    return matrix

# Position equilbrium, in this case obtained algebraically
X_f0 = np.array([0., 0.]) # Extinction
X_f1 = np.array([ c/(d*b), a/b]) #Periodic equilibrium

# Linearize around these fixed points using Jacobian

def jacX(A, t=0):
    """ Return Jacobian matrix evaluated at X """
    x = A[0]
    y = A[1]
    matrix = np.array([[a-b*y, -b*x],
                      [d*b*y, d*b*x - c]])
    return matrix

# Calculate linearizations
A_f0 = jacX(X_f0)
A_f1 = jacX(X_f1)
#----------------------------------------

print("Extinction Jacobian: \n" + str(A_f0))
print("Periodic Jacobian: \n" + str(A_f1))

# Calculate Eigenvalues of periodic solution
lambda1, lambda2 = np.linalg.eigvals(A_f1)
period = 2*np.pi/np.abs(lambda1)
print("Period = " + str(period))

# Integrate system of ODEs
t = np.linspace(0, 15, 1000) #time
x = 10 # Starting no. Prey
y = 5 # Starting no. Predators
X0 = [x,y]

print('Integrating ODE...')
X, infodict = integrate.odeint(dX_dt, X0, t, full_output=True)
print(infodict['message'])

#Plot Periodic solution
prey, predator = X.T #Assign values by transposing integration result
f1 = p.figure() #Initialize Figure

#Plot data
p.plot(t, prey, 'b-', label='Prey')
p.plot(t, predator, 'r-', label='Predators')
p.grid() #gridlines
p.legend(loc='best') #legend
p.xlabel('Time') #labels
p.ylabel('Population')

p.title('Lotka-Volterra model periodic steady state')

# Importantly, you cannot get a time series out of
# generalised models because there is no functional forms specified
# for an ode solver to do its thing

#----------------------------------------
# Plotting phase plane and trajectories
#----------------------------------------

values = np.linspace(0.3, 0.9, 5) # X0 in values between stable points
vcolours = p.cm.autumn_r(np.linspace(0.3, 1, len(values))) #colours for trajectories

f2 = p.figure()
#Plot trajectories
for v, col in zip(values, vcolours):
    X0 = v * X_f1 #start point
    X = integrate.odeint(dX_dt, X0, t) #integrate
    p.plot(X[:,0], X[:,1], lw=3.5*v, color=col, label='X0=(%.f, %.f)' % (X0[0], X0[1]))

#Define grid and compute direction at each ponit
    # Get axes limits
ymax = p.ylim(ymin=0)[1]
xmax = p.xlim(xmin=0)[1]
nb_points = 20

x = np.linspace(0, xmax, nb_points)
y = np.linspace(0, ymax, nb_points)

X1, Y1 = np.meshgrid(x, y) # create grid
DX1, DY1 = dX_dt([X1,Y1]) # compute infinitesimal growth rate at each point in grid
M = (np.hypot(DX1,DY1) ) #slope for each point
M[M==0] = 1. # Avoid zeros
DX1 /= M #Normalize arrow components
DY1 /= M

#----------------------------------------
# Draw direction fields, use plt's quiver function
# Colours correspond to speed, can also remove normalization

p.title('Phase space of Lotka-Volterra equations')
Q = p.quiver(X1, Y1, DX1, DY1, M, pivot='mid', cmap=p.cm.jet)
p.xlabel('Population of Prey')
p.ylabel('Population of Predators')
p.legend()
p.grid()
p.xlim(0, xmax)
p.ylim(0, ymax)


import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401 unused import
from sklearn.neural_network import MLPRegressor

random.seed(2018321086)

a1 = np.array([2,2]).reshape(2,1)
a2 = np.array([3,-3]).reshape(2,1)
a1_t = np.transpose(a1)
a2_t = np.transpose(a2)

x1 = np.random.normal(0, 1, 1000).reshape(1, 1000)
x2 = np.random.normal(0, 1, 1000).reshape(1, 1000)
x_data = np.concatenate((x1, x2), axis=0)

h1 = (1/(1+np.exp(-np.matmul(a1_t, x_data))))
h2 = (np.matmul(a2_t, x_data))**2
h3 = 0.3*np.random.normal(0, 1, 1000).reshape(1, 1000)
y_data = h1+h2+h3

#a. plot the surface of responses using function Y = sigmoid(a1_t *%* x_data) + (a2_t %*% x_data_**2)
x1_to_plot = np.repeat(np.arange(-2, 2, 0.05), [80]*80)
x2_to_plot = np.tile(np.arange(-2, 2, 0.05), 80)
y_data_to_plot = (1/(1+np.exp(-(2*x1_to_plot + 2*x2_to_plot)))) + (3*x1_to_plot + -3*x2_to_plot)**2

plot1 = plt.figure()
plot2 = plot1.add_subplot(111, projection='3d')
plot2.scatter(x1_to_plot, x2_to_plot, y_data_to_plot, c='b', marker=".")
plot2.set_xlabel('x1')
plot2.set_ylabel('x2')
plot2.set_zlabel('y')
plt.show()

#b. perform a neural network analysis using both R and Python. Fix the number of hidden layer as one. Use other options as default.
x1_test = np.random.normal(0, 1, 1000).reshape(1, 1000)
x2_test = np.random.normal(0, 1, 1000).reshape(1, 1000)
x_data_test = np.concatenate((x1, x2), axis=0)

h1_test = (1/(1+np.exp(-np.matmul(a1_t, x_data_test))))
h2_test = (np.matmul(a2_t, x_data_test))**2
h3_test = 0.3*np.random.normal(0, 1, 1000).reshape(1, 1000)
y_data_test = h1_test+h2_test+h3_test

x_data_transpose = np.transpose(x_data)
y_data_transpose = np.transpose(y_data)
x_data_test_transpose = np.transpose(x_data_test)
y_data_test_transpose = np.transpose(y_data_test)

model_neuralnet = MLPRegressor(hidden_layer_sizes=(1), solver='lbfgs')
model_neuralnet.fit(x_data_transpose, y_data_transpose)

training_error = sum(sum((model_neuralnet.predict(x_data_transpose)-y_data)**2))/1000
testing_error = sum(sum((model_neuralnet.predict(x_data_transpose)-y_data_test)**2))/1000

print("training_error = %f, testing_error = %f"%(training_error, testing_error))

#c. vary the number of hidden nodes in the hidden layer from 2 up to 10.
list_training_error = []
list_testing_error = []

for i in range(1,11):
    model_neuralnet_ith = MLPRegressor(hidden_layer_sizes=(i), solver='lbfgs')
    model_neuralnet_ith.fit(x_data_transpose, y_data_transpose)
    training_error = sum(sum((model_neuralnet_ith.predict(x_data_transpose)-y_data)**2))/1000
    testing_error = sum(sum((model_neuralnet_ith.predict(x_data_test_transpose)-y_data_test)**2))/1000
    list_training_error.append(training_error)
    list_testing_error.append(testing_error)

result_c = pd.DataFrame(np.concatenate((np.array(list_training_error).reshape(10, 1), np.array(list_testing_error).reshape(10, 1)), axis=1), columns=['training', 'testing'])
print(result_c)

#d. plot the training and test error curves as a function of the number of hidden nodes.
x_axis = range(1, 11)
plt.figure()
plt.plot(x_axis, list_training_error,'b--')
plt.plot(x_axis, list_testing_error, 'r--')
plt.xlabel('# of hidden nodes')
plt.ylabel('error')
plt.show()

#e. determine the minimum # of hidden nodes needed to perform well for this task.
list_testing_error
np.argmin(list_testing_error)


# import math

import matplotlib.pyplot as plt
import numpy as np

# def Trapezoidal( x,y,n):
#
# a=x
# b=y
# h=(b-a)/n
#
#  def fun(x):
#      return (1 / (1 + x * x))
#  sum = (fun(a)+fun(b))
#
#  i=1
#  while i<n:
#     sum =sum +2*fun(a+h*i)
#     i+=1
#  return sum*(h/2)
# #fun = lambda x: (1 / (1 + x * x))
# result =Trapezoidal( 0,1,6)
# print(result)
'''
def Trapezoidal(fun, x,y,n):

 a=x
 b=y
 h=(b-a)/n

 # def fun(x):
 #     return (1 / (1 + x * x))
 sum = (fun(a)+fun(b))

 i=1
 while i<n:
    sum =sum +2*fun(a+h*i)
    i+=1
 return sum*(h/2)
fun = lambda x: (1 / (1 + x * x))
result =Trapezoidal(fun, 0,1,6)
print(result)

'''
x = float(input("Enter the value of initial point(try 0.1): "))
y = float(input("Enter the value of final point(try 1.3): "))
n = int(input("number of intervals: "))

def Trapezoidal(x, y, n):
    h = (y - x) / n

    def fun(x):
        return 5 * x * np.exp(-2 * x)

    sum = (fun(x) + fun(y))

    i = 1
    while i < n:
        sum = sum + 2 * fun(x + h * i)
        i += 1
    return sum * (h / 2)

    #fun = lambda x: (1 / (1 + x * x))

result = Trapezoidal(x, y, n)
print("The root is: ", " %.5f" % result)  ###--------------Plotting----------------

x = np.linspace(-5, 7, 40)
fun = 5 * x * (np.exp(-2 * x))
plt.plot(x, fun, 'g', label="5x exp(-2*x), range -5:5:40")
plt.xlabel("x")
plt.ylabel("Y")
plt.grid()
plt.title("f(x)= 5x exp(-2x)")
plt.savefig("Trapezoidal.png")
plt.show()


import numpy as np
import scipy.sparse as sparse
import scipy.linalg as la

def grad(f, x):
    '''
        Input:
            f: lambda function
            x: function args
        Output:
            grad_f: function gradient at x
    '''
    n = len(x)
    grad_f = np.zeros(n)

    E = np.diag([pow(np.finfo(float).eps, 1/3) * (abs(a) + 1) for a in x])

    for i in range(n):
        grad_f[i] = (f(x + E[:, i]) - f(x - E[:, i])) * (0.5 / E[i, i])

    return grad_f

def hess(f, x):
    '''
        Input:
            f: lambda function
            x: function args
        Output:
            hess_f: hessian of f at x
    '''
    n = len(x)
    hess_f = np.zeros([n, n])

    E = np.diag([pow(np.finfo(float).eps, 1/4) * (abs(a) + 1) for a in x])

    for i in range(n):
        for j in range(n):
            hess_f[i, j] = (  f(x + E[:, i] + E[:, j])
                            - f(x - E[:, i] + E[:, j])
                            - f(x + E[:, i] - E[:, j])
                            + f(x - E[:, i] - E[:, j]) ) * (0.25 / (E[i, i] * E[j, j]))

    return hess_f

def coor_descent(f, x0, tol=1e-5, maxiter=100):
    '''
        Input:
            f - func to minimize
            x0 - initial point
            tol - tolerance
            maxiter - max num of iterations
        Output:
            xf - final aproximation of x*
            iterations - number of iterations to reach soln
    '''

    xf = x0
    n = len(x0)
    iterations = 0
    c_1 = 0.1

    d = np.zeros(n)
    grad_f = grad(f, xf)

    while iterations < maxiter and np.linalg.norm(grad_f, np.inf) > tol:

        #Find descent direction
        i = np.argmax(abs(grad_f))
        d[i] = -np.sign(grad_f[i])

        #Direction coef.
        alfa = 1

        while f(xf + alfa*d) > f(xf) - alfa*c_1*grad_f[i]:
            alfa /= 2

        #Next iteration
        xf = xf + alfa*d
        iterations += 1
        d[i] = 0
        grad_f = grad(f, xf)

    return xf, iterations

def max_descent(f, x0, tol=1e-5, maxiter=100):
    '''
        Input:
            f - func to minimize
            x0 - initial point
            tol - tolerance
            maxiter - max num of iterations
        Output:
            xf - final aproximation of x*
            iterations - number of iterations to reach soln
    '''

    xf = x0
    n = len(x0)
    iterations = 0
    c_1 = 0.1

    grad_f = grad(f, xf)

    while iterations < maxiter and np.linalg.norm(grad_f, np.inf) > tol:

        #Find descent direction
        d = - grad_f / np.linalg.norm(grad_f)

        #Direction coef.
        alfa = 1

        while f(xf + alfa*d) > f(xf) + alfa*c_1*np.dot(grad_f, d):
            alfa /= 2

        #Next iteration
        xf = xf + alfa*d
        iterations += 1
        grad_f = grad(f, xf)

    return xf, iterations

def newton_descent(f, x0, tol=1e-5, maxiter=100):
    '''
        Input:
            f - func to minimize
            x0 - initial point
            tol - tolerance
            maxiter - max num of iterations
        Output:
            xf - final aproximation of x*
            iterations - number of iterations to reach soln
    '''

    xf = x0
    n = len(x0)
    iterations = 0
    c_1 = 0.1

    grad_f = grad(f, xf)
    hess_f = hess(f, xf)
    while iterations < maxiter and np.linalg.norm(grad_f, np.inf) > tol:

        #Find descent direction
        d = np.linalg.solve(hess_f, -grad_f)

        #Direction coef.
        alfa = 1

        while f(xf + alfa*d) > f(xf) + alfa*c_1*np.dot(grad_f, d):
            alfa /= 2

        #Next iteration
        xf = xf + alfa*d
        iterations += 1
        grad_f = grad(f, xf)
        hess_f = hess(f, xf)

    return xf, iterations

def trust_region(f, x0, r, min_quality=0.25, max_r=np.inf, tol=1e-5, maxiter=1000):

    i = 0
    xk = x0
    fk = f(xk)
    gk = grad(f, xk)
    Bk = hess(f, xk)

    while i < maxiter and np.linalg.norm(gk, np.inf) > tol:

        mk = lambda d : fk + np.dot(gk, d) + 0.5 * np.dot(d, np.dot(Bk, d))
        dk = cauchy_point(gk, Bk, r)
        quality = (fk - f(xk + dk))/(fk - mk(dk))

        if quality < 0.25:
            r = 0.25 * r
        elif quality > 0.75 and r - np.linalg.norm(dk) < 1e-10 :
            r = min(2*r, max_r)

        if quality > min_quality:
            xk = xk + dk
            fk = f(xk)
            gk = grad(f, xk)
            Bk = hess(f, xk)
            i += 1

    return xk, i

def cauchy_point(gk, Bk, r):
    gk_norm = np.linalg.norm(gk)
    p = -(r/gk_norm) * gk

    pBp = np.dot(p, np.dot(Bk, p))

    alpha = min(1, (r * gk_norm / pBp)) if pBp > 0 else 1

    p *= 0.99 * alpha

    return p


import sympy as sp
import math
from sympy.printing import pprint
from sympy.parsing.sympy_parser import parse_expr, standard_transformations

def parse(s):
    return parse_expr(s, transformations=standard_transformations)

def incerteza_absoluta(f: sp.Expr) -> sp.Expr:
    uf = 0
    udict = {}

    for s in f.free_symbols:
        udict[s] = sp.Symbol('u' + str(s))
        uf += udict[s] ** 2 * sp.simplify(f.diff(s) ** 2)
    uf = sp.sqrt(uf)
    return uf

def incerteza_relativa(f: sp.Expr, v: sp.Symbol) -> sp.Expr:
    uf = 0
    udict = {}

    for s in f.free_symbols:
        udict[s] = sp.Symbol('u' + str(s))
        df = sp.simplify(f.diff(s) ** 2 / f ** 2)
        uf += udict[s] ** 2 * df
    uf = v * sp.sqrt(uf)
    return uf

key = ""
while key != "sair":
    print("""\n
Qual o símbolo da grandeza da qual você quer achar a incerteza?

detalhe: não use 'I' ou 'i' pois gera problemas com números complexos

exemplo: Vlim

""")
    simb = sp.symbols(input())

    print("""
Qual a fórmula para essa grandeza?

escreva como você escreveria uma expressão em Python.
insira quantos símbolos extras forem necessários, mas não use I ou i.

Algumas funções são conhecidas:
 - exponenciação: use ** em vez de ^
 - multiplicação: sempre escreva explicitamente 2*a, nunca 2a
 - algumas funções suportadas: cos(x), sin(x), ln(x), log(x), etc.

exemplo: 2*(ρ - ρl)*g / (9*a)

""")

    ok = False
    while not ok:
        print("escreva a fórmula:\n")
        try:
            form = parse(input())
            print("\nSua expressão:")
            pprint(form)
            if input("\nParece OK? (s/n)\n") == 's':
                ok = True
        except SyntaxError:
            print("Há um problema com a fórmula... tente de novo")

    print()
    print(f"""Como você quer a formatação da incerteza?
    (r): relativa (padrão) - a resposta sai na forma '{simb} * raiz de alguma coisa'
    (a): absoluta - a resposta sai na forma 'raiz de alguma coisa'""")
    if input() == 'a':
        inc = incerteza_absoluta(form)
    else:
        inc = incerteza_relativa(form, simb)

    print()
    print(f"A expressão da incerteza para {simb} é:")
    pprint(inc)


import numpy as np

def extract_pca_ts(multi_dim_ts):
    """
    This function reduces a multi-dimensional time-series to 1d using PCA

    :param multi_dim_ts: multi-dimensional time-series as a pandas dataframe
    :type multi_dim_ts: DataFrame
    :return: 1-dimensional time-series
    :rtype: 1d array
    """
    # compute vector with the mean of each time series
    means_vec = multi_dim_ts.agg('mean').values
    # compute the covariance matrix of the multi-dim time-series data
    cov_mat = multi_dim_ts.cov().values
    # extract eigenvalues and eigenvectors (the PCs) of the covariance matrix
    e_val, e_vec = np.linalg.eigh(cov_mat)
    # get the eigenvector with the highest eigenvalue (i.e. the 1st PC)
    pc1_vec = e_vec[np.argmax(e_val)]
    # compute the 1-dim time series as the data's projection on the 1st PC
    ts_1d = np.dot((multi_dim_ts.values - means_vec), pc1_vec)
    return ts_1d


import numpy as np
from sympy import Matrix,diff,simplify,lambdify,Symbol, cos, sin,cosh,sinh,pi
u=Symbol('u', real=True)
v=Symbol('v', real=True)

class Surface():

    def __init__(self,S,umin,umax,vmin,vmax):
        self.S=S
        self.umax=umax
        self.umin=umin
        self.vmax=vmax
        self.vmin=vmin
        self.u_v_range=[umin,umax,vmin,vmax]

    def cal(self):
        global X,Y,Z,Gauss_Curvature

        xu=Matrix([diff(f,u) for f in self.S])
        xv=Matrix([diff(f,v) for f in self.S])
        xuu=Matrix([diff(f,u) for f in xu])
        xvv=Matrix([diff(f,v) for f in xv])
        xuv=Matrix([diff(f,v) for f in xu])
        E=simplify(xu.dot(xu))
        G=simplify(xv.dot(xv))
        F=simplify(xu.dot(xv))

        H1=Matrix([xuu,xu,xv]).reshape(3,3)
        H2=Matrix([xvv,xu,xv]).reshape(3,3)
        H3=Matrix([xuv,xu,xv]).reshape(3,3)
        K=simplify(((H1.det()*H2.det() -(H3.det()**2)))/ (xu.norm()**2*xv.norm()**2 -F*F)**2)

        #Pass to numpy
        du=float(self.umax-self.umin)/100
        dv=float(self.vmax-self.vmin)/100
        [U,V] = np.mgrid[self.umin:self.umax+du:du,self.vmin:self.vmax+dv:dv]
        # convert Sympy formula to numpy lambda functions
        F1=lambdify((u,v), self.S[0], "numpy")
        F2=lambdify((u,v), self.S[1], "numpy")
        F3=lambdify((u,v), self.S[2], "numpy")
        F4=lambdify((u,v), K, "numpy")
        #Calculate numpy arrays
        self.X=F1(U,V)
        self.Y=F2(U,V)
        self.Z=F3(U,V)
        self.Gauss_Curvature=F4(U,V)
        #X=F1(U,V)
        #Y=F2(U,V)
        #Z=F3(U,V)
        #Gauss_Curvature=F4(U,V)

Torus = Surface([(2 + cos(v))*cos(u),(2 + cos(v))*sin(u),sin(v)],0,2*pi,0,2*pi)
Helix = Surface([u*cos(v),u*sin(v),2*v],-5,5,0,2*pi)
Hyperboloid_one_sheet=Surface([2*cosh(u/2)*cos(v), 2*cosh(u/2)*sin(v), u],-3,3,0,2*pi)
Ellipsoid=Surface([3*cos(u)*cos(v),4*cos(u)*sin(v),5*sin(u)],0,2*pi,0,pi)
Elliptic_Paraboloid=Surface([2*u*cos(v), 3*u*sin(v),u*u],0,2,0,2*pi)


import numpy as np

def recursive_dynamic_imp(n, val_array):
    if val_array[n] != -1:
        return val_array[n]
    else:
        val_array[n] = recursive_dynamic_imp(n-1,val_array)+\
                       recursive_dynamic_imp(n-2,val_array)
        return val_array[n]

def recursive_dynamic(n):
    val_array = np.full(n+1,-1, dtype=np.int64)
    val_array[0] = 0
    val_array[1] = 1
    return recursive_dynamic_imp(n, val_array)

def iterative(n):
    if n < 2:
        return n
    # initial values
    fh = np.int64(1)
    fl = np.int64(0)
    for i in range(2,n+1):
        # Save in temp location
        temp = fh
        # get new high value
        fh = fh+fl
        # save old value in low value
        fl = temp
    return fh

def build_power_matrix(A, n):
    if n == 1:
        return A
    if n%2 == 0:
        Anew = build_power_matrix(A,n//2)
        return np.matmul(Anew,Anew)
    else:
        Aev = build_power_matrix(A,n//2)
        return np.matmul(Aev,np.matmul(Aev,A))

def power_matrix(n):
    if n == 0:
        return 0
    if n <= 2:
        return 1
    init_vec = np.array([[1],[1]], dtype=np.int64)
    A = np.array([[1,1],[1,0]], dtype=np.int64)
    A_power = build_power_matrix(A,n-2)
    return np.matmul(A_power,init_vec)[0][0]


# Vendor
import numpy as np

def relu(a: np.ndarray) -> np.ndarray:
    return np.maximum(a, 0)

def softmax(a: np.ndarray) -> np.ndarray:
    return np.exp(a - np.max(a)) / np.exp(a - np.max(a)).sum(1)[..., None]

def sigmoid(a: np.ndarray) -> np.ndarray:
    return 1. / (1. + np.exp(-np.array(a, dtype=np.float64)))


import numpy as np

def mean_squared_error(estimates, targets):
    """
    Mean squared error measures the average of the square of the errors (the
    average squared difference between the estimated values and what is
    estimated. The formula is:

    MSE = (1 / n) * \sum_{i=1}^{n} (Y_i - Yhat_i)^2

    Implement this formula here, using numpy and return the computed MSE

    https://en.wikipedia.org/wiki/Mean_squared_error

    Args:
        estimates(np.ndarray): the estimated values (should be the same shape as targets)
        targets(np.ndarray): the ground truth values

    Returns:
        MSE(int): mean squared error calculated by above equation
    """
    diffs = []
    if estimates.ndim == 1:
        for n in range(estimates.size):
            diff = estimates[n] - targets[n]
            diffs.append(diff)
    else:
        for i in range(estimates.shape[0]):
            for j in range(estimates.shape[1]):
                diff = estimates[i][j] - targets[i][j]
                diffs.append(diff)
    diffs = np.array(diffs)
    diffs = np.square(diffs)
    error = np.mean(diffs)
    return error


# --------------
# Code starts here

import numpy as np

# Code starts here

# Adjacency matrix
adj_mat = np.array([[0,0,0,0,0,0,1/3,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                   [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                  [0,0,1/2,1/3,0,0,1/3,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/3,0]])

# Compute eigenvalues and eigencevectrs
eigenvalues, eigenvectors = np.linalg.eig(adj_mat)
print("Eigen Values: ",eigenvalues)
#for i in range (len(eigenvalues)):
#    print (eigenvectors[0:i])
# Eigen vector corresponding to 1
eigenvector_1=abs(eigenvectors[:,0])
#print(eigenvector_1)
eigen_1=eigenvector_1/np.linalg.norm(eigenvectors[:,0],1)
print("Normalised Vector: ",eigen_1)
# most important page
#page=np.where(eigen_1==eigen_1.max())
page=np.where(eigen_1==np.amax(eigen_1))[0][0]+1
print("Page = ",page)

# Code ends here

# --------------
# Code starts here

# Initialize stationary vector I
init_I=[1,0,0,0,0,0,0,0]
#print(adj_mat)
# Perform iterations for power method
for _ in range (10):
    init_I=np.dot(adj_mat, init_I)/np.linalg.norm(init_I, 1)
 #   return init_I
power_page=np.where(np.max(init_I) == init_I)[0][0] + 1
print("Power Page = ",power_page)
# Code ends here

# --------------
# Code starts here

# New Adjancency matrix
# New Adjancency matrix
new_adj_mat = np.array([[0,0,0,0,0,0,0,0],
                   [1/2,0,1/2,1/3,0,0,0,0],
                  [1/2,0,0,0,0,0,0,0],
                   [0,1,0,0,0,0,0,0],
                   [0,0,1/2,1/3,0,0,1/2,0],
                   [0,0,0,1/3,1/3,0,0,1/2],
                   [0,0,0,0,1/3,0,0,1/2],
                   [0,0,0,0,1/3,1,1/2,0]])

# Initialize stationary vector I
new_init_I=[1,0,0,0,0,0,0,0]

# Perform iterations for power method
for _ in range(10):
    new_init_I=np.dot(new_adj_mat,new_init_I)/np.linalg.norm(new_init_I,1)
print (new_init_I)

# Code ends here

# --------------
# Alpha value
alpha = 0.85

# Code starts here

# Modified adjancency matrix
#G=alpha*S + (1-alpha)*1/n*l
G= (alpha*new_adj_mat) + ((1-alpha)*np.ones(new_adj_mat.shape)/len(new_adj_mat))
print (G)
# Initialize stationary vector I
final_init_I=[1,0,0,0,0,0,0,0]

# Perform iterations for power method
for _ in range(1000):
    final_init_I=np.dot(G,final_init_I)/np.linalg.norm(final_init_I,1)
print (final_init_I)
# Code ends here


# -*- coding: utf-8 -*-
"""
Created on Wed Jun 26 12:47:38 2019

@author: Miranda
"""
from sympy import mod_inverse as mminv
from sympy import isprime as isprime
import math
import random
import time
#Addition

def wadd(p1,p2,a,b,p):
    """Adds p1 and p2 on a WNF curve"""
    if math.isinf(p1[0]) and math.isinf(p2[0]):
        return math.inf,1 #O + O = O
    if math.isinf(p1[0]): return p2 #O + p2 = p2
    if math.isinf(p2[0]): return p1 #p1 + O = p1
    else:
        x1,y1 = p1 #assigns coordinates
        x2,y2 = p2 #assigns coordinates
        if y1**2 % p != (x1**3 + a*x1 + b) % p: #checks if point is on curve
            return False #otherwise reject
        if y2**2 % p != (x2**3 + a*x2 + b) % p: #checks if point is on curve
            return False #otherwise reject
        if x1==x2 and -y2 % p == y1:
            return math.inf,1 #(x1,y1) + (x1,-y1) = O
        elif x1==x2:
            #point doubling
            sl = ((3 * x1**2 + a)*mminv(2*y1,p)) % p #slope
            x = (sl**2 - x1 - x2) % p
            return x, -(sl * (x - x1) + y1) % p
        else:
            #point addition
            sl = (y2-y1)*mminv(x2-x1,p) % p #slope
            x = (sl**2 - x1 - x2) % p
            return x, -(sl * (x - x1) + y1) % p

def madd(p1,p2,A,B,p):
    """Adds p1 and p2 on a Montgomery curve"""
    if B*(A**2 - 4) == 0 % p: #checks Montgomery requirements
        print("Singular Curve")
        return
    if math.isinf(p1[0]) and math.isinf(p2[0]):
        return math.inf,1 #O + O = O
    if math.isinf(p1[0]): return p2 #O + p2 = p2
    if math.isinf(p2[0]): return p1 #p1 + O = p1
    else:
        x1,y1 = p1 #assigns coordinates
        x2,y2 = p2 #assigns coordinates
        if B*y1**2 % p != (x1**3 + A*x1**2 + x1) % p: #checks if point is on curve
            return False #otherwise reject
        if B*y2**2 % p != (x2**3 + A*x2**2 + x2) % p: #checks if point is on curve
            return False #otherwise reject
        if x1==x2 and -y2 % p == y1:
            return math.inf,1 #(x1,y1) + (x1,-y1) = O
        elif x1==x2:
            #point doubling
            sl = ((3 * x1**2 + 2*A*x1 + 1)*mminv(2*B*y1,p)) % p #slope
            x = (B*(sl**2) - 2*x1 - A) % p
            return x, (sl*(x1 - x) - y1) % p
        else:
            #point addition
            sl = (y2-y1)*mminv(x2-x1,p) % p #slope
            x = (B*(sl**2) - x1 - x2 - A) % p
            return x, (sl*(x1 - x) - y1) % p

def eadd(p1,p2,a,d,p):
    """
    Adds p1 and p2 on a twsited Edwards curve
    We assume that there exists a pt of order 4, so identity is (0,1)
    """
    if d*(1 - d) == 0 % p: #checks Edwards requirements
        print("Singular Curve")
        return
    x1,y1 = p1
    x2,y2 = p2 #check if in curve here
    if (a*x1**2 + y1**2) % p != (1 + d*(x1**2)*(y1**2)) % p: #checks point is on the curve
        return False #otherwise reject
    if (a*x2**2 + y2**2) % p != (1 + d*(x2**2)*(y2**2)) % p: #checks point is on the curve
        return False #otherwise reject
    if p1 == (0,1) and p2 == (0,1):
        return 0,1 #O + O = O
    if p1 == (0,1): return p2 #O + p2 = p2
    if p2 == (0,1): return p1 #p1 + O = p2
    if y1==y2 and -x2 % p == x1: #(x1, y1) + (-x1, y1) = O
        return 0,1
    else:
        #point addition
        dxy = d*x1*x2*y1*y2 % p
        return (x1*y2 + y1*x2)*mminv(1+dxy,p) % p, (y1*y2 - a*x1*x2)*mminv(1-dxy,p) % p

#Multiplication

def wmul(p1,m,a,b,p):
    """Multiply p1 by m on a WNF curve"""
    """Works - prelim checks"""
    start = time.process_time() * 1000
    kp = math.inf,1 #starts at O
    q = p1 #starts at 1P
    k = bin(m)[2:] #converts m to binary
    for ii in reversed(k): #reverses binary digits
        if ii == '1': #if k(ii) = 1, add q to kp
            kp = wadd(kp,q,a,b,p)
        q=wadd(q,q,a,b,p) #double q each iteration
    print(time.process_time()*1000 - start)
    return kp

def mmul(p1,m,A,B,p):
    """Multiply p1 by m on a Montgomery curve"""
    start = time.process_time() * 1000
    kp = math.inf,1 #starts at O
    q = p1 #starts at 1P
    k = bin(m)[2:] #converts m to binary
    for ii in reversed(k): #reverses binary digits
        if ii == '1': #if k(ii) = 1, add q to kp
            kp = madd(kp,q,A,B,p)
        q=madd(q,q,A,B,p) #double q each iteration
    print(time.process_time() * 1000 - start)
    return kp

def emul(p1,m,a,d,p):
    """Multiply p1 by m on a twsited Edwards curve"""
    start = time.process_time() * 1000
    kp = 0,1 #starts at O
    q = p1 #starts at 1P
    k = bin(m)[2:] #converts m to binary
    for ii in reversed(k): #reverses binary digits
        if ii == '1': #if k(ii) = 1, add q to kp
            kp = eadd(kp,q,a,d,p)
        q=eadd(q,q,a,d,p) #double q each iteration
    print(time.process_time()*1000 - start)
    return kp

#Key Production

def wkeys(g,n,a,b,p):
    d = random.randint(1,n-1) #n order of generator G
    H = wmul(g,d,a,b,p)
    return d, H

def mkeys(g,n,A,B,p):
    d = random.randint(1,n-1) #n order of generator G
    H = mmul(g,d,A,B,p)
    return d, H

def ekeys(g,n,a,d,p):
    d = random.randint(1,n-1) #n order of generator G
    H = emul(g,d,a,d,p)
    return d, H


import sympy
import sys

def ValidateSymbols(symbols: str) -> list:
    if not symbols:
        print('Error: symbols string is null or empty.')
        return None

    symbols = symbols.split(' ')

    if len(symbols) < 1:
        print('Error: no valid symbols found in symbols string.')
        return None

    for symbol in symbols:
        if len(symbol) > 1:
            print('Error: symbol \'{0}\' is malformed.'.format(symbol))
            return None

    return symbols

def ParseEquation(symbols: str, fx: str) -> sympy.core.expr.Expr:
    fx = sympy.sympify(fx)

    return fx

def Derivative(symbols: str, fx: str) -> str:
    symbols = ValidateSymbols(symbols)

    # Declare an equation that we want to differentiate
    fx = ParseEquation(symbols, fx)

    # Create the variables for symbols we'll be using from the symbols param
    for symbol in symbols:
        locals()[symbol] = sympy.Symbol(symbol)

    # Differentiate it.  Should yield: 5*x**4 + 28*x**3
    derivative = sympy.diff(fx)

    return derivative

def PartialDerivative(symbols: str, fx: str, variable: str) -> str:
    symbols = ValidateSymbols(symbols)

    # Declare an equation that we want to differentiate
    fx = ParseEquation(symbols, fx)

    # Create the variables for symbols we'll be using from the symbols param
    for symbol in symbols:
        locals()[symbol] = sympy.Symbol(symbol)

    # Differentiate it.  Should yield: 5*x**4 + 28*x**3
    derivative = sympy.diff(fx, variable)

    return derivative

def Evalutate(symbols: str, fx: str, subs: dict) -> float:
    symbols = ValidateSymbols(symbols)

    # Declare an equation that we want to differentiate
    fx = ParseEquation(symbols, fx)

    # Create the variables for symbols we'll be using from the symbols param
    for symbol in symbols:
        locals()[symbol] = sympy.Symbol(symbol)

    result = fx.subs(subs).evalf()

    return result

def Solve(symbols: str, fx: str) -> sympy.sets.sets.Set:
    symbols = ValidateSymbols(symbols)

    # Declare an equation that we want to differentiate
    fx = ParseEquation(symbols, fx)

    # Create the variables for symbols we'll be using from the symbols param
    for symbol in symbols:
        locals()[symbol] = sympy.Symbol(symbol)

    result = sympy.solveset(fx, locals()[symbols[0]])

    return result

def main() -> int:
    # Power Rule
    fx = 'x**5 + 7*x**4 + 3'

    # Differentiate it.  Should yield: 5*x**4 + 28*x**3
    derivative = Derivative('x', fx)
    print(derivative)

    # Product Rule
    fx = 'x**2 + 1'
    gx = 'cos(x)'

    # Differentiate it.  Should yield: 2*x*cos(x) - (x**2 + 1)*sin(x)
    derivative = Derivative('x', '(' + fx + ') * (' + gx + ')')
    print(derivative)

    # Chain Rule
    fx = '(x**2 - 3*x + 5)**3'

    # Differentiate it.  Should yield: (6*x - 9)*(x**2 - 3*x + 5)**2
    derivative = Derivative('x', fx)
    print(derivative)

    # Partial Derivatives

    # f(x) = x^2yz^5
    fx = 'x**2 * y * z**5'

    # Partial derivative with respect to x: 2*x*y*z**5
    derivative = PartialDerivative('x y z', fx, 'x')
    print(derivative)

    # Partial derivative with respect to y: x**2*z**5
    derivative = PartialDerivative('x y z', fx, 'y')
    print(derivative)

    # Partial derivative with respect to z: 5*x**2*y*z**4
    derivative = PartialDerivative('x y z', fx, 'z')
    print(derivative)

    # Utility function for pizza and cookies
    fx = 'sqrt(p * c)'

    # Differentiate f(x) with respect to p: sqrt(c*p)/(2*p)
    MUp = PartialDerivative('p c', fx, 'p')
    print(MUp)

    # Differentiate f(x) with respect to c: sqrt(c*p)/(2*c)
    MUc = PartialDerivative('p c', fx, 'c')
    print(MUc)

    # If c is 2 and p is 5:
    subs = {'c':2, 'p':5}

    result = Evalutate('p c', MUp, subs)
    print(result)

    result = Evalutate('p c', MUc, subs)
    print(result)

    return 0

if __name__ == '__main__':
    sys.exit(main())


import numpy as np

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

def relu(x):
    return np.maximum(x, 0.0)

debug = True

class NeuralNetwork(object):
    def __init__(self, input_nodes, hidden_nodes, output_nodes, learning_rate):
        # Set number of nodes in input, hidden and output layers.
        self.input_nodes = input_nodes
        self.hidden_nodes = hidden_nodes
        self.output_nodes = output_nodes

        # Initialize weights
        self.weights_input_to_hidden = np.random.normal(0.0, self.input_nodes ** -0.5,
                                                        (self.input_nodes + 1, self.hidden_nodes + 1))

        self.weights_hidden_to_output = np.random.normal(0.0, self.hidden_nodes ** -0.5,
                                                         (self.hidden_nodes + 1, self.output_nodes))
        self.lr = learning_rate

        #### : Set self.activation_function to your implemented sigmoid function ####

        # this contains both the activation function and its derivative
        self.activation_function = sigmoid  # Replace 0 with your sigmoid calculation.

    def train(self, features, targets):
        ''' Train the network on batch of features and targets.

            Arguments
            ---------

            features: 2D array, each row is one data record, each column is a feature
            targets: 1D array of target values
        '''

        if debug:
            print("features.shape=", features.shape)
            print("weights_input_to_hidden.shape=", self.weights_input_to_hidden.shape)
            print("weights_hidden_to_output.shape=", self.weights_hidden_to_output.shape)

        features = np.hstack((np.ones((features.shape[0], 1)), features))
        n_records = features.shape[0]

        delta_weights_i_h = np.zeros(self.weights_input_to_hidden.shape)
        delta_weights_h_o = np.zeros(self.weights_hidden_to_output.shape)
        for X, y in zip(features, targets):
            final_outputs, hidden_outputs = self.forward_pass_train(X)
            # Implement the backproagation function below
            delta_weights_i_h, delta_weights_h_o = self.backpropagation(final_outputs, hidden_outputs, X, y,
                                                                        delta_weights_i_h, delta_weights_h_o)

        self.update_weights(delta_weights_i_h, delta_weights_h_o, n_records)

    def forward_pass_train(self, X):
        ''' Implement forward pass here

            Arguments
            ---------
            X: features batch
        '''
        ### Forward pass ###

        # : Hidden layer
        # signals into hidden layer
        hidden_inputs = np.dot(X, self.weights_input_to_hidden)
        hidden_outputs = self.activation_function(hidden_inputs)  # signals from hidden layer

        # : Output layer -
        # signals into final output layer
        final_inputs = np.dot(hidden_outputs, self.weights_hidden_to_output)
        final_outputs = final_inputs  # signals from final output layer

        return final_outputs, hidden_outputs

    def backpropagation(self, final_outputs, hidden_outputs, X, y, delta_weights_i_h, delta_weights_h_o):
        ''' Implement backpropagation

            Arguments
            ---------
            final_outputs: output from forward pass
            y: target (i.e. label) batch
            delta_weights_i_h: change in weights from input to hidden layers
            delta_weights_h_o: change in weights from hidden to output layers
        '''
        ## # this code is from the excercises
        ## output_error_term = error * sigmoid_prime(h)
        ## hidden_error = np.dot(output_error_term, weights_hidden_output)
        ## hidden_error_term = hidden_error * sigmoid_prime(hidden_input)
        #
        # >> output_error_term ()
        # >> hidden_error_term (2,)
        # >> correction_i_h: (6, 2)
        # >> correction_h_o: (2,)

        ### Backward pass ###

        # : Output error
        error = y - final_outputs  # Output layer error is the difference between desired target and actual output.

        # in this line only, I had a look at this solution: https://github.com/absalomhr/Predicting-Bike-Sharing-Patterns/blob/master/my_answers.py
        hidden_error = np.dot(self.weights_hidden_to_output, error)

        # : Backpropagated error terms -
        # f_prime_final = (final_outputs * (1 - final_outputs))
        output_error_term = error

        # : Calculate the hidden layer's contribution to the error
        f_prime_hidden = (hidden_outputs * (1 - hidden_outputs))
        hidden_error_term = hidden_error * f_prime_hidden

        # Weight step (input to hidden)
        delta_weights_i_h += hidden_error_term * X[:, None]
        # Weight step (hidden to output)
        delta_weights_h_o += output_error_term * hidden_outputs[:, None]
        return delta_weights_i_h, delta_weights_h_o

    def update_weights(self, delta_weights_i_h, delta_weights_h_o, n_records):
        ''' Update weights on gradient descent step

            Arguments
            ---------
            delta_weights_i_h: change in weights from input to hidden layers
            delta_weights_h_o: change in weights from hidden to output layers
            n_records: number of records

        '''
        self.weights_hidden_to_output += self.lr * delta_weights_h_o / n_records  # update hidden-to-output weights with gradient descent step
        self.weights_input_to_hidden += self.lr * delta_weights_i_h / n_records  # update input-to-hidden weights with gradient descent step

    def run(self, features):
        ''' Run a forward pass through the network with input features

            Arguments
            ---------
            features: 1D array of feature values
        '''

        # #### Implement the forward pass here ####
        # exactly the same as the forward_pass_train()
        final_outputs, hidden_inputs = self.forward_pass_train(features)
        return final_outputs

#########################################################
# Set your hyperparameters here
##########################################################
iterations = 1000
learning_rate = 0.5
hidden_nodes = 25
output_nodes = 1


from numpy import *
import numpy.linalg as linalg

x = array([1, 2, 3])
y = array([
	[1, 2],
	[3, 4],
])

# scalar product
r = x.dot(x)
print('scalar product', r)

# transpose
print('y\n', y)
print('y transpose\n', y.T)

# matrix multiplication
r = y.dot(y)
print('matrix multiplication')
print(r)

# matrix inverse
r = linalg.inv(y)
print('matrix inverse\n', r)

# matrix determinant
r = linalg.det(y)
print('determinant', r)

# solve system of equation and least-square
A = y
b = array([1, 2])
exact = linalg.solve(A, b)
least_square = linalg.lstsq(A,b, rcond=-1)[0]
s = """
solving system exactly, then by least-square
	A x = b
"""
print(s, exact, '\n', least_square)

# covariance
r = cov(x, -2*x)
print('covariance \n', r)

# print correlation
r = corrcoef(x, -2*x)
print('correlation \n', r)


# plots Bayesian updates to uniform beta prior
from scipy.stats import beta
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm

x = np.linspace(0, 1, num = 100)
plt.plot(x, beta(1, 1).pdf(x), label = "Prior = beta(1,1)", color = cm.get_cmap('Blues_r')(100))
plt.plot(x, beta(1.4, 2.3).pdf(x), label = "Prior = beta(1.4,2.3)", color = cm.get_cmap('Greens_r')(100))
for i in range(1,20,2):
    plt.plot(x, beta(1+i,2).pdf(x), label = "Uniform After {} heads".format(i), cm.get_cmap('Blues_r')(100-4*i))
    plt.plot(x, beta(1.4+i,2.3).pdf(x), label = "Biased ~fter {} heads".format(i), cm.get_cmap('Greens_r')(100-4*i))

plt.legend()
plt.title("Updates to uniform prior distribution")
plt.show()


#########################################################################################
## playing with numpy

import numpy as np

a = np.array([1,2,3,4,5,6])
print(a)
print(a[::2])
print(a[-1])

aa = np.array(np.arange(12)).reshape((3,4))
bb = np.array(np.arange(12)).reshape((4,3))
print('\naa =')
print(aa)
print('\nbb =')
print(bb)

print('\nIndexing a 2-D array: aa[:3, :]')
print(aa[:3, :]) # the second : means "all items of that axis"

print('\nElement-by-element operation (like .* in MATLAB): ')

print('\n - exponentiation')
print(aa**2.7) # exponentiation is **

print('\n - square root')
print(np.sqrt(aa)) # square root

print('\n - Error from aa + bb')
try:
    print(aa + bb) # addition (throws an error because aa and bb are different shapes)
except ValueError as err:
    print(err)
print('\n - aa + bb.T works')
print(aa + bb.T) # need to take transpose of bb so it has the same shape as aa

print('\nMatrix multiplication using @: aa @ bb')
print(aa @ bb)

print('\nMatrix multiplication using @: bb @ aa')
print(bb @ aa)

print('\nConcatenate two arrays')
cc = np.concatenate((aa,aa), axis=1)
print(cc)

print('\nFlatten and array')
print(aa.flatten())

print('\n Make a Boolean mask of aa >= 5')
mask = aa >= 5
print(mask)

print('\n Use the mask to get just those elements of aa: aa[mask]')
print(aa[mask])
print('Note that this returns just a 1-D arrray (flattened)')

b = a
b[3] = 1
print(a)
print(b)

b = a.copy()
b[3] = 1
print(a)
print(b)

print('\nMean of array a is ', np.mean(1))

print('\nStandard deviation of array a is ', np.std(a))

print('\nMaximum element of array a is ', np.amax(a))

print('\nIndex of maximum element of array a is ', np.argmax(a))

af = a.astype(float)
af[5] = float('nan')
print(af)

print("Mean of array af with mean function is", np.mean(af))
print("Mean of array af with nanmean function is ", np.nanmean(af))

print("add elements at the end of an array: ", np.append(a, 7))
print("insert elements to array: ", np.insert(a, 3, 10))
print("delete nth element from array: ", np.delete(a, [4]))

b = np.array([1,-2,3,-4,5,-6])
print(np.abs(b))

c = np.concatenate((a,b))
print("concatenate multiple arrays: ", c)

d = np.hsplit(c,3)
print("split arrays: ", d)
print("transpose arrays: ", np.transpose(d))

print("3 random numbers from normal distribution: ", np.random.normal(size=3))
print("3 random intergers between 1 and 10: ", np.random.randint(high=10, low=1, size=3))

print("sign of each element in array: ", np.sign(b))

d = np.array([1.15, 3.62, 8.51])
print("round decimal values: ", np.around(d))
print("round decimal values with 1 decimal places: ", np.around(d, decimals=1))

#########################################################################################
## using pickle to have the code save outputs

import sys, os
import numpy as np
import pickle

# local imports
sys.path.append(os.path.abspath('../shared'))
import my_module as mymod
from importlib import reload
reload(mymod)

# make sure the output directory exists
this_dir = os.path.abspath('.').split('/')[-1]
out_dir = '../../' + this_dir + '_output/'
print('Creating ' + out_dir + ', if needed')
mymod.make_dir(out_dir)

# save array as a pickle file
out_fn = out_dir + 'pickled_array.p'
pickle.dump(c, open(out_fn, 'wb')) # 'wb' is for write binary

# read the array back in
c = pickle.load(open(out_fn, 'rb')) # 'rb is for read binary

#########################################################################################
## using argparse to add command line arguments to code

import argparse

def boolean_string(s):
    # this function helps with getting Boolean input
    if s not in ['False', 'True']:
        raise ValueError('Not a valid boolean string')
    return s == 'True' # note use of ==

# create the parser object
parser = argparse.ArgumentParser()

parser.add_argument('-a', '--a_string', default='multiply numbers by changing b and c values: ', type=str)
parser.add_argument('-b', '--integer_b', default=10, type=int)
parser.add_argument('-c', '--integer_c', default=2, type=int)
parser.add_argument('-v', '--verbose', default=True, type=boolean_string)

args = parser.parse_args()

print(args.a_string)
print(args.integer_b * args.integer_c)


from sympy import *

# Based on a study of population projections for 2000 to 2050,
# the projected population of a group of people (in millions) can be modeled by the following exponential function,
# where t=0 corresponds to 2000 and 0 <= t <= 50.

t = symbols( 't', positive = True )
r = 1.078
A = 11.27*r**t
years = 14

r = 1.078
A = 11.27*r**t
years = 14

a = A.subs( { t: years } )
b = A.subs( { t: 0 } )

avg_chg = round( ( a - b ) / years, 3 )
round( avg_chg * 1000000 ) # Estimate the average rate of change in the population from 2000 to 2014.

dA = Derivative( A, t ).doit()
instanious_rate_of_change = dA.subs( { t: years } )
round( instanious_rate_of_change, 3 ) * 1000000 # Estimate the instantaneous rate of change in the population in 2014.


import numpy as np

def _calculate_along(n, x=None, dx=1.):
    if x is None:
        x = np.arange(0., n * dx, dx)

    return x

def forward_integration(y, x=None, dx=1., initial=0.):
    x = _calculate_along(y.size, x, dx)
    return np.cumsum(np.insert(y[:-1] * (x[1:] - x[:-1]), 0, initial))

def backward_integration(y, x=None, dx=1., final=0.):
    x = _calculate_along(y.size, x, dx)
    return np.cumsum(np.insert(y[1:] * (x[1:] - x[:-1]), y.size - 1, final))

def trapezoidal_integration(y, x=None, dx=1., initial=0.):
    x = _calculate_along(y.size, x, dx)
    return np.cumsum(np.insert((y[:-1] + y[1:]) / 2. * (x[1:] - x[:-1]), 0, initial))

def forward_difference(y, x=None, dx=1.):
    """
    1st order forward differencing scheme, using backwards differencing at the boundary value.
    Parameters
    ----------
    y : array_like
        Array of function values to differentiate.
    x : array_like
        Array of x-values for which to differentiate w.r.t.
    dx : float
        Step used to create `x` if not provided
    """
    x = _calculate_along(y.size, x, dx)
    return np.insert((y[1:] - y[:-1]) / (x[1:] - y[:-1]), 0, (y[1] - y[0]) / (x[1] - x[0]))

def backward_difference(y, x=None, dx=1.):
    """
    1st order back differencing scheme, using forward differencing at the boundary value.
    Parameters
    ----------
    y : array_like
        Array of function values to differentiate.
    x : array_like
        Array of x-values for which to differentiate w.r.t.
    dx : float
        Step used to create `x` if not provided
    """
    x = _calculate_along(y.size, x, dx)
    return np.insert((y[:-1] - y[1:]) / (x[:-1] - x[1:]), y.size - 1, (y[-1] - y[-2]) / (x[-1] - x[-2]))

def central_difference(y, x=None, dx=1.):
    """
    1st order central differencing scheme, using forward/backwards differencing at the boundary values.
    Parameters
    ----------
    y : array_like
        Array of function values to differentiate.
    x : array_like
        Array of x-values for which to differentiate w.r.t.
    dx : float
        Step used to create `x` if not provided
    """
    x = _calculate_along(y.size, x, dx)
    return np.insert(np.insert((y[1:-1] - y[:-2]) / (x[1:-1] - y[:-2]), 0, (y[1] - y[0]) / (x[1] - x[0])),
                     y.size - 1, (y[-1] - y[-2]) / (x[-1] - x[-2]))

def central_difference_2nd(y, x=None, dx=1.):
    """
    2nd order central differencing scheme, using 1st order forward/backwards differencing at the boundary values.
    Parameters
    ----------
    y : array_like
        Array of function values to differentiate.
    x : array_like
        Array of x-values for which to differentiate w.r.t.
    dx : float
        Step used to create `x` if not provided
    """
    x = _calculate_along(y.size, x, dx)
    hd = x[2:] - x[1:-1]
    hs = x[1:-1] - x[:-2]
    hd2 = hd ** 2.
    hs2 = hs ** 2.
    yd = y[2:]
    ys = y[:-2]

    dy = (hs2 * yd + (hd2 - hs2) * y[1:-1] - hd2 * ys) / (hs * hd * (hd + hs))

    return np.insert(np.insert(dy, 0, (y[1] - y[0]) / (x[1] - x[0])), y.size - 1, (y[-1] - y[-2]) / (x[-1] - x[-2]))


'''
@author: Hsinglu Liu
@version: 1.0
@Date: 2020.11.3
'''

import math
from sympy import *

# define symbol variable
x_1 = symbols('x_1')
x_2 = symbols('x_2')

# define objective function
fun = 2 * x_1 * x_2 + 2 * x_2 - x_1 ** 2 - 2 * x_2 ** 2
fun

# take derivative of x_1 and x_2
grad_1 = diff(fun, x_1)
grad_2 = diff(fun, x_2)

# define parameters
MaxIter = 100
epsilon = 0.0001

# define initial point
x_1_value = 0.5
x_2_value = 0.5

iter_cnt = 0
current_step_size = 10000

grad_1_value = (float)(grad_1.subs({x_1:x_1_value, x_2: x_2_value}).evalf())
grad_2_value = (float)(grad_2.subs({x_1:x_1_value, x_2: x_2_value}).evalf())

current_obj = fun.subs({x_1: x_1_value, x_2: x_2_value}).evalf()

print('itCnt: %2d  cur_point (%3.2f, %3.2f)   cur_Obj: %5.4f     grad_1: %5.4f     grad_2 : %5.4f     step_size : %5.4f' % (iter_cnt, x_1_value, x_2_value, current_obj, grad_1_value, grad_2_value, current_step_size))

# while (iter_cnt <= MaxIter and abs(grad_1_value) + abs(grad_2_value) >= epsilon):
while(abs(grad_1_value) + abs(grad_2_value) >= epsilon):
    iter_cnt += 1
    # find the step size
    t = symbols('t')
    x_1_updated = x_1_value + grad_1_value * t
    x_2_updated = x_2_value + grad_2_value * t
    Fun_updated = fun.subs({x_1: x_1_updated, x_2: x_2_updated})
    grad_t = diff(Fun_updated, t)
    t_value = solve(grad_t, t)[0]  # solve grad_t == 0

    # update x_1_value and x_2_value
    grad_1_value = (float)(grad_1.subs({x_1: x_1_value, x_2: x_2_value}).evalf())
    grad_2_value = (float)(grad_2.subs({x_1: x_1_value, x_2: x_2_value}).evalf())

    x_1_value = (float)(x_1_value + t_value * grad_1_value)
    x_2_value = (float)(x_2_value + t_value * grad_2_value)

    current_obj = fun.subs({x_1: x_1_value, x_2: x_2_value}).evalf()
    current_step_size = t_value

    print('itCnt: %2d  cur_point (%3.2f, %3.2f)   cur_Obj: %5.4f     grad_1: %5.4f     grad_2 : %5.4f     step_size : %5.4f' % (iter_cnt, x_1_value, x_2_value, current_obj, grad_1_value, grad_2_value, current_step_size))