import numpy as np
from numpy import linalg as LA

def covariance(x, y):
    x_mean = np.mean(x)
    y_mean = np.mean(y)
    x_diff = x - x_mean
    y_diff = y - y_mean
    cov_xy = np.sum(np.multiply(x_diff, y_diff))/(x.size-1)

    return cov_xy

class SingleValueDecompositon:

    def SVD(self, A):
        A = np.array(A)

        M1 = np.dot(A,A.T)

        M2 = np.dot(A.T,A)

        eig_val1, eig_vec1 = LA.eig(M1)
        sg_val = np.flip(np.sort(np.sqrt(eig_val1)))
        # U = eig_vec1[:,np.flip(np.argsort(eig_val1))]

        eig_val2, eig_vec2 = LA.eig(M2)
        # print(eig_vec2)
        V = eig_vec2[:,np.flip(np.argsort(eig_val2))]

        S = np.zeros(A.shape)
        for i in range(len(sg_val)):
            S[i][i] = sg_val[i]
        U=[]
        # print(np.dot(A,V[:,0].T))
        for idx, i in enumerate(sg_val):
            U.append(np.dot(A,V[:,idx].T)/i)
        U=np.array(U).T
        # print("U: ",U)
        # print("S: ",S)
        # print("V: ",V)
        # print("A:")
        A_r = np.dot(np.dot(U, S),V.T)

        # print(np.allclose(A,A_r))
        if np.allclose(A, A_r):
            return U, S, V

class TotalLeastSquare:
    def fit_line(self, x, y):
        x = np.array(x)
        y = np.array(y)

        x_mean = np.mean(x)
        y_mean = np.mean(y)
        U = np.vstack([x-x_mean, y-y_mean]).T
        A = np.dot(U.T, U)
        svd = SingleValueDecompositon()
        U, S, V = svd.SVD(A)
        a, b = V[:, V.shape[1]-1]
        d = a*x_mean + b*y_mean
        m = -a/b
        c = d/b

        return m, c

class LinearLeastSquare:

    def fit_line(self, x, y):
        x = np.array(x)
        y = np.array(y)
        x_square_sum = np.sum(np.square(x))

        x_mean = np.mean(x)
        y_mean = np.mean(y)
        x_sum = np.sum(x)
        y_sum = np.sum(y)
        xy_sum = np.sum(np.multiply(x,y))
        n = x.size
        ss_xx = x_square_sum - x_sum**2/n
        ss_xy = xy_sum - x_sum*y_sum/n

        m = ss_xy/ss_xx
        c = y_mean - m*x_mean

        return m,c
    def fit_parabola(self, x, y):
        x = np.array(x)
        y = np.array(y)
        x_square = np.square(x)
        A = np.vstack([x_square,x,np.ones(x.size)]).T
        Y = y.reshape(-1,1)
        beta = np.dot((np.dot(np.linalg.inv(np.dot(A.T,A)),A.T)),Y)
        return beta

class RANSAC():

    def computerError(self, x, y, m, c):
        error = y - m*x - c
        return error**2
    def fit_line(self, x, y, threshold, p=0.95, e=0.5, optimal_fit = False):
        N = int(np.ceil(np.log(1-p)/np.log(1 - np.power(1 - e, 2))))
        n = 2
        N = np.max([N, 50])
        x = np.array(x)

        y = np.array(y)
        m_best = 0
        c_best = 0
        n_inliers_max=0
        inlier_mask_best = np.zeros(x.size)
        for i in range(N):

            idxs_r = np.random.choice(x.size, size = n)

            x_r = x[idxs_r]
            y_r = y[idxs_r]

            ls = LinearLeastSquare()
            m, c = ls.fit_line(x_r, y_r)

            error_list = self.computerError(x, y, m, c)
            inlier_mask = error_list<threshold
            # print(error_list, inlier_mask)
            n_inliers = np.count_nonzero(error_list<threshold)
            # print("n_in", n_inliers)
            # print("n_max", n_inliers_max)
            if n_inliers > n_inliers_max:
                n_inliers_max = n_inliers
                m_best = m
                c_best = c
                inlier_mask_best = inlier_mask

            if n_inliers_max/x.size >= p:
                print("HERE")
                break
        # plt.scatter(x[np.invert(inlier_mask_best)], y[np.invert(inlier_mask_best)], color = 'tab:orange')
        # plt.scatter(x[inlier_mask_best], y[inlier_mask_best], color='b')
        # print(np.invert(inlier_mask_best))
        # print(m_best, c_best)
        # y1 = m_best*x + c_best
        # plt.plot(x, y1, color='r')
        # plt.show()
        if optimal_fit:
            m_best, c_best = ls.fit_line(x[inlier_mask_best], y[inlier_mask_best])
        return m_best, c_best, inlier_mask_best


from sympy import *

m1,m2,r1,r2,i1,i2,l1,l2,a1,a2,t,g = symbols('m1 m2 r1 r2 i1 i2 l1 l2 a1 a2 t g')

# positions
x1 = r1*sin(a1(t))
y1 = -r1*cos(a1(t))
x2 = l1*sin(a1(t)) + r2*sin(a1(t)+a2(t))
y2 = -l1*cos(a1(t)) - r2*cos(a1(t)+a2(t))

# velocities
x1d = diff(x1,t)
y1d = diff(y1,t)
x2d = diff(x2,t)
y2d = diff(y2,t)
a1d = diff(a1(t),t)
a2d = diff(a2(t),t)

# linear kinetic energy
Tlin1 = 0.5 * m1 * ((x1d*x1d) + (y1d*y1d))
Tlin2 = 0.5 * m2 * ((x2d*x2d) + (y2d*y2d))

# rotational kinetic energy
Trot1 = 0.5 * i1 * a1d * a1d
Trot2 = 0.5 * i2 * (a1d+a2d) * (a1d+a2d)

# total kinetic energy
T = Tlin1 + Tlin2 + Trot1 + Trot2

# potential energy
U1 = m1 * g * ( r1*(1-cos(a1(t))) )
U2 = m2 * g * ( l1*(1-cos(a1(t))) + r2*(1-cos(a1(t)-a2(t))) )
U = U1 + U2

# lagrangian L
L = T - U
L = nsimplify(L)

# compute generalized forces (toruqes) Qj
dldq1 = simplify(diff(L,a1(t)))
dldqd1 = simplify(diff(L,diff(a1(t),t)))
ddtdldqd1 = simplify(diff(dldqd1,t))
Q1 = ddtdldqd1 - dldq1

dldq2 = simplify(diff(L,a2(t)))
dldqd2 = simplify(diff(L,diff(a2(t),t)))
ddtdldqd2 = simplify(diff(dldqd2,t))
Q2 = ddtdldqd2 - dldq2

# simplify!
# converts floats that are really integers to integers, gets rid of "1.0"
Q1 = simplify(nsimplify(Q1))
Q2 = simplify(nsimplify(Q2))
# magic sauce to further simplify with some trigonometric identities
Q1 = Q1.rewrite(exp).expand().powsimp().rewrite(sin).expand()
Q2 = Q2.rewrite(exp).expand().powsimp().rewrite(sin).expand()
# collect derivative terms
Q1 = collect(Q1, Derivative(Derivative(a1(t),t),t))
Q1 = collect(Q1, Derivative(Derivative(a2(t),t),t))
Q2 = collect(Q2, Derivative(Derivative(a1(t),t),t))
Q2 = collect(Q2, Derivative(Derivative(a2(t),t),t))
# collect sin() terms
Q1 = collect(Q1, sin(a1(t)))
Q2 = collect(Q2, sin(a1(t)))

pprint(Q1)
pprint(Q2)


from math import e, pi
from typing import List

import numpy as np

