# -*- coding: utf-8 -*-
"""
Created on Mon Jan 20 22:03:53 2020

@author: Hossein
"""
import cmath as mathematician
import random
import numpy

def bairstow( roots_list , coefficients_list , r0 , s0 ):

    # coefficients_list = ax^2 + bx + c -> [c,a,b]
    tolerance = 1E-6
    n = len(coefficients_list) - 1

    #if there was only one Number in the input
    if(n == 0):
        return coefficients_list[0]

    # Polynomial Degree 1
    elif ( n == 1 ):
        roots_list.append( float(coefficients_list[0]/coefficients_list[1])*-1)

    #polynomial Degree 2
    elif ( n == 2 ):
        #b^2 - 4ac
        delta = coefficients_list[1]**2 - 4*coefficients_list[2]*coefficients_list[0]
        root1 = (-coefficients_list[1] + mathematician.sqrt(delta))/(2.0*coefficients_list[2])
        root2 = (-1*coefficients_list[1] - mathematician.sqrt(delta))/(2.0*coefficients_list[2])
        roots_list.append(root1)
        roots_list.append(root2)

    #if the polynomial had a degree > 2
    elif (n >= 3 ):

        while(True):
            b = [0] * ( n + 1 )

            #Calculate B's
            b[n] = coefficients_list[n]
            b[n-1] = coefficients_list[n-1] + r0 * b[n]
            for i in range(n-2 , -1 , -1):
                b[i] = coefficients_list[i] + r0 * b[i+1] + s0 * b[i+2]

            #Calculate C's
            c = [0] * ( n + 1 )
            c[n] = b[n]
            c[n-1] = b[n-1] + r0 * c[n]
            for i in range(n-2 , -1 , -1):
                c[i] = b[i] + r0 * c[i+1] + s0 * c[i+2]

            #Calculating D's
            D  = calculateDeterminant (c[1] , c[2] , c[2] , c[3])
            D1 = calculateDeterminant (-b[0] , c[2] , -b[1] , c[3])
            D2 = calculateDeterminant (c[1] , -b[0] , c[2] , -b[1])

            if (  D == 0 ):
                return None

            #Delta
            delta_r = float(D1/D)
            r0 = r0 + delta_r
            delta_s = float(D2/D)
            s0 = s0 + delta_s

            #check if the difference is greater than our tolerance (epsilon) or not
            if ( abs(b[0]) < tolerance and abs(b[1]) < tolerance ):
                break

        #End of While
        dis = (r0)**(2.0) + (4.0 * (s0))
        x1 = (r0 - (mathematician.sqrt(dis)))/(2)
        x2 = (r0 + (mathematician.sqrt(dis)))/(2)
        roots_list.append(x1)
        roots_list.append(x2)
        return bairstow(roots_list , b[2:] , r0 , s0 )

###################  Utility Functions #################33

def calculateDeterminant(index00 , index01 , index10 , index11 ):
    return ( index00 * index11 - index01*index10 )

def atoi(a_list):
    return list(map(lambda x : int(x) , a_list))

def printList(a_message, a_list):
    print(a_message)
    list(map(lambda x : print(x) , a_list))

def sigma(a_list):
    summation = 0
    for number in a_list :
        if( (number.imag > 0)  or (numpy.iscomplex(number) == False) ):
            summation += number
    return summation

def printOutput(a_complexNumber):
    realPart = a_complexNumber.real
    imaginaryPart = a_complexNumber.imag
    print('%.6f '%realPart ," ", '%.6f'%imaginaryPart )
############## MAIN #####################
    '''
    -3x^5 + 2x^4 + x^3 + 0x^2 - x - 1
    #Sample Input : inputCoefficients = [-3 , 2 , 1 , 0 , -1 , -1]
    '''
def main():
    #Get Input :
    n = input()
    #print("Sample -> Ax^2 + Bx + C => A B C" )
    #print("Enter The Coefficients as said Above: ")
    inputCoefficients = list(reversed(input().split(' ')))
    inputCoefficients = atoi(inputCoefficients)

    #Initial Elemnets and Values
    roots_list = []
    #s0 = random.random()
    #r0 = random.random()
    s0 = -1.9
    r0 = -2.1
    #Printing Coefficients
    #print("Input Coefficints : " , inputCoefficients)
    #print("===========================")

   #Main Function Call
    bairstow(roots_list , inputCoefficients , r0 , s0)

    #Printing Calculated Roots
    #printList("Roots : " , roots_list)
    #print("===========================")

    #Printing the output Summation
    #print("Output : ( Positive Roots Summation) ")
    summation = sigma(roots_list)
    printOutput(summation)
