# Question 1 Lab Assignment 02
# @AB Satyaprakash, 180123062

# imports
from math import exp, sqrt
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

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

def getOptionPrice(S0, K, T, r, sig, M, p, u, d, dt):
    callList = [0]*(M+1)
    putList = [0]*(M+1)

    for i in range(M+1):
        callList[i] = max(S0*(u**i)*(d**(M-i)) - K, 0)
        putList[i] = max(0, K - S0*(u**i)*(d**(M-i)))

    for i in range(M):
        for j in range(M-i):
            callList[j] = ((1-p)*callList[j] + p*callList[j+1])*exp(-r*dt)
            putList[j] = ((1-p)*putList[j] + p*putList[j+1])*exp(-r*dt)
    return callList[0], putList[0]

# --------------------------------------------------------------------------------------

# Find the Initial prices of European Put and Call Options
# Given Initial Values
S0, K, T, M, r, sig = 100, 100, 1, 100, 0.08, 0.2

dt = T/M

u1 = exp(sig*sqrt(dt))
d1 = exp(-sig*sqrt(dt))
p1 = (exp(r*dt)-d1)/(u1-d1)

u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
p2 = (exp(r*dt)-d2)/(u2-d2)

callp1, putp1 = getOptionPrice(S0, K, T, r, sig, M, p1, u1, d1, dt)
callp2, putp2 = getOptionPrice(S0, K, T, r, sig, M, p2, u2, d2, dt)

print('The Call price for set 1 is: {}'.format(callp1))
print('The Put price for set 1 is: {}'.format(putp1))
print('The Call price for set 2 is: {}'.format(callp2))
print('The Put price for set 2 is: {}'.format(putp2))

# -------------------------------------------------------------------------------------------

# Draw 2-D Plots
# (a) S0 from 50 - 150 in steps of 5
S0 = np.arange(50, 155, 5)
callp1, putp1, callp2, putp2 = np.zeros(S0.shape[0]), np.zeros(
    S0.shape[0]), np.zeros(S0.shape[0]), np.zeros(S0.shape[0])

for i in range(S0.shape[0]):
    K, T, M, r, sig = 100, 1, 100, 0.08, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0[i], K, T, r, sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0[i], K, T, r, sig, M, p2, u2, d2, dt)

plt.plot(S0, callp1, color='red')
plt.title('Plot of Call option at t=0 vs S(0) (50-150) for Set 1 ')
plt.xlabel('Stock Price at t = 0')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(S0, putp1, color='green')
plt.title('Plot of Put option at t=0 vs S(0) (50-150) for Set 1 ')
plt.xlabel('Stock Price at t = 0')
plt.ylabel('Price of Put option')
plt.show()

plt.plot(S0, callp2, color='red')
plt.title('Plot of Call option at t=0 vs S(0) (50-150) for Set 2 ')
plt.xlabel('Stock Price at t = 0')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(S0, putp2, color='green')
plt.title('Plot of Put option at t=0 vs S(0) (50-150) for Set 2 ')
plt.xlabel('Stock Price at t = 0')
plt.ylabel('Price of Put option')
plt.show()

# (b) K from 50 - 150 in steps of 5
K = np.arange(50, 155, 5)
callp1, putp1, callp2, putp2 = np.zeros(K.shape[0]), np.zeros(
    K.shape[0]), np.zeros(K.shape[0]), np.zeros(K.shape[0])

for i in range(K.shape[0]):
    S0, T, M, r, sig = 100, 1, 100, 0.08, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K[i], T, r, sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K[i], T, r, sig, M, p2, u2, d2, dt)

plt.plot(K, callp1, color='red')
plt.title('Plot of Call option at t=0 vs K (50-150) for Set 1 ')
plt.xlabel('Strike Price (K)')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(K, putp1, color='green')
plt.title('Plot of Put option at t=0 vs K (50-150) for Set 1 ')
plt.xlabel('Strike Price (K)')
plt.ylabel('Price of Put option')
plt.show()

plt.plot(K, callp2, color='red')
plt.title('Plot of Call option at t=0 vs K (50-150) for Set 2 ')
plt.xlabel('Strike Price (K)')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(K, putp2, color='green')
plt.title('Plot of Put option at t=0 vs K (50-150) for Set 2 ')
plt.xlabel('Strike Price (K)')
plt.ylabel('Price of Put option')
plt.show()

# (c) r from 0 to 0.2 in steps of 0.01
r = np.arange(0, 0.21, 0.01)
callp1, putp1, callp2, putp2 = np.zeros(r.shape[0]), np.zeros(
    r.shape[0]), np.zeros(r.shape[0]), np.zeros(r.shape[0])