def fft(p: List[complex]) -> List[complex]:
    # radix 2 Cooley-Tukey algorithm
    n = len(p)

    if not n and (not (n & (n - 1))):  # check if n is power of 2
        raise ValueError("This implementation requires arrays with length power of two")

    if n == 1:
        return p

    y_even, y_odd = fft(p[::2]), fft(p[1::2])

    omega = e ** (-2j * pi / n)  # primitive nth root of unity
    y = [0] * n
    for i in range(n // 2):
        y[i] = y_even[i] + omega ** i * y_odd[i]
        y[i + n // 2] = y_even[i] - omega ** i * y_odd[i]
    return y

def ifft(p: List[complex]) -> List[complex]:
    # Take complex conjugates
    p = [np.conj(x) for x in p]
    # Calculate normal fft
    p = fft(p)
    # Take complex conjugates again and scale by 1/n
    n = len(p)
    return [1 / n * np.conj(x) for x in p]

def test_fft_ifft():
    # Random 7th degree polynomial with coefficients from (-10, 10)
    x = np.random.rand(8) * 20 - 10

    # Verify fft gives same results as numpy's fft
    assert np.allclose(fft(x), np.fft.fft(x))
    assert np.allclose(ifft(x), np.fft.ifft(x))
    assert np.allclose(ifft(fft(x)), x)

if __name__ == "__main__":
    test_fft_ifft()


import numpy as np
import matplotlib.pyplot as plt

def map_lin_to_log(val, base):
    # it has to be defined with a min=0.0 and max=1.0
    return base**(1.0 - val)

def shift_normalize(val, base):
    # it has to be defined with min=0.0
    shift_val = map_lin_to_log(0.0, base)
    val -= shift_val
    # it has to be defined with max=1.0
    norm_val = map_lin_to_log(1.0, base) - shift_val
    val /= norm_val
    return val

def shape_function_lin(val): return 1-val

def shape_function_exp(val, exp): return (1-val)**exp

# TODO: implement checks for min=0.0 and max=1.0
# 0.0 pure shear -> less values
# 1.0 pure bending -> more values
my_exp = 3
my_power = 2
my_base = 10**my_power

alpha = np.linspace(0.0, 1.0, 25)

my_mapped_val = [shift_normalize(
    map_lin_to_log(val, my_base), my_base) for val in alpha]

# linear shape functions for linearly spaced values
n1_s = [shape_function_lin(val) for val in alpha]
n1_b = [1-val for val in n1_s]

# exponential shape functions for log spaced values
n2_s = [shape_function_exp(val, my_exp) for val in my_mapped_val]
n2_b = [1-val for val in n2_s]

#
for my_val in np.linspace(0.0, 1.0, 10):
    print('my_val: ', my_val)
    print('n2_s: ', shape_function_exp(shift_normalize(
        map_lin_to_log(my_val, my_base), my_base), my_exp))
    print('n2_b: ', 1-shape_function_exp(shift_normalize(
        map_lin_to_log(my_val, my_base), my_base), my_exp))
    print('\n')
#

plt.figure(1)
plt.title('Linear spacing mapped to log')
plt.plot(alpha, alpha, marker='o', label='original')
plt.plot(alpha, my_mapped_val, marker='<', label='mapped')
plt.legend()

plt.figure(2)
plt.title('Linear spacing and linear shape functions')
plt.plot(alpha, n1_b, marker='o', label='n1_b')
plt.plot(alpha, n1_s, marker='<', label='n1_s')
plt.legend()

plt.figure(3)
plt.title('Lin-to-log spacing and exp shape functions')
plt.plot(my_mapped_val, n2_b, marker='o', label='n2_b')
plt.plot(my_mapped_val, n2_s, marker='<', label='n2_s')
plt.legend()

plt.figure(4)
plt.title('Lin-to-log spacing and exp shape functions')
plt.semilogy(my_mapped_val, n2_b, marker='o', label='n2_b')
plt.semilogy(my_mapped_val, n2_s, marker='<', label='n2_s')
plt.legend()

plt.show()


import numpy as np

a = np.array([0, 30, 45, 60, 90])
print('不同角度的正弦值：')
# 通过乘 pi/180 转化为弧度
print(np.sin(a * np.pi / 180))
print('\n')
print('数组中角度的余弦值：')
print(np.cos(a * np.pi / 180))
print('\n')
print('数组中角度的正切值：')
print(np.tan(a * np.pi / 180))

a = np.array([0, 30, 45, 60, 90])
print('含有正弦值的数组：')
sin = np.sin(a * np.pi / 180)
print(sin)
print('\n')
print('计算角度的反正弦，返回值以弧度为单位：')
inv = np.arcsin(sin)
print(inv)
print('\n')
print('通过转化为角度制来检查结果：')
print(np.degrees(inv))
print('\n')
print('arccos 和 arctan 函数行为类似：')
cos = np.cos(a * np.pi / 180)
print(cos)
print('\n')
print('反余弦：')
inv = np.arccos(cos)
print(inv)
print('\n')
print('角度制单位：')
print(np.degrees(inv))
print('\n')
print('tan 函数：')
tan = np.tan(a * np.pi / 180)
print(tan)
print('\n')
print('反正切：')
inv = np.arctan(tan)
print(inv)
print('\n')
print('角度制单位：')
print(np.degrees(inv))

a = np.array([1.0, 5.55, 123, 0.567, 25.532])
print('原数组：')
print(a)
print('\n')
print('舍入后：')
print(np.around(a))
print(np.around(a, decimals=1))
print(np.around(a, decimals=-1))

a = np.array([-1.7, 1.5, -0.2, 0.6, 10])
print('提供的数组：')
print(a)
print('\n')
print('取整 修改后的数组：')
print(np.floor(a))

a = np.array([-1.7, 1.5, -0.2, 0.6, 10])
print('提供的数组：')
print(a)
print('\n')
print('向上取整 修改后的数组：')
print(np.ceil(a))


import numpy as np

def sigmoid(z):
	"""
	Compute a sigmoid function
	"""
	return 1.0 / (1.0 + np.exp(-z))

def sigmoid_grad(z):
	"""
	Compute the gradient of a sigmoid function
	"""
	sig_z = sigmoid(z)
	return sig_z * (1.0 - sig_z)

def cost_function(params, input_lyr_sz, hidden_lyr_sz, n_labels, X, y, lmbda):
	"""

	"""
	Theta1 = np.reshape(params[0:hidden_lyr_sz * (input_lyr_sz, + 1)], [hidden_lyr_sz, input_lyr_sz, + 1], order='F')
	Theta2 = np.reshape(params[hidden_lyr_sz * (input_lyr_sz + 1):], [n_labels, hidden_lyr_sz + 1], order='F')
	m = np.shape(X)[0]
	a1 = np.hstack((np.ones(m, 1), X))
	z2 = np.dot(a1, Theta1.T)
	a2 = sigmoid(z2)
	a2 = np.hstack((np.ones(m, 1), a2))
	z3 = np.dot(a2, Theta2.T)
	a3 = sigmoid(z3)
	ey = np.identity(n_labels)
	y = ey[y, :]
	reg_Theta1 = Theta1[:,1:]
	reg_Theta2 = Theta2[:,1:]
	reg = (lmbda / (2.0 * m)) * np.sum(np.ravel(reg_Theta1) ** 2) + np.sum(np.ravel(reg_Theta2 ** 2))
	J = (1.0 / m) + np.sum(np.sum(np.multiply(-y, np.log(a3)) - np.multiply(1 - y, np.log(1 - a3)))) + reg
	d3 = a3 - y
	d2 = np.multiply(np.dot(d3, Theta2[:,1:]), sigmoid_grad(z2))
	Theta1_grad = (1 / m) * (np.dot(d2.T, a1) + lmbda * Theta1)
	Theta2_grad = (1 / m) * (np.dot(d3.T, a2) + lmbda * Theta2)
	reg1 = np.multiply(lmbda / m, Theta1[:,0])
	reg2 = np.multiply(lmbda / m, Theta2[:,0])
	Theta1_grad[:,0] = Theta1_grad[:,0] - reg1;
	Theta2_grad[:,0] = Theta2_grad[:,0] - reg2;
	grad = np.vstack((np.ravel(Theta1_grad), np.ravel(Theta2_grad)))
	return J, grad

def predict(Theta1, Theta2, X):
	"""

	"""
	m = np.shape(X)[0]
	h1 = sigmoid(np.dot(np.hstack((np.ones(m, 1), X)), Theta1.T))
	h2 = sigmoid(np.dot(np.hstack((np.ones(m, 1), h1)), Theta2.T))
	return np.argmax(h2, axis=1)

def rand_init_weights(L_in, L_out, epsilon):
	"""

	"""
	return np.rand(L_out, 1 + L_in) * 2 * epsilon - epsilon


import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from matplotlib.ticker import FormatStrFormatter
from matplotlib.ticker import LinearLocator

from mpl_toolkits.mplot3d.axes3d import Axes3D

def f(x0: np.ndarray, x1: np.ndarray) -> np.ndarray:
    """Rosenbrock Funktion."""
    return 100 * (x0 ** 2 - x1) ** 2 + (x0 - 1) ** 2

def f_prime_x0(x0: np.ndarray, x1: np.ndarray) -> np.ndarray:
    """Derivative of f w.r.t. x0."""
    return 2 * (200 * x0 * (x0 ** 2 - x1) + x0 - 1)

def f_prime_x1(x0: np.ndarray, x1: np.ndarray) -> np.ndarray:
    """Derivative of f w.r.t. x0."""
    return -200 * (x0 ** 2 - x1)

def plot_rosenbrock(x_start: np.ndarray, gradient_steps: list = None) -> None:
    """Plot the gradient steps."""
    fig = plt.figure(figsize=(12, 8))
    ax = Axes3D(fig)

    s = 0.3
    X = np.arange(-2, 2.0 + s, s)
    Y = np.arange(-2, 3.0 + s, s)

    # Create the mesh grid(s) for all X/Y combos.
    X, Y = np.meshgrid(X, Y)
    # Rosenbrock function w/ two parameters using numpy Arrays
    Z = f(X, Y)

    surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, linewidth=0, alpha=0.8, cmap=cm.coolwarm)
    # Global minimum
    ax.scatter(1, 1, f(1, 1), color="red", marker="*", s=200)
    # Starting point
    x0, x1 = x_start
    ax.scatter(x0, x1, f(x0, x1), color="green", marker="o", s=200)

    # Eps off set of the z axis, to plot the points above the surface for better visualization
    eps = 50
    if gradient_steps:
        for (x0, x1) in gradient_steps:
            ax.scatter(x0, x1, f(x0, x1) + eps, color="green", marker="o", s=25)

    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")
    ax.zaxis.set_major_locator(LinearLocator(10))
    ax.zaxis.set_major_formatter(FormatStrFormatter("%.02f"))
    fig.colorbar(surf, shrink=0.5, aspect=5)
    plt.show()


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

def dist(a, b):
  return (a - b) ** 2

def dtw(a, b):
  m = len(a)
  n = len(b)
  f = [[0] * n for i in range(m)]
  trace = [[0] * n for i in range(m)]

  f[0][0] = dist(a[0], b[0])
  trace[0][0] = (-1, -1)

  for i in range(1, m):
    f[i][0] = f[i - 1][0] + dist(a[i], b[0])
    trace[i][0] = (i - 1, 0)

  for i in range(1, n):
    f[0][i] = f[0][i - 1] + dist(a[0], b[i])
    trace[0][i] = (0, i - 1)

  for i in range(1, m):
    for j in range(1, n):
      if f[i - 1][j] < f[i][j - 1] and f[i - 1][j] < f[i - 1][j - 1]:
        f[i][j] = f[i - 1][j]
        trace[i][j] = (i - 1, j)
      elif f[i][j - 1] < f[i - 1][j] and f[i][j - 1] < f[i - 1][j - 1]:
        f[i][j] = f[i][j - 1]
        trace[i][j] = (i, j - 1)
      else:
        f[i][j] = f[i - 1][j - 1]
        trace[i][j] = (i - 1, j - 1)
      f[i][j] += dist(a[i], b[j])

  i, j = (m - 1, n - 1)
  path = []

  while not (i == -1):
    path.append((i, j))
    (i, j) = trace[i][j]

#  print "DTW dist = ", math.sqrt(f[m - 1][n - 1])
#  print "Path: ", path
#  valDist = 0
#  for i in range(len(path)):
#      valDist += dist(a[path[i][0]], b[path[i][1]])
#  print "Val dist = ", math.sqrt(valDist)
  return math.sqrt(f[m - 1][n - 1]), path

def plot_dtw(a, b, alabel, blabel, path, distance, offset=1):
  na = len(a)
  nb = len(b)

  xa = range(0, na)
  xb = np.arange(0, nb) + offset

  pairs = []
  path_x = []
  path_y = []
  for t in path:
    pairs.append([xa[t[0]], xb[t[1]]])
    pairs.append([a[t[0]], b[t[1]]])
    path_x.append(t[0])
    path_y.append(t[1])

  plt.plot(color='k', linestyle='--', linewidth=1, *pairs)
  plt.plot(xa, a, color='b', linestyle='-.', marker='o', linewidth=2, label=alabel)
  plt.plot(xb, b, color='r', linewidth=2, marker='x', label=blabel)
  plt.title('DTW distance = %.2f' % distance, fontsize=18)
  plt.legend(fontsize=13)


# A study on optimizing revenue from a website considered dividing customers into two groups based on a value x between 0 and 1,
# where x measures the proportion of the total bandwidth requested by a customer.
# Customers with a request less than x were considered low revenue, and those above x high revenue.
# The expected revenue from the low revenue customers was described by the following function,
# where C and k are positive constants based on attributes of the website and the customers.

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

domain_end = 20

g_xlim = [0, 40]
g_ylim = [0, 70]

# eq

x = sp.Symbol( 'x' )
C, k = sp.symbols( 'C k', positive = True )
R = C * k * ( 1 - np.e**( -k*x ) )
dR = diff( R, x )

lam_x = sp.lambdify( x, R, modules=[ "numpy", "sympy" ] )

x_vals = range = np.arange( 0.001, 1, 1/20.)
y_vals = lam_x( x_vals )


import numpy as np
import seaborn.apionly as sns
import matplotlib.pyplot as plt

sns.set(style="whitegrid", context="notebook")

iris2 = sns.load_dataset('iris')

def covariance(X, Y):
    xhat = np.mean(X)
    yhat = np.mean(Y)
    epsilon = 0
    for x, y in zip(X, Y):
        epsilon = epsilon + (x - xhat) * (y - yhat)
    return epsilon / (len(X) - 1)

# Testing results agains existing function
print("My covariance function: {}".format(covariance([1, 3, 4], [1, 0, 2])))
print("Numpy covariance function: {}".format(np.cov([1, 3, 4], [1, 0, 2])))

def correlation(X, Y):
    return (covariance(X, Y) / (np.std(X, ddof=1) * np.std(Y, ddof=1)))  # we had to indicat ddof=1 the unbiased std

print("My Correlation: {}".format(correlation([1, 1, 4, 3], [1, 0, 2, 2])))
print("Numpy corrcoef: {}".format(np.corrcoef([1, 1, 4, 3], [1, 0, 2, 2])))

# Start seeing a general view of the data to try to determine what is the best approach

sns.pairplot(iris2, height=3.0)
plt.show()
# Based on the results, we chose
X = iris2['petal_width']
Y = iris2['petal_length']

plt.scatter(X, Y)

# plt.show()

# Creating the prediction function
def predict(alpha, beta, x_i):
    return beta * x_i + alpha

# Defining the error function
def error(alpha, beta, x_i, y_i):  # L1
    return y_i - predict(alpha, beta, x_i)

def sum_sq_e(alpha, beta, x, y):  # L2
    return sum(error(alpha, beta, x_i, y_i) ** 2 for x_i, y_i in zip(x, y))

# Now we define a function implementing the correlation method to find the parameters for our regression
def correlation_fit(x, y):
    beta = correlation(x, y) * np.std(y, ddof=1) / np.std(x, ddof=1)
    alpha = np.mean(y) - beta * np.mean(x)
    return alpha, beta

alpha, beta = correlation_fit(X, Y)
print("Alpha: {}".format(alpha))
print("Beta: {}".format(beta))

plt.scatter(X, Y)
xr = np.arange(0, 3.5)
plt.plot(xr, (xr * beta) + alpha)
plt.show()


import colorsys
import numpy as np
from matplotlib import image

def pca(samples):
    """
    :param [in] samples: N-by-M matrix; each row is a M-dimensional sample
        NOTE: samples should be normalized: for every dimension, avg = 0 and std = 1
    :return (importance, newBases, newCoords)
        -   importance: M array; covar / importance of each new basis
        -   newBases: M-by-M matrix; each col is a new basis
        -   newCoords: N-by-M matrix; each row is the new coord of the original sample
                expressed with the new bases. A.k.a. score; loading
        NOTE: the original samples could be reconstructed as
                samples = newCoords * newBases.T
    """
    (sampleCount, coordLength) = samples.shape
    # NOTE: using "sample covar" here (divided by n-1 instead of n)
    covar = np.matmul(samples.T, samples).astype(np.double) / (sampleCount - 1)

    # Singular-value decomposition gives the eigenvalues (S), left-eigenvectors (U)
    # and right-eigenvectors (V)
    # since we put newBases on the right hand side of newCoords, we need the right-
    # eigenvectors
    U, S, VT = np.linalg.svd(covar)
    # col of V are principal directions
    V = VT.T

    # A eigenvalue represents the covar explained by this principal component
    # more ability to explain covar --> higher importance
    importance = S
    newBases = V
    newCoords = np.matmul(samples, V)

    return (importance, newBases, newCoords)

def loadGrayscaleImage(filename):
    img = image.imread(filename)

    # color image: (rows, cols, channels)
    if (len(img.shape) > 2):
        print("converting to grayscale")
        # convert to grayscale ("Y"/luma in YIQ)
        img = colorsys.rgb_to_yiq(r=img[:, :, 0], g=img[:, :, 1], b=img[:, :, 2])[0]
    return img


import matplotlib.pyplot as plt

import numpy as np

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

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

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

    i = 0
    sum = 0
    #sum=fun(x)+fun(y)
    while i <= n:
        if i == 0:
            sum = sum + fun(x)
        elif i == n:
            sum = sum + fun(y)
        elif i % 2 == 0:
            sum = sum + 2 * fun(x + h * i)
        else:
            sum += 4 * fun(x + h * i)
        i += 1
    return (h / 3) * sum

from scipy.integrate import quad

def integrate(p):
    return 5 * p * np.exp(-2 * p)

exact_value = quad(integrate, 0.1, 1.3)
print(" The true value of integration is ", "%.5f" % exact_value[0])
result_38 = simpson_third(x, y, n)

print(" The value of integration is ", "%.5f" % result_38)

abs_err = ( result_38 - exact_value[0])

print(" error is: ",abs_err)

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


# 'coordinate_utility.py' contains a number of coordinate transformation functions.

import numpy as np

def spherical_2_cartesian(r, phi, theta, units='degrees'):
    """Converts spherical polar coordinates into cartesian coordinates.

    Parameters
    ----------
    r : array
        Radial distance.
    phi : array
        Longitudinal coordinates (radians = [0, 2*pi]).
    theta : array
        Latitude coordinates (radians = [0, pi]).
    units : str
        Units of phi and theta given in either degrees or radians.

    Returns
    -------
    x, y, z : array
        'euclidean': euclidean coordinates.
    """
    phi = np.copy(phi)
    theta = np.copy(theta)
    if units == 'degrees':
        phi, theta = np.deg2rad(phi), np.deg2rad(theta)
    elif units == 'radians':
        pass
    else:
        raise AssertionError("Unexpected value entered for 'units', only supports either degrees or radians", units)
    x = r * np.cos(phi) * np.sin(theta)
    y = r * np.sin(phi) * np.sin(theta)
    z = r * np.cos(theta)
    return x, y, z

def celestial_2_cartesian(r, ra, dec, units='degrees', output='both'):
    """Converts spherical polar coordinates into cartesian coordinates.

    Parameters
    ----------
    r : array
        Radial distance.
    ra : array
        Longitudinal celestial coordinates.
    dec : array
        Latitude celestial coordinates.
    units : str
        Units of ra and dec given in either degrees or radians.
    output : {'cartesian', 'both'}, optional
        Determines whether to output only the cartesian or both cartesian and spherical coordinates.

    Returns
    -------
    phi, theta : array
        'spherical': spherical polar coordinates.
    x, y, z : array
        'cartesian': euclidean coordinates.
    """
    phi = np.copy(ra)
    theta = np.copy(dec)
    if units == 'degrees':
        phi, theta = np.deg2rad(phi), np.deg2rad(theta)
    elif units == 'radians':
        pass
    else:
        raise AssertionError("Unexpected value entered for 'units', only supports either degrees or radians", units)
    theta = np.pi / 2. - theta
    if output == 'spherical':
        return phi, theta
    else:
        x = r * np.cos(phi) * np.sin(theta)
        y = r * np.sin(phi) * np.sin(theta)
        z = r * np.cos(theta)
        if output == 'cartesian':
            return x, y, z
        elif output == 'both':
            return phi, theta, x, y, z
        else:
            raise AssertionError("Unexpected value entered for 'output', should be either 'cartesian' or 'both'.",
                                 output)

def spherical_2_unit_sphere(phi, theta, units='degrees'):
    """Project coordinates on a sphere into cartesian coordinates on a unit sphere.

    Parameters
    ----------
    phi : array
        Longitudinal coordinates (radians = [0, 2*pi]).
    theta : array
        Latitude coordinates (radians = [0, pi]).
    units : {'degrees', 'radians'}, optional
        Units of phi and theta given in either 'degrees' or 'radians'.

    Returns
    -------
    x, y, z : array
        cartesian coordinates.
    """
    if np.isscalar(phi) is True:
        return spherical_2_cartesian(1., phi, theta, units=units)
    else:
        return spherical_2_cartesian(np.ones(len(phi)), phi, theta, units=units)

def celestial_2_unit_sphere(ra, dec, units='degrees', output='both'):
    """Project coordinates on a sphere into cartesian coordinates on a unit sphere.

    Parameters
    ----------
    ra : array
        Longitudinal celestial coordinates.
    dec : array
        Latitude celestial coordinates.
    units : {'degrees', 'radians'}, optional
        Units of ra and dec given in either 'degrees' or 'radians'.
    output : {'cartesian', 'both'}, optional
        Determines whether to output only the euclidean or both euclidean and spherical coordinates.

    Returns
    -------
    phi, theta : array
        'spherical': spherical polar coordinates.
    x, y, z : array
        'cartesian': cartesian coordinates.
    """
    if np.isscalar(ra) is True:
        return celestial_2_cartesian(1., ra, dec, units=units, output=output)
    else:
        return celestial_2_cartesian(np.ones(len(ra)), ra, dec, units=units, output=output)

def perpendicular_distance_2_angle(distance):
    """Converts distances on a unit sphere to angular distances projected across a unit sphere.

    Parameters
    ----------
    distance : array
        Perpendicular distances across (i.e. going on the surface) of a unit sphere.

    Returns
    -------
    angular_distance : array
        The angular distance of points across a unit sphere.
    """
    angular_distance = 2. * np.arcsin(distance / 2.)
    return angular_distance


import numpy as np
import matplotlib.pyplot as plt
import math
from KnotVectors import OpenUniformKnotVector, PeriodicKnotVector

def CoxDeBoorRecursion(i, order, Knots, t):
    if order == 1:
        return Knots[i] <= t and t < Knots[i + 1]
    else:
        ftt = (t - Knots[i]) * CoxDeBoorRecursion(i, order - 1, Knots, t)
        ftb = Knots[i + order - 1] - Knots[i]
        stt = (Knots[i + order] - t) * CoxDeBoorRecursion(i + 1, order - 1, Knots, t)
        stb = Knots[i + order] - Knots[i + 1]

        first = ftt / ftb
        if math.isnan(first):
            first = 0.

        second = stt / stb
        if math.isnan(second):
            second = 0.

        return first + second

def BasisGraph(Knots, order, i, T):
    values = []
    for t in T:
        values.append(CoxDeBoorRecursion(i, order, Knots, t))
    return np.asarray(values)

if __name__ == "__main__":
    order = 3
    countPoints = 4
    Knots = OpenUniformKnotVector(order, countPoints, True)

    T = np.arange(0., 1.0, 0.01)

    for i in range(countPoints):
        plt.plot(T, BasisGraph(Knots, order, i, T))

    plt.show()

    Knots = PeriodicKnotVector(order, countPoints, True)

    for i in range(countPoints):
        plt.plot(T, BasisGraph(Knots, order, i, T))

    plt.show()


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

np.seterr(all='raise')
factor = 2.0

class LinearModel(object):
    """Base class for linear models."""

    def __init__(self, theta=None):
        """
        Args:
            theta: Weights vector for the model.
        """
        self.theta = theta

    def fit(self, X, y):
        """Run solver to fit linear model. You have to update the value of
        self.theta using the normal equations.

        Args:
            X: Training example inputs. Shape (n_examples, dim).
            y: Training example labels. Shape (n_examples,).
        """
        #(X.TX)x = X.Ty
        a = np.dot(X.transpose(), X)
        b = np.dot(X.transpose(), y)
        self.theta = np.linalg.solve(a, b)

    def create_poly(self, k, X):
        """
        Generates a polynomial feature map using the data x.
        The polynomial map should have powers from 0 to k
        Output should be a numpy array whose shape is (n_examples, k+1)

        Args:
            X: Training example inputs. Shape (n_examples, 2).
        """
        polynomial = X[:, 0]
        for power in range(1 , k + 1):
            polynomial = np.column_stack((polynomial, np.power(X[:,1], power)))
        return polynomial

    def create_sin(self, k, X):
        """
        Generates a sin with polynomial featuremap to the data x.
        Output should be a numpy array whose shape is (n_examples, k+2)

        Args:
            X: Training example inputs. Shape (n_examples, 2).
        """
        polynomial = X[:, 0]
        for power in range(1 , k + 1):
            polynomial = np.column_stack((polynomial, np.power(X[:,1], power)))
        polynomial = np.column_stack((polynomial, np.sin(X[:,1])))
        return polynomial

    def predict(self, X):
        """
        Make a prediction given new inputs x.
        Returns the numpy array of the predictions.

        Args:
            X: Inputs of shape (n_examples, dim).

        Returns:
            Outputs of shape (n_examples,).
        """
        predictions = np.dot(X, self.theta)
        return predictions

def run_exp(train_path, sine=False, ks=[1, 2, 3, 5, 10, 20], filename='plot.png'):
    train_x, train_y=util.load_dataset(train_path,add_intercept=True)
    plot_x = np.ones([1000, 2])
    plot_x[:, 1] = np.linspace(-factor*np.pi, factor*np.pi, 1000)
    plt.figure()
    plt.scatter(train_x[:, 1], train_y)
    for k in ks:
        '''
        Our objective is to train models and perform predictions on plot_x data
        '''
        linearModel = LinearModel()
        if sine == True:
            polynomial = linearModel.create_sin(k, train_x)
        else:
            polynomial = linearModel.create_poly(k, train_x)
        linearModel.fit(polynomial, train_y)
        #Predictions
        if sine == True:
            polynomial = linearModel.create_sin(k, plot_x)
        else:
            polynomial = linearModel.create_poly(k, plot_x)
        plot_y = linearModel.predict(polynomial)
        '''
        Here plot_y are the predictions of the linear model on the plot_x data
        '''
        plt.ylim(-2, 2)
        plt.plot(plot_x[:, 1], plot_y, label='k=%d' % k)

    plt.legend()
    plt.savefig(filename)
    plt.clf()

def main(train_path, val_path):
    run_exp(train_path, sine=True, filename='train_sine.png')
    run_exp(train_path, sine=False, filename='train_poly.png')
    run_exp(val_path, sine=True, filename='eval_sine.png')
    run_exp(val_path, sine=False, filename='eval_poly.png')

if __name__ == '__main__':
    main(train_path='train.csv', val_path='valid.csv')


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

#Numpy practice

#importing numpy

import numpy as np

# Create an array of 10 zeros

tza = np.zeros(10)

# Create an array of 10 ones

tna = np.ones(10)

 # Create an array of 10 Fives

tfa = np.ones(10) *5

# Create an array of integers from 10 - 50

aa = np.arange(10,51)

# Create a 3x3 matrix with values ranging from 0 to 8

ab = np.arange(9).reshape(3,3)

# Create a 3x3 Identity Matrix

ac = np.eye(3)

# Create a random number between 0 and 1

ad = np.random.rand(1)

# use numpy to generate an array of 25 random numbers from normal distribution

ae = np.random.randn(25)

# create a 10 by 10 matrix with values between 0 - 1

af = np.arange(0,100)/100
af.reshape(10,10)

# create an array of 20 linearly spacedpoints between 0 and 1

ag = np.linspace(0,1,20)

# matrix related operations

mat = np.arange(1,26).reshape(5,5)

#rows and columns selections within a matrix

#mat[2:,:]

# Sum of all values in a matrix

sumofmat = np.sum(mat)

# Get standard deviation of the values in mat

stdofmat = np.std(mat)

#get sum of all comlums in mat

sumofcolmat = mat.sum(axis = 0)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 25 10:59:01 2020

@author: Jean-Philippe Kuntzer
"""

import numpy as np
import matplotlib.pyplot as plt

def main():
    x = np.linspace(-1,1,10000)
    xp = np.abs(x)
    y1 = phi(xp,'L')
    y2 = phi(xp,'TPS')
    y3 = phi(xp,'G')
    y4 = phi(xp,'HMQ')
    y5 = phi(xp,'HIMQ')
    y6 = phi(xp,'C0')
    y7 = phi(xp,'C2')
    y8 = phi(xp,'C4')
    y9 = phi(xp,'C6')
    y10 = phi(xp,'EH')

    # linewidth
    lwdth = 10
    titleSize = 28
    labelSize = 20

    fig = plt.figure()
    ax = plt.subplot(111)
    ax.plot(x, y1, label='$Linear: \phi = r $', linewidth=lwdth)
    # ax.plot(x, y2, label='$\phi = r^2 ln(r) $', linewidth=lwdth)
    ax.plot(x, y3, 'r', label='Gaussian: $\phi = e^{-\epsilon r^2}$', linewidth=lwdth)
    # ax.plot(x, y4, label='$\phi = \sqrt{1+(\epsilon r)^2}$', linewidth=lwdth)
    # ax.plot(x, y5, label='$\phi = (\sqrt{1+(\epsilon r)^2})^{-1}$', linewidth=lwdth)
    ax.plot(x, y6, label='Wendland $C_0: \phi = (1−r)^2$', linewidth=lwdth)
    # ax.plot(x, y7, label='$\phi = (1−r)^4 (4r+ 1)$', linewidth=lwdth)
    # ax.plot(x, y8, label='$\phi = (1−r)^6 (35r^2+ 18r+ 3)$', linewidth=lwdth)
    # ax.plot(x, y9, label='$\phi = (1−r)^8 (32r^3+ 25r^2+ 8r+ 1)$', linewidth=lwdth)
    # ax.plot(x, y10, label='Euclid hat $ \phi = \pi*((1/12 r^3) - r \epsilon^2 + 4/3 \epsilon^3)$', linewidth=lwdth)

    plt.title('Common radial basis functions', fontsize=titleSize)
    ax.legend(loc='upper right',fontsize=labelSize)
    plt.xlabel('x',fontsize=labelSize)
    plt.ylabel('$\phi(x)$',fontsize=labelSize)
    plt.xticks(fontsize=labelSize)
    plt.yticks(fontsize=labelSize)
    plt.show()

# φ(r) =
# φ(r) = (1−r)4(4r+ 1)
# φ(r) = (1−r)6(35r2+ 18r+ 3)
# φ(r) = (1−r)8(32r3+ 25r2+ 8r+ 1)

def phi(r,fun):
    """
    Set of radial basis functions that the user can choose of. After some
    test "Wendland C2" seems to be the better choice, but this is really
    up to user preference.
    """
    eps = 1
    # r = np.linalg.norm(x1-x2)
    if fun == "G":
        # Gaussian
        phi_x = np.exp(-eps*r**2)
    elif fun == "L":
        # Linear
        phi_x = r
    elif fun == 'cubic':
        # Cubic
        phi_x = r**3
    elif fun == "TPS":
        # Thin plate spline
        phi_x = r**2 * np.log(r)
    elif fun == "Po4":
        # Polyharmonic spline
        phi_x = r**3 * np.log(r**r)
    elif fun == "Po4":
        # Polyharmonic spline
        phi_x = r**5 * np.log(r**r)
    elif fun == "HMQ":
        # Hardy's multiquadratic
        phi_x = (eps**2 + r**2)**0.5
    elif fun == "HIMQ":
        # Hardy's inverse multiquadratic
        phi_x = 1/(eps**2 + r**2)**0.5
    elif fun == "C0":
        # Wendland's C0
        phi_x = (1-r)**2
    elif fun == "C2":
        # Wendland's C2
        phi_x = (1-r)**4 * (4*r + 1)
    elif fun == "C4":
        # Wendland's C4
        phi_x = (1-r)**6 * (35*r**2 + 18*r + 3)
        # print('C4')
    elif fun == "C6":
        # Wendland's C6
        phi_x = (1-r)**8 * (32*r**3 + 25*r**2 + 8*r + 1)
    elif fun == "EH":
        # Euclid's hat
        phi_x = np.pi*((1/12*r**3) - r*eps**2 + 4/3*eps**3)
    return phi_x

if __name__ == "__main__":
    main()


import numpy as np
from math import pow,exp
# NumPy provides familiar mathematical functions such as sin, cos, and exp. In NumPy, these are
# called “universal functions”(ufunc). Within NumPy, these functions operate elementwise on an
# array, producing an array as output.

# exp Return e raised to the power of x. where e = 2.71828183
# both are almost same there is minor difference between both
print("<---- exp function explanation ---->")
print("Power : ",pow(2.71828183,2))
print("Exponential : ",exp(2))

print("<---- Universal functions ---->")
a = np.arange(3)
print("a Array : ",a)
print("exp of a : ",np.exp(a))
print("sqrt of a : ",np.sqrt(a))
b = np.array([4,5,-6])
print("add : ",np.add(a,b))


import pandas as pd
import numpy as np

"""object oriented path generator, based on numpy and pandas"""

class GeometricBrownianMotion:
    """simulate simple geometric brownian motion path with drift term and volatility term to be constant"""
    """ dS_t = mu*S_t*dt + sigma*S_t*dW_t, W_t stands for standard Brownian motion"""
    """https://en.wikipedia.org/wiki/Geometric_Brownian_motion"""
    def fit(self,mu,sigma):
        self.mu=mu
        self.sigma=sigma
    def __condition_mean(self,S_pre,mu_pre,sigma_pre,time_step):
        """
        param S_pre: previous spot price
        param mu_pre: previous drift rate
        param sigma_pre: previous volatility
        param time_step: constant time step, e.g dt
        return: the condition mean given previous spot, drift, and volatility
        """
        return np.log(S_pre) + (mu_pre - 0.5*sigma_pre**2)*time_step
    def __condition_std(self,sigma_pre,time_step):
        """
        param sigma_pre: previous volatility
        param time_step: constant time step, e.g dt
        return: the condition std given volatility and time step
        """
        return np.sqrt(sigma_pre**2*time_step)

    def simulate(self,s0,time_step,T,N=10000,Random_State=42):
        """
        param s0: initial stock spot price
        param time_step: constant time step, e.g dt
        param T: time to maturity
        param N: number of samples to generate
        param Random_State: fixed random state for simulation
        return: stock prices follow geometric Brownian motion from time 0 to time T
        """
        self.T=T
        time_steps = np.arange(0,T+time_step,time_step,dtype=float)
        rand = np.random.RandomState(Random_State)
        samples = np.empty((N,len(time_steps)))
        samples[:,0]=s0 # set initial value

        """The next stock price log-normal distributed given previous"""
        for i in range(1,len(time_steps)):
            samples[:,i] = np.exp(
                rand.normal(
                    self.__condition_mean(S_pre=samples[:,i-1],mu_pre=self.mu,sigma_pre=self.sigma,time_step=time_step),
                    self.__condition_std(sigma_pre=self.sigma,time_step=time_step)
                )
            )
        return pd.DataFrame(samples,index=range(1,N+1),columns=time_steps)


import numpy as np

def l2distance(X,Z=None):
    '''
    Compute the Euclidean distance matrix.
    Syntax:
    D=l2distance(X,Z)
    Input:
    X: nxd data matrix with n vectors (rows) of dimensionality d
    Z: mxd data matrix with m vectors (rows) of dimensionality d

    Output:
    Matrix D of size nxm
    D(i,j) is the Euclidean distance of X(i,:) and Z(j,:)

    call with only one input:
    l2distance(X)=l2distance(X,X)
    '''
    if Z is None:
        Z=X

    n,d1=X.shape
    m,d2=Z.shape
    assert (d1==d2), "Dimensions of input vectors must match!"

    G = X @ Z.T
    S = np.tile(np.diag(X @ X.T), (m,1)).T
    R = np.tile(np.diag(Z @ Z.T), (n,1))

    D2 = S + R - 2 * G
    D = D2.copy()
    D[D < 0.0000000001] = 0
    D = np.sqrt(D)
    return D

def findknn(xTr,xTe,k):
    '''
    Find the k nearest neighbors of xTe in xTr.

    Input:
    xTr = nxd input matrix with n row-vectors of dimensionality d
    xTe = mxd input matrix with m row-vectors of dimensionality d
    k = number of nearest neighbors to be found

    Output:
    indices = kxm matrix, where indices(i,j) is the i^th nearest neighbor of xTe(j,:)
    dists = Euclidean distances to the respective nearest neighbors
    '''

    dists_all = l2distance(xTr, xTe)
    indices = np.argsort(dists_all, axis = 0)[:k]
    dists = np.sort(dists_all, axis = 0)[:k]

    return indices, dists

def accuracy(truth,preds):
    '''
    Analyze the accuracy of a prediction against the ground truth

    Input:
    truth = n-dimensional vector of true class labels
    preds = n-dimensional vector of predictions

    Output:
    accuracy = scalar (percent of predictions that are correct)
    '''

    truth = truth.flatten()
    preds = preds.flatten()

    return (sum(truth == preds)/len(truth))

def mode(datalist):
    '''
    Find the mode of a list of data
    '''
    contents = set(datalist)
    maximum = 0
    for c in contents:
        num = datalist.count(c)
        if num > maximum:
            maximum = num
            m = c
    return m

def modeC(array):
    '''
    Find the mode in each column of an array
    '''
    m = np.zeros(array.shape[1])
    for i in range(array.shape[1]):
        m[i] = mode(list(array[:,i]))
    return m

def knnclassifier(xTr,yTr,xTe,k):
    '''
    k-nn classifier

    Input:
    xTr = nxd input matrix with n row-vectors of dimensionality d
    xTe = mxd input matrix with m row-vectors of dimensionality d
    k = number of nearest neighbors to be found

    Output:

    preds = predicted labels, ie preds(i) is the predicted label of xTe(i,:)
    '''

    yTr = yTr.flatten()

    ind, dist = findknn(xTr,xTe,k)

    return (modeC(yTr[ind]))


"""
Solver for single-stage, zero-sum matrix-form games using scipy default
linear programming routines.

Original by Matthew Farrugia-Roberts, 2021

Students
* please note this implementation is not guaranteed to be free of errors,
  for example it has not been extensively tested.
* please report bugs to <matt.farrugia@unimelb.edu.au>.
* please feel free adapt for your own use-case.
"""

import numpy as np
import scipy.optimize as opt

def solve_game(V, maximiser=True, rowplayer=True):
    """
    Given a utility matrix V for a zero-sum game, compute a mixed-strategy
    security strategy/Nash equilibrium solution along with the bound on the
    expected value of the game to the player.
    By default, assume the player is the MAXIMISER and chooses the ROW of V,
    and the opponent is the MINIMISER choosing the COLUMN. Use the flags to
    change this behaviour.

    Parameters
    ----------
    * V: (n, m)-array or array-like; utility/payoff matrix;
    * maximiser: bool (default True); compute strategy for the maximiser.
        Set False to play as the minimiser.
    * rowplayer: bool (default True); compute strategy for the row-chooser.
        Set False to play as the column-chooser.

    Returns
    -------
    * s: (n,)-array; probability vector; an equilibrium mixed strategy over
        the rows (or columns) ensuring expected value v.
    * v: float; mixed security level / guaranteed minimum (or maximum)
        expected value of the equilibrium mixed strategy.

    Exceptions
    ----------
    * OptimisationError: If the optimisation reports failure. The message
        from the optimiser will accompany this exception.
    """
    V = np.asarray(V)
    # lprog will solve for the column-maximiser
    if rowplayer:
        V = V.T
    if not maximiser:
        V = -V
    m, n = V.shape
    # ensure positive
    c = -V.min()
    Vpos = V + c
    # solve linear program
    res = opt.linprog(
        np.ones(n),
        A_ub=-Vpos,
        b_ub=-np.ones(m),
    )
    if res.status:
        raise OptimisationError(res.message) # TODO: propagate whole result
    # compute startegy and value
    v = 1 / res.x.sum()
    s = res.x * v
    v = v - c # re-scale
    if not maximiser:
        v = -v
    return s, v

class OptimisationError(Exception):
    """For if the optimiser reports failure."""

if __name__ == "__main__":
    # Rock paper scissors example (row player, maximiser)
    print("test: rock paper scissors")
    RPS = np.array([
        [  0, -1, +1 ],
        [ +1,  0, -1 ],
        [ -1, +1,  0 ],
    ])
    print("game:", *RPS, sep="\n ")
    print("soln:", *solve_game(RPS))
    print("true:", np.array([1/3, 1/3, 1/3]), 0.0)
    print()

    print("test: textbook example")
    # Hespanha textbook example (column player, minimiser)
    A = np.array([
        [  3,  0 ],
        [ -1,  1 ],
    ])
    print("game:", A, sep="\n")
    print("soln:", *solve_game(A, maximiser=False, rowplayer=False))
    print("true:", np.array([1/5, 4/5]), 3/5)
    print()


"""
Defines functions to manipulate matrices.
"""

import numpy as np
import scipy.linalg as la

def diagonalise(A):
    """Diagonalise a matrix.

    Parameters:
        A (ndarry): A square matrix.

    Returns:
        ndarray: A diagonalised matrix.
    """
    eigenvals, eigenvecs = la.eig(A)
    return np.diag(eigenvals)

def matrixOfEigenvec(A):
    """Calculate a matrix of eigenvectors.
    Parameters:
        A (ndarray): A square matrix.

    Returns:
        ndarray: A matrix of eigenvectors
    """
    eigenvals, eigenvecs = la.eig(A)
    return eigenvecs

#----------------------------------FOR DEBUGGING------------------------------
def printNonZeroMatrixElements(A):
    """Prints non-zero matrix elements

    Parameters:
        A (ndarray): A matrix
    """
    for i, row in enumerate(A):
        for j, element in enumerate(row):
            if(element != 0):
                print(f"[{i}, {j}] = {element}")


""" Orbital Mechanics for Engineering Students Example 1.6
Question:
Given the position, velocity, and acceleration of a particle P at a given
instant (R, V, A), find the coordinates of the center of curvature at that
instant.
Written by: J.X.J. Bannwarth
"""
from numpy import array, cross, dot
from numpy.linalg import norm

def PrintVec(v, name, unit = ''):
    print(f"{name} = {v[0]:.4f} i + {v[1]:.4f} j + {v[2]:.4f} k {unit}")

# Title
print("Orbital Mechanics for Engineering Students Example 1.6")

# Point P
R = array([250., 630., 430.])
V = array([90., 125., 170.])
A = array([16., 125., 30.])

# Tangential vector
Ut = V / norm(V)
PrintVec(Ut, "U_t")

# Binormal vector
Ub = cross(V,A) / norm(cross(V,A))
PrintVec(Ub, "U_b")

# Normal vector
Un = cross(Ub, Ut)
PrintVec(Un, "U_n")

# Normal acceleration
an = dot(A,Un)

# Radius of curvature
rho = norm(V)**2 / an
print(f"rho = {rho:.4f} m")

# Location of center of curvature
RCP = rho*Un
RC = R + RCP
print("Final answer:")
PrintVec(RC, "R_C", "m")


import numpy as np

class ContigencyMatrix:
    def __init__(self, y_true, y_pred):
        """
        Contigency matrix calculated as intersection of indices

        Matrix:
        rows: prediction labels
        cols: ground truth labels

        Example:
        ground truth labels: [0,0,0,1,1,1]
        prediction labels  : [0,0,1,0,1,1]

        TP = sum([F,F,F,T,T,T] *
                 [F,F,T,F,T,T]) = 2
        FP = sum([T,T,T,F,F,F] *
                 [F,F,T,F,T,T]) = 1
        """
        self.y_true = y_true
        self.y_pred = y_pred

        # Sort labels by frequency. Base class equals the most frequent class
        self.vals, self.cnts = np.unique(y_true, return_counts=True)
        if len(self.vals) == 2:
            self.binary = True
            if 0 in self.vals:
                self.labels = np.concatenate((self.vals[self.vals!=0], [0]))
        else:
            self.binary = False
            self.labels = tuple(self.vals[np.argsort(self.cnts)])

        # Sanity check
        if len(self.labels) < 2:
            raise ValueError('Only one class in the train')

        # Indices of Real and Prediction values by label
        self.true_idx = {label: y_true==label for label in self.labels}
        self.pred_idx = {label: y_pred==label for label in self.labels}

        self.mtx = np.zeros((len(self.labels), len(self.labels)))
        for row, row_label in enumerate(self.labels):
            for col, col_label in enumerate(self.labels):
                self.mtx[row, col] = np.sum(self.true_idx[col_label]*self.pred_idx[row_label])

    def accuracy(self):
        return np.trace(self.mtx)/np.sum(self.mtx)

    def precision(self):
        if self.binary:
            self.precision_score = self.mtx[0,0]/sum(self.mtx[0,:])
        else:
            self.precision_score = np.array([self.mtx[i,i]/sum(self.mtx[i,:]) for i in range(self.mtx.shape[0])])
        return np.average(self.precision_score)

    def recall(self):
        if self.binary:
            self.recall_score = self.mtx[0,0]/sum(self.mtx[:,0])
        else:
            self.recall_score = np.array([self.mtx[i,i]/sum(self.mtx[:,i]) for i in range(self.mtx.shape[0])])
        return np.average(self.recall_score)

    def f1(self):
        self.f1_score = 2*np.average(self.recall_score)*np.average(self.precision_score)/(
            np.average(self.recall_score+self.precision_score))
        return self.f1_score

def multiclass_accuracy(prediction, ground_truth):
    """
    Computes metrics for multiclass classification

    Arguments:
    prediction, np array of int (num_samples) - model predictions
    ground_truth, np array of int (num_samples) - true labels

    Returns:
    accuracy - ratio of accurate predictions to total samples
    """

    # TODO: Implement computing accuracy
    cm = ContigencyMatrix(ground_truth, prediction)
    return cm.accuracy()


import numpy as np
import ast

def vandermonde(x,y):
    x = ast.literal_eval(x)
    y = ast.literal_eval(y)
    y = np.array(y)
    x = np.array(x)
    vanx = np.vander(x)
    yT = y.T
    invX = np.linalg.inv(vanx) # Get the inverse of matrix x
    dotPoint = np.dot(invX,yT) # dot point between inverse x and transpose Y
    sizePol = len(dotPoint)
    countPol = sizePol
    polynom = ""
    for i in range(sizePol):
        polynom += str(np.round(dotPoint[i],4))+ "*x^"+str(countPol-1)+"+"
        countPol -= 1
        if(countPol == 1):
            break
    polynom += str(round(dotPoint[sizePol-1],4))
    polynom = polynom.replace(" ", "").replace("--", "+").replace("++", "+").replace("+-", "-").replace("-+", "-")
    results = {
        'vMatrix': vanx.tolist(),
        'values': np.round(dotPoint,14).tolist(),
        'polynom': polynom
    }
    return results


# For this part of the assignment, please implement your own code for all computations,
# Do not use inbuilt functions like fft from either numpy, opencv or other libraries

from numpy import exp, pi, zeros

class DFT:

    def forward_transform(self, matrix):
        """Computes the forward Fourier transform of the input matrix
        takes as input:
        matrix: a 2d matrix
        returns a complex matrix representing fourier transform"""
        print(type(matrix), '<<<<<<')

        # matrix[r][c] --> matrix[y][x]
        # (u,v) <- (x,y) -> (c,r)

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0] , matrix.shape[1]

        for v in range(0,matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                exp( -1j*2*pi*( (u*x/L) + (v*y/K) ) )
                _matrix[v,u] = (( 1 / (K*L) )+0j) * _sum

        return _matrix

    def inverse_transform(self, matrix):
        """Computes the inverse Fourier transform of the input matrix
        matrix: a 2d matrix (DFT) usually complex
        takes as input:
        returns a complex matrix representing the inverse fourier transform"""

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0], matrix.shape[1]

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                exp( 1j*2*pi*( (u*x/L) + (v*y/K) ) )
                _matrix[v,u] = _sum

        return _matrix

    def magnitude(self, matrix):
        """Computes the magnitude of the DFT
        takes as input:
        matrix: a 2d matrix
        returns a matrix representing magnitude of the dft"""

        from numpy import sqrt

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                matrix[v,u] = sqrt( (matrix[v,u].real)**2 + (matrix[v,u].imag)**2 )

        return matrix

    def discrete_cosine_tranform(self, matrix):
        """Computes the discrete cosine transform of the input matrix
        takes as input:
        matrix: a 2d matrix
        returns a matrix representing discrete cosine transform"""

        from numpy import cos, sin

        _matrix = zeros(matrix.shape, dtype=complex)
        K, L = matrix.shape[0], matrix.shape[1]

        for v in range(0, matrix.shape[0]):
            for u in range(0, matrix.shape[1]):
                _sum = 0
                for y in range(0, matrix.shape[0]):
                    for x in range(0, matrix.shape[1]):
                        _sum += matrix[y,x] * \
                                cos( (pi/L)*(x+0.5)*u ) * \
                                cos( (pi/K)*(y+0.5)*v )
                _matrix[v,u] = _sum

        return _matrix