########################################## RUN #############

main()


from scipy.integrate import quad
import numpy as np
from prettytable import PrettyTable

def integral_solution(interval_bounds, family):
    answers = []

    for function in family:
        answers.append(quad(lambda x: eval(function), interval_bounds[0], interval_bounds[1])[0])

    return answers

def midpoint_rule(interval_bounds, partitions, family):
    intervals = list()  # array of all dx's
    dx = ((interval_bounds[1] - interval_bounds[0]) / partitions)
    step = interval_bounds[0]

    while step <= interval_bounds[1]:
        intervals.append(step)
        step += dx

    answers = [0.0] * len(family)
    i = 0
    while i < len(intervals) - 1:
        x = ((intervals[i] + intervals[i+1]) / 2.0)  # evaluate each with x
        for j in range(0, len(family)):
            answers[j] += 2*(eval(family[j]))
        i += 1
    return answers

def trapezoidal_rule(interval_bounds, partitions, family):
    x = list()  # array of all dx's
    dx = ((interval_bounds[1] - interval_bounds[0]) / partitions)
    step = interval_bounds[0]

    while step <= interval_bounds[1]:
        x.append(step)
        step += dx

    x = np.array(x)

    answers = []
    for function in family:
        if function.isdigit():
            answers.append(np.trapz(int(function) + (0*x), x))  # check if constant
        else:
            answers.append(np.trapz(eval(function), x))
    return answers

def find_weight(midpoint_rule_value, trapezoidal_rule_value, integral_value, family):
    answers = [0] * len(family)
    i = 0
    while i <= len(answers) - 1:
        if trapezoidal_rule_value[i] - midpoint_rule_value[i] == 0:
            answers[i] = 0
        else:
            answers[i] = (trapezoidal_rule_value[i] - integral_value[i]) / \
                         (trapezoidal_rule_value[i] - midpoint_rule_value[i])
        i += 1

    return answers

def table(family, midpoint_value, trapezoidal_value, integral_value, weights):
    table = PrettyTable()
    table.add_column("Functions", family)
    table.add_column("Midpoint Values", midpoint_value)
    table.add_column("Trapezoidal Values", trapezoidal_value)
    table.add_column("Integral Values", integral_value)
    table.add_column("Scalar Constants", weights)

    return table

def main():
    family = []
    print("Warning: Use ** for exponents instead of ^")
    while True:
        function = input("Please enter your next function or press q to finish: ")
        if function == "q" or function == "Q":
            break
        else:
            family.append(function)
    partitions = int(input("How many partitions? "))
    start = int(input("Interval start? "))
    end = int(input("Interval end? "))
    interval_bounds = [start, end]

    integral_value = integral_solution(interval_bounds, family)
    midpoint_value = midpoint_rule(interval_bounds, partitions, family)
    trapezoidal_value = trapezoidal_rule(interval_bounds, partitions*2, family)
    weights = find_weight(midpoint_value, trapezoidal_value, integral_value, family)
    print(table(family, midpoint_value, trapezoidal_value, integral_value, weights))

if __name__ == main():
    main()


import numpy as np
import time
import cmath

def dft(x):
    x = np.asarray(x, dtype=np.cdouble)
    N = x.shape[0]
    n = np.arange(N)
    k = n.reshape((N, 1))
    M = np.exp(-2j * np.pi * k * n / N)
    return np.dot(M, x)

def inv_dft(C):
    xa = np.asarray(C, dtype=float)
    N = xa.shape[0]
    n = np.arange(N)
    k = n.reshape((N, 1))
    M = np.exp(2j * np.pi * k * n / N)
    M_inverse = np.linalg.inv(M)
    VC = np.dot(M_inverse,C)
    return VC

#code to do pointwise multiplication
def dft_multiply(a,b):
    VA=dft(a)
    VB=dft(b)
    C = []
    for i in range(VA.shape[0]):
        C.append(VA[i]*VB[i])
    return C