for i in range(r.shape[0]):
    S0, K, T, M, sig = 100, 100, 1, 100, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r[i]*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    p2 = (exp(r[i]*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K, T, r[i], sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K, T, r[i], sig, M, p2, u2, d2, dt)

plt.plot(r, callp1, color='red')
plt.title('Plot of Call option at t=0 vs r (0-0.2) for Set 1 ')
plt.xlabel('Interest Rate (r)')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(r, putp1, color='green')
plt.title('Plot of Put option at t=0 vs r (0-0.2) for Set 1 ')
plt.xlabel('Interest Rate (r)')
plt.ylabel('Price of Put option')
plt.show()

plt.plot(r, callp2, color='red')
plt.title('Plot of Call option at t=0 vs r (0-0.2) for Set 2 ')
plt.xlabel('Interest Rate (r)')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(r, putp2, color='green')
plt.title('Plot of Put option at t=0 vs r (0-0.2) for Set 2 ')
plt.xlabel('Interest Rate (r)')
plt.ylabel('Price of Put option')
plt.show()

# (d) sig from 0 to 0.3 in steps of 0.01
sig = np.arange(0.01, 0.31, 0.01)  # avoid getting sigma = 0 exactly as it'd lead to div by 0
callp1, putp1, callp2, putp2 = np.zeros(sig.shape[0]), np.zeros(
    sig.shape[0]), np.zeros(sig.shape[0]), np.zeros(sig.shape[0])

for i in range(sig.shape[0]):
    S0, K, T, M, r = 100, 100, 1, 100, 0.08
    dt = T/M

    u1 = exp(sig[i]*sqrt(dt))
    d1 = exp(-sig[i]*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    d2 = exp(-sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K, T, r, sig[i], M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K, T, r, sig[i], M, p2, u2, d2, dt)

plt.plot(sig, callp1, color='red')
plt.title('Plot of Call option at t=0 vs sig (0-0.3) for Set 1 ')
plt.xlabel('Value of Sigma')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(sig, putp1, color='green')
plt.title('Plot of Put option at t=0 vs sig (0-0.3) for Set 1 ')
plt.xlabel('Value of Sigma')
plt.ylabel('Price of Put option')
plt.show()

plt.plot(sig, callp2, color='red')
plt.title('Plot of Call option at t=0 vs sig (0-0.3) for Set 2 ')
plt.xlabel('Value of Sigma')
plt.ylabel('Price of Call option')
plt.show()

plt.plot(sig, putp2, color='green')
plt.title('Plot of Put option at t=0 vs sig (0-0.3) for Set 2 ')
plt.xlabel('Value of Sigma')
plt.ylabel('Price of Put option')
plt.show()

# (e) M from 50-150 in steps of 1, and K for 3 values 95, 100 and 105
K = np.arange(95, 110, 5)
M = np.arange(50, 151, 1)

for j in range(K.shape[0]):
    callp1, putp1, callp2, putp2 = np.zeros(M.shape[0]), np.zeros(
        M.shape[0]), np.zeros(M.shape[0]), np.zeros(M.shape[0])

    for i in range(M.shape[0]):
        S0, T, r, sig = 100, 1, 0.08, 0.2
        dt = T/M[i]

        u1 = exp(sig*sqrt(dt))
        d1 = exp(-sig*sqrt(dt))
        p1 = (exp(r*dt)-d1)/(u1-d1)

        u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
        d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
        p2 = (exp(r*dt)-d2)/(u2-d2)

        callp1[i], putp1[i] = getOptionPrice(S0, K[j], T, r, sig, M[i], p1, u1, d1, dt)
        callp2[i], putp2[i] = getOptionPrice(S0, K[j], T, r, sig, M[i], p2, u2, d2, dt)

    plt.plot(M, callp1, color='red')
    plt.title('Plot of Call option at t=0 vs M (50-150) for Set 1 and K = {} '.format(K[j]))
    plt.xlabel('No. of Steps (M)')
    plt.ylabel('Price of Call option')
    plt.show()

    plt.plot(M, putp1, color='green')
    plt.title('Plot of Put option at t=0 vs M (50-150) for Set 1 and K = {} '.format(K[j]))
    plt.xlabel('No. of Steps (M)')
    plt.ylabel('Price of Put option')
    plt.show()

    plt.plot(M, callp2, color='red')
    plt.title('Plot of Call option at t=0 vs M (50-150) for Set 2 and K = {} '.format(K[j]))
    plt.xlabel('No. of Steps (M)')
    plt.ylabel('Price of Call option')
    plt.show()

    plt.plot(M, putp2, color='green')
    plt.title('Plot of Put option at t=0 vs M (50-150) for Set 2 and K = {} '.format(K[j]))
    plt.xlabel('No. of Steps (M)')
    plt.ylabel('Price of Put option')
    plt.show()

# -------------------------------------------------------------------------------------------

# Draw 3-D Plots
# We'll have C(5,2) = 10 cases taking 2 parameters at a time and then plotting 4 graphs for each case.

# (a) S0 and K taken together
# Generate 600 uniform discrete random numbers in range 1 - 300
S0 = np.random.randint(1, 300+1, 600)
K = np.random.randint(1, 300+1, 600)

callp1, putp1, callp2, putp2 = np.zeros(S0.shape[0]), np.zeros(
    S0.shape[0]), np.zeros(S0.shape[0]), np.zeros(S0.shape[0])

for i in range(S0.shape[0]):
    T, M, r, sig = 1, 100, 0.08, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0[i], K[i], T, r, sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0[i], K[i], T, r, sig, M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, K, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and K (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Strike Price (K)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, K, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and K (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Strike Price (K)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, K, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and K (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Strike Price (K)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, K, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and K (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Strike Price (K)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (b) S0 and r taken together
# Generate 600 uniform discrete random numbers in range 1 - 300 and in range 0 to 0.2

S0 = np.random.randint(1, 300+1, 600)
r = np.random.uniform(0, 0.2, 600)

callp1, putp1, callp2, putp2 = np.zeros(S0.shape[0]), np.zeros(
    S0.shape[0]), np.zeros(S0.shape[0]), np.zeros(S0.shape[0])

for i in range(S0.shape[0]):
    K, T, M, sig = 100, 1, 100, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r[i]*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    p2 = (exp(r[i]*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0[i], K, T, r[i], sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0[i], K, T, r[i], sig, M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, r, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and r (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, r, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and r (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, r, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and r (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, r, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and r (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (c) S0 and sigma taken together
# Generate 600 uniform discrete random numbers in range 1 - 300 and in range 0 to 0.2

S0 = np.random.randint(1, 300+1, 600)
sig = np.random.uniform(0.1, 0.3, 600)

callp1, putp1, callp2, putp2 = np.zeros(S0.shape[0]), np.zeros(
    S0.shape[0]), np.zeros(S0.shape[0]), np.zeros(S0.shape[0])

for i in range(S0.shape[0]):
    K, T, M, r = 100, 1, 100, 0.08
    dt = T/M

    u1 = exp(sig[i]*sqrt(dt))
    d1 = exp(-sig[i]*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    d2 = exp(-sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0[i], K, T, r, sig[i], M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0[i], K, T, r, sig[i], M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, sig, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and sig (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, sig, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and sig (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, sig, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and sig (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, sig, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and sig (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (d) S0 and M taken together
# Generate 600 uniform discrete random numbers in range 1 - 300
S0 = np.random.randint(1, 300+1, 600)
M = np.random.randint(1, 300+1, 600)

callp1, putp1, callp2, putp2 = np.zeros(S0.shape[0]), np.zeros(
    S0.shape[0]), np.zeros(S0.shape[0]), np.zeros(S0.shape[0])

for i in range(S0.shape[0]):
    T, K, r, sig = 1, 100, 0.08, 0.2
    dt = T/M[i]

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0[i], K, T, r, sig, M[i], p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0[i], K, T, r, sig, M[i], p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, M, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and M (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, M, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and M (Set 1)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, M, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and M (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(S0, M, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with S(0) and M (Set 2)')
ax.set_xlabel('Stock Price at t=0 - S(0)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (e) K and r taken together
# Generate 600 uniform discrete random numbers in range 1 - 300 and in range 0 to 0.2

K = np.random.randint(1, 300+1, 600)
r = np.random.uniform(0, 0.2, 600)

callp1, putp1, callp2, putp2 = np.zeros(K.shape[0]), np.zeros(
    K.shape[0]), np.zeros(K.shape[0]), np.zeros(K.shape[0])

for i in range(K.shape[0]):
    S0, T, M, sig = 100, 1, 100, 0.2
    dt = T/M

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r[i]*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    p2 = (exp(r[i]*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K[i], T, r[i], sig, M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K[i], T, r[i], sig, M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, r, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and r (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, r, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and r (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, r, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and r (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, r, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and r (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Interest Rate (r)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (f) K and sig taken together
# Generate 600 uniform discrete random numbers in range 1 - 300 and in range 0 to 0.3

K = np.random.randint(1, 300+1, 600)
sig = np.random.uniform(0.1, 0.3, 600)

callp1, putp1, callp2, putp2 = np.zeros(K.shape[0]), np.zeros(
    K.shape[0]), np.zeros(K.shape[0]), np.zeros(K.shape[0])

for i in range(K.shape[0]):
    S0, T, M, r = 100, 1, 100, 0.08
    dt = T/M

    u1 = exp(sig[i]*sqrt(dt))
    d1 = exp(-sig[i]*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    d2 = exp(-sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K[i], T, r, sig[i], M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K[i], T, r, sig[i], M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, sig, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and sig (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, sig, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and sig (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, sig, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and sig (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, sig, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and sig (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (g) K and M taken together
# Generate 600 uniform discrete random numbers in range 1 - 300
K = np.random.randint(1, 300+1, 600)
M = np.random.randint(1, 300+1, 600)

callp1, putp1, callp2, putp2 = np.zeros(K.shape[0]), np.zeros(
    K.shape[0]), np.zeros(K.shape[0]), np.zeros(K.shape[0])

for i in range(K.shape[0]):
    T, S0, r, sig = 1, 100, 0.08, 0.2
    dt = T/M[i]

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r-sig*sig/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K[i], T, r, sig, M[i], p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K[i], T, r, sig, M[i], p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, M, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and M (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, M, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and M (Set 1)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, M, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with K and M (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(K, M, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with K and M (Set 2)')
ax.set_xlabel('Strike Price (K)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (h) r and sig taken together
# Generate 600 uniform discrete random numbers in range 0 to 0.2 and in range 0 to 0.3

r = np.random.uniform(0, 0.2, 600)
sig = np.random.uniform(0.1, 0.3, 600)

callp1, putp1, callp2, putp2 = np.zeros(r.shape[0]), np.zeros(
    r.shape[0]), np.zeros(r.shape[0]), np.zeros(r.shape[0])

for i in range(r.shape[0]):
    S0, K, T, M = 100, 100, 1, 100
    dt = T/M

    u1 = exp(sig[i]*sqrt(dt))
    d1 = exp(-sig[i]*sqrt(dt))
    p1 = (exp(r[i]*dt)-d1)/(u1-d1)

    u2 = exp(sig[i]*sqrt(dt)+(r[i]-sig[i]*sig[i]/2)*dt)
    d2 = exp(-sig[i]*sqrt(dt)+(r[i]-sig[i]*sig[i]/2)*dt)
    p2 = (exp(r[i]*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K, T, r[i], sig[i], M, p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K, T, r[i], sig[i], M, p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, sig, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with r and sig (Set 1)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, sig, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with r and sig (Set 1)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, sig, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with r and sig (Set 2)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, sig, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with r and sig (Set 2)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('Value of Sigma')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (i) r and M taken together
# Generate 600 uniform discrete random numbers in range 0 to 0.2 and in range 1 to 300

r = np.random.uniform(0, 0.2, 600)
M = np.random.randint(1, 301, 600)

callp1, putp1, callp2, putp2 = np.zeros(r.shape[0]), np.zeros(
    r.shape[0]), np.zeros(r.shape[0]), np.zeros(r.shape[0])

for i in range(r.shape[0]):
    S0, K, T, sig = 100, 100, 1, 0.2
    dt = T/M[i]

    u1 = exp(sig*sqrt(dt))
    d1 = exp(-sig*sqrt(dt))
    p1 = (exp(r[i]*dt)-d1)/(u1-d1)

    u2 = exp(sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    d2 = exp(-sig*sqrt(dt)+(r[i]-sig*sig/2)*dt)
    p2 = (exp(r[i]*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K, T, r[i], sig, M[i], p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K, T, r[i], sig, M[i], p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, M, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with r and M (Set 1)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, M, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with r and M (Set 1)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, M, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with r and M (Set 2)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(r, M, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with r and M (Set 2)')
ax.set_xlabel('Interest Rate (r)')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# (j) sig and M taken together
# Generate 600 uniform discrete random numbers in range 0 to 0.3 and in range 1 to 300

sig = np.random.uniform(0.1, 0.3, 600)
M = np.random.randint(1, 301, 600)

callp1, putp1, callp2, putp2 = np.zeros(sig.shape[0]), np.zeros(
    sig.shape[0]), np.zeros(sig.shape[0]), np.zeros(sig.shape[0])

for i in range(sig.shape[0]):
    S0, K, T, r = 100, 100, 1, 0.08
    dt = T/M[i]

    u1 = exp(sig[i]*sqrt(dt))
    d1 = exp(-sig[i]*sqrt(dt))
    p1 = (exp(r*dt)-d1)/(u1-d1)

    u2 = exp(sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    d2 = exp(-sig[i]*sqrt(dt)+(r-sig[i]*sig[i]/2)*dt)
    p2 = (exp(r*dt)-d2)/(u2-d2)

    callp1[i], putp1[i] = getOptionPrice(S0, K, T, r, sig[i], M[i], p1, u1, d1, dt)
    callp2[i], putp2[i] = getOptionPrice(S0, K, T, r, sig[i], M[i], p2, u2, d2, dt)

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(sig, M, callp1, c='r')
ax.set_title('Scatter Plot of Initial Option Price with sig and M (Set 1)')
ax.set_xlabel('Value of Sigma')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(sig, M, putp1, c='g')
ax.set_title('Scatter Plot of Initial Option Price with sig and M (Set 1)')
ax.set_xlabel('Value of Sigma')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(sig, M, callp2, c='r')
ax.set_title('Scatter Plot of Initial Option Price with sig and M (Set 2)')
ax.set_xlabel('Value of Sigma')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Call Option')
ax.invert_yaxis()
plt.show()

fig = plt.figure(figsize=(8, 8))
ax = Axes3D(fig)
ax.scatter(sig, M, putp2, c='g')
ax.set_title('Scatter Plot of Initial Option Price with sig and M (Set 2)')
ax.set_xlabel('Value of Sigma')
ax.set_ylabel('No. of Steps (M)')
ax.set_zlabel('Price of European Put Option')
ax.invert_yaxis()
plt.show()

# -------------------------------------------------------------------------------------------
# Question 1 complete

Semester 6/MA 374 (Financial Engg. Lab)/Lab 2/180123062_AB Satyaprakash_q1.py


from sympy import *
import math

def bisection(f, a, b, eps, eta=1e-16, verbose=False):
    """二分法求根

    对方程 f(x) = 0 在区间 [a, b]，使用二分法求根。
    做 ceil((log(((b - a) / eps), 2) - 1) 次迭代，使结果满足精度 eps。

    实现参考: 数值分析[谷根代，杨晓忠 等著]2011年版.P18.算法1

    Args:
        f: function, 一元函数，表示要求根的方程: f(x) = 0.
        a, b: float, 有根区间 [a, b] 的端点.
        eps: float, 给定精度.

        eta: float, 当 abs(f(x)) <= eta 时停止计算, default 1e-16.
        verbose: bool, 打印出二分法计算的表格, default False.

    Returns:
        (x_final, N)

        x_final: float, 二分法求得的近似根
        N: int, 迭代次数

    Raises:
        ValueError: 给定的 f(a) * f(b) < 0 时无法求解，抛出异常
    """

    if f(a) * f(b) > 0:
        raise ValueError("rootless interval: f(a) * f(b) < 0")

    N = math.ceil((log(((b - a) / eps), 2) - 1).evalf(5))

    if verbose:
        print(f'N = {N}\n')

    x = (a + b) / 2

    if verbose:
        print(f'n \t (a, b) \t f(x_n)')
        print('-'*35)

    for n in range(N+1):
        if abs(f(x)) <= eta:
            break

        if verbose:
            print(f'{n} \t ({a}, {b}) \t f({x})={f(x)}')

        if f(x) * f(a) < 0:
            b = x
        else:
            a = x
        x = (a + b) / 2
        n += 1

    if verbose:
        print(f'{n} \t ({a}, {b}) \t -')

    x_final = (a + b) / 2
    if verbose:
        print(f'\nresult: x = ({a}+{b})/2 = {x_final}')

    return x_final, N+1


from time import time
import matplotlib
#this will prevent the figure from popping up
matplotlib.use('Agg')
from matplotlib import pyplot as plt
import numpy as np
import pycuda.autoinit
from pycuda import gpuarray
from pycuda.elementwise import ElementwiseKernel

# first string: the input, pycuda::complex<float> is a special type from pycuda
# variable "i" is reserved for the thread number
# z(0,0) is the starting point
mandel_ker = ElementwiseKernel(
"pycuda::complex<float> *lattice, float *mandelbrot_graph, int max_iters, float upper_bound",
"""
mandelbrot_graph[i] = 1;

pycuda::complex<float> c = lattice[i];
pycuda::complex<float> z(0,0);

for (int j = 0; j < max_iters; j++)
    {

     z = z*z + c;

     if(abs(z) > upper_bound)
         {
          mandelbrot_graph[i] = 0;
          break;
         }

    }

""",
"mandel_ker")

def gpu_mandelbrot(width, height, real_low, real_high, imag_low, imag_high, max_iters, upper_bound):

    # we set up our complex lattice as such, matrix not supported by pycuda
    real_vals = np.matrix(np.linspace(real_low, real_high, width), dtype=np.complex64)
    imag_vals = np.matrix(np.linspace(imag_high, imag_low, height), dtype=np.complex64) * 1j

    # put them on top of each other
    mandelbrot_lattice = np.array(real_vals + imag_vals.transpose(), dtype=np.complex64)

    # alternative:
    # real = np.linspace(real_low, real_high, width)
    # imag = np.linspace(real_low, real_high, width) * 1j
    # mesh1, mesh2 = np.meshgrid(real, imag)
    # lattice = mesh1 + mesh2

    # copy complex lattice to the GPU
    mandelbrot_lattice_gpu = gpuarray.to_gpu(mandelbrot_lattice)

    # allocate an empty array on the GPU
    mandelbrot_graph_gpu = gpuarray.empty(shape=mandelbrot_lattice.shape, dtype=np.float32)

    mandel_ker(mandelbrot_lattice_gpu, mandelbrot_graph_gpu, np.int32(max_iters), np.float32(upper_bound))

    mandelbrot_graph = mandelbrot_graph_gpu.get()

    return mandelbrot_graph

if __name__ == '__main__':

    t1 = time()
    mandel = gpu_mandelbrot(512,512,-2,2,-2,2,256, 2)
    t2 = time()

    mandel_time = t2 - t1

    t1 = time()
    fig = plt.figure(1)
    plt.imshow(mandel, extent=(-2, 2, -2, 2))
    plt.savefig('mandelbrot.png', dpi=fig.dpi)
    t2 = time()

    dump_time = t2 - t1

    print('It took {} seconds to calculate the Mandelbrot graph.'.format(mandel_time))
    print('It took {} seconds to dump the image.'.format(dump_time))


# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
import seaborn as sns

import numpy as np

import tensorflow as tf
tf.enable_eager_execution()

import tensorflow_probability as tfp
from tensorflow_probability import edward2 as ed
tfd = tfp.distributions

# ===========================================================================
# Constant
# ===========================================================================
a = 8
b = 0.5
mu = 0
n_samples = 100000

# ===========================================================================
# Following the generative procedure
# ===========================================================================
# Step 1: generate the precision Beta
beta_dist = tfd.Gamma(concentration=a, rate=b)
beta = beta_dist.sample(n_samples)
# the prior probability
p_beta_given_a_and_b = beta_dist.prob(beta)

# Step 2: generate the data point
# scale is standard deviation
x_dist = tfd.Normal(loc=mu, scale=tf.sqrt(1 / beta))
x = x_dist.sample()
# the likelihood
p_x_given_mu_and_beta = x_dist.prob(x)

# ====== plotting the prior ====== #
plt.figure()
sns.distplot(beta.numpy(), bins=120, kde=True)
plt.title(r"Prior distribution: $p(\beta|a=%g, b=%g)$" % (a, b))

# ====== plotting the likelihood ====== #
plt.figure()
sns.distplot(x.numpy(), bins=120, kde=True)
plt.title(r"Likelihood distribution: $p(X|\mu=%g, \sigma=\sqrt{\beta^{-1}})$" % mu)

# ====== plotting the posterior ====== #
# the posterior probability, this is only
# proportionally, not exactly because we omit
# the evidence p(X)
# If we want to calculate p(X), we need to marginalize out
# beta using sum rule:
# p(X) = p(X, beta_1) +  p(X, beta_2) + ... +  p(X, beta_∞)
# This is not easy
p_beta_given_x = p_x_given_mu_and_beta * p_beta_given_a_and_b
p_beta_given_x = p_beta_given_x / tf.reduce_sum(p_beta_given_x)
posterior_dist = tfd.Categorical(probs=p_beta_given_x)

beta = beta.numpy()
posterior = []
for i in range(n_samples // 2000):
  idx = posterior_dist.sample(2000).numpy()
  posterior.append(beta[idx])
posterior = np.concatenate(posterior)

plt.figure()
sns.distplot(posterior, bins=120, kde=True)
plt.title(r"Sampled posterior distribution: $p(\beta|X)$")

# ====== plotting the close form solution ====== #
a0 = a + n_samples / 2
b0 = b + n_samples / 2 * np.var(x.numpy())
posterior_dist = tfd.Gamma(concentration=a0, rate=b0)
posterior = posterior_dist.sample(n_samples)

plt.figure()
sns.distplot(posterior, bins=120, kde=True)
plt.title(
    r"Closed form solution: $p(\beta|X) \sim Gamma(a=%g, b=%g)$"
    % (a0, b0))

from odin import visual as V
V.plot_save('/tmp/tmp.pdf', dpi=200)


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

import numpy as np

def isPositiveDefinite(matrix, debug=False):
    """
    Taken from: https://stackoverflow.com/a/44287862
    """

    # '\u2713' = ✓ (unicode tick character)
    if debug:
        print('Analysing symmetry of the matrix: \n', matrix)
        print()

    # Check if it is symmetric
    if isSymmetric(matrix, debug):
        # Using cholesky to check the check if it's positive deffinite
        try:
            np.linalg.cholesky(matrix)
            print(u'[\u2713] Matrix is positive definite.')
            return True
        except np.linalg.LinAlgError:
            print('[X] Matrix is not positive definite.')
            return False
    else:
        return False

def isSymmetric(matrix, debug=False):

    # '\u2713' = ✓ (unicode tick character)
    if debug:
        print('Analysing symmetry of the matrix: \n', matrix)
        print()

    if np.allclose(matrix, matrix.T):
        print(u'[\u2713] Matrix is symmetric.')
        return True
    else:
        print('[X] Matrix is NOT symmetric.')
        return False

def diagDominant(matrix, debug=False):
    """Print if a matrix is diagonal dominant

    Taken from: https://stackoverflow.com/a/43074838
    """

     # '\u2713' = ✓ (unicode tick character)

    if debug:
        print('Analysing matrix: \n', matrix)
        print()

    D = np.diag(np.abs(matrix)) # Find diagonal coefficients

    S = np.sum(np.abs(matrix), axis=1) - D # Find row sum without diagonal

    if np.all(D > S):
        print(u'[\u2713] Matrix is strictly diagonally dominant.')
        return True
    else:
        print('[X] Matrix is NOT strictly diagonally dominant')
        return False

def useJacobiGaussSeidel(matrix, debug=False):
    """Given an nxn matrix, tells if Gauss-Seidel or Jacobi could be used.

    Lineal system of equations: Ax = b

    Arguments:
        matrix: Should be matrix A of the system.

    Returns:
        True if either Jacobi or Gauss-Seidel could be used
    """
    matrixNumDim = np.ndim(matrix)
    if (matrixNumDim != 2):
        raise RuntimeError("I'm not prepared for this. </3")

    matrixShape = np.shape(matrix)
    if (matrixShape[0] != matrixShape[1]):
        print('[X] Matrix is not squared')
        return False

    jacobi = diagDominant(matrix, debug)
    posDef = isPositiveDefinite(matrix, debug)

    if posDef:
        print(u'[\u2713] Gauss-Seidel can be used.')
        print('[X] Jacobi can not be used')
        return True

    elif jacobi:
        print(u'[\u2713] Gauss-Seidel can be used.')
        print(u'[\u2713] Jacobi can be used')
        return True

    else:
        print('[X] Gauss-Seidel can not be used.')
        print('[X] Jacobi can not be used')
        return False

if __name__ == "__main__":
    # Strictly diagonally dominant
    I = np.array([[-4, 2, 1], [1, 6, 2], [1, -2, 5]])
    print('Testing: \n', I,'\n')
    diagDominant(I)

    # Diagonally dominant (not strictly)
    I = np.array([[3, -2, 1], [1, -3, 2], [-1, 2, 4]])
    print('Testing: \n', I,'\n')
    diagDominant(I)

    # Not diagonally dominant
    I = np.array([[-2, 2, 1], [1, 3, 2], [1, -2, 0]])
    print('Testing: \n', I,'\n')
    diagDominant(I)

    # Positive definite
    I = np.eye(4)
    print('Testing: \n', I,'\n')
    isPositiveDefinite(I)

    I = np.array([[2, -1, 0], [-1, 2, -1], [0, -1, 2]])
    print('Testing: \n', I,'\n')
    isPositiveDefinite(I)

    # NOT positive definite
    I = np.array([[1, 2], [2, 1]])
    print('Testing: \n', I,'\n')
    isPositiveDefinite(I)

    print('\nJacobi/Gauss-Seidel ?')

    # Strictly diagonally dominant and not definite positive.
    I = np.array([[-4, 2, 1], [1, 6, 2], [1, -2, 5]])
    print('Testing: \n', I,'\n')
    useJacobiGaussSeidel(I)

    # Positive definite and diagonally dominant
    I = np.eye(4)
    print('Testing: \n', I,'\n')
    useJacobiGaussSeidel(I)


from sympy import Rational as R
from sympy import E, Symbol, Abs, cos, sin, pi
from programa import integral, transformar

x = Symbol("x")
y = Symbol("y")
u = Symbol("u")
v = Symbol("v")
θ = Symbol("θ")
r = Symbol("r")

# Ejemplo 1
v1 = (x < 1)
v2 = (y > 0)
v3 = (y < x)
funcion = E**(-(x**2))

print(integral(funcion, v1, v2, v3))

# Ejemplo 2
list_rels = [x-2*y <= 0, x-2*y >= -4, x+y >= 1, x+y <= 4]
func = 3*x*y

vars_reem = [u, v]
dicc_reem = {x: R(1, 3)*(2*u + v),
             y: R(1, 3)*(u - v)}

det, func_reem, list_rels_reem = transformar(func, list_rels,
                                             vars_reem, dicc_reem)
print(integral(Abs(det)*func_reem, *list_rels_reem))

# Ejemplo 3
list_rels = [x**2 + y**2 >= 1, x**2 + y**2 <= 5]
func = x**2 + y

vars_reem = [r, θ]
dicc_reem = {x: r*cos(θ),
             y: r*sin(θ)}

det, func_reem, list_rels_reem = transformar(func, list_rels,
                                             vars_reem, dicc_reem)
list_rels_reem += [r >= 0, θ >= 0, θ <= 2*pi]
print(integral(det*func_reem, *list_rels_reem))


# -*- coding: utf-8 -*-
"""
Implementation of 4th order Runge - Kutta method for the ODE solving like y'(x) = f(x)
The implemented equation is analogue to the Simpon's integration rule
@author: ssklykov
"""
# %% Import section
import SampleF
import numpy as np
import matplotlib.pyplot as plt
from EulerSolver import predcorrEuler, numberOfSteps, checkInputs

# %% Implementation of the algorithm
def rungeCutta4(f,h:float,y0:float,xStart:float,xFinish:float) -> tuple:
    """Simple single step (~h*4) ODE Runge-Cutta solver. f - input function (y'(x) = f(x)),\
        h - step size, y0 - initial value, xStart - first value for solving an input ODE, xFinish - finishing value for \
        calculations. Returning a tuple that composes of xMesh values and yValues - the solution for an ODE"""
    # Checking input parameters for validity
    if (checkInputs(f, h, xStart, xFinish)):
        return None
    # Defying exact number of steps between interval points [xStart,xFinish] or number of points for modelling and returning
    n = numberOfSteps(xStart, xFinish, h); nEven = int((xFinish-xStart) / h) + 1
    # Values initialization
    yValues = np.zeros(n,dtype='float'); xMesh = np.zeros(n,dtype='float') # init mesh values
    yValues[0] = float(y0); xMesh[0] = float(xStart)
    # Calculations
    if (n == nEven):
        for i in range(1,n):
            xMesh[i] = xMesh[i-1] + h
            yValues[i] = yValues[i-1] + (h*(f(xMesh[i-1]) + f(xMesh[i]) + 4*f(xMesh[i-1]+0.5*h))/6) # like Simpson's rule
    else:
        for i in range(1,n-1):
            xMesh[i] = xMesh[i-1] + h
            yValues[i] = yValues[i-1] + (h*(f(xMesh[i-1]) + f(xMesh[i]) + 4*f(xMesh[i-1]+0.5*h))/6)
        hLastStep = xFinish - xMesh[n-2]; xMesh[n-1] = xFinish
        yValues[n-1] = yValues[n-2] + (hLastStep*(f(xMesh[n-2]) + f(xMesh[n-1]) + 4*f(xMesh[n-2]+0.5*h))/6)
    return (xMesh,yValues)

# %% Testing parameters
h = 0.25; y0 = 0.0; xStart = 0.0; xFinish = 4.0; nDigits = 2
#%% Testing
f1 = SampleF.SampleFunctions(1,nDigits); y1 = f1.getValue # For referring to a function it can be called without () notation
(x,y) = predcorrEuler(y1,h,y0,xStart,xFinish)
(x2,y2) = rungeCutta4(y1,h,y0,xStart,xFinish)

# %% Theoretical values calculation
yTh = np.zeros(len(x),dtype='float')
for i in range(len(x)):
    yTh[i] = f1.getTheoreticalValue(x[i])
yTh2 = np.zeros(len(x2),dtype='float')
for i in range(len(x2)):
    yTh2[i] = f1.getTheoreticalValue(x2[i])
# %% Comparison results on grahps
# x.round(nDigits); y.round(nDigits)
# plt.figure(); plt.xlabel("x"); plt.ylabel("y(x) solution"); plt.plot(x,y,label='numerical solution',linewidth=3)
# plt.plot(x,yTh,label='theoretical function',linewidth=2); plt.legend(); plt.title("Euler solver with prediction / correction")

# %% 2nd Comparison results on graphs
x2.round(nDigits); y2.round(nDigits)
plt.figure(); plt.xlabel("x"); plt.ylabel("y(x) solution"); plt.plot(x2,y2,'bo',label='numerical solution',linewidth=4)
plt.plot(x2,yTh2,'r',label='theoretical function',linewidth=2); plt.legend(); plt.title("Runge-Cutta 4th order solver")


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

from numpy import linspace, logspace, diff, zeros
from numpy import cos, sin, exp, log, pi
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp

####################################
# Implementación genérica de Taylor 2
# f(t,x): primera derivada de x respecto al tiempo
# g(t,x): segunda derivada de x respecto al tiempo
# x0: condición inicial
# t0, tf: tiempo inicial y final
# h: paso de integración
####################################
def taylor2(f,g,x0,t0,tf,h):
    N = int((tf-t0)/h)           #número de puntos
    t = linspace(t0,tf,N+1)
    n = x0.shape[0]         #dimensión del problema
    x = zeros((n,N+1))
    x[:,0] = x0
    i = h*h/2.0
    for k in range(N):
        x[:,k+1] = x[:,k]+h*f(t[k],x[:,k])+i*g(t[k],x[:,k])

    return t,x

####################################
# Implementación genérica de Heun
# f(t,x): derivada de x respecto al tiempo
# x0: condición inicial
# t0, tf: tiempo inicial y final
# h: paso de integración
####################################
def heun(f,x0,t0,tf,h):
    N = int((tf-t0)/h)          #número de puntos
    t = linspace(t0,tf,N+1)
    n = x0.shape[0]             #dimensión del problema
    x = zeros((n,N+1))
    x[:,0] = x0
    for k in range(N):
        f1 = h*f(t[k],x[:,k])
        f2 = h*f(t[k]+h,x[:,k]+f1)
        x[:,k+1] = x[:,k]+(f1+f2)/2.0

    return t,x

####################################
# Implementación genérica de Cauchy
# f(t,x): derivada de x respecto al tiempo
# x0: condición inicial
# t0, tf: tiempo inicial y final
# h: paso de integración
####################################
def cauchy(f,x0,t0,tf,h):
    N = int((tf-t0)/h)          #número de puntos
    t = linspace(t0,tf,N+1)
    n = x0.shape[0]             #dimensión del problema
    x = zeros((n,N+1))
    x[:,0] = x0
    h2 = h/2.0
    for k in range(N):
        x[:,k+1] = x[:,k]+h*f(t[k]+h2,x[:,k]+h2*f(t[k],x[:,k]))

    return t,x

########################
# EJEMPLO
########################
R = 1e3	            #Valor de la resistencia
C = 1e-6	        #Valor de la capacidad
w = 2.0*pi*1000     #frecuencia angular de la señal de entrada
A = 1.0		        #amplitud de la señal de entrada
T = 5*2*pi/w	    #simulo cinco ciclos

####################################
# Solución
def xsol(t):
    x = -exp(-t/(R*C))+cos(w*t)+w*R*C*sin(w*t)
    x = (A/(1+(w*R*C)**2))*x
    return x

####################################
# Derivada primera de x
def dx(t,x):
    return ((A*cos(w*t)-x)/(R*C))

####################################
# Derivada segunda de x
def d2x(t,x):
    return ((-A*w*sin(w*t)-((A*cos(w*t)-x)/(R*C)))/(R*C))

####################################
# Plot ejemplo
def plotejemplo(h):
    x0 = zeros(1)
    t,xh = heun(dx,x0,0,T,h)
    t,xc = cauchy(dx,x0,0,T,h)
    t,xt = taylor2(dx,d2x,x0,0,T,h)
    x = xsol(t)
    fig, ax = plt.subplots()
    ax.plot(t, x, label='Solución')
    ax.plot(t, xh[0,:], label='Heun')
    ax.plot(t, xc[0,:], label='Cauchy')
    ax.plot(t, xt[0,:], label='Taylor 2')
    ax.legend()
    plt.title('Ejemplo')
    fig, ax = plt.subplots()
    ax.plot(t, xh[0,:]-x, label='Heun')
    ax.plot(t, xc[0,:]-x, label='Cauchy')
    ax.plot(t, xt[0,:]-x, label='Taylor 2')
    ax.legend()
    plt.title('Error')

####################################
# Errores ejemplo
def errorejemplo():
    n = 5
    N = logspace(1,5,n)
    h = T/N
    eh = zeros(n)
    ec = zeros(n)
    et = zeros(n)
    x0 = zeros(1)
    x = xsol(T)
    for k in range(n):
        t,xh = heun(dx,x0,0,T,h[k])
        t,xc = cauchy(dx,x0,0,T,h[k])
        t,xt = taylor2(dx,d2x,x0,0,T,h[k])
        eh[k] = abs(xh[0,-1]-x)
        ec[k] = abs(xc[0,-1]-x)
        et[k] = abs(xt[0,-1]-x)
    fig, ax = plt.subplots()
    ax.loglog(h, eh, label='Heun')
    ax.loglog(h, ec, label='Cauchy')
    ax.loglog(h, et, label='Taylor 2')
    # ax.loglog(h, (h/h[0])**2*eh[0], 'b--')
    # ax.loglog(h, (h/h[0])**2*ec[0], 'r--')
    # ax.loglog(h, (h/h[0])**2*et[0], 'g--')
    ax.legend()
    plt.xlabel('h')
    plt.ylabel('error')
    plt.title('Error')
    print(diff(log(eh))/diff(log(h)))
    print(diff(log(ec))/diff(log(h)))
    print(diff(log(et))/diff(log(h)))

####################################
# Estimación error ejemplo
def esterrorejemplo(h):
    x0 = zeros(1)
    x = xsol(T)
    t,xh1 = heun(dx,x0,0,T,h)
    t,xc1 = cauchy(dx,x0,0,T,h)
    t,xt1 = taylor2(dx,d2x,x0,0,T,h)
    t,xh2 = heun(dx,x0,0,T,h/2)
    t,xc2 = cauchy(dx,x0,0,T,h/2)
    t,xt2 = taylor2(dx,d2x,x0,0,T,h/2)

    eh = abs(xh2[0,-1]-x)
    ec = abs(xc2[0,-1]-x)
    et = abs(xt2[0,-1]-x)
    eeh= abs(xh1[0,-1]-xh2[0,-1])/3.0
    eec= abs(xc1[0,-1]-xc2[0,-1])/3.0
    eet= abs(xt1[0,-1]-xt2[0,-1])/3.0
    print(abs(eh-eeh)/eh*100)
    print(abs(ec-eec)/ec*100)
    print(abs(et-eet)/et*100)
    return eh,eeh,ec,eec,et,eet

####################################
# Comparación con RK45
def comp45(h):
    x0 = zeros(1)
    t,xh = heun(dx,x0,0,T,h)
    # s45 = solve_ivp(dx,[0,T],x0,method='RK45', t_eval=None,
    #                 rtol = 1e-6, atol = 1e-8)
    # s45 = solve_ivp(dx,[0,T],x0,method='RK45', t_eval=None,
    #                 rtol = 1e-13, atol = 1e-14)
    s45 = solve_ivp(dx,[0,T],x0,method='RK45', t_eval=t,
                    rtol = 1e-13, atol = 1e-14)
    x = xsol(t)
    x1 = xsol(s45.t)

    fig, ax = plt.subplots()
    ax.plot(t, x, label='Solución')
    ax.plot(t, xh[0,:], label='Heun')
    ax.plot(s45.t, s45.y[0,:], label='RK45')
    ax.legend()
    plt.title('Ejemplo')
    fig, ax = plt.subplots()
    ax.plot(t[:-1],diff(t), label='Heun')
    ax.plot(s45.t[:-1],diff(s45.t), label='RK45')
    ax.legend()
    plt.title('Paso de integración')

    fig, ax = plt.subplots()
    ax.semilogy(t,abs(xh[0,:]-x), label='Heun')
    ax.semilogy(s45.t,abs(s45.y[0,:]-x1), label='RK45')
    ax.legend()
    plt.title('Error')

    print(s45.t.shape)
    print(t.shape)

####################################
# Plot ejemplo
def plotejemplo_ruku4(h, fruku4):
    x0 = zeros(1)
    t, xrk4 = fruku4(dx,x0,0,T,h)

    t,xt = taylor2(dx,d2x,x0,0,T,h)

    print("Calculating solution...")
    x = xsol(t)

    print("Plotting solution and RuKu 4.")
    fig, ax = plt.subplots()
    ax.plot(t, x, label='Solución')
    print(f"t: {t}\nxrk4: {xrk4}\nxrk4[0,:]: {xrk4[0:1]}")
    ax.plot(t, xrk4[:], label='RuKu 4')
    ax.legend()
    plt.title('Ejemplo')

    print("Plotting error.")
    fig, ax = plt.subplots()
    ax.plot(t, xrk4[:]-x, label='RuKu 4')
    #plt.axis("off")
    #ax.legend()
    #plt.title('Error')

    print("Showing plots.")
    plt.show()

# plotejemplo(T/10000)
# plt.close('all')
# errorejemplo()
# esterrorejemplo(T/1e4)
#comp45(T/1000)


import numpy as np
import numpy.linalg as la
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.cm as cm
#from tqdm import tqdm
import sys
from matplotlib.animation import FuncAnimation

def gershgorin_bound(A):
    '''
        return geshgorin bound on spectral radius of A
    '''
    N,M = A.shape
    assert(N==M, 'matrix not square')
    specrad = 0
    for i in range(0, N):
        diag_elem = A[i,i]
        sum_non_diag = 0.0
        for j in range(0, M):
            if j != i:
                sum_non_diag += abs(A[i,j])
        cur_specrad = diag_elem + abs(sum_non_diag)
        if cur_specrad>= specrad:
            specrad = cur_specrad
    return specrad

def specrad(A):
    eigs = la.eigvals(A)
    return np.max(eigs)

def track_specrad(A, tstep, title):
    dat = specrad(A)
    if tstep==1:
        open("{0}.txt".format(title), 'w').close()
    f = open("{0}.txt".format(title), "a")
    f.write('{0}: {1}\n'.format(tstep, dat))
    f.close()
    return dat

def color_grads(steps):
    blue_max = np.array([0.2, 0.2, 0.99])
    red_max = np.array([0.99, 0.2, 0.2])
    green_max = np.array([0.2, 0.99, 0.2])
    blue_min = np.array([0.09, 0.09, 0.4])
    red_min = np.array([0.3, 0.09, 0.09])
    green_min = np.array([0.09, 0.3, 0.09])
    B = []
    R = []
    G = []
    for n in np.linspace(0.0, 1.0, steps):
        B += [blue_max*n+(1-n)*blue_min]
        G += [green_max*n+(1-n)*green_min]
        R += [red_max*n+(1-n)*red_min]

    return R, G, B


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

import numpy as np

class Normalizer:
    """
    MLP 相关的数据标准化器(归一化)

    标准化处理对于计算距离的机器学习方法是非常重要的，因为特征的尺度不同会导致计算出来的距离倾向于尺度大的特征，
    为保证距离对每一列特征都是公平的，必须将所有特征缩放到同一尺度范围内

    Author: xrh
    Date: 2021-07-10

    """

    @staticmethod
    def tow_norm_normalize(X):
        """
        对所有样本进行二范数归一化

        ref:
        https://blog.csdn.net/hqh131360239/article/details/79061535

        :param X: shape(N,m)
                  N - 样本的个数
                  m - 样本的维度
        :return:
        """

        X_norm_2 = np.linalg.norm(X, ord=2, axis=1, keepdims=True)  # 每一个样本的模(二范数) shape(N,1)

        # X_norm_2 = X_norm_2.reshape(-1,1)

        X = X / X_norm_2

        return X

    @staticmethod
    def Z_Score_normalize(X):
        """
        0均值归一化( Z-Score Normalization)

        对所有特征进行 0均值归一化, 将特征归一化为均值为0 方差为1

        :param X: shape(N,m)
                  N - 样本的个数
                  m - 样本的维度
        :return:
        """

        # N, m = np.shape(X)  # N 个样本, m 个特征

        mu = np.mean(X, axis=0)  # 每一个特征的均值 shape:(m,)

        s = np.std(X, axis=0)  # 每一个特征的标准差 shape:(m,)

        X = (X - mu) / s

        return X

    @staticmethod
    def min_max_normalize(Xarray):
        """
        对特征进行 min-max 标准化，将数据缩放到0-1之间

        :param Xarray:
        :return:
        """

        for f in range(Xarray.shape[1]):
            maxf = np.max(Xarray[:, f])
            minf = np.min(Xarray[:, f])
            for n in range(Xarray.shape[0]):
                Xarray[n][f] = (Xarray[n][f] - minf) / (maxf - minf)
        return Xarray


import numpy as np
import math

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

def dist_intersect(x, y):
    s = 0
    for q, v in zip(x, y):
        s = s + min(q, v)

    s = s / np.sum(x) + s / np.sum(y)
    s = s / 2

    return 1 - s

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

def dist_l2(x, y):
    s = 0
    for q, v in zip(x, y):
        s = s + pow(q - v, 2)

    return s

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

def dist_chi2(x, y):
    s = 0
    for q, v in zip(x, y):
        if q + v != 0:
            s = s + pow(q - v, 2) / (q + v)
        else:
            s = s + pow(q - v, 2)

    return s

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


import matplotlib.pyplot as plt
import numpy as np
import math
g = 9.8
m = 0.3
k = 0.1
def f1 (v) :
    return v
def f2x (vx,vy,m):
    return - (k * (vx*vx + vy*vy) * vx) / (m * math.sqrt(vx*vx + vy*vy))
def f2y (vx,vy,m):
    return - g - (k * (vx*vx + vy*vy) * vy) / (m * math.sqrt(vx*vx + vy*vy))
def runge_kutta (x,y,vx,vy,f1,f2x,f2y,filename):
    alpha = 1
    beta = 1/2
    h = 0.01
    x_list = []
    vx_list = []
    y_list = []
    vy_list = []
    x_list.append(x)
    vx_list.append(vx)
    y_list.append(y)
    vy_list.append(vy)
    for i in range (200):
        x_new = x + h * ( (1-beta) * f1(vx) + beta * f1(vx + alpha * h * f1(vx)))
        vx_new = vx + h * ( (1-beta) * f2x(vx,vy,m) + beta * f2x(vx+alpha*h*f2x(vx,vy,m),vy+alpha*h*f2y(vx,vy,m),m))
        y_new = y + h * ( (1-beta) * f1(vy) + beta * f1(vy + alpha * h * f1(vy)))
        vy_new = vy + h * ( (1-beta) * f2y(vx,vy,m) + beta * f2y(vx+alpha*h*f2x(vx,vy,m),vy+alpha*h*f2y(vx,vy,m),m))
        x_list.append(x_new)
        vx_list.append(vx_new)
        y_list.append(y_new)
        vy_list.append(vy_new)
        x = x_new
        vx = vx_new
        y = y_new
        vy = vy_new
        if y < 0 :
            break
    fig = plt.figure(figsize=(12, 8))
    plt.xlim(0,13.411)
    plt.ylim(0,5)
    plt.plot([6.767, 6.767], [0, 1.554], 'k-', lw=2)
    plt.plot(x_list, y_list)
    plt.savefig(filename)
if __name__ == '__main__' :
    runge_kutta (1,2,100,30,f1,f2x,f2y,"high_clear.png")
    runge_kutta (1,2,130,10,f1,f2x,f2y,"driven_clear.png")
    runge_kutta (3,3.5,80,-30,f1,f2x,f2y,"smash.png")


"""
相似度计算
"""
import math
import numpy as np
from numpy.linalg import norm

def cosineSim(a, b, dtype=np.ndarray):
    """
    余弦相似度。如果是集合,公式为：a与b的交集的长度/根号(a的长度*b的长度);
    如果是向量，公式为a与b的点积/a的范数*b的范数
    :param dtype: set或np.ndarray
    :param a: 集合或向量a
    :param b: 集合或向量b
    :return: a与b的余弦相似度
    """
    if dtype == set:
        return len(a & b) / math.sqrt(len(a) * len(b))
    elif dtype == np.ndarray:
        return np.dot(a, b) / (norm(a) * norm(b))
    else:
        return None

def jaccardSim(a: set, b: set) -> float:
    """
    Jaccard相似度，公式为：a与b的交集的长度/a与b的并集的长度
    :param a:
    :param b:
    :return: a与b的Jaccard相似度
    """
    return len(a & b) / len(a | b)

if __name__ == '__main__':
    a = np.array([1, 0, 0])
    b = np.array([1, 1, 0])
    print(cosineSim(a,b))


import numpy as np
import random as rm
states = ["Sleep","Icecream","Run"]

# Possible sequences of events
transitionName = [["SS","SR","SI"],["RS","RR","RI"],["IS","IR","II"]]

# Probabilities matrix (transition matrix)
transitionMatrix = np.array([[0.2,0.6,0.2],[0.1,0.6,0.3],[0.2,0.7,0.1]])
# A function that implements the Markov model to forecast the state/mood.
def Markov_forecast(initial_state, steps, transitionMatrix):
    """
    Do Markov chain forecasting
    INPUTS
    initial_state    : The initial state
    steps            : number of predictions
    transitionMatrix : Array of states conditional probability

    OUTPUTS
    states : array of forecasting states
    """
    states = np.zeros((steps), dtype=int)
    n = transitionMatrix.shape[0]
    s = np.arange(1,n+1)

    states[0] = initial_state
    for i in range(steps-1):
        states[i+1] = np.random.choice(s,p=transitionMatrix[states[i]-1])

    return states

# Function that forecasts the possible state for the next 2 days
activity_forecast(2)

def activity_forecast(days):
    # Choose the starting state
    activityToday = "Sleep"
    activityList = [activityToday]
    i = 0
    prob = 1
    while i != days:
        if activityToday == "Sleep":
            change = np.random.choice(transitionName[0],replace=True,p=transitionMatrix[0])
            if change == "SS":
                prob = prob * 0.2
                activityList.append("Sleep")
                pass
            elif change == "SR":
                prob = prob * 0.6
                activityToday = "Run"
                activityList.append("Run")
            else:
                prob = prob * 0.2
                activityToday = "Icecream"
                activityList.append("Icecream")
        elif activityToday == "Run":
            change = np.random.choice(transitionName[1],replace=True,p=transitionMatrix[1])
            if change == "RR":
                prob = prob * 0.5
                activityList.append("Run")
                pass
            elif change == "RS":
                prob = prob * 0.2
                activityToday = "Sleep"
                activityList.append("Sleep")
            else:
                prob = prob * 0.3
                activityToday = "Icecream"
                activityList.append("Icecream")
        elif activityToday == "Icecream":
            change = np.random.choice(transitionName[2],replace=True,p=transitionMatrix[2])
            if change == "II":
                prob = prob * 0.1
                activityList.append("Icecream")
                pass
            elif change == "IS":
                prob = prob * 0.2
                activityToday = "Sleep"
                activityList.append("Sleep")
            else:
                prob = prob * 0.7
                activityToday = "Run"
                activityList.append("Run")
        i += 1
    return activityList

# To save every activityList
list_activity = []
count = 0

# `Range` starts from the first count up until but excluding the last count
for iterations in range(1,10000):
        list_activity.append(activity_forecast(2))

# Check out all the `activityList` we collected
#print(list_activity)

# Iterate through the list to get a count of all activities ending in state:'Run'
for smaller_list in list_activity:
    if(smaller_list[2] == "Run"):
        count += 1

# Calculate the probability of starting from state:'Sleep' and ending at state:'Run'
percentage = (count/10000) * 100
print("The probability of starting at state:'Sleep' and ending at state:'Run'= " + str(percentage) + "%")


from cmath import pi
import numpy as np
from pyparsing import col
from .utils import check_2D, check_dimension

def _calc_gaussian_value(i,j,sigma,half_size):
    return 1/(2*np.pi * sigma**2)* np.exp( -1* ( ( i-(half_size+1) )**2 + (j-(half_size+1))**2  ) /(2*sigma**2)  )

def create_gaussian_filter(size,sigma):
    if(size%2 ==0):
        raise ValueError()

    filter= np.zeros((size,size))
    half_size=size//2

    for i in range(size):
        for j in range(size):
            filter[i][j]= _calc_gaussian_value(i,j,sigma,half_size)
    return filter

def is_in_edge_range(image_shape, coords, filter_size):
    y,x= coords
    filter_size =filter_size//2

    print(x,y,filter_size)
    return (x-filter_size)<-1 or (x+filter_size) > image_shape[1] or (y-filter_size)<-1 or (y+filter_size) > image_shape[0]

def convolve(arr1, arr2):
    return np.sum(np.multiply(arr1,arr2))

def filter_image(image,filter, egde=""): # 3x3 5x5 array
    check_2D(image)

    filter_row_size, filter_col_size =filter.shape
    row_n = image.shape[0] - filter_row_size-1
    col_n = image.shape[1] - filter_col_size-1

    filtered=np.zeros((row_n, col_n) )

    for row_i in range(row_n):
        for col_i in range(col_n):
            current_arr= image[row_i:row_i+filter_row_size,col_i:col_i+filter_col_size]
            filtered[row_i][col_i]= convolve(current_arr,filter)

    return filtered

def _median(arr):
    return int(np.median(arr))

def _min(arr):
    return np.min(arr)
def _max(arr):
    return np.max(arr)

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

def _diff(arr):
    return _max(arr) - _min(arr)

def _filter_nonlinear(image, shape,function):

    check_2D(image)
    filter_row_size, filter_col_size =shape
    row_n = image.shape[0] - filter_row_size-1
    col_n = image.shape[1] - filter_col_size-1

    filtered=np.zeros((row_n, col_n) )

    for row_i in range(row_n):
        for col_i in range(col_n):
            current_arr= image[row_i:row_i+filter_row_size,col_i:col_i+filter_col_size]
            filtered[row_i][col_i]= function(current_arr)

    return filtered

def medfilt(image,shape):
    return _filter_nonlinear(image,shape,_median)

def minfilt(image,shape):
    return _filter_nonlinear(image,shape, _min)

def maxfilt(image,shape):
    return _filter_nonlinear(image,shape,_max)

def rangefilt(image,shape):
    return _filter_nonlinear(image,shape,_diff)

def meanfilt(image,shape):
    return _filter_nonlinear(image,shape,_mean)


#IIR Analog and Digital Butterworth HPF filter design
from scipy import signal
import numpy as np
import matplotlib.pyplot as plt
from pylab import clf
#Analog High pass butterworth filter, order =3, fc=100Hz
#b = numerator coefficients, a = denominator coefficients
#w = frequency variable, h= frequency response
b,a = signal.iirfilter(3,100,btype ='high',analog=True, ftype ='butter')
w,h = signal.freqs(b,a,1000)

fig1 = plt.figure(1)
ax = fig1.add_subplot(1,1,1)
ax.semilogx(w,20*np.log10(np.maximum(abs(h),1e-5)))
ax.set_title('Butterworth IIR Analog HPF frequency response')
ax.set_xlabel('Frequency in Hz---->')
ax.set_ylabel('Amplitude in dB----->')
#ax.axis((10,1000,-100,10))
ax.grid(which='both',axis='both')
plt.show()

#Digital High pass butterworth filter, order =3, fc=100Hz
fs =1000
sos = signal.iirfilter(3,100/fs,btype ='high',analog=False, ftype ='butter',output='sos')
w,h = signal.sosfreqz(sos,1000)

fig2 = plt.figure(2)
clf()
ax = fig2.add_subplot(1,1,1)
ax.plot(w/np.pi,20*np.log10(np.maximum(abs(h),1e-5)))
ax.set_title('Butterworth IIR Digital HPF frequency response')
ax.set_xlabel('Normalized Digital Frequency ---->')
ax.set_ylabel('Amplitude in dB----->')
#ax.axis((10,1000,-100,10))
ax.grid(which='both',axis='both')
plt.show()

#Result
##>>> b
##array([1., 0., 0., 0.])
##>>> a
##array([1.e+00, 2.e+02, 2.e+04, 1.e+06])
##>>> sos
##array([[ 0.72944072, -0.72944072,  0.        ,  1.        , -0.72654253,
##         0.        ],
##       [ 1.        , -2.        ,  1.        ,  1.        , -1.64755222,
##         0.73233892]])
##>>>


"""Is channel Hermiticity-preserving."""
from typing import List, Union

import numpy as np

from toqito.channel_ops import kraus_to_choi
from toqito.matrix_props import is_hermitian

def is_herm_preserving(
    phi: Union[np.ndarray, List[List[np.ndarray]]],
    rtol: float = 1e-05,
    atol: float = 1e-08,
) -> bool:
    r"""
    Determine whether the given channel is Hermitian-preserving [WatH18]_.

    A map :math:`\Phi \in \text{T} \left(\mathcal{X}, \mathcal{Y} \right)` is
    *Hermitian-preserving* if it holds that

    .. math::
        \Phi(H) \in \text{Herm}(\mathcal{Y})

    for every Hermitian operator :math:`H \in \text{Herm}(\mathcal{X})`.

    Examples
    ==========

    The map :math:`\Phi` defined as

    .. math::
        \Phi(X) = X - U X U^*

    is Hermitian-preserving, where

    .. math::
        U = \frac{1}{\sqrt{2}}
        \begin{pmatrix}
            1 & 1 \\
            -1 & 1
        \end{pmatrix}.

    >>> import numpy as np
    >>> from toqito.channel_props import is_herm_preserving
    >>> unitary_mat = np.array([[1, 1], [-1, 1]]) / np.sqrt(2)
    >>> kraus_ops = [[np.identity(2), np.identity(2)], [unitary_mat, -unitary_mat]]
    >>> is_herm_preserving(kraus_ops)
    True

    We may also verify whether the corresponding Choi matrix of a given map is
    Hermitian-preserving. The swap operator is the Choi matrix of the transpose map, which is
    Hermitian-preserving as can be seen as follows:

    >>> import numpy as np
    >>> from toqito.perms import swap_operator
    >>> from toqito.channel_props import is_herm_preserving
    >>> unitary_mat = np.array([[1, 1], [-1, 1]]) / np.sqrt(2)
    >>> choi_mat = swap_operator(3)
    >>> is_herm_preserving(choi_mat)
    True

    References
    ==========
    .. [WatH18] Watrous, John.
        "The theory of quantum information."
        Section: "Linear maps of square operators".
        Cambridge University Press, 2018.

    :param phi: The channel provided as either a Choi matrix or a list of Kraus operators.
    :param rtol: The relative tolerance parameter (default 1e-05).
    :param atol: The absolute tolerance parameter (default 1e-08).
    :return: True if the channel is Hermitian-preserving, and False otherwise.
    """
    # If the variable `phi` is provided as a list, we assume this is a list
    # of Kraus operators.
    if isinstance(phi, list):
        phi = kraus_to_choi(phi)

    # Phi is Hermiticity-preserving if and only if its Choi matrix is Hermitian.
    if phi.shape[0] != phi.shape[1]:
        return False
    return is_hermitian(phi, rtol=rtol, atol=atol)


# LUPPFactors.py
#
# Daniel R. Reynolds
# SMU Mathematics
# Math 4315

# imports
import numpy

def LUPPFactors(A):
    """
    usage: [L,U,P1,P2] = LUPPFactors(A)

    Row-oriented LU factorization with complete pivoting; constructs the factorization
          P1 A P2 = LU  <=>  A = P1^T L U P2^T
    This function checks that A is square, and attempts to catch the case where A
    is singular.

    Inputs:
      A - square n-by-n matrix

    Outputs:
      L  - unit-lower-triangular matrix (n-by-n)
      U  - upper-triangular matrix (n-by-n)
      P1 - permutation matrix (n-by-n)
      P2 - permutation matrix (n-by-n)
    """

    # check input
    m, n = numpy.shape(A)
    if (m != n):
        raise ValueError("LUPPFactors error: matrix must be square")

    # set singularity tolerance
    tol = 1000*numpy.finfo(float).eps

    # create output matrices
    U = A.copy()
    L = numpy.eye(n)
    P1 = numpy.eye(n)
    P2 = numpy.eye(n)
    for k in range(n-1):               # loop over pivots
      si = k                           # determine pivot position
      sj = k
      for i in range(k,n):
        for j in range(k,n):
          if (numpy.abs(U[i,j]) > numpy.abs(U[si,sj])):
            si = i
            sj = j
      if (numpy.abs(U[si,sj]) < tol):  # check for singularity
        raise ValueError("LUPPFactors error: matrix is [close to] singular")
      for j in range(k,n):             # swap rows in U
        tmp = U[k,j]
        U[k,j] = U[si,j]
        U[si,j] = tmp
      for j in range(k):               # swap rows in L
        tmp = L[k,j]
        L[k,j] = L[si,j]
        L[si,j] = tmp
      for j in range(n):               # swap rows in P1
        tmp = P1[k,j]
        P1[k,j] = P1[si,j]
        P1[si,j] = tmp
      for i in range(n):               # swap columns in U
        tmp = U[i,k]
        U[i,k] = U[i,sj]
        U[i,sj] = tmp
      for i in range(n):               # swap columns in P2
        tmp = P2[i,k]
        P2[i,k] = P2[i,sj]
        P2[i,sj] = tmp
      for i in range(k+1,n):           # loop over remaining rows
        L[i,k] = U[i,k]/U[k,k]         # compute multiplier
        for j in range(k,n):           # update remainder of matrix row
          U[i,j] -= L[i,k]*U[k,j]

    return [L, U, P1, P2]

# end function


from scipy.stats import beta

np.random.seed(42)
theta_real = 0.35
ntrials = 100
data = stats.bernoulli.rvs(p=theta_real, size=ntrials)

N = ntrials; N1 = sum(data); N0 = N-N1; # Sufficient statistics
aprior = 1; bprior = 1; # prior
apost = aprior + N1; bpost = bprior + N0 # posterior

# Interval function
alpha = 0.05
CI1 = beta.interval(1-alpha, apost, bpost)
print('{:0.2f}--{:0.2f}'.format(CI1[0], CI1[1])) # (0.06:0.52)

# Use the inverse CDF (percent point function)
l  = beta.ppf(alpha/2, apost, bpost)
u  = beta.ppf(1-alpha/2, apost, bpost)
CI2 = (l,u)
print('{:0.2f}--{:0.2f}'.format(CI2[0], CI2[1])) # (0.06:0.52)

# Use Monte Carlo sampling
samples = beta.rvs(apost, bpost, size=10000)
samples = np.sort(samples)
CI3 = np.percentile(samples, 100*np.array([alpha/2, 1-alpha/2]))
print('{:0.2f}--{:0.2f}'.format(CI3[0], CI3[1])) # (0.06:0.51)
print(np.mean(samples))


# --------------
#Header files
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

#Reading of the file
data=pd.read_csv(path)
data['Gender'].replace('-','Agender',inplace=True)
gender_count = data['Gender'].value_counts()
plt.bar(gender_count.index, gender_count)
plt.show()

alignment = data['Alignment'].value_counts()
plt.figure(figsize=(6,6))
plt.bar(alignment.index, alignment)
plt.title('Character alignment')
plt.show()

scdf = data[['Strength','Combat']].copy()
sc_covariance = scdf.cov().iloc[0,1]
sc_strength = scdf['Strength'].std()
sc_combat = scdf['Combat'].std()

icdf = data[['Intelligence','Combat']].copy()
ic_covariance = icdf.cov().iloc[0,1]
ic_intelligence = icdf['Intelligence'].std()
ic_combat = icdf['Combat'].std()

sc_pearson = round(sc_covariance/(sc_strength*sc_combat), 2)
print("Pearson's Correlation Coefficient between Strength and Combat : ",sc_pearson)

ic_pearson = round(ic_covariance/(ic_intelligence*ic_combat), 2)
print("Pearson's Correlation Coefficient between Intelligence and Combat : ", ic_pearson)

#Find out who are the best of the best in this superhero universe by finding the value of quantile =0.99 for the Total colum and subset the df whose Total is higher than this quantile.
total_high = data['Total'].quantile(q=0.99)

super_best = data[data['Total'] > total_high]
super_best_names = list(super_best['Name'])
print(super_best_names)
# Code starts here


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

def func(x):
    return -0.5 * x + 0

def error():
    return 2 * rd.uniform(-0.5, 0.5)

def noise(x_list):
    rd.seed(12)
    y_list = [func(x_i) + error() for x_i in x_list]
    return y_list

def get():
    x_list = np.linspace(-2, 2, 16)
    y_list = noise(x_list)
    return x_list, y_list

def show_noise_data(x_noise, y_noise):
    fig, ax = plt.subplots()
    ax.scatter(x_noise, y_noise)
    ax.set_title('Noise data')  # заголовок для Axes
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.grid()
    fig.set_figwidth(7)  # ширина и
    fig.set_figheight(7)  # высота "Figure"
    plt.savefig("images/Noise data.png")
    plt.show()

def mse(y_actual, y_pred):
    mse_error = 0
    for y, y_prime in zip(y_actual, y_pred):
        mse_error += (y - y_prime) ** 2
    return mse_error

def neuron(x_list, w1, w0):
    return [w1 * x + w0 for x in x_list]

def find_optimal_d(c_m, x_noise, y_noise):
    d_cur = rd.uniform(-1, 1)
    min_mse = mse(y_noise, neuron(x_noise, c_m, d_cur))
    d_min = d_cur
    for i in range(1, 1000):
        d_cur = rd.uniform(-1, 1)
        new_mse = mse(y_noise, neuron(x_noise, c_m, d_cur))
        if new_mse < min_mse:
            min_mse = new_mse
            d_min = d_cur
    return d_min

def find_optimal(x_noise, y_noise, data_=None):
    if data_ is None:
        data_ = []
    c_m = np.linspace(-1.5, 0.5, 1000)
    c_opt = c_m[0]
    d_opt = find_optimal_d(c_opt, x_noise, y_noise)
    min_mse = mse(y_noise, neuron(x_noise, c_opt, d_opt))
    data_.append([d_opt, c_opt, min_mse])
    for i in range(1, 1000):
        d_new = find_optimal_d(c_m[i], x_noise, y_noise)
        new_mse = mse(y_noise, neuron(x_noise, c_m[i], d_new))

        if new_mse < min_mse:
            d_opt = d_new
            c_opt = c_m[i]
            min_mse = new_mse
            data_.append([d_new, c_m[i], new_mse])
    return c_opt, d_opt, min_mse, data

def show_regression(x_noise, y_noise, c_opt, d_opt):
    fig, ax = plt.subplots()
    ax.scatter(x_noise, y_noise)
    ax.set_title('Found function')  # заголовок для Axes
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    fig.set_figwidth(7)  # ширина и
    fig.set_figheight(7)  # высота "Figure"
    ax.plot(x_noise, neuron(x_noise, c_opt, d_opt), color='red', linewidth=2)
    ax.grid()
    plt.legend(["regression", "data"])
    plt.savefig("images/Regression.png")
    plt.show()

if __name__ == '__main__':
    X, Y = get()
    show_noise_data(X, Y)
    data = []
    c, d, mse_min, data = find_optimal(X, Y, data)
    show_regression(X, Y, c, d)
    df = pd.DataFrame(data, columns=["w0", "w1", "error"])
    df.to_csv("results/data.csv")
    print(df)
    print("Optimal weights:")
    print(f"w0 = {d}")
    print(f"w1 = {c}")
    print(f"MSE = {mse_min}")
    print("Linear regression:")
    print(f"y = {c}x + {d}")


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

class KMean:
    ## Class Variables ##
    X = n_k = _tolerance = _max_itr = cluster = None
    centroids = []

    ## Constructors ##
    def __init__(self, n_k, tolerance=0.0001, max_itr=1000):
        self.n_k = n_k
        self._tolerance = tolerance
        self._max_itr = max_itr

    ## Methods ##
    def parse_data(self, file_path, sep):
        df = pd.read_csv(file_path, sep=sep)
        self.X = df.values

    def clusterify(self):
        for i in range(self._max_itr):
            self._init_cluster_classes()
            self._classify_points_to_cluster()

            new_centroid = self._update_centroid()

            if self._has_converged(new_centroid):
                self.centroids = new_centroid
                break
            else:
                self.centroids = new_centroid

    def _classify_points_to_cluster(self):
        if len(self.centroids) == 0:  # RANDOM init centroid
            for point in self.X:
                self.cluster[np.random.randint(0, len(self.cluster))].append(point)
        else:
            for point in self.X:
                dist = [self.euclidean_dist(point, centroid) for centroid in self.centroids]
                classified_index = dist.index(min(dist))
                self.cluster[classified_index].append(point)

    def _update_centroid(self):
        new_centroid = []

        for i, cluster in enumerate(self.cluster):
            new_centroid.append(np.average(cluster, axis=0))

        return new_centroid

    def _has_converged(self, new_centroid):
        if len(self.centroids) == 0:
            return False
        else:
            for i, centroid in enumerate(self.centroids):
                old_centroid = centroid
                curr_centroid = new_centroid[i]

                if np.sum((curr_centroid - old_centroid) / old_centroid * 100.0) > self._tolerance:
                    return False
                else:
                    return True

    def _init_cluster_classes(self):
        self.cluster = []

        for i in range(self.n_k):
            self.cluster.append([])

    def init_cluster_center(self, method="FORGY"):
        if method == "FIRST_K":
            for i in range(self.n_k):
                self.centroids.append(self.X[i])
        elif method == "FORGY":
            index_list = np.random.choice(len(self.X), self.n_k)
            for index in index_list:
                self.centroids.append(self.X[index])
        elif method == "RANDOM":
            return
        else:
            raise NotImplementedError

    @staticmethod
    def euclidean_dist(p1, p2):
        sqr_dist = 0
        for i in range(len(p1)):
            sqr_dist += (p1[i] - p2[i]) ** 2

        return np.sqrt(sqr_dist)

def main():
    k_mean = KMean(3)
    k_mean.parse_data("heightWeight.csv")
    k_mean.init_cluster_center("FORGY")
    k_mean.clusterify()

    colors = 10 * ['r', 'g', 'c', 'b', 'k']

    for i, cluster in enumerate(k_mean.cluster):
        color = colors[i]
        plt.scatter(k_mean.centroids[i][0], k_mean.centroids[i][1], s=130, color=color, marker='x')

        for points in cluster:
            plt.scatter(points[0], points[1], color=color, s=30)

    plt.show()

if __name__ == "__main__":
    main()


import numpy as np

from si.supervised.supervised_model import SupervisedModel

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

class LogisticRegression(SupervisedModel):
    # https://datascience-enthusiast.com/R/logistic_regression.html

    def __init__(self, epochs=1000, lr=0.001):
        super(LogisticRegression, self).__init__()
        self.theta = None
        self.epochs = epochs
        self.lr = lr
        self.history = None
        self.X, self.y = None, None
        self.is_fitted = False

    def fit(self, dataset):
        if not dataset.hasLabel():
            raise Exception("Data has no labels.")
        X, self.y = dataset.getXy()
        self.X = np.hstack((np.ones((X.shape[0], 1)), X))
        self.is_fitted = True
        self.train(self.X, self.y)
        return self.X, self.y

    def train(self, X, y):
        m, n = X.shape
        self.history = {}
        self.theta = np.zeros(n)
        for epoch in range(self.epochs):
            z = np.dot(X, self.theta)
            h = sigmoid(z)
            # grad = 1 / m * np.dot(X.T, (h - y))
            grad = np.dot(X.T, (h - y))
            self.theta -= self.lr * grad
            self.history[epoch] = [self.theta[:], self.cost()]
        return self.theta

    def predict(self, x):
        if not self.is_fitted:
            raise Exception("The model hasn't been fitted yet.")
        _x = np.hstack(([1], x))
        return np.round(sigmoid(np.dot(self.theta, _x)))

    def cost(self, X=None, y=None):
        if not self.is_fitted:
            raise Exception("The model hasn't been fitted yet.")

        X = np.hstack((np.ones((X.shape[0], 1)), X)) if X is not None else self.X
        y = y if y is not None else self.y

        y_pred = sigmoid(np.dot(self.theta, X.T))
        epsilon = 1e-5
        return (((-y).dot(np.log(y_pred + epsilon))) - ((1 - y).dot(np.log(1 - y_pred + epsilon)))) / y.size
        # change this

# Implement Regularização L1 / L2

class LogisticRegressionReg(LogisticRegression):
    def __init__(self, epochs=1000, lr=0.001, lbd=1):
        super().__init__(epochs, lr)
        self.lbd = lbd

    def train(self, X, y):
        m, n = X.shape
        self.history = {}
        self.theta = np.zeros(n)
        for epoch in range(self.epochs):
            z = np.dot(X, self.theta)
            h = sigmoid(z)
            grad = np.dot(X.T, (h - y))
            # grad = 1 / m * np.dot(X.T, (h - y))
            reg = self.lbd / m * np.hstack(([0], self.theta[1:]))
            self.theta -= self.lr * (grad + reg)
            self.history[epoch] = [self.theta[:], self.cost()]
        return self.theta

    def cost(self, X=None, y=None):
        if not self.is_fitted:
            raise Exception("The model hasn't been fitted yet.")
        X = np.hstack((np.ones((X.shape[0], 1)), X)) if X is not None else self.X
        y = y if y is not None else self.y

        y_pred = sigmoid(np.dot(self.theta, X.T))
        m = y.size
        epsilon = 1e-5
        reg = self.lbd / (2 * m) * np.sum(np.square(self.theta))
        return (((-y).dot(np.log(y_pred + epsilon))) - ((1 - y).dot(np.log(1 - y_pred + epsilon)))) / m + reg


"""
Functions related to Fractal Algorithms

Public Methods :
  * get_fibonocci_number - Returns the Fibonnoci Number for the requested index
  * multibrot_algorithm - Performs a single interation of the Multibrot Formula
  * newton_method_algorithm - Performs a single iteration of the Newton Method Formula
  * evaluate_polynomial_1d - Performs a single iteration of a Polynomial Formula
"""

import numpy

_PHI = (1 + 5**0.5) / 2.0

def get_fibonocci_number(index):
    """
    Returns the Fibonocci Number for the requested index

    Parameters :
      * index - Index of the requested Fibonocci Number
          (indexes 2 and below will simply return the requested index value)
    """
    if index < 2:
        return index

    return int(round((_PHI**index - (1 - _PHI)**index) / 5**0.5))

def multibrot_algorithm(z_values, c_values, power):
    """
    Perform an iteration of the Multibrot Polynomial Formula (z = z^power + c) and return the
    resulting values

    Parameters :
      * z_values - Input z values for the Polynomial Formula
      * c_values - Input c values for the Polynomial Formula
      * power - Exponential power to use in the Polynomial Formula
    """
    z_values_new = numpy.copy(z_values)
    exponent_value = numpy.copy(z_values)
    for exponent_counter in range(0, power - 1):
        z_values_new = numpy.multiply(exponent_value, z_values_new)

    z_values_new = numpy.add(z_values_new, c_values)
    return z_values_new

def newton_method_algorithm(coefficient_array, coefficient_array_derivative, z_values, c_values):
    """
    Perform an iteration of the Newton Method Algorithm (z = z - (f(z) / f_deriv(z))) and return
    the difference in iteration values and the resulting values

    Parameters :
      * coefficient_array - An array describing a Polynomial Formula in exponential order
          (ie. [ 1, 2, 3 ] = c + 2z + 3z**2 ; [ 4, 0, 1, 0, 5 ] = 4c + z**2 + 5z**4)
      * coefficient_array_derivative - An array describing the Derivative Polynomial Formula
          of coefficient_array in exponential order
          (ie. [ 1, 2, 3 ] = c + 2z + 3z**2 ; [ 4, 0, 1, 0, 5 ] = 4c + z**2 + 5z**4)
      * z_values - Input z values for the Polynomial Formula
      * c_values - Input c values for the Polynomial Formula
    """
    coefficient_func_values = evaluate_polynomial_1d(coefficient_array, z_values, c_values)
    coefficient_deriv_func_values = evaluate_polynomial_1d(coefficient_array_derivative,
                                                           z_values, c_values)
    func_values = numpy.divide(coefficient_func_values, coefficient_deriv_func_values)

    z_values_new = numpy.add(z_values, -func_values)
    iteration_diff = numpy.add(z_values, -z_values_new)

    return [iteration_diff, z_values_new]

def evaluate_polynomial_1d(coefficient_array, z_values, c_values):
    """
    Perform an iteration of the Polynomial Formula provided and return the resulting values

    Parameters :
      * coefficient_array - An array describing a Polynomial Formula in exponential order
          (ie. [ 1, 2, 3 ] = c + 2z + 3z**2 ; [ 4, 0, 1, 0, 5 ] = 4c + z**2 + 5z**4)
      * z_values - Input z values for the Polynomial Formula
      * c_values - Input c values for the Polynomial Formula
    """
    # Calculate Polynomial Constant Expression
    constant_coefficient = coefficient_array[0]
    constant_values = c_values * constant_coefficient

    # Calculate Polynomial Exponential Expressions
    exponent_accumulator = numpy.ones(z_values.shape, dtype=z_values.dtype)
    exponent_values = numpy.empty(z_values.shape, dtype=z_values.dtype)
    z_values_new = numpy.zeros(z_values.shape, dtype=z_values.dtype)
    for exponent_counter in range(1, len(coefficient_array)):
        exponent_accumulator = numpy.multiply(exponent_accumulator, z_values)
        exponent_coefficient = coefficient_array[exponent_counter]

        exponent_values = numpy.multiply(exponent_accumulator, exponent_coefficient)
        z_values_new = numpy.add(z_values_new, exponent_values)

    # Add Constant and Exponential Expressions
    z_values_new = z_values_new + constant_values

    return z_values_new


"""Link functions related to power function."""

# author: Benjamin Cross
# email: btcross26@yahoo.com
# created: 2019-08-26

from typing import Union

import numpy as np

from .base_class import BaseLink

class PowerLink(BaseLink):
    """Implementation of power link functions."""

    def __init__(self, power: Union[float, int], summand: float = 0.0):
        """
        Initialize a power link instance - i.e., link = (y + summand) ** power.

        Extends BaseLink class intializer.

        Parameters
        ----------
        power: float or int
            Exponent of the link function.

        summand: float or int
            Summand of the link function.
        """
        super().__init__()
        self.power_ = power
        self.summand_ = summand

    def _link(self, y: np.ndarray) -> np.ndarray:
        """
        Get the link, eta, as a function of y.

        Overrides BaseLink._link.
        """
        return (y + self.summand_) ** self.power_

    def _inverse_link(self, eta: np.ndarray) -> np.ndarray:
        """
        Get the target, y, as a function of the link, `eta`.

        Overrides BaseLink._inverse_link.
        """
        return eta ** (1.0 / self.power_) - self.summand_

    def dydeta(self, y: np.ndarray) -> np.ndarray:
        """
        Get the derivative of `y` with respect to the link as a function of y.

        Overrides BaseLink.dydeta.
        """
        return (1.0 / self.power_) * (y + self.summand_) ** (1.0 - self.power_)

    def d2ydeta2(self, y: np.ndarray) -> np.ndarray:
        """
        Get the second derivative of `y` with respect to the link as a function of y.

        Overrides BaseLink.d2ydeta2.
        """
        return self.dydeta(y) * (1.0 / self.power_ - 1.0) / self._link(y)

class SqrtLink(PowerLink):
    """Square root link function implementation."""

    def __init__(self) -> None:
        """
        Class initializer.

        Extends PowerLink class intializer by specifying power=0.5 and summand=0.0.
        """
        super().__init__(power=0.5)

class CubeRootLink(PowerLink):
    """Cube root link function implementation."""

    def __init__(self) -> None:
        """
        Class initializer.

        Extends PowerLink class initializer by specifying power=1/3 and summand=0.0.
        """
        super().__init__(power=(1.0 / 3.0))

class ReciprocalLink(PowerLink):
    """Inverse link function implementation."""

    def __init__(self, summand: float = 0.0):
        """
        Class initializer.

        Extends PowerLink class initializer by specifying power=-1 and summand=0.0.
        """
        super().__init__(power=-1.0, summand=summand)


### import libraries ###
import numpy as np
from mpmath import *
import matplotlib.pyplot as plt
### make MATLAB-like commands for use in Python ###
fft, fftshift, ifft = np.fft.fft, np.fft.fftshift, np.fft.ifft
abs, rndm, exp = np.abs, np.random.normal, np.exp
def sech(x) :  return 1/np.cosh(x)
np.set_printoptions(precision=3)

L,n,pi = 10,64,np.pi
t2 = np.linspace(-L,L,n+1)
t = t2[0:n]
k = (2*pi/(2*L)) * np.append(np.arange(0,n/2),np.arange(-n/2,0))

u=sech(t)
ut = fft(u)
noise = 0.5
rn = rndm(0,noise,[n]) + 1j*rndm(0,noise,[n])
utn = ut + rn
un = ifft(utn)
filter = exp(-0.2*k**2)

ks = fftshift(k)
fs = fftshift(filter)
utns = fftshift(utn)

### PLOTS ###
fig, (ax1, ax2) = plt.subplots(2)
ax1.plot(t,u, label='signal, real space')
ax1.plot(t,un, label='noisy signal, real space (transformed back)')
ax1.plot(t,filter)
ax2.plot(ks, utns/np.max(utns))
ax2.plot(ks, abs(utns)/np.max(utns))
ax2.plot(ks, fs)
plt.savefig('fftNoisySignal.png')

### NOTES ###
'''
u = sech(t) : a simple signal (hyperbolic secant)
ut :
ks : shifted frequencies for k-space plots
noise : magnitude of noise
rn : COMPLEX noise
'''

### QUESTIONS ###
'''
1) when plot in k space, what part of complex plane are we plotting?
'''


from typing import Tuple

import numpy as np

from . import modulus, inverse

def gcd_poly(poly1: np.poly1d, poly2: np.poly1d, p: int) -> np.poly1d:
    """Seek the gcd of two polynomials over Fp.

    Args:
        poly1 (np.poly1d): A polynomial.
        poly2 (np.poly1d): A polynomial.
        p (int): A prime number.

    Returns:
        np.poly1d: gcd(poly1, poly2) over Fp.
    """
    def poly2monic(poly: np.poly1d)\
            -> Tuple[np.poly1d, Tuple[np.poly1d, np.poly1d]]:
        highest_degree_coeff = poly.coeffs[0]
        if highest_degree_coeff == 1:
            return poly, (np.poly1d([1]), np.poly1d([1]))

        inv_hdc = inverse.inverse_el(highest_degree_coeff, p)
        coeffs = poly.coeffs * inv_hdc
        return np.poly1d(modulus.modulus_coeffs(coeffs, p)),\
            (np.poly1d([highest_degree_coeff]), np.poly1d([inv_hdc]))

    if len(poly1.coeffs) < len(poly2.coeffs):
        poly1, poly2 = poly2, poly1

    poly2_monic, hdc = poly2monic(poly2)
    _, r = np.polydiv(poly1 * hdc[1], poly2_monic)
    r = np.poly1d(modulus.modulus_coeffs(r.coeffs, p)) * hdc[0]
    r = modulus.modulus_poly_over_fp(r, p)

    if r.coeffs[0] == 0:
        return poly2

    return gcd_poly(poly2, r, p)


'''
    Problem 77 : Prime summations

    Desc :
        It is possible to write ten as the sum of primes in exactly five different ways:

        7 + 3
        5 + 5
        5 + 3 + 2
        3 + 3 + 2 + 2
        2 + 2 + 2 + 2 + 2

        What is the first value which can be written as the sum of primes in over five thousand different ways?
'''
import numpy as np
from numba import jit

@jit
def getPrimes(n):
    arr = np.ones(n + 1, dtype=bool)
    arr[0], arr[1] = 0, 0
    for i in range(2, n + 1):
        if arr[i]:
            arr[i * i:n + 1:i] = False
    return arr.nonzero()[0]

def compute_sum_matrix(n, primes):
    arr = np.zeros((len(primes), n + 1))
    p = primes[0]
    arr[0, p:n + 1:p] = 1

    for i in range(1, len(primes)):
        p = primes[i]
        arr[i, 0] = 1
        for j in range(1, n + 1):
            if j < p:
                arr[i, j] = arr[i - 1, j]
            else:
                arr[i, j] = arr[i - 1, j] + arr[i, j - p]

    return arr

if __name__ == "__main__":
    n = 100
    primes = getPrimes(n)
    print("Number of primes : ", len(primes))
    sum_matrix = compute_sum_matrix(n, primes)
    # print(sum_matrix)
    res = n
    for i, row in enumerate(sum_matrix):
        for j, value in enumerate(row):
            if value > 5000:
                if j < res:
                    res = j
    print(res)


import math
import numpy as np

def lb_to_kg(x):
	return .45 * x

def kg_to_lb(x):
	return x / .45

def force(m, a):
	return m * a

def rad_to_deg(rad):
	return rad * 180. / np.pi

def deg_to_rad(deg):
	return deg * np.pi / 180.

def mi_to_m(mi):
	return mi / 1609.34

def cm_to_m(m):
	return m / 100.

def radius_to_circum(r):
	return np.pi * (r**2.)

def hours_to_sec(h):
	return h * 3600.

def rev_to_rad(rev):
	return 2. * np.pi * rev

def rad_to_rev(rad):
	return rad / (2. * np.pi)

def mi_ph_to_me_ps(mi_ph):
	me_ph = mi_to_m(mi_ph)
	me_ps = me_ph * hours_to_sec(1.)
	return me_ps

def ang_vel_rad_ps_to_lin_vel_me_ps(ang_vel, wheel_diam):
	wheel_radius = wheel_diam / 2. # cm
	cm_ps = rad_to_rev(ang_vel) * radius_to_circum(wheel_radius)
	return cm_ps / 100.

def hypotenuse(x, y):
	return math.sqrt(x**2. + y**2)

def circum_ratio_to_angle_rad(ratio):
	return ratio * 2. * np.pi

def circum_ratio_to_angle_deg(ratio):
	return ratio * 360.

def arc_cm_angle_rad_to_radius_cm(arc, theta):
	return arc / theta

def chord_from_radius_cm_and_angle_rad(r, theta):
	return 2. * r * math.sin(theta/2.)

def vec_2D_to_components(vec, theta):
	return vec * math.cos(theta), vec * math.sin(theta)

def base_angles_of_isosceles_triangle_rad(theta):
	return (np.pi - theta)/2.

# target_ang_vel = me_ps
def wheel_diam_cm_to_rad_ps(d, target_ang_vel):
	me_per_rev = cm_to_m(radius_to_circum(d/2))
	rev_ps = target_ang_vel / me_per_rev
	return rev_to_rad(rev_ps)

#rotate x,y around xo,yo by theta (rad)
def rotate(x,y,xo,yo,theta):
	xr=math.cos(theta)*(x-xo)-math.sin(theta)*(y-yo)   + xo
	yr=math.sin(theta)*(x-xo)+math.cos(theta)*(y-yo)  + yo
	return [xr,yr]


"""

"""

from numpy import mean, median, abs, sum, cumsum, histogram, sqrt
from sklearn.metrics import mean_squared_error, r2_score

def rmse(y_true, y_pred):
    """
rmse
    description:
        computes RMSE
"""
    return sqrt(mean_squared_error(y_true, y_pred))

def all_metrics(y_true, y_pred):
    """
metrics
    description:
        computes a standard set of performance metrics using true and predicted values
            * training and test set R-squared (R2)
            * training and test set root mean squared error (RMSE)
            * training and test set mean absolute error (MAE)
            * cumulative error distribution at the <1, <3, <5, and <10% levels
              for training and test set (CE135A)
"""
    # compute metrics
    abs_y_err = abs(y_pred - y_true)
    r2 = r2_score(y_true, y_pred)
    mae = mean(abs_y_err)
    mdae = median(abs_y_err)
    rmse_ = rmse(y_true, y_pred)
    y_err_percent = 100. * abs_y_err / y_true
    mre = mean(y_err_percent)
    mdre = median(y_err_percent)
    cum_err = cumsum(histogram(y_err_percent, [_ for _ in range(101)])[0])
    cum_err = 100. * cum_err / sum(cum_err)
    ce1, ce3, ce5, ceA = cum_err[0], cum_err[2], cum_err[4], cum_err[9]
    summary = {'R2': r2, 'MAE': mae, 'MDAE': mdae, 'MRE': mre, 'MDRE': mdre, 'RMSE': rmse_, 'CE135A': [ce1, ce3, ce5, ceA]}
    return summary


# Import
import numpy as np

# Global Variables

# Classes

# Functions
def multiply_ex1():
	print("--------------------------");
	print("Example 1. [2x3] x [3x2] = [2x2]")
	A = np.array([[1,2,3], [4,5,6]])
	print(A)
	print("shape: " + str(A.shape))

	B = np.array([[1,2], [3,4], [5,6]])
	print(B)
	print("shape: " + str(B.shape))

	Y = np.dot(A, B)
	print(Y)
	print("shape: " + str(Y.shape))

def multiply_ex2():
	print("--------------------------");
	print("Example 2. [3x2] x [2x1] = [3x1]")
	A = np.array([[1,2], [3,4], [5,6]])
	print(A)
	print("shape: " + str(A.shape))

	B = np.array([7,8])
	print(B)
	print("shape: " + str(B.shape))

	Y = np.dot(A, B)
	print(Y)
	print("shape: " + str(Y.shape))

def multiply_ex3():
	print("--------------------------");
	print("Example 3. [1x2] x [2x3] = [1x3]")
	A = np.array([1,2])
	print(A)
	print("shape: " + str(A.shape))

	B = np.array([[1,3,5], [2,4,6]])
	print(B)
	print("shape: " + str(B.shape))

	Y = np.dot(A, B)
	print(Y)
	print("shape: " + str(Y.shape))

def main():
	print("--------------------------");
	print("multiplication of matrices")
	multiply_ex1()
	multiply_ex2()
	multiply_ex3()

if __name__ == '__main__':
	main()


import numpy as np
import matplotlib.pyplot as plt
from .integrate import Integrate

class Riemann(Integrate):
    """
    Compute the Riemann sum of f(x) over the interval [a,b].

    Parameters
    ----------
    f : function
    A single variable function f(x), ex: lambda x:np.exp(x**2)
    """
    def __init__(self, f):
        Integrate.__init__(self, f)
        self.N = 25

    def compute_integral(self, a, b, N = 25, method='midpoint'):
        """
        Approximate the value of the integral of f(x) dx from a to b with N sub-intervals using left, right or midpoint method

        Parameters
        ----------
        a , b : any numbers
        Endpoints of the interval [a,b]

        N : integer
        Number of subintervals of equal length in the partition of [a,b]

        method : string
        Determines the kind of Riemann sum:\n
        right : Riemann sum using right endpoints\n
        left : Riemann sum using left endpoints\n
        midpoint (default) : Riemann sum using midpoints

        Returns
        -------
        float
            Approximation of the integral given by the Riemann sum.

        Examples
        --------
        >>> compute_integral(0,np.pi/2,1000), f = lambda x:1 / (1 + x**2)
        approx = 1.3731040812301096
        actual = 1.373400766945016
        """
        self.a = a
        self.b = b
        self.N = N

        dx = (self.b - self.a) / self.N
        x = np.linspace(self.a, self.b, self.N+1)

        if method == 'left':
            x_left = x[:-1] # from 0 to N-1
            return np.sum(self.f(x_left)*dx)
        elif method == 'right':
            x_right = x[1:] # from 1 to N
            return np.sum(self.f(x_right)*dx)
        elif method == 'midpoint':
            x_mid = (x[:-1] + x[1:])/2  # all N but averaged
            return np.sum(self.f(x_mid)*dx)
        else:
            raise ValueError("Method must be 'left', 'right' or 'midpoint'.")

    def plot_function(self):

        x = np.linspace(self.a, self.b, self.N+1)
        y = self.f(x)

        X = np.linspace(self.a, self.b, 5*self.N+1)
        Y = self.f(X)

        plt.figure(figsize=(15,5))

        plt.subplot(1,3,1)
        plt.plot(X,Y,'b')
        x_left = x[:-1] # Left endpoints
        y_left = y[:-1]
        plt.plot(x_left,y_left,'b.',markersize=10)
        plt.bar(x_left, y_left,width=(self.b - self.a) / self.N, alpha=0.2, align='edge', edgecolor='b')
        plt.title('Left Riemann Sum, N = {}'.format(self.N))

        plt.subplot(1,3,2)
        plt.plot(X,Y,'b')
        x_mid = (x[:-1] + x[1:])/2 # Midpoints
        y_mid = self.f(x_mid)
        plt.plot(x_mid, y_mid, 'b.', markersize=10)
        plt.bar(x_mid, y_mid,width=(self.b - self.a) / self.N, alpha=0.2, edgecolor='b')
        plt.title('Midpoint Riemann Sum, N = {}'.format(self.N))

        plt.subplot(1, 3, 3)
        plt.plot(X, Y, 'b')
        x_right = x[1:] # Right endpoints
        y_right = y[1:]
        plt.plot(x_right, y_right,'b.', markersize=10)
        plt.bar(x_right, y_right,width=-(self.b - self.a) / self.N, alpha=0.2, align='edge', edgecolor='b')
        plt.title('Right Riemann Sum, N = {}'.format(self.N))

        plt.show()