import csv
import numpy as np
import scipy
import scipy.stats as stats

def get_simple_regression_samples(n,b0=-0.3,b1=0.5,error=0.2,seed=None):
    if seed:
        np.random.seed(seed)

    trueX =  np.random.uniform(-1,1,n)
    trueT = b0 + (b1*trueX)
    return np.array([trueX]).T, trueT + np.random.normal(0,error,n)

def fit_linear_lstsq(xdata,ydata):
    """
    y = b0 + b1*x
    """

    matrix = []
    n,d = xdata.shape

    for i in range(n):
        matrix.append([1.0, xdata[i,0]])

    coeffs = scipy.linalg.basic.lstsq(matrix,ydata)[0]
    #yFit =coeffs[0] + (coeffs[1] * xdata)

    return coeffs

def get_rsme(targets,predictions):
    """return the root mean sq error"""
    if targets.shape[0] != predictions.shape[0] or targets.shape[1] != predictions.shape[1]:
        raise Exception("bad dims")

    return(np.linalg.norm(predictions - targets) / np.sqrt(n))

if __name__ == "__main__":

    ## simple linear regression
    n = 12
    x,y = get_simple_regression_samples(n,seed=42)
    print('x = %s'%[round(_x,2) for _x in x])
    print('y = %s'%[round(_y,2) for _y in y])

    y_pred, coeffs = fit_linear_lstsq(x,y)

    print(coeffs)

    #slope, intercept, r_value, p_value, std_err = stats.linregress(t,t_predict)
    #print intercept,slope