#driver code
i = 4
poly_a = list(np.random.randint(low = -1000, high = 1000, size = i))
poly_b = list(np.random.randint(low = -1000, high = 1000, size = i))
start_time = time.time()
dft_res_a=dft(poly_a)
dft_res_b=dft(poly_b)
C = dft_multiply(dft_res_a,dft_res_b)
VC=inv_dft(C)
print(np.fft.ifft(C))
print(VC)
print("\n\n")
#print(time.time()-start_time)


import numpy as np
import math
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

n=100

X = np.random.uniform(low=-5, high=5, size=(n,))
noise = np.random.normal(0, 0.1, n) # epsilon
y = np.sin(X)+noise

def kernel(a):
    return 1.0/(math.sqrt(2*math.pi)) * math.exp((-math.pow(a,2)/2))

# Nadaraya-Watson estimator
def estimator(x_k,X,y,h):
    num = 0
    denom = 0
    for x_i,y_i in zip(X,y):
        num += y_i*kernel(abs(x_i-x_k)/h)
        denom += kernel(abs(x_i-x_k)/h)
    return num/denom

def loss_func(y,y_hat):
    return (y-y_hat)**2

n = 200
X = np.random.uniform(low=-5, high=5, size=(n,))
noise = np.random.normal(0, 0.1, n) # epsilon
y = np.sin(X)+noise
plt.scatter(X,y)

plt.show()

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=42)

H = [1.0, 0.75, 0.5, 0.25, 0.1, 0.05, 0.01]
for h in H:
    f_hat = [estimator(x_k,X_train,y_train,h) for x_k in X_train]

    print('Train error:',np.mean(loss_func(y_train, f_hat)))

# plt.scatter(X, np.sin(X), alpha=0.5)
# plt.plot(X,y)
# plt.title('Scatter plot -- 2pts')
# plt.xlabel('X')
# plt.ylabel('y')
# plt.show()


import numpy as np
from scipy import integrate

def simpson_rule(xs, ys):
    n = len(xs)
    h = (xs[len(xs) - 1] - xs[0]) / n

    integral = ys[0]
    for i in range(0, int(n/2)):
        integral += 4 * ys[2 * i]

    for i in range(0, int(n/2 + 1)):
        integral += 2 * ys[2 * i - 1]

    integral += ys[len(ys) - 1]
    integral *= h / 3
    return integral

xs = list(np.arange(1, 100, 0.1))
ys = [np.exp(-x**2) * np.log(x)**2 for x in xs]

custom_int = simpson_rule(xs, ys)
lib_int = integrate.simps(ys, xs)

print("Custom 1: " + str(custom_int))
print("Library 1: " + str(lib_int))

ys = [1 / (x**3-2*x-5) for x in xs]

custom_int = simpson_rule(xs, ys)
lib_int = integrate.simps(ys, xs)

print("Custom 2: " + str(custom_int))
print("Library 2: " + str(lib_int))

ys = [x**5 * np.exp(-x) * np.sin(x) for x in xs]

custom_int = simpson_rule(xs, ys)
lib_int = integrate.simps(ys, xs)

print("Custom 3: " + str(custom_int))
print("Library 3: " + str(lib_int))


import numpy as np
import warnings

class SO3():
    @staticmethod
    def eul2rotm(eul, type='zyx'):
        rads = np.reshape(eul, (3,))
        x, y, z = rads
        Rx = np.array([[1, 0, 0],
                       [0, np.cos(x), -np.sin(x)],
                       [0, np.sin(x), np.cos(x)]])
        Ry = np.array([[np.cos(y), 0, np.sin(y)],
                       [0, 1, 0],
                       [-np.sin(y), 0, np.cos(y)]])
        Rz = np.array([[np.cos(z), -np.sin(z), 0],
                       [np.sin(z), np.cos(z), 0],
                       [0, 0, 1]])
        if type == 'zyx':
            R = np.matmul(np.matmul(Rz, Ry), Rx)
        else:
            R = np.eye(3)
        return R

    @staticmethod
    def rotm2eul(R, type='zyx'):
        if type == 'zyx':
            sy = np.sqrt(R[0, 0] * R[0, 0] + R[1, 0] * R[1, 0])
            singular = sy < 1e-6
            if not singular:
                x = np.arctan2(R[2, 1], R[2, 2])
                y = np.arctan2(-R[2, 0], sy)
                z = np.arctan2(R[1, 0], R[0, 0])
            else:
                x = np.arctan2(-R[1, 2], R[1, 1])
                y = np.arctan2(-R[2, 0], sy)
                z = 0
            result = np.array([x, y, z])
        else:
            result = np.array([0, 0, 0])
        return result

    @staticmethod
    def get_exp(w, type='zyx'):
        w = np.reshape(w, (3))
        th = np.sqrt(np.dot(w, w))
        wx = SO3.get_skew(w)
        dR = np.eye(3) if th == 0 else np.eye(3) + np.sin(th) / th * wx + (1 - np.cos(th)) / th ** 2 * np.matmul(wx, wx)
        return dR

    @staticmethod
    def get_dR(R_prev, R_curr):
        return np.matmul(np.linalg.inv(R_prev), R_curr)

    @staticmethod
    def get_log(R, type='zyx'):
        with warnings.catch_warnings():
            warnings.filterwarnings('error')
            try:
                th = np.arccos((np.trace(R) - 1)/2)
            except:
                R = np.round(R, 6)
                th = np.arccos((np.trace(R) - 1)/2)
        skew = np.zeros((3,3)) if th == 0 else th/(2*np.sin(th))*(R - R.T)
        return skew

    @ staticmethod
    def get_skew(w):
        w = np.reshape(w, (3))
        x, y, z = w
        wx = np.array([[0, -z, y],
                       [z, 0, -x ],
                       [-y, x, 0]])
        return wx

    @ staticmethod
    def get_w(wx):
        w = np.array([-wx[1,2], wx[0,2], -wx[0,1]])
        return w

class SE3():
    @ staticmethod
    def get_V(w):
        th = np.sqrt(np.dot(w, w))
        skew = SO3.get_skew(w)
        V = np.eye(3) if th == 0 else np.eye(3) + (1-np.cos(th))/th**2*skew +(th-np.sin(th))/th**3*np.matmul(skew, skew)
        return V

    @staticmethod
    def get_dT(T_prev, T_curr):
        return np.matmul(np.linalg.inv(T_prev), T_curr)

    @staticmethod
    def get_Rnt(T):
        return T[:3, :3], T[:3, 3]

    @staticmethod
    def get_exp(w, u):
        R = SO3.get_exp(w)
        V = SE3.get_V(w)
        t = np.matmul(V, u)
        T = np.eye(4)
        T[:3, :3] = R
        T[:3, 3] = t
        return T

    @staticmethod
    def get_log(T):
        R, t = SE3.get_Rnt(T)
        skew = SO3.get_log(R)
        w = SO3.get_w(skew)
        V = SE3.get_V(w)
        u = np.matmul(np.linalg.inv(V), t)
        return w, u

def rotatePoints3D(R, pts3):
    N = pts3.shape[0]
    result = np.zeros_like(pts3)
    for i in range(0, N):
        result[i] = np.matmul(R, pts3[i])
    return result

def transformPoints4D(T, pts3D):
    N = pts3D.shape[0]
    pts4D = np.ones((N, 4))
    pts4D[:,:3] = pts3D
    result = np.zeros_like(pts4D)
    for i in range(0, N):
        result[i] = np.matmul(T, pts4D[i])
    return result[:,0:3]

if __name__ == '__main__':
    eul = np.array([10, 20, 30])*np.pi/180
    # R = Lie_SO3.eul2rotm(eul)
    # skew = Lie_SO3.get_log(R)
    # w = Lie.get_w(skew)
    # wx = Lie.get_skew(w)
    # R = Lie_SO3.get_exp(w)
    # print(Lie_SO3.rotm2eul(R)*180/np.pi)
    T = np.eye(4)
    T[:3, :3] = SO3.eul2rotm(eul)
    T[:3, 3] = np.array([1, 2, 3])
    print(T)
    w, u = SE3.get_log(T)
    T_recon = SE3.get_exp(w, u)
    print(np.round(T - T_recon, 2))


# -*- coding: utf-8 -*-
"""
Created on Mon Oct 14 10:15:31 2019

@author: hamil
"""

import numpy as np
import matplotlib.pyplot as plt
import scipy
from scipy import optimize
from scipy import interpolate
from scipy.interpolate import lagrange
from scipy.interpolate import interp1d
from scipy.interpolate import CubicSpline

e = [0,25,50,75,100,125,150,175,200]
g_e = [10.6, 16.0, 45.0, 83.5, 52.8, 19.9, 10.8, 8.25, 4.7]
err = [9.34, 17.9, 41.5, 85.5, 51.5, 21.5, 10.8, 6.29, 4.14]