# start import modules
import numpy as np
import matplotlib.pyplot as plt
# end import modules

savefig=True

# start simulation
np.random.seed(999)         # for reproducibility
pH=0.6                       # biased coin
flips=np.random.rand(2**12) # simulates 4096 coin flips
heads=flips<pH              # boolean array, heads[i]=True if flip i is heads
# end simulation

# start bayesian setup
def prior(pH):
    p=np.zeros_like(pH)
    p[(0<=pH)&(pH<=1)]=1      # allowed range: 0<=pH<=1
    return p                # uniform prior
def likelihood(pH,data):
    N = len(data)
    no_of_heads = sum(data)
    no_of_tails = N - no_of_heads
    return pH**no_of_heads * (1-pH)**no_of_tails
def posterior(pH,data):
    p=prior(pH)*likelihood(pH,data)
    norm=np.trapz(p,pH)
    return p/norm
# end bayesian setup

# start plotting
pH=np.linspace(0,1,1000)
fig, axs = plt.subplots(nrows=4,ncols=3,sharex=True,sharey='row',figsize=(14,14))
axs_vec=np.reshape(axs,-1)
axs_vec[0].plot(pH,prior(pH))
for ndouble in range(11):
    ax=axs_vec[1+ndouble]
    ax.plot(pH,posterior(pH,heads[:2**ndouble]))
    ax.text(0.1, 0.8, '$N={0}$'.format(2**ndouble), transform=ax.transAxes)