plt.scatter(e,g_e, s=5)
plt.title("Initial Plot of the table values")
plt.ylabel('G(E)')
plt.xlabel('E')
plt.show()

answer1 = lagrange(e,g_e)
print('Coeffs for lagrange interpolation', answer1.coeffs)
eq = np.poly1d(answer1)
print(np.poly1d(answer1))

#Now use this poly to plot the cross sections every 5 MeV
x = np.arange(0,210, 5)
y = eq(x)
y = np.array(y)
plt.plot(x,y)
plt.scatter(e, g_e, color = 'pink')
plt.title("Plot of cross sections of data with interpolated function")
plt.show()

#Using this produced graph we can determine the peak position
max_value = np.max(y)
print("Peak position:", max_value)

#Now to find the FWHM
#locate the two values near the half-way point on the curve then determine the distance between
half_way = max_value / 2
x_array = []
y_array = []
for numm in x:
    y = eq(numm)
    y_array.append(y)
    if y <= (half_way + 4) and y >= (half_way - 4):
        x_array.append(numm)

dist = x_array[3] - x_array[2]
print("The FWHM:", dist)

#My experimental values and predicted theory are in agreement for the full width half maximum. There is not
#as much agreement for the peak positing. My peak position is slightly greater in value by about 5 points.
#I find this interesting. Even though the peak is off, the FWHM is actually on par.

#Now use interpolate over 3 data point sections
#determine the 3 x and y values for each set
e1 = [e[0], e[1], e[2]]
g_e1 = [g_e[0], g_e[1], g_e[2]]

e2 = [e[1], e[2], e[3]]
g_e2 = [g_e[1], g_e[2], g_e[3]]

e3 = [e[2], e[3], e[4]]
g_e3 = [g_e[2], g_e[3], g_e[4]]

e4 = [e[3], e[4], e[5]]
g_e4 = [g_e[3], g_e[4], g_e[5]]

e5 = [e[4], e[5], e[6]]
g_e5 = [g_e[4], g_e[5], g_e[6]]

e6 = [e[5], e[6], e[7]]
g_e6 = [g_e[5], g_e[6], g_e[7]]

e7 = [e[6], e[7], e[8]]
g_e7 = [g_e[6], g_e[7], g_e[8]]

#approx the fit and determine the equation
step1 = lagrange(e1,g_e1)
eq1 = np.poly1d(step1)
#print(step1)
step2 = lagrange(e2, g_e2)
eq2 = np.poly1d(step2)
#print(step2)
step3 = lagrange(e3, g_e3)
eq3 = np.poly1d(step3)
#print(step3)
step4 = lagrange(e4, g_e4)
eq4 = np.poly1d(step4)
#print(step4)
step5 = lagrange(e5, g_e5)
eq5 = np.poly1d(step5)
#print(step5)
step6 = lagrange(e6, g_e6)
eq6 = np.poly1d(step6)
#print(step6)
step7 = lagrange(e7, g_e7)
eq7 = np.poly1d(step7)
#print(step6)

#use equation to create sample x and y values to plot everything
x1 = np.arange(0,205, 5)
y1 = eq(x1)
y2 = eq2(x1)
y3 = eq3(x1)
y4 = eq4(x1)
y5 = eq5(x1)
y6 = eq6(x1)
y7 = eq7(x1)

#Plot everything
plt.plot(x1, y7, label = 'set-7')
plt.plot(x1, y6, label = 'set-6')
plt.plot(x1, y5, label = 'set-5')
plt.plot(x1, y4, label = 'set-4')
plt.plot(x1, y3, label = 'set-3')
plt.plot(x1, y2, label = 'set-2')
plt.plot(x1, y1, label = 'set-1')
plt.scatter(e,g_e)
plt.title("All Lagrange Interpolations for groups of 3")
plt.legend()
plt.show()

#Looking at the first set closer:
plt.plot(x1, y1, label = 'set-1')
plt.scatter(e,g_e)
plt.title('Lagrange Interpolation of first set of 3 points')
plt.show()

#Try a 1d linear interpolation and a 1d cubic spline
answer2 = interp1d(e, g_e, kind= 'linear')
answer3 = CubicSpline(e,g_e)
lin_y = answer2(x1)
cubic_y = answer3(x1)

plt.plot(x1, lin_y)
plt.scatter(e, g_e)
plt.title('Linear 1D Interpolation')
plt.show()

plt.plot(x1,cubic_y)
plt.scatter(e, g_e)
plt.title('Cubic Spline Interpolation')
plt.show()

#When looking at each graph for the different types of interpolation, it seems the cubic spline
#is providing the best fit curve for the data provided that goes through each of the points.
#When stepping through the dataset every three points, the first set provides the best curvefit when is
#comparable to the lagrange interpolation. Both the lagrange interpolation and the first set fit the data,
#but has issues with the end points. When viewing the different sets of 3, each one is fitting for the particular
#set of points, and therefore, if combined, would probably look like the first set, pretty close, but still off
#a bit. The linear 1-D interpolation, also fit the data well, from point ot point, but it is pretty severe
#and most likely not like the actual function. The Cubic spline is the better curved version of the linear 1-
#version so to speak. It hits all the points, but is continuous.


'''
Analyze the distribution of data
In Zero skewed mean=median=mode
'''
import matplotlib.pyplot as plt
import pandas as pd

dataset = pd.read_csv('07_zero_skewed_data.csv')

dataset['salary'].mean()  # Mean 40000
dataset['salary'].median()  # Median 40000
from scipy import stats

stats.mode(dataset['salary'])  # Mode 40000
# visualization
# Histogram of Salary
dataset['salary'].plot(kind='hist', title='Histogram for Salary', color='c')
# use kde for density plot
dataset['salary'].plot(kind='kde', title='Density plot for Salary', color='c')

# import essential libraries
import numpy as np

# Employee Salaries
np_array = np.array(
    [10000, 10000, 20000, 20000, 30000, 30000, 30000, 40000, 40000, 40000, 40000, 40000, 50000, 50000, 50000, 60000,
     60000, 70000, 70000])
np.mean(np_array)
np.median(np_array)

from scipy import stats

stats.mode(np_array)  # Mode 40000
plt.hist(np_array, bins=5)


from numpy import *

def compute_error_for_line_given_points(b,m,points):
	totalError = 0
	#for every point
	for i in range(0,len(points)):
		#get the x value
		x = points[i,0]
		#get the y value
		y = points[i,1]
		#get the difference, square it, add it to the total
		totalError+=(y-(m*x+b))**2
	return totalError/float(len(points))

def step_gradient(b_current, m_current,points, learning_rate):
	#starting points for gradients
	b_gradient = 0
	m_gradient = 0
	N = float(len(points))
	for i in range(0,len(points)):
		x = points[i,0]
		y = points[i,1]
		#direction with respect to b and m
		#computing partial derivatives of our error functions

		b_gradient += -(2/N)*(y-((m_current*x)+b_current))
		m_gradient += -(2/N)*x*(y-((m_current*x)+b_current))

	new_b = b_current-(learning_rate*b_gradient)
	new_m = m_current-(learning_rate*m_gradient)
	return [new_b,new_m]

def gradient_descent_runner(points,starting_b,starting_m,learning_rate,num_iterations):
	#starting b and m
	b = starting_b
	m = starting_m

	#gradient descent
	for i in range(num_iterations):
		#update b and m with more accurate b and m by performing gradient step
		b,m = step_gradient(b,m,array(points), learning_rate)
	return [b,m]

def run():
	#step 1 Collect our data
	#2 loops in genfromtxt
	# each line in file to sequence of strings
	# second loop converting each string to its appropriate data type
	points = genfromtxt('data.csv', delimiter=',')

	#Step 2. Define Hyperparameteres
	# Hyperparameters nothing but a fancy word for parameters
	#how fast should our model converge
	learning_rate = 0.0001
	#y = mx+b(slope formula)
	initial_b = 0
	initial_m = 0
	num_iterations = 1000

	# Step 3. Training the model
	print ('starting gradient at b = {}, m={}, error = {}'.format(initial_b,initial_m,compute_error_for_line_given_points(initial_b,initial_m,points)))
	print ('Running...')
	[b,m] = gradient_descent_runner(points,initial_b,initial_m,learning_rate,num_iterations)
	print ('Ending point at {} iterations, b = {}, m={}, error = {}'.format(num_iterations,b,m,compute_error_for_line_given_points(b,m,points)))

if __name__ == "__main__":
	run()