for row in range(4): axs[row,0].set_ylabel('$p(p_H|D_\mathrm{obs},I)$')
for col in range(3): axs[-1,col].set_xlabel('$p_H$')
# end plotting

if savefig:
    fig.savefig('../fig/coinflipping_fig_1.png')

if not savefig:
    plt.show()


# -*- coding: utf-8 -*-
"""
Created on Fri May  3 16:04:00 2019

@author: Miranda
"""

from sympy import mod_inverse as mminv
from sympy import isprime as isprime
import math
import random
import time
time.time() * 10000

def legendres(dis,p):
    """legendre symbol"""
    ls = pow(dis, (p-1)//2, p)
    if ls == -1 % p:
        return -1
    else: return ls

print(legendres(12,13))

def modroot(a,p):
    """Finds the square root of a modulo p"""
    a = a%p #reduces mod p
    ls=legendres(a,p) #checks Legendre Symbol
    if isprime(p) == 0:
        print("p not prime") #rejects if p not prime
        return
    if ls != 1:
        print("No real root") #rejects if Legendre Symbol is -1
        return False
    if p % 4 == 3: #if p mod 4 = 3, then sqrt of a must be +/- a^(p+1)/4
        b = pow(a, (p+1)//4, p)
        return b % p, -b % p
    else: #otherwise, go to TS algorithm
        return tonellishanks(a,p)

def tonellishanks(a,p):
    """Tonelli-Shanks algorithm for modular square roots"""
    #function is only ever called by modroot, so we need not check the Legendre Symbol
    t = (p-1)//2 #initialises t
    s = 1 #initialises s
    while t % 2 == 0:
        t = t//2 #divides t by two until the odd factor is found
        s = s+1 #stores power of two
    u = 2 #initialises u
    while legendres(u,p) == 1:
        u = random.randint(0,p) #chooses new u until a non-square is found
    v = pow(u,t,p) #computes v
    b = pow(a,(t+1)//2, p) #computes first guess
    #Now we find m - one binary digit at a time
    m = 0 #initialises m
    for ii in range(s-1): #from 0 to s-2:
        mii = pow(a,t*2**(s-2-ii),p) #computes value to decide if m_ii is 1 or 0
        if mii == 1:    mi=0 #stores m_ii
        else:   mi=1
        m = (m + mi*(2**(ii))) % p #computes m up to most current binary digit
    b = b*pow(v,m,p) % p #computes positive solution
    if b**2 % p != a % p: #double check
        b=tonellishanks(a,p)[0] #repeat if incorrect
    return b, -b % p

def modcube(a,p):
    """Finds the cube root of a modulo p --- algorithm by Prof. Cameron"""
    if p % 3 == 1 and p % 9 != 1: #Cameron method
        c = pow(a, (p - 1)//3, p) #check if cube root exists
        if c != 1: return  False #if not, reject
        else:
            if p%9==4: #if p mod 9 = 4...
                k = (2*p + 1)//9 #k can be given as this
            elif p%9==7: #if p mod 9 = 7...
                k = (p+2)//9 #k can be given as this
            else:
                k = random.randint(0,(p+1)/2) #otherwise choose random k
                while True:
                    k = k+1 #increase k until...
                    if 3*k % ((p-1)//3) == 1: #suitable k is found
                        break
            b = pow(a,k,p) #compute cube root
        return b
    elif p % 3 == 2: #we can use short formula:
        b = pow(a, (2*p-1)//3, p)
        return b

def w2check(a,b,p):
    """Checks for point of order two on WNF curve"""
    """Returns x coordinate alpha if exists"""
    dis = -16*(4 * a**3 + 27 * b**2) % p #calculates discriminant
    ldis = legendres(dis,p) #finds Legendre Symbol of discriminant
    if dis == 0:
        print("Discriminant = 0") #rejects if discriminant is 0
        return
    if ldis % 2 == 1:
        #1 root if -1, 3 if 1, doing both cases here
        r = ((b**2)*mminv(4,p) + (a**3)*mminv(27,p)) % p #using cubic formulae
        rt = modroot(r,p) #sqrt of r
        if not rt:
            print("No transformation") #if sqrt does not exist, no alpha exists
            return
        else:
            rt = rt[0] #assign one root to use in algebra
        bf = -b*mminv(2,p) % p #cubic formulae
        alpha = (modcube(bf+rt,p) + modcube(bf-rt,p)) #cubic formulae
        if (alpha**3 + a*alpha + b) % p == 0:
            return alpha % p #double checking value of alpha
        else:
            print("error") #reject otherwise, output alpha for error checking
        return alpha % p
    else:
        print("No point of order two")
        return

#Points

def wtom(a,b,p):
    """Converts WNF curve to Montgomery"""
    """Checks for conversion"""
    start = time.process_time()*1000
    alpha = w2check(a,b,p) #finds point of order two on W
    if not alpha:
        print("No conversion") #if no such point, reject
        return
    else:
        if not modroot(3 * alpha**2 + a, p): #checks if square root exists
            print("No conversion") #otherwise, reject
            return
        B = mminv(max(modroot(3 * alpha**2 + a, p)), p) #calulates B
        A = 3*alpha*B % p #calulates A
        print(B,"y**2 = x**3 +",A,"x**2 + x") #prints curve equation
        print("An Isomorphism!") #all existing W to M mappings are isomorphisms
        print(time.process_time()*1000 - start)
        return A,B,alpha

def mtow(A,B,p):
    """Converts Montgomery curve to WNF"""
    #conversion always exists - no need to check
    start = time.process_time() *1000
    a = ((3-A**2)*mminv(3*B**2,p)) % p #calculates a
    b = ((2*A**3 - 9*A)*mminv(27*B**3,p)) % p #calculates b
    print("y**2 = x**3 + ",a, "x +", b) #prints curve equation
    print("An Isomorphism!") #all existing W to M mappings are isomorphisms
    print(time.process_time()*1000 - start)
    return a,b

def mtoe(A,B,p):
    """Converts Montgomery curve to twisted Edwards"""
    """Checks for conversion"""
    #start = time.process_time()*1000
    if A < 3 % p and A > -2 % p: #checks requirement of A
        print("A in {-2,2}") #if fails, reject
        return
    elif B == 0 % p: #checks B non zero
        print("B = 0") #otherwise reject
        return
    a = ((A+2)*mminv(B,p)) % p #calculates a
    d = ((A-2)*mminv(B,p)) % p # calculates d
    print(a,"x**2 + y**2 = 1 + ",d,"x**2y**2") #prints curve equation
    if legendres(a,p) == legendres(d,p):
        print("Not an Isomorphism :(") #not isomorphic if a & d both square/not square
    elif legendres(a,p) == 1:
        print("An Isomorphism!") #isomorphic if a square, d not square
    elif legendres(a,p) == -1:
        print("Not an Isomorphism :(") #not isomorphic if a not square
   # print(time.process_time()*1000 - start)
    return a,d

def etom(a,d,p):
    """Converts twisted Edwards to Montgomery"""
    """Checks for conversion"""
    #start = time.process_time() *1000
    if a == d: #checks parameters distinct
        print("a=d") #otherwise reject
        return
    elif a == 0 or d == 0: #checks parameters non zero
        print("Must be non zero") #otherwise reject
        return
    A = (2*(a+d)*mminv(a-d,p)) % p #calculates A
    B = (4*mminv(a-d,p)) % p #calculates B
    print(B,"y**2 = x**3 +",A,"x**2 + x") #prints curve equation
    if legendres(a,p) == legendres(d,p):
        print("Not an Isomorphism :(") #not isomorphic if a & d both square/not square
    elif legendres(a,p) == 1:
        print("An Isomorphism!") #isomorphic if a square, d not square
    #print(time.process_time()*1000 - start)
    return A,B

def etow(a,d,p):
    """Converts twisted Edwards to Weierstrass"""
    """Uses that W must have a pt of order 4 therefore T is transformable to M
    """
    A,B = etom(a,d,p)
    a,b = mtow(A,B,p)
    return a,b

def wtoe(a,b,p):
    """Converts Weierstrass to twisted Edwards """
    """Uses that W must have a pt of order 4 therefore T is transformable to M
    """
    start = time.process_time() *1000
    A,B,al = wtom(a,b,p)
    a,d = mtoe(A,B,p)
    print(time.process_time()*1000 - start)
    return a,d

#Curves
def pwtom(p1,a,b,p,A,B):
    """Transforms a Weierstrass point to a Montgomery point"""
    x1,y1 = p1 #assigns coordinates
    if math.isinf(p1[0]): return math.inf,1 #inf -> inf
    if y1**2 % p != (x1**3 + a*x1 + b) % p: #checks if point is on curve
        return False #otherwise reject
    if not A: #if user does not input A, calulates it
        s,alpha = wtom(a,b,p)[1],wtom(a,b,p)[2] #finds B, alpha from wtom
        if not alpha: #checks if a point of order two exists
            print("No conversion") #otherwise reject
            return
    else:
        alpha = A*mminv(3*B,p) % p #if user inputs A,B calculate alpha from them
        s = B #as above
    #exceptional points
    if p1 == (alpha,0): return 0,0 #if point has order two, map to 0,0
    #general points
    else:
        return s*(x1-alpha) % p, s*y1 % p

def pmtow(p1,A,B,p):
    """Transforms a Montgomery point to a Weierstrass point"""
    start = time.process_time()*1000
    x1,y1 = p1 #assigns coordinates
    if B*y1**2 % p != (x1**3 + A*x1**2 + x1) % p: #checks if point is on curve
        return False #otherwise reject
    #exceptional points
    if math.isinf(x1):
        print(time.process_time()*1000 - start)
        return math.inf,1 #inf -> inf
    #general points
    else:
        print(time.process_time()*1000 - start)
        return (x1*mminv(B,p) + A*mminv(3*B,p)) % p, y1*mminv(B,p) % p

def pmtoe(p1,A,B,p):
    """Transforms a Montgomery point to a twisted Edwards point"""
    start = time.process_time()*1000
    if A < 3 % p and A > -2 % p: #checks requirements on A for curve mapping
        print("A in {-2,2}") #if fails, reject
        return False
    elif B == 0 % p: #checks requirements on B for curve mapping
        print("B = 0") #if fails, reject
        return False
    x1,y1 = p1 #assigns coordinates
    if math.isinf(x1): return 0,1 #inf -> (0,1)
    if B*y1**2 % p != (x1**3 + A*x1**2 + x1) % p: #checks if point is on curve
        return False #otherwise reject
    #exceptional points
    elif y1 == 0 % p:
        print(y1)
        if x1 == 0 % p: return 0, -1 % p #(0,0) -> (0,-1)
        elif 2*x1 % p == (-A + modroot((A+2)*(A-2),p)[0]) % p:
                return math.inf,2 #point of order two on M -> inf ('positive' root)
        elif 2*x1 % p == (-A - modroot((A+2)*(A-2),p)[0]) % p:
                return math.inf,-2 #point of order two on M -> inf ('negative' root)
        else: return
    elif x1 == -1 % p:
        if y1 == modroot((A-2)*mminv(B,p),p):
            return math.inf,4 #point of order four on M -> inf ('positive' root)
        elif y1 == -modroot((A-2)*mminv(B,p),p) % p:
            return math.inf,-4 #point of order four on M -> inf ('negative' root)
    #general points
    else:
        print(time.process_time()*1000 - start)
        return x1*mminv(y1,p) % p, (x1-1)*mminv(x1+1,p) % p

def petom(p1,a,d,p):
    """Transforms a twisted Edwards point to a Montgomery point"""
    if a == d: #checks curve mapping requirements
        print("a=d") #if fails, reject
        return False
    elif a == 0 or d == 0:
        print("Must be non zero")
        return False
    x1,y1 = p1 #assigns coordinates
    #exceptional points
    if math.isinf(x1):
        A = (2*(a+d)*mminv(a-d,p)) % p #calculates A
        B = (4*mminv(a-d,p)) % p #calculates B
        if y1 % p == 2:
            x = (-A + modroot((A+2)*(A-2),p)[0])*mminv(2,p) % p #point of order two on M ('positive' root)
            return x,0
        elif y1 % p == -2 % p:
            x = (-A - modroot((A+2)*(A-2),p)[0])*mminv(2,p) % p #point of order two on M ('negative' root)
            return x,0
        elif y1 % p == 4:
            y = modroot((A-2)*mminv(B,p),p) #point of order four on M ('positive' root)
            return -1,y
        elif y1 % p == -4 % p:
            y = -modroot((A-2)*mminv(B,p),p) % p #point of order four on M ('negative' root)
            return -1,y
        else: return #no other points at infinity should exist on a twisted Edwards curve
    if (a*x1**2 + y1**2) % p != (1 + d*(x1**2)*(y1**2)) % p: #checks point is on the curve
        return False #otherwise reject
    elif x1 % p == 0 and y1 % p == 1: return math.inf,1 #(0,1) -> inf
    elif x1 % p == 0 and y1 % p == -1 % p: return 0,0 #(0,-1) -> (0,0)
    #general points
    else:
        return (1+y1)*mminv(1-y1,p) % p, (1+y1)*mminv(x1*(1-y1),p) % p

def petow(p1,a,d,p):
    """Transforms a twisted Edwards point to a Weierstrass point"""
    if a == d: #checks curve mapping requirements
        print("a=d") #if fails, reject
        return
    elif a == 0 or d == 0:
        print("Must be non zero")
        return
    x1,y1 = p1 #assigns coordinates
    #exceptional points
    if math.isinf(x1):
        #computes inf seperately, since may be (inf,4) or (inf,2)
        p2 = petom(p1,a,d,p) #computes point at infinity -> point on montgomery
        A,B = (2*(a+d)*mminv(a-d,p)) % p, (4*mminv(a-d,p)) % p #calculates A and B
        p3 = pmtow(p2,A,B,p) #computes point on montgomery -> point on W
        return p3
    if (a*x1**2 + y1**2) % p != (1 + d*(x1**2)*(y1**2)) % p: #checks point is on curve
        return False #otherwise reject
    elif x1 % p == 0 and y1 % p == 1:
        return math.inf,1 #(0,1) -> inf -> inf
    else:
        p2 = petom(p1,a,d,p) #computes point on Montgomery
        A,B = (2*(a+d)*mminv(a-d,p)) % p, (4*mminv(a-d,p)) % p #calculates A and B
        p3 = pmtow(p2,A,B,p) #computes point on W
        return p3

def pwtoe(p1,a,b,p):
    """Transforms a Weierstrass point to a Montgomery point"""
    """Need to check req for W (pt of order 4 exists etc)
    """
    x1,y1 = p1 #assigns cordinates
    if math.isinf(x1):
        return 0,1 #inf -> inf -> (0,1)
    if y1**2 % p != (x1**3 + a*x1 + b) % p: #checks point is on curve
        return False #otherwise reject
    else:
        A,B,al = wtom(a,b,p) #calculate Montgomery parameters
        p2 = pwtom(p1,a,b,p,A,B) #calculate point on Montgomery
        p3 = pmtoe(p2,A,B,p) #calculate point on twisted Edwards
        return p3


import numpy
import math
LOG_BASE=2.0

def H(X, bins):
    """
    Calculates the entropy of X.
    If X is already discretised, set \ref bins to the amount of bins, aka states of X.
    If X is not discretised, \ref bins will be used, to diskretise X into  \ref bins states

    @param X numpy vector
    @param bins If X is already diskretised, amount of bins of X. If X is not discretised, amount of bins to diskretise X into
    @return Returns the entropy of X
    """
    p_x, _ = numpy.histogram(X, bins=bins)
    p_x = p_x/len(X)
    H_ = 0
    for p in p_x:
        if p > 0:
            H_ -= p*math.log(p)
    H_ = H_/math.log(bins)
    return H_

def H_cond(X, Y, bins):
    """
    Calculates the conditional entropy of X depending on Y.
    If X and Y are already discretised, set \ref bins to the amount of bins, aka states of X and Y.
    If X and Y are not discretised, \ref bins will be used, to diskretise X and Y into \ref bins states

    @param X numpy vector depending on \ref Y
    @param Y numpy vector
    @param bins If X and Y are already diskretised specify the amount of bins of X and Y. If X and Y are not discretised specifies the amount of bins to diskretise X into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y), to diskretise X and Y independently, or as a single value to diskretise X and Y similar, e.g. bins = amount_of_bins
    @return Returns the conditional entropy
    """
    if (type(bins) == list):
        ybins = bins[1]
        xbins = bins[0]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        base = bins

    p_xy, _,_ = numpy.histogram2d(X,Y, bins=[xbins, ybins])
    p_xy = p_xy / len(X)
    p_y, _ = numpy.histogram(Y, bins=ybins)
    p_y = p_y/len(Y)
    H = 0
    for i in range(ybins):
        for j in range(xbins):
            if (p_xy[j,i] > 0):
                p_cond = p_xy[j,i]/p_y[i]
                H -= p_y[i] * p_cond * math.log(p_cond)
    H = H/math.log(base)
    return H

def I(X,Y,bins):
    """
    Calculates the mutual information of X and Y.
    If X and Y are already discretised, set \ref bins to the amount of bins, aka states of X and Y.
    If X and Y are not discretised, \ref bins will be used, to diskretise X and Y into \ref bins states

    @param X numpy vector
    @param Y numpy vector
    @param bins If X and Y are already diskretised specify the amount of bins of X and Y. If X and Y are not discretised specifies the amount of bins to diskretise X and Y into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y), to diskretise X and Y independently, or as a single value to diskretise X and Y similar, e.g. bins = amount_of_bins
    @return Returns the mutual information
    """
    if (type(bins) == list):
        ybins = bins[1]
        xbins = bins[0]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        base = bins

    p_xy, _,_ = numpy.histogram2d(X,Y, bins=[xbins, ybins])
    p_xy = p_xy / len(X)
    p_y, _ = numpy.histogram(Y, bins=ybins)
    p_y = p_y/len(Y)
    p_x, _ = numpy.histogram(X, bins=xbins)
    p_x = p_x/len(X)
    I_ = 0
    for i in range(xbins):
        for j in range(ybins):
            if p_x[i] > 0 and p_y[j] > 0 and p_xy[i,j] > 0:
                I_ += p_xy[i,j] * math.log(p_xy[i,j]/(p_x[i]*p_y[j]))

    I_ = I_/math.log(base)

    return I_

def I_cond(X,Y,Z,bins):
    """
    Calculates the conditional mutual information of X and Y depending on Z.
    If X, Y and Z are already discretised, set \ref bins to the amount of bins, aka states of X, Y and Z.
    If X, Y and Z are not discretised, \ref bins will be used, to diskretise X, Y and Z into \ref bins states

    @param X numpy vector
    @param Y numpy vector
    @param Z numpy vector
    @param bins If X, Y and Z are already diskretised specify the amount of bins of X, Y and Z. If X, Y and Z are not discretised specifies the amount of bins to diskretise X, Y and Z into
                bins can be spcified as tuple, e.g. bins = (bins_x, bins_y, bins_z), to diskretise X, Y and Z independently, or as a single value to diskretise X, Y and Z similar, e.g. bins = amount_of_bins
    @return Returns the mutual information
    """
    if (type(bins) == list):
        xbins = bins[0]
        ybins = bins[1]
        zbins = bins[2]
        base = LOG_BASE
    else:
        ybins = bins
        xbins = bins
        zbins = bins
        base = bins

    XYZ = numpy.transpose(numpy.array([X,Y,Z]))
    p_xyz, _ = numpy.histogramdd(XYZ, bins=[xbins, ybins, zbins])
    p_xyz = p_xyz/ len(Z)
    p_xz, _, _ = numpy.histogram2d(X,Z, bins=[xbins, zbins])
    p_xz = p_xz / len(X)
    p_yz, _, _ = numpy.histogram2d(Y,Z, bins=[ybins, zbins])
    p_yz = p_yz / len(Y)
    p_z, _ = numpy.histogram(Z, bins=zbins)
    p_z = p_z/len(Z)

    I = 0
    for i in range(xbins):
        for j in range(ybins):
            for k in range(zbins):
                if (p_xyz[i,j,k] > 0 and
                p_xz[i,k] > 0 and
                p_yz[j,k] > 0 and
                p_z[k] > 0):
                    I += p_xyz[i,j,k] * math.log((p_z[k] * p_xyz[i,j,k]) / (p_xz[i,k] * p_yz[j,k]))
    I = I/math.log(base)
    return I


import numpy as np

def trapezine(func, z_min, z_max, epsilon):
    """
    Perform extended trapezoidal rule to calculate numerical integration

    Input:
        func: the function to be integrated
        z_min: lower limit of the integration
        z_max: upper limit of the integration
        epsilon: desired accuracy of the integration. e.g. 1e-8
    Returns:
        new_I: the numerical integration result with desired accuracy
        err: error of the process O(h^3)"""
    # Setting global variable
    evaluation = 0

    # Initial values of iteration
    h = z_max - z_min  # range of integration
    n = 2       # number of points
    # Initial value of integration with 1 step
    new_I = h * (func(z_max) + func(z_min)) / 2
    relative_accuracy = 1    # Assign a non-zero value to start the loop
    evaluation += 2

    # iteration until reach the desired relative accuracy or reach the machine
    # accuracy (machine epsilon)
    while relative_accuracy >= epsilon:
        h = h / 2  # update step-size
        z_array = np.arange(z_min, z_max + h, h)
        n = len(z_array)
        old_I = new_I  # update compared value
        # Half the previous value and add new points - reduce the repeated
        # evaluations of function
        new_I = old_I / 2 + h * \
            sum(func(z_array[2 * i - 1]) for i in range(1, int((n - 1) / 2) + 1))
        relative_accuracy = abs((new_I - old_I) / old_I)
        # Half of the terms in the new integral is "truly evaluated" in this
        # iteration step
        evaluation += int(n / 2 - 1)
        # print(n,h,new_I,relative_accuracy)
    # Evaluating error of the integration
    err = new_I * 0.5 * relative_accuracy

    # number of terms evaluated
    print("Number of evaluated terms: ", int(evaluation))
    return new_I, err

def simpson(func, z_min, z_max, epsilon):
    """
    Perform extended Simpson's rule to calculate numerical integration value.

    Input:
        func: the function to be integrated
        z_min: lower limit of the integration
        z_max: upper limit of the integration
        epsilon: desired accuracy of the integration. e.g. 1e-8
    Returns:
        new_I: the numerical integration result with desired accuracy
        err: error of the process O(h^5)"""
    # Setting global variable
    evaluation = 0
    # Initial values
    h = z_max - z_min
    # Implement the trapezoidal summation for the simpson's rule
    # Inital summation of 2-point trapezine
    T_j = h * (func(z_max) + func(z_min)) / 2
    h = h / 2
    # Update z_array just like trapezoidal
    z_array = np.arange(z_min, z_max + h, h)
    n = len(z_array)
    # Intial iterated trapezodial term
    T_j1 = T_j / 2 + h * sum(func(z_array[int(2 * t - 1)])
                             for t in range(1, int((n - 1) / 2) + 1))
    # Inital integral of simpson's method with 2 terms from trapezoidal method
    new_I = 4 * T_j1 / 3 - T_j / 3
    relative_accuracy = 1      # Assign a non-zero value to old_I to start the while loop
    evaluation += 3
    # Iterate until reach desired accuracy
    while relative_accuracy >= epsilon:
        old_I = new_I
        h = h / 2         # Similar to trapezoidal, half the step-size each loop
        # update z-array just like trapezoidal
        z_array = np.arange(z_min, z_max + h, h)
        n = len(z_array)
        T_j = T_j1        # update the T(rapezodial) summation
        T_j1 = T_j / 2 + h * \
            sum(func(z_array[int(2 * t - 1)]) for t in range(1, int((n - 1) / 2) + 1))
        new_I = 4 * T_j1 / 3 - T_j / 3
        evaluation += n // 2         # Evaluation number added similar to trapezoidal rule
        relative_accuracy = abs((new_I - old_I) / old_I)
    # Evaluate the error
    err = new_I * 0.5 * relative_accuracy

    # number of terms evaluated
    # each loop uses 2 generations of trapezoidal summation so evaluation is
    # 1.5 times of the number of terms in the last trapezoidal summation
    print("Number of evaluated terms: ", int(evaluation))
    return new_I, err


"""
Module for working with Bernstein polynomials.
"""

import numpy as np
import sympy as sp
import typing
import vorpy.symbolic

def bernstein_polynomial (t:typing.Any, *, degree:int, k:int) -> typing.Any:
    """
    Returns the kth Bernstein polynomial of given degree in the given symbol.
    It's assumed that the interval for the Bernstein polynomial is [0,1].

    TODO: This should also specify the interval over which the polynomial is defined.
    TODO: Should be able to specify dtype.
    """
    if degree < 0:
        raise TypeError(f'expected degree (which was {degree}) to be nonnegative.')
    if k < 0 or k > degree:
        raise ValueError(f'expected 0 <= k <= degree (but k = {k} and degree = {degree})')

    s = 1-t
    return sp.binomial(degree,k) * s**(degree-k) * t**k

def bernstein_polynomial_basis (t:typing.Any, *, degree:int) -> np.ndarray:
    """
    Returns a vector of Bernstein polynomials of the specified degree with k = 0 to degree.
    If t is a symbol, then this is a symbolic vector.  If t is a float, then this is a float
    valued vector.

    TODO: This should also specify the interval over which the polynomial is defined.
    TODO: Should be able to specify dtype.
    """
    return np.array([bernstein_polynomial(t, degree=degree, k=k) for k in range(degree+1)])

def bernstein_polynomial_L2_inner_product (t:typing.Any, *, degree:int) -> np.ndarray:
    B = bernstein_polynomial_basis(t, degree=degree)

    def L2_inner_product (f0, f1) -> typing.Any:
        return sp.integrate(f0*f1, (t, 0, 1))

    return np.array([
        [L2_inner_product(b_i,b_j) for b_j in B]
        for b_i in B
    ])

def bernstein_polynomial_L2_inner_product_inv (t:typing.Any, *, degree:int) -> np.ndarray:
    B = bernstein_polynomial_basis(t, degree=degree)

    def L2_inner_product (f0, f1) -> typing.Any:
        return sp.integrate(f0*f1, (t, 0, 1))

    L2_in_B = bernstein_polynomial_L2_inner_product(t, degree=degree)
    return np.array(sp.Matrix(L2_in_B).inv().tolist())


import scipy.integrate as spi

def arc_length(f,a,b,h=0.001,N=1000):
    '''Approximate the arc length of y=f(x) from x=a to x=b.

    Parameters
    ----------
    f : (vectorized) function of one variable
    a,b : numbers defining the interval [a,b]
    h : step size to use in difference formulas
    N : number of subintervals in trapezoid method

    Returns
    -------
    Approximation of the integral \int_a^b \sqrt{1 + (f'(x))^2} dx
    representing the arc length of y=f(x) from x=a to x=b.
    '''
    x = np.linspace(a,b,N+1)
    y = f(x)

    # Compute central difference formula for x_k for 1 <= k <= N-1
    h = np.min([h,(b-a)/N]) # Make sure that h is smaller than the size of the subintervals
    x_interior = x[1:-1]
    df_interior = (f(x_interior + h) - f(x_interior - h))/(2*h)

    # Use forward/backward difference formula at the endpoints
    df_a = (f(a + h) - f(a))/h
    df_b = (f(b) - f(b - h))/h
    df = np.hstack([[df_a],df_interior,[df_b]])

    # Compute values of the integrand in arc length formula
    y = np.sqrt(1 + df**2)

    # Compute the integral
    L = spi.trapz(y,x)

    return L

print(arc_length(lambda x: x,0,1))


from base_regression import BasicRegression

import numpy as np

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

def softmax(z):
    # Avoid numerical overflow by removing max
    # See: http://cs231n.github.io/linear-classify/
    e = np.exp(z - np.max(z))
    return e / np.sum(e)

class LogisticRegression(BasicRegression):
    """Binary logistic regression with gradient descent optimizer."""
    def _loss(self, theta):
        X, y, C = self.X, self.y, self.C

        predict = sigmoid(X.dot(theta))
        # prevent overflow or underflow
        predict = np.clip(predict, 1e-15, 1 - 1e-15)
        loss = - np.mean(np.sum(y * np.log(predict) + (1 - y) * np.log(1 - predict)))
        grad = (1.0 / self.n_samples) * (X.T.dot(predict - y))

        # Add regularization to the loss.
        loss += 0.5 * C * np.sum(theta * theta)
        grad += C * theta

        return loss, grad

    def _predict(self, X=None, threshold=0.5):
        """Predict class labels for samples in X."""
        X = self._add_intercept(X)
        predict = sigmoid(X.dot(self.theta))
        return np.where(predict > threshold , 1, 0)

class Softmax(BasicRegression):
    """Multi-class logistic regression with gradient descent optimizer."""
    def init_weights(self):
        """Mutli-class weights"""
        self.num_classes = np.max(self.y) + 1 # assume y takes values 0...K-1 where K is number of classes
        return np.random.normal(size=(self.n_features + 1, self.num_classes), scale=0.5)

    def _loss(self, theta):
        """
        Naive implementation for softmax loss due to its simplicity and readibility
        For vectorized version: https://github.com/transedward/cs231n/blob/master/assignment1/cs231n/classifiers/softmax.py#L62
        """
        X, y, C, n_samples, n_features = self.X, self.y, self.C, self.n_samples, self.n_features
        loss = 0.0
        grad = np.zeros_like(theta)
        for i in range(n_samples):
            scores = X[i].dot(theta)
            probability = softmax(scores)
            loss += -np.log(probability[y[i]])
            # gradient for softmax loss
            # http://ufldl.stanford.edu/wiki/index.php/Softmax_Regression
            for j in range(self.num_classes):
                grad[:, j] += (probability[j] - (j == y[i])) * X[i, :]

        # Right now the loss is a sum over all training examples, but we want it
        # to be an average instead so we divide by n_samples.
        loss /= n_samples
        grad /= n_samples

        # Add regularization to the loss.
        loss += 0.5 * C * np.sum(theta * theta)
        grad += C * theta

        return loss, grad

    def _predict(self, X=None):
        """Predict class labels for samples in X."""
        X = self._add_intercept(X)
        scores = X.dot(self.theta)
        predict = np.argmax(scores, axis=1)
        return predict


# coding: utf-8

from math import log
import copy
import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
import urllib.request, urllib.error
import numpy as np

pd.set_option("display.width",2000)

# Import and prepare data

url = "https://raw.githubusercontent.com/datadista/datasets/master/COVID%2019/nacional_covid19.csv"
base_name = "COVID-19-nacional"
fname =  base_name + ".csv"
urllib.request.urlretrieve(url,fname)
#try:
#    url = base_url + dt.datetime.today().strftime("%Y-%m-%d") + ".csv"
#    fname =  base_name + dt.datetime.today().strftime("%Y-%m-%d") + ".csv"
#    urllib.request.urlretrieve(url,fname)
#except urllib.error.HTTPError as e:
#    url = base_url + dt.datetime.today().strftime("%Y-%m-%d") + "_0.csv"
#    fname = base_name + dt.datetime.today().strftime("%Y-%m-%d") + "_0.csv"
#    urllib.request.urlretrieve(url)

def parse_date(fecha):
    return dt.datetime.strptime(fecha,"%Y-%m-%d").date()

df = pd.read_csv(fname,
                parse_dates={"dates": ["fecha"]},
                date_parser=parse_date,index_col="dates")

df.sort_index(inplace=True)

print("Doubling time (deaths) = {:} days".format(
        log(2)/log(df.fallecimientos[-1]/df.fallecimientos[-2])
     ))
print("Doubling time (cases) = {:} days".format(
        log(2)/log(df.casos[-1]/df.casos[-2])
     ))

#Plot
fig, ax = plt.subplots(2,1)
fig.suptitle("COVID-19 Spain Growth Rate")
x = df.index[-15:]

y = df.fallecimientos[-15:]
ax[0].set_title("(cumulative) deaths in Spain",y=0.8)
ax[0].plot(x,y,color="r",marker='.',label="Deaths")
double2days = [df.fallecimientos[-3]*2**(i/2) for i in range(3)]
double3days = [df.fallecimientos[-3]*2**(i/3) for i in range(3)]
double5days = [df.fallecimientos[-3]*2**(i/5) for i in range(3)]
ax[0].plot(df.index[-3:],double2days,label="Doubles every 2 days")
ax[0].plot(df.index[-3:],double3days,label="Doubles every 3 days")
ax[0].plot(df.index[-3:],double5days,label="Doubles every 5 days")
ax[0].set_yscale("log")

y = df.casos[-15:]
ax[1].set_title("(cumulative) cases in Spain",y=0.8)
ax[1].plot(x,y,color="k",marker='.',label="Confirmed cases")
double2days = [df.casos[-3]*2**(i/2) for i in range(3)]
double3days = [df.casos[-3]*2**(i/3) for i in range(3)]
double5days = [df.casos[-3]*2**(i/5) for i in range(3)]
ax[1].plot(df.index[-3:],double2days,label="Doubles every 2 days")
ax[1].plot(df.index[-3:],double3days,label="Doubles every 3 days")
ax[1].plot(df.index[-3:],double5days,label="Doubles every 5 days")
ax[1].set_yscale("log")

#plt.gcf().autofmt_xdate()
plt.legend()

for ax in fig.get_axes():
    ax.label_outer()

plt.show()


import math
import numpy as np

def vector_from_angle_and_length(angle, length):
    vector = (length * math.sin(angle), length * math.cos(angle))
    vector = np.array(vector)
    return vector

def polar_coordinates(x, y):
    angle = math.atan2(x, y)
    if angle < 0.0:
        angle += 2.0 * math.pi
    length = math.sqrt(x**2 + y**2)
    return angle, length

def get_distance(x1, y1, x2, y2):
    return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)


from sympy import *
t, dt = symbols('t dt')
P, Q = symbols('P Q', cls=Function)

# Target expression P(t_{n+1/2})*Q(t_{n+1/2})
# Simpler: P(0)*Q(0)
# Arithmetic means of each factor:
# 1/4*(P(-dt/2) + P(dt/2))*(Q(-dt/2) + Q(dt/2))
# Arithmetic mean of the product:
# 1/2*(P(-dt/2)*Q(-dt/2) + P(dt/2)*Q(dt/2))
# Let's Taylor expand to compare

target = P(0)*Q(0)
num_terms = 6
P_p = P(t).series(t, 0, num_terms).subs(t, dt/2)
print(P_p)
P_m = P(t).series(t, 0, num_terms).subs(t, -dt/2)
print(P_m)
Q_p = Q(t).series(t, 0, num_terms).subs(t, dt/2)
print(Q_p)
Q_m = Q(t).series(t, 0, num_terms).subs(t, -dt/2)
print(Q_m)

product_mean = Rational(1,2)*(P_m*Q_m + P_p*Q_p)
product_mean = simplify(expand(product_mean))
product_mean_error = product_mean - target

factor_mean = Rational(1,2)*(P_m + P_p)*Rational(1,2)*(Q_m + Q_p)
factor_mean = simplify(expand(factor_mean))
factor_mean_error = factor_mean - target

print('product_mean_error:', product_mean_error)
print('factor_mean_error:', factor_mean_error)


import numpy as np
# 矩阵乘法的几何意义 https://zhuanlan.zhihu.com/p/64606322
# 矩阵乘法  矩阵积 点积  matrix product
a = np.array([[4, 5], [6, 7]])
b = np.arange(4).reshape((2, 2))
print(a)
# [[4 5]
#  [6 7]]
print(b)
# [[0 1]
#  [2 3]]
# 矩阵乘法
c_dot = np.dot(a,b)
print(c_dot)
# [[10 19]
#  [14 27]]
c_dot_2 = a.dot(b)
# [[10 19]
#  [14 27]]
print(a @ b)

print(a * b)
"""
a1,1 * b1,1 + a1,2 * b2,1 + a1,n * bn,1  ,  a1,1 * b1,2 + a1,2 * b2,2 + a1,n * bn,2
a2,1 * b1,1 + a2,2 * b2,1 + a2,n * bn,1  ,  a2,1 * b1,2 + a2,2 * b2,2 + a2,n * bn,2
= A 的行数 ，B的列数
= [
[4*0+5*2,4*1+5*3],
[6*0+7*2,6*1+7*3]
]
"""


"""This module provides functions related to redemptions and interest."""

from typing import Optional, Union, Tuple
import pandas as pd
import numpy as np

def determine_annuity(
    months_to_legal_maturity: int, outstanding_balance: float, interest_rate:
        float) -> float:
    """Calculate the (monthly) annuity.
    For mortgage with specified months_to_legal_maturity (>=1), outstanding
    balance, and interest rate (decimal, annual).
    """
    tau = interest_rate / 12
    kappa = (1 + tau) ** months_to_legal_maturity
    return outstanding_balance * tau * kappa / (kappa - 1)

def determine_interest(outstanding_balance: float, interest_rate:
                       float) -> float:
    """Determine the interest of a mortgage.
    In a month in case the principal at start of the month is given by
    outstanding_balance and the interest rate is given by interest_rate
    (as decimal, annual).
    """
    return outstanding_balance * interest_rate / 12

def determine_redemption_linear(
    months_to_legal_maturity: int, outstanding_balance: float
) -> float:
    """Determine the redemption of linear mortgage.

    In month t in case the principal at start of the month is given by outstanding_balance
    """
    return outstanding_balance / months_to_legal_maturity

def determine_redemption_bullet(months_to_legal_maturity: int, outstanding_balance: float
) -> float:
    """Determine the redemption of bullet mortgage.

    In a month in case the principal at start of the month is given by outstanding_balance.
    """
    return outstanding_balance if months_to_legal_maturity == 1 else 0

def determine_redemption_annuity(
    months_to_legal_maturity: int,
    outstanding_balance: float,
    interest_rate: float,
    annuity: Optional[float] = None,
) -> float:
    """Calculate the redemption of an annuity mortgage.

    On basis of the outstanding_balance at the start of the month, the interest rate, and the
    months to legal maturity. The annuity can optionally be given in case it has
    been calculated elsewhere.
    """
    if annuity is None:
        annuity = determine_annuity(
            months_to_legal_maturity, outstanding_balance, interest_rate
        )
    return annuity - determine_interest(outstanding_balance, interest_rate)

def determine_redemption(
    months_to_legal_maturity: int,
    outstanding_balance: float,
    mortgage_type: str,
    interest_rate: Optional[float] = None,
) -> float:
    """Determine the redemption of mortgage.

    For mortgage of type mortgage_type, in a month in case the principal at
    start of the month is given by outstanding_balance. In case of
    annuity the interest_rate is required as input.
    """
    if mortgage_type == "bullet":
        return determine_redemption_bullet(
            months_to_legal_maturity, outstanding_balance
        )
    elif mortgage_type == "linear":
        return determine_redemption_linear(
            months_to_legal_maturity, outstanding_balance
        )
    elif mortgage_type == "annuity":
        return determine_redemption_annuity(
            months_to_legal_maturity, outstanding_balance, interest_rate, None
        )


import numpy as np
from scipy.signal import fftconvolve
from scipy.ndimage.filters import convolve

def gaussian2(size, sigma):
    """Returns a normalized circularly symmetric 2D gauss kernel array

    f(x,y) = A.e^{-(x^2/2*sigma^2 + y^2/2*sigma^2)} where

    A = 1/(2*pi*sigma^2)

    as define by Wolfram Mathworld
    http://mathworld.wolfram.com/GaussianFunction.html
    """
    A = 1 / (2.0 * np.pi * sigma**2)
    x, y = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1]
    g = A * np.exp(-((x**2 / (2.0 * sigma**2)) + (y**2 / (2.0 * sigma**2))))
    return g

def fspecial_gauss(size, sigma):
    """Function to mimic the 'fspecial' gaussian MATLAB function
    """
    x, y = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1]
    g = np.exp(-((x**2 + y**2) / (2.0 * sigma**2)))
    return g / g.sum()

size = 11
sigma = 1.5
window = fspecial_gauss(size, sigma)

def ssim(img1, img2, cs_map=False):
    """Return the Structural Similarity Map corresponding to input images img1
    and img2 (images are assumed to be uint8)

    This function attempts to mimic precisely the functionality of ssim.m a
    MATLAB provided by the author's of SSIM
    https://ece.uwaterloo.ca/~z70wang/research/ssim/ssim_index.m
    """
    # img1 = img1.astype('float32')
    # img2 = img2.astype('float32')

    K1 = 0.01
    K2 = 0.03
    L = 255
    C1 = (K1 * L) ** 2
    C2 = (K2 * L) ** 2

    mu1 = convolve(window, img1, mode='nearest')
    mu2 = convolve(window, img2, mode='nearest')
    mu1_sq = mu1 * mu1
    mu2_sq = mu2 * mu2
    mu1_mu2 = mu1 * mu2
    sigma1_sq = convolve(window, img1 * img1, mode='nearest') - mu1_sq
    sigma2_sq = convolve(window, img2 * img2, mode='nearest') - mu2_sq
    sigma12 = convolve(window, img1 * img2, mode='nearest') - mu1_mu2

    if cs_map:
        return (((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) /
                ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)),
                (2.0 * sigma12 + C2) / (sigma1_sq + sigma2_sq + C2))
    else:
        return (((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) /
                ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)))