# Implementing discrete FFT for micropython.
#
# Some doc:
# https://stackoverflow.com/questions/25735153/plotting-a-fast-fourier-transform-in-python
# https://www.reddit.com/r/Python/comments/804w3l/need_help_understanding_numpy_fft/
# https://github.com/numpy/numpy/blob/v1.14.0/numpy/fft/helper.py#L130-L175
# https://jakevdp.github.io/blog/2013/08/28/understanding-the-fft/
# https://github.com/peterhinch/micropython-fft/blob/master/algorithms.py

import math, cmath

# Naive, recursive version of the FFT
# see https://forum.micropython.org/viewtopic.php?f=2&t=208&hilit=fft
def fft_slow(x):
    N = len(x)
    if N <= 1:
        return x
    even = fft_slow(x[0::2])
    odd =  fft_slow(x[1::2])
    T = [cmath.exp(-2j*cmath.pi*k/N)*odd[k] for k in range(N//2)]
    return [even[k] + T[k] for k in range(N//2)] + [even[k] - T[k] for k in range(N//2)]

# Faster implementation
# see https://github.com/peterhinch/micropython-fft/blob/master/algorithms.py
def fft(x, forward=True, scale=False, real=True):

    res = list(x)
    n = len(res)

    # Calculate the number of points: n = 2**m
    log2_n = math.log2(n)
    assert log2_n % 1 == 0, "Lenght of sample not a power of 2!"
    m = int(log2_n)

    # Do the bit reversal
    i2 = n >> 1  # i2 = n / 2
    j = 0
    for i in range(n-1):
        if i<j:
            res[i], res[j] = res[j], res[i]
        k = i2
        while (k <= j):
            j -= k
            k >>= 1
        j+=k

    # Compute the FFT
    c = 0j-1
    l2 = 1
    for l in range(m):
        l1 = l2
        l2 <<= 1
        u = 0j+1
        for j in range(l1):
            for i in range(j, n, l2):
                i1 = i+l1
                t1 = u * res[i1]
                res[i1] = res[i] - t1
                res[i] += t1
            u *= c
        ci = math.sqrt((1.0 - c.real) / 2.0) # Generate complex roots of unity

        if forward:
            ci = -ci                         # for forward transform

        cr = math.sqrt((1.0 + c.real) / 2.0) # phi = -pi/2 -pi/4 -pi/8...
        c = cr + ci*1j                       #complex(cr,ci)

    # Scaling for forward transform
    if (scale and forward):
        for i in range(n):
            res[i] = res[i] / math.sqrt(n)

    if real:
        return res[:n//2 + 1]
    return res

# Computes the discrete FFT sample frequencies
# see https://github.com/numpy/numpy/blob/v1.14.0/numpy/fft/helper.py#L130-L175
def fft_freq_real(w, d=1.0):
    # w : window length
    # d : sample spacing

    val = 1.0 / (w * d)
    N   = w // 2 + 1
    res = range(0,N)
    return [r * val for r in res]

#### Tests (just making sure everything work as expected
if __name__ == '__main__':

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

    # number of samples
    N = 1024
    # sample spacing
    T = 1.0 / 800.0
    # creating the dummy dataset
    x = np.linspace(0.0, N*T, N)
    y = list(np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x))

    # fft and computing the frequencies
    out = fft(y)
    out_f = fft_freq_real(N, T)

    # some plots
    _, ax = plt.subplots(2)
    ax[0].plot(x, y)
    ax[1].plot(out_f, [abs(o) for o in out])
    plt.show()

    # Testing the result against numpy's implementation and a slower implementation for complex inputs (general case)
    x = list(np.random.random(N))
    print("Test 1: " + str(np.allclose(fft(x, real=False), np.fft.fft(x))))
    print("Test 2: " + str(np.allclose(np.fft.fft(x), fft_slow(x))))
    print("Test 3: " + str(np.allclose(fft(x, real=False), fft_slow(x))))

    # Testing real inputs
    print("Test 4: " + str( np.allclose( fft(x), np.fft.rfft(x))))

    # Testing frequencies real
    print("Test 5: " + str(np.allclose(fft_freq_real(60, 1/60), np.fft.rfftfreq(60,1/60))))

    # Timing
    print("Timing fft (real): " + str(timeit.timeit('fft(x)', globals=globals(), number=100)))
    print("Timing fft_slow: " + str(timeit.timeit('fft_slow(x)', globals=globals(), number=100)))
    print("Timing np.fft.rfft: " + str(timeit.timeit('np.fft.rfft(x)', globals=globals(), number=100)))

    # Another application: finding the base frequency inside noise
    # see http://nickdnickd.com/blog/2018/01/16/Let-There-Be-Sound
    two_pi = 2 * np.pi
    phase = np.pi/2
    frequency = 440    # "A" note in Hz
    duration_s = 0.03125 / 2
    sample_rate = 2**15  # Hz or samples/second

    def createCosineAudioSignal(freq, duration_s, sample_rate):
        two_pi = 2 * np.pi
        x_samples = range(int(duration_s*sample_rate))
        x_time = [(float(x_sample) / float(sample_rate)) for x_sample in x_samples]
        cosine_signal = np.cos([two_pi * frequency * time_el + phase for time_el in x_time])
        return (cosine_signal, x_samples, x_time)

    def addAwgNoiseToSignal(input_discr_signal, snr_dB):
        """Calculates signal power and generates additive white gaussian noise to match SNR param"""
        sigp_dB = 10 * (np.log10(np.linalg.norm(input_discr_signal,2))**2) /\
                             len(input_discr_signal)
        noisep_dB = sigp_dB - snr_dB
        noisep = 10**(noisep_dB/10)
        noise = np.sqrt(noisep)*np.random.randn(len(input_discr_signal))
        return [sig + noise for (sig,noise) in zip(input_discr_signal, noise)]

    # Base signal
    x_samples = range(int(duration_s*sample_rate))
    x_time = [(float(x_sample) / float(sample_rate)) for x_sample in x_samples]
    (y_0, x_samples, x_time) = createCosineAudioSignal(frequency, duration_s, sample_rate)

    # Construct a new signal based off y_0 but with added noise to match the snr
    y_1 = addAwgNoiseToSignal(y_0, -15)

    # FFT
    F_y_1 = fft(y_1)
    freq  = fft_freq_real(len(x_samples), 1 / sample_rate)

    # Plots
    _, ax2 = plt.subplots(3)
    ax2[0].plot(x_time, y_0)
    ax2[1].plot(x_time, y_1)
    ax2[2].plot(freq, [abs(y) for y in F_y_1] )
    ax2[2].set_xlim(xmax=2*frequency, xmin = 0)
    plt.show()


"""
Example of Newton-Raphson method for finding roots

MIT License

Copyright (c) 2021 Luiz Gustavo da Rocha Charamba

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

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

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

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

def f(x):
  return x**6/6 - 3*x**4 - 2*x**3/3 + 27*x**2/2 + 18*x - 30

def d_f(x):
  return x**5 - 12*x**3 -2*x**2 + 27*x + 18

def newton_raphson(x):
    return x - f(x) / d_f(x)

x = -4.5

d = {"x" : [x], "f(x)": [f(x)]}
nr_X = []
nr_Y = []
iterations = 1000
for i in range(0, iterations):
  x = newton_raphson(x)
  d["x"].append(x)
  nr_X.append(x)
  d["f(x)"].append(f(x))
  nr_Y.append(f(x))

print("Iterations: ", len(nr_X))
print(pd.DataFrame(d, columns=['x', 'f(x)']))

X = []
Y = []
left_limit = -5
right_limit = 5
step_sample = 0.01

for x in np.arange(left_limit, right_limit, step_sample):
    y = f(x)
    X.append(x)
    Y.append(y)

plt.plot(X, Y)
plt.plot(nr_X, nr_Y, color='r', marker='o')
plt.plot([left_limit, right_limit], [0,0], color="black")
plt.show()


#T# the remainder is calculated using the modulo % operator
b = 5; a = 3
num1 = b % a  # 2

#T# to calculate the remainder between lists or arrays element-wise, the numpy package is used
import numpy as np

#T# the remainder function of the numpy package calculates the remainder of the division of the first array by the second, element-wise, it supports array broadcasting
arr1 = np.array([[12, 13, 9, 11], [8, 8, 7, 12]])
arr2 = np.array([7, 7, 5, 6])
arr3 = np.remainder(arr1, arr2) # array([[5, 6, 4, 5], [1, 1, 2, 0]])

#T# the remainder % operator can be used to calculate the remainder from a sequence of numpy arrays
arr1 = np.array([[12, 13, 9, 11], [8, 8, 7, 12]])
arr2 = np.array([7, 7, 5, 6])
arr3 = np.array([3, 3, 3, 3])
arr4 = arr1 % arr2 % arr3 # array([[2, 0, 1, 2], [1, 1, 2, 0]])


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

# 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]


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())