# -*- coding: utf-8 -*-
"""
Author: Paschalis Giakoumoglou
Date: Fri Jun 18 00:42:40 2021
"""
import numpy as np
import math

def euler(f, x0, t0, t1, dt):
    print("============================== Euler's Method ============================== ")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        x[n + 1, :] = x[n, :] + dt * f(t[n], x[n, :])
        print(f"x[{n+1}] = x[{n}] + dt*f(t[{n}], x[{n}]) = {x[n, :]} + {dt}*{f(t[n], x[n, :])} = {x[n, :]} + {dt}*{f(t[n], x[n, :])} = {x[n + 1, :]}")
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def improved_euler(f, x0, t0, t1, dt): #not for systems
    print("==============================  Improved Euler's Method ============================== ")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt * f(t[n], x[n])
        k1 = dt * f(t[n+1], x[n]+k0)
        x[n+1, :] = x[n, :] + 1/2*(k0 + k1)
        print(f"k0 = dt * f(t[n], x[n]) = {dt} * {f(t[n], x[n])} = {k0}")
        print(f"k1 = dt * f(t[n+1], x[n]+k0) = {dt} * {f(t[n+1], x[n]+k0)} = {k1}")
        print(f"x[{n+1}] = x[{n}] + 1/2*(k0+k1) = {x[n, :]} + 1/2*({k0}+{k1}) = {x[n, :] + 1/2*(k0 + k1)}")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def mid_point(f, x0, t0, t1, dt):
    print("============================== Mid Point Method ==============================")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt *f(t[n], x[n, :])
        k1 = f(t[n]+1/2*dt, x[n, :]+1/2*k0)
        x[n+1, :] = x[n, :] + dt*k1
        print(f"k0 = dt *f(t[n], x[n, :]) ={dt}*{f(t[n], x[n])} = {k0}")
        print(f"k1 = f(t[n]+1/2*dt, x[n, :]+1/2*k0) = {dt}*f({t[n]+1/2*dt},{x[n]+1/2*k0}) = {dt}*{f(t[n]+1/2*dt, x[n]+1/2*k0)} = {k1}")
        print(f"x[{n + 1}] = x[{n}] + {dt}*{k1} = {x[n + 1]} ")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def runge_kutta(f, x0, t0, t1, dt): #not for systems
    print("============================== Runge Kutta Method ==============================")
    print()
    t = np.arange(t0, t1 + dt, dt)
    x = np.zeros((len(t), len(x0)), np.float64)
    x[0, :] = x0
    for n in range(len(t) - 1):
        k0 = dt*f(t[n], x[n, :])
        k1 = dt*f(t[n]+1/2*dt, x[n, :]+1/2*k0)
        k2 = dt*f(t[n]+1/2*dt, x[n, :]+1/2*k1)
        k3 = dt*f(t[n+1], x[n, :]+k2)
        x[n+1, :] = x[n, :] + 1/6*(k0 + 2*k1 + 2*k2 + k3)
        print(f"k0 = {dt}*f({t[n]}, {x[n]}) = {dt}*{f(t[n], x[n])} = {dt*f(t[n], x[n])}")
        print(f"k1 = {dt}*f({t[n]+1/2*dt}, {x[n]+1/2*k0}) = {dt}*{f(t[n]+1/2*dt, x[n, :]+1/2*k0)} ={k1}")
        print(f"k2 = {dt}*f({t[n]+1/2*dt}, {x[n]+1/2*k1}) = {dt}*{f(t[n]+1/2*dt, x[n, :]+1/2*k1)} ={k2}")
        print(f"k3 = {dt}*f({t[n+1]}, {x[n]+k2}) = {dt}*{f(t[n+1], x[n, :]+k2)} ={k3}")
        print(f"x[{n + 1}] = x[{n}] + 1/6*(k0 + 2*k1 + 2*k2 + k3) = {x[n, :]} + 1/6*({k0} + 2*{k1} + 2*{k2} + {k3}) = {x[n+1, :]}")
        print()
    print()
    print('Summing up:')
    for n in range(len(t)):
        print(f"t[{n}] = {t[n]} -> x[{n}] = {x[n,:]}")
    print()
    #return t, x

def mult_steps(f, x0, t0, t1, dt):
    print("============================== Multi Step Method ==============================")
    print()
    x=5

if __name__ == "__main__":

    # Example 1
    def f(t, x):
        return -x+t+1

    x0 = np.array([1])
    t0 = 0
    t1 = 1
    dt = 0.1 # = (t0-t1)/N

    euler(f, x0, t0, t1, dt)
    improved_euler(f, x0, t0, t1, dt)
    mid_point(f, x0, t0, t1, dt)
    runge_kutta(f, x0, t0, t1, dt)

    # Example 2: System
    def F(t, x):
        return np.array([1.1 * x[0] - 0.4 * x[0] * x[1], 0.4 * x[0] * x[1] - 0.1 * x[1]])

    x0 = np.array([1,2])
    t0 = 0
    t1 = 1
    dt = 0.1 # = (t0-t1)/N

    euler(F, x0, t0, t1, dt)
    improved_euler(F, x0, t0, t1, dt)
    mid_point(F, x0, t0, t1, dt)
    runge_kutta(F, x0, t0, t1, dt)

    #Example 2018
    def G(t, x):
        return np.array([-3*x[0]+2*x[1], 3*x[0]-4*x[1]])

    x0 = np.array([0, 0.5])
    t0 = 0
    t1 = 0.4
    dt = 0.2
    runge_kutta(G, x0, t0, t1, dt)

    #Example 2019
    def f(t, x):
        return t**2-math.exp(x)*math.sin(t)

    x0 = np.array([1])
    t0 = 0
    t1 = 0.5
    dt = 0.25
    runge_kutta(f, x0, t0, t1, dt)

    #Example 2019
    def f(t, x):
        return t**3 - 4*x
    x0 = np.array([1])
    t0  = 0
    t1 = 0.4
    dt = 0.2
    improved_euler(f, x0, t0, t1, dt)
    mid_point(f, x0, t0, t1, dt)


import time
import random

from numba import jit
from contextlib import contextmanager

@jit(nopython=True)
def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

@contextmanager
def timing(description: str) -> None:
    start = time.perf_counter()
    yield
    end = time.perf_counter()

    print(f"{description}: {end-start:.6f}")

if __name__=="__main__":

    with timing("First iteration "):
        pi = monte_carlo_pi(1_000_000)

    with timing("Second iteration"):
        pi = monte_carlo_pi(1_000_000)


"""

Friedman function (5 random inputs, scalar output)
======================================================================

In this example, PCE is used to generate a surrogate model for a given set of 5D data.

Friedman function
----------------------------------------

.. math:: f(x) = 10 sin(\pi x_1x_2) + 20(x_3 - 0.5)^2 + 10x_4 + 5x_5

**Description:**  Dimensions: 5

**Input Domain:**  This function is evaluated on the hypercube :math:`x_i \in [0,1]` for all i = 1, …, 5.

**Reference:**  Friedman, J. H. (1991). Multivariate adaptive regression splines. The annals of statistics, 19(1), 1-67.
"""

# %% md
#
# Import necessary libraries.

# %%

import numpy as np
import matplotlib.pyplot as plt
from UQpy.distributions import Uniform, JointIndependent
from UQpy.surrogates import *

# %% md
#
# Define the function.

# %%

def function(x):
    return 10*np.sin(np.pi*x[:,0]*x[:,1]) + 20*(x[:,2]-0.5)**2 + 10*x[:,3] + 5*x[:,4]

# %% md
#
# Create a distribution object, generate samples and evaluate the function at the samples.

# %%

np.random.seed(1)

dist = Uniform(loc=0, scale=1)

marg = [dist]*5
joint = JointIndependent(marginals=marg)

n_samples = 200
x = joint.rvs(n_samples)
y = function(x)

# %% md
#
# Create an object from the PCE class. Then compute PCE coefficients using least squares regression.

# %%

max_degree = 3

polynomial_basis = TotalDegreeBasis(joint, max_degree)
least_squares = LeastSquareRegression()
pce = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=least_squares)
pce.fit(x,y)

# %% md
#
# Compute PCE coefficients using Lasso regression.

# %%

polynomial_basis = TotalDegreeBasis(joint, max_degree)
lasso = LassoRegression()
pce2 = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=lasso)
pce2.fit(x,y)

# %% md
#
# Compute PCE coefficients using Ridge regression.

# %%

polynomial_basis = TotalDegreeBasis(joint, max_degree)
ridge = RidgeRegression()
pce3 = PolynomialChaosExpansion(polynomial_basis=polynomial_basis, regression_method=ridge)
pce3.fit(x,y)

# %% md
# Error Estimation
# -----------------
# Validation error.

# %%

n_samples = 100
x_val = joint.rvs(n_samples)
y_val = function(x_val)

y_pce = pce.predict(x_val).flatten()
y_pce2 = pce2.predict(x_val).flatten()
y_pce3 = pce3.predict(x_val).flatten()

error = np.sum(np.abs(y_pce - y_val)/np.abs(y_val))/n_samples
error2 = np.sum(np.abs(y_pce2 - y_val)/np.abs(y_val))/n_samples
error3 = np.sum(np.abs(y_pce3 - y_val)/np.abs(y_val))/n_samples

print('Validation error, LSTSQ-PCE:', error)
print('Validation error, LASSO-PCE:', error2)
print('Validation error, Ridge-PCE:', error3)

# %% md
# Moment Estimation
# -----------------
# Returns mean and variance of the PCE surrogate.

# %%

n_mc = 1000000
x_mc = joint.rvs(n_mc)
y_mc = function(x_mc)
mean_mc = np.mean(y_mc)
var_mc = np.var(y_mc)

print('Moments from least squares regression :', pce.get_moments())
print('Moments from LASSO regression :', pce2.get_moments())
print('Moments from Ridge regression :', pce3.get_moments())
print('Moments from MC integration: ', mean_mc, var_mc)


import math
import numba

import numpy as np
# import autograd.numpy as np
# from autograd import grad

@numba.jit
def to_rad(deg):
    return np.pi * deg / 180.0

@numba.jit
def iiwa_forward_kinematics(joints, out='xyz'):
    ''' Direct Kinematics from iiwa structure. Using its dimensions and angles.
    '''
    #joints = [0,0,0,0,0,0,0]
    DH=np.array([[joints[0], 0.34, 0, -90],
                 [joints[1], 0.0, 0, 90],
                 [joints[2], 0.4, 0, 90],
                 [joints[3], 0.0, 0, -90],
                 [joints[4], 0.4, 0, -90],
                 [joints[5], 0.0, 0, 90],
                 [joints[6], 0.126, 0, 0]])
    Tr = np.eye(4)

    for i in range(0, len(DH)):
        t = DH[i, 0]
        d = DH[i, 1]
        a = DH[i, 2]
        al= DH[i, 3]
        # rad_al = math.radians(al)
        rad_al = to_rad(al)
        T = np.array([[np.cos(t), -np.sin(t)*np.cos(rad_al), np.sin(t)*np.sin(rad_al), a*np.cos(t)],
              [np.sin(t), np.cos(t)*np.cos(rad_al), -np.cos(t)*np.sin(rad_al), a*np.sin(t)],
              [0, np.sin(rad_al), np.cos(rad_al), d],
              [0, 0, 0, 1]])
        Tr = np.matmul(Tr, T)
    #pp = [Tr[0,3], Tr[1,3], T[2,3]]
    #pp
    if out=='xyz':
        return Tr[0:3,3]
        # return [Tr[0,3], Tr[1,3], Tr[2,3]]
    if out=='matrix':
        return Tr
    return None

@numba.jit
def iiwa_jacobian(state):
    fun = iiwa_forward_kinematics
    eps = 0.001
    jacobian = np.zeros((3,7))

    inp = np.array(state)
    selector = np.array([0,1,2,3,4,5,6])

    for i in selector:
        jacobian[:,i] = (fun(inp + eps* (selector == i)) - fun(inp - eps* (selector == i))) / (2*eps)

    # print(jacobian)
    return jacobian

# def jacobian_test():
#     J = iiwa_jacobian(7*[0.5])
#     eef_vel = np.dot(J, np.array(7*[0.1]))
#
# import timeit
#
#
# print(timeit.timeit(jacobian_test, number=1000))
#
# jacobian_test()
# jacobian_test()
# jacobian_test()
#
# print('timeit')
#
# print(timeit.timeit(jacobian_test, number=1000))
#
# print(timeit.timeit(jacobian_test, number=1000))

# jacobian_test()

# grad_iiwa_fk = grad(iiwa_forward_kinematics)
# print(grad_iiwa_fk(7*[0.01]))

# print(iiwa_forward_kinematics(7*[0.01]))


# -*- coding: utf-8 -*-
#%% NumPyの読み込み
import numpy as np
#   SciPyのstatsモジュールの読み込み
import scipy.stats as st
#   MatplotlibのPyplotモジュールの読み込み
import matplotlib.pyplot as plt
#   日本語フォントの設定
from matplotlib.font_manager import FontProperties
import sys
if sys.platform.startswith('win'):
    FontPath = 'C:\\Windows\\Fonts\\meiryo.ttc'
elif sys.platform.startswith('darwin'):
    FontPath = '/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc'
elif sys.platform.startswith('linux'):
    FontPath = '/usr/share/fonts/truetype/takao-gothic/TakaoPGothic.ttf'
else:
    print('このPythonコードが対応していないOSを使用しています．')
    sys.exit()
jpfont = FontProperties(fname=FontPath)
#%% ベルヌーイ分布の成功確率qの事前分布
fig1 = plt.figure(num=1, facecolor='w')
q = np.linspace(0, 1, 250)
plt.plot(q, st.uniform.pdf(q), 'k-')
plt.plot(q, st.beta.pdf(q, 4, 6), 'k--')
plt.xlim(0, 1)
plt.ylim(0, 2.8)
plt.legend(['(A) 一様分布 ($\\alpha$ = 1, $\\beta$ = 1)',
            '(B) ベータ分布 ($\\alpha$ = 4, $\\beta$ = 6)'],
            loc='best', frameon=False, prop=jpfont)
plt.xlabel('成功確率 q', fontproperties=jpfont)
plt.ylabel('確率密度', fontproperties=jpfont)
plt.savefig('pybayes_fig_beta_prior.png', dpi=300)
plt.show()


import numpy as np
import matplotlib.pyplot as plt

def vandermonde(x, d):
  # Expanding x in Vandermonde Matrix: f(x) = 1 + x + x^2 ... + x^d
  X = np.matrix(np.polynomial.polynomial.polyvander(x, d))
  return X

def polynomial_regression(x, y, d):
  # Expanding x in Vandermonde Matrix
  X = vandermonde(x, d)
  # Calculating model weights: W = Inv(X.T * X) * X.T * Y
  #w = np.linalg.inv(np.transpose(X) * X) * np.transpose(X) * np.transpose(np.matrix(y))
  w = np.linalg.lstsq(X, y)[0]
  return np.transpose(np.matrix(w))

def bayesian_regression(x, y, d, var_o):
  # Expanding x in Vandermonde Matrix
  X = vandermonde(x, d)
  # Calculating y variance
  var_y = np.var(y)
  # Calculating model weights: W = Inv(X.T * X) * X.T * Y
  w = np.linalg.inv(np.transpose(X) * X + var_y / var_o * np.identity(X.shape[1])) * np.transpose(X) * np.transpose(np.matrix(y))
  return w

# Importing dataset
## Dataset path
dataset_path = 'data/whData.dat'
## Column names
dt = np.dtype([('w', np.float), ('h', np.float), ('g', np.str_, 1)])
## Loading the dataset
dataset = np.loadtxt(dataset_path, dtype=dt, comments='#', delimiter=None)
## Loading dataset without outliers
dataset_without_outliers = dataset[np.where(dataset['w'] > 0)]
## Loading dataset outliers (w < 0)
dataset_outliers = dataset[np.where(dataset['w'] < 0)]

# Heights from the training data
train_data = dataset_without_outliers['h']
# Weights from the training data
train_labels = dataset_without_outliers['w']
# Heights to predict their corresponding weights
test_data = dataset_outliers['h']

# Regression line X points to draw the model fitted line
regressionX = np.linspace(np.amin(train_data)-5, np.amax(train_data)+5, 1000)

# Regression for d = 5
d = 5
# Expand regression line X points in Vandermonde Matrix
X_regression = vandermonde(regressionX, d)
# Expand outlier height points in Vandermonde Matrix
X_test = vandermonde(test_data, d)

# Polynomial Regression
## Calculating the coefficients (weights) of the polynomial regression fitted line
Wp = polynomial_regression(train_data, train_labels, d)
# Calculating the fitted line Y points
yp = X_regression * Wp
# Calculating the corresponding weights (prediction values)
predictions = X_test * Wp

## Calculating the coefficients (weights) of the bayesian regression fitted line
var_o = 3.0
Wb = bayesian_regression(train_data, train_labels, d, var_o)
# Calculating the fitted line Y points
yb = X_regression * Wb
# Calculating the corresponding weights (prediction values)
predictionsb = X_test * Wb

# Plot the results
## Plot the data
plt.plot(train_data, train_labels, 'k.', label='Data')
## Plot the polynomial fitted line
plt.plot(regressionX, yp, 'b-', label='Polynomial Fit')
## plot the bayesian fitted line
plt.plot(regressionX, yb, 'g-', label='Bayesian Regression')
## Setting axes limits
plt.xlim(np.amin(train_data)-5, np.amax(train_data)+5)
plt.ylim(np.amin(train_labels)-5, np.amax(train_labels)+5)
plt.legend(loc='lower right')
plt.savefig("out/task23/bayesian_regression.png", bbox_inches="tight", pad_inches=0)
plt.show()


import numpy as np

#SE(2) functions
def matrix_log_SO2(SO2_mat) :
  #ln(R) in SO(3) = theta
  return np.arctan2(SO2_mat[1,0], SO2_mat[0, 0])

def matrix_log_SE2(SE2_mat) :
  theta = matrix_log_SO2(SE2_mat[0:2, 0:2])
  if (theta < 1e-6) :
    A = 1
    B = 0
  else :
    A = np.sin(theta)/theta
    B = (1-np.cos(theta))/theta

  v_inv = 1/(A**2 + B**2) * np.array([[A, B], [-B, A]])
  mat_log = np.array(np.matmul(v_inv, SE2_mat[0:2, 2]))
  mat_log = np.append(mat_log, theta)

  return mat_log

def matrix_exp_so2(theta) :
  #reconstruct R.
  return np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])

def matrix_exp_se2(twist) :
  theta = twist[-1]
  R = matrix_exp_so2(theta)

  #V converges to I2
  if (theta < 1e-6) :
    V = np.eye(2)
  else:
    V = 1/theta * np.array([[np.sin(theta), -(1 - np.cos(theta))], [(1-np.cos(theta)), np.sin(theta)]])

  mat_exp = np.zeros((3,3))
  mat_exp[0:2, 0:2] = R
  mat_exp[0:2, 2] = np.matmul(V, twist[0:2])
  mat_exp[2, 2] = 1

  return mat_exp

def get_twist_SE2(Xstart, pos_end, pos_future=None) :

  Xend = np.zeros((3,3))

  Xend[-1,-1] = 1
  Xend[0:2, 2] = pos_end

  #compute end direction (face in direction of future step i.e. end+1)
  if not pos_future is None:
    next_displacement = pos_future - pos_end
    next_theta = np.arctan2(next_displacement[1], next_displacement[0])
    Xend[0:2, 0:2] = np.array([[np.cos(next_theta), -np.sin(next_theta)], [np.sin(next_theta), np.cos(next_theta)]])
  else :
    Xend[0:2, 0:2] = Xstart[0:2, 0:2]

  # set_trace()
  twist_SE2 = matrix_log_SE2(np.matmul(np.linalg.inv(Xstart), Xend))

  return twist_SE2, Xend

def twist_motion(Xstart, twist, s=1) :
  return np.matmul(Xstart, s * matrix_exp_se2(twist))


import numpy as np
from enum import Enum

""" All the activation functions available (feel free to add more) """

def identity(val):
    return val

def binarystep(val):
    return (0 if val < 0 else 1)

def sigmoid(val, activationResponse=1/4.924273):
    return 1 / (1 + np.exp(-(val/activationResponse)))

def tanh(val):
    return np.tanh(val)

def relu(val):
    return np.maximum(0, val)

def leakyrelu(val):
    return (val*0.01 if val < 0 else val)

def absval(val):
    return abs(val)

def gaussian(val):
    return np.exp(-(val**2))

def arctan(val):
    return np.arctan(val)

def sine(val):
    return np.sin(val)

def cosine(val):
    return np.cos(val)

class ActivationFunctions(Enum):
    IDENTITY = identity
    BINARYSTEP = binarystep
    SIGMOID = sigmoid
    TANH = tanh
    RELU = relu
    LEAKYRELU = leakyrelu
    ABSVAL = absval
    GAUSSIAN = gaussian
    ARCTAN = arctan
    SINE = sine
    COSINE = cosine


# K=funLtoK(L)
# The function funLtoK(L) converts a kernel L matrix into  a (normalized)
# kernel K matrix. The K matrix has to be semi-positive definite.

import numpy as np  # NumPy package for arrays, random number generation, etc

##TEMP: Testing
#B=np.array([[3, 2, 1], [4, 5,6], [9, 8,7]]);
#
#L=np.matmul(B.transpose(),B);
#
#

def funLtoK(L):
    #METHOD 1 -- using eigen decomposition.
    #This method doesn't need inverse calculating and seems to more stable.
    eigenValL, eigenVectLK = np.linalg.eig(L)  # eigen decomposition
    eigenValK = eigenValL/(1+eigenValL)  # eigenvalues of K
    eigenValK = np.diagflat(eigenValK)  # eigenvalues of L as diagonal matrix
    # recombine from eigen components
    K = np.matmul(np.matmul(eigenVectLK, eigenValK), eigenVectLK.transpose())
    K = np.real(K)  # make sure all values are real
    return K

#K=funLtoK(L)
# K=
#array([[ 0.76022099,  0.33480663, -0.09060773],
#       [ 0.33480663,  0.3320442 ,  0.32928177],
#       [-0.09060773,  0.32928177,  0.74917127]])

# #METHOD 2 -- standard approach.
# #Slightly faster, seems to be less stable.
# K=np.matmul(L,np.linalg.inv(L+np.eye(L.shape[0])));


#%% setup
import numpy as np
from matplotlib import pyplot as plt

def euler(f, y_0, start, stop, step):
    w_i = y_0
    w = [w_i]
    t = np.arange(start, stop+step, step)  # include endpoint, therefore add step
    for t_i in t[:-1]:
        w_i += step*f(t_i, w_i)
        w.append(w_i)

    return t, w

def y_a(t):
    return 0.5*t*t + 1

def y_b(t):
    return np.e ** (t*t*t/3)

def dydt_a(t, y):
    return t

def dydt_b(t, y):
    return t*t*y

#%% run
h = 0.1
a, b = 0., 1.
y_0 = 1.
t, w_a = euler(dydt_a, y_0, a, b, h)
_, w_b = euler(dydt_b, y_0, a, b, h)

#%% print a
print("t:       ", '|', "w:       ", '|', "error:  ")
for i in range(len(t)):
    print(f'{t[i]:+6f} | {w_a[i]:+6f} | {y_a(t[i]) - w_a[i]:+6f}')

#%% print b ØVING
print("t:       ", '|', "w:       ", '|', "error:  ")
for i in range(len(t)):
    print(f'{t[i]:+6f} | {w_b[i]:+6f} | {y_b(t[i]) - w_b[i]:+6f}')

#%% plot a
plt.plot(t, w_a, 'r')
plt.scatter(t, w_a, c='r', s=15)
plt.plot(t, y_a(t), 'b')
plt.show()

#%% plot b
plt.plot(t, w_b, 'r')
plt.scatter(t, w_b, c='r', s=15)
plt.plot(t, y_b(t), 'b')
plt.show()


from numpy import mgrid, sum

def moments2e(image):
	"""
	for testing purposes only
	for 'production' use, please use cv2 or other implemented moments

	SOURCE: https://github.com/shackenberg/Image-Moments-in-Python/blob/master/moments.py
	This function calculates the raw, centered and normalized moments
	for any image passed as a numpy array.

	Further reading:
	https://en.wikipedia.org/wiki/Image_moment
	https://en.wikipedia.org/wiki/Central_moment
	https://en.wikipedia.org/wiki/Moment_(mathematics)
	https://en.wikipedia.org/wiki/Standardized_moment
	http://opencv.willowgarage.com/documentation/cpp/structural_analysis_and_shape_descriptors.html#cv-moments

	compare with:
	import cv2
	cv2.moments(image)

	"""
	assert len(image.shape) == 2 # only for grayscale images
	x, y = mgrid[:image.shape[0],:image.shape[1]]
	moments = {}
	moments['mean_x'] = sum(x*image)/sum(image)
	moments['mean_y'] = sum(y*image)/sum(image)

	# raw or spatial moments
	moments['m00'] = sum(image)
	moments['m01'] = sum(x*image)
	moments['m10'] = sum(y*image)
	moments['m11'] = sum(y*x*image)
	moments['m02'] = sum(x**2*image)
	moments['m20'] = sum(y**2*image)
	moments['m12'] = sum(x*y**2*image)
	moments['m21'] = sum(x**2*y*image)
	moments['m03'] = sum(x**3*image)
	moments['m30'] = sum(y**3*image)

	# central moments
	# moments['mu01']= sum((y-moments['mean_y'])*image) # should be 0
	# moments['mu10']= sum((x-moments['mean_x'])*image) # should be 0
	moments['mu11'] = sum((x-moments['mean_x'])*(y-moments['mean_y'])*image)
	moments['mu02'] = sum((y-moments['mean_y'])**2*image) # variance
	moments['mu20'] = sum((x-moments['mean_x'])**2*image) # variance
	moments['mu12'] = sum((x-moments['mean_x'])*(y-moments['mean_y'])**2*image)
	moments['mu21'] = sum((x-moments['mean_x'])**2*(y-moments['mean_y'])*image)
	moments['mu03'] = sum((y-moments['mean_y'])**3*image)
	moments['mu30'] = sum((x-moments['mean_x'])**3*image)

	# opencv versions
	#moments['mu02'] = sum(image*(x-m01/m00)**2)
	#moments['mu02'] = sum(image*(x-y)**2)

	# wiki variations
	#moments['mu02'] = m20 - mean_y*m10
	#moments['mu20'] = m02 - mean_x*m01

	# central standardized or normalized or scale invariant moments
	moments['nu11'] = moments['mu11'] / sum(image)**(2/2+1)
	moments['nu12'] = moments['mu12'] / sum(image)**(3/2+1)
	moments['nu21'] = moments['mu21'] / sum(image)**(3/2+1)
	moments['nu20'] = moments['mu20'] / sum(image)**(2/2+1)
	moments['nu03'] = moments['mu03'] / sum(image)**(3/2+1) # skewness
	moments['nu30'] = moments['mu30'] / sum(image)**(3/2+1) # skewness
	return moments


import numpy as np

class SecantMethod:
    def __init__(self, f, e, max_itterations):
        self.name              = 'Secant'
        self.f                 = f
        self.epsilon           = e
        self.itterations       = 0
        self.max_itterations   = max_itterations

    def method(self, xn_2, xn_1):
        return ( xn_2 * self.f(xn_1) - xn_1 * self.f(xn_2) ) / ( self.f(xn_1) - self.f(xn_2) )

    def find(self, x1, x2):
        if np.absolute(self.f(x2)) < self.epsilon or self.itterations == self.max_itterations:
            return x2
        if np.absolute(self.f(x1)) < self.epsilon:
            return x1
        self.itterations += 1
        return self.find(x2, self.method(x1, x2))


import sympy.physics.mechanics as me
import sympy as sm
import math as m
import numpy as np

x, y = me.dynamicsymbols('x y')
xd, yd = me.dynamicsymbols('x y', 1)
e = sm.cos(x)+sm.sin(x)+sm.tan(x)+sm.cosh(x)+sm.sinh(x)+sm.tanh(x)+sm.acos(x)+sm.asin(x)+sm.atan(x)+sm.log(x)+sm.exp(x)+sm.sqrt(x)+sm.factorial(x)+sm.ceiling(x)+sm.floor(x)+sm.sign(x)
e = (x)**2+sm.log(x, 10)
a = sm.Abs(-1*1)+int(1.5)+round(1.9)
e1 = 2*x+3*y
e2 = x+y
am = sm.Matrix([e1.expand().coeff(x), e1.expand().coeff(y), e2.expand().coeff(x), e2.expand().coeff(y)]).reshape(2, 2)
b = (e1).expand().coeff(x)
c = (e2).expand().coeff(y)
d1 = (e1).collect(x).coeff(x,0)
d2 = (e1).collect(x).coeff(x,1)
fm = sm.Matrix([i.collect(x)for i in sm.Matrix([e1,e2]).reshape(1, 2)]).reshape((sm.Matrix([e1,e2]).reshape(1, 2)).shape[0], (sm.Matrix([e1,e2]).reshape(1, 2)).shape[1])
f = (e1).collect(y)
g = (e1).subs({x:2*x})
gm = sm.Matrix([i.subs({x:3}) for i in sm.Matrix([e1,e2]).reshape(2, 1)]).reshape((sm.Matrix([e1,e2]).reshape(2, 1)).shape[0], (sm.Matrix([e1,e2]).reshape(2, 1)).shape[1])
frame_a = me.ReferenceFrame('a')
frame_b = me.ReferenceFrame('b')
theta = me.dynamicsymbols('theta')
frame_b.orient(frame_a, 'Axis', [theta, frame_a.z])
v1=2*frame_a.x-3*frame_a.y+frame_a.z
v2=frame_b.x+frame_b.y+frame_b.z
a = me.dot(v1, v2)
bm = sm.Matrix([me.dot(v1, v2),me.dot(v1, 2*v2)]).reshape(2, 1)
c=me.cross(v1, v2)
d = 2*v1.magnitude()+3*v1.magnitude()
dyadic=me.outer(3*frame_a.x, frame_a.x)+me.outer(frame_a.y, frame_a.y)+me.outer(2*frame_a.z, frame_a.z)
am = (dyadic).to_matrix(frame_b)
m = sm.Matrix([1,2,3]).reshape(3, 1)
v=m[0]*frame_a.x +m[1]*frame_a.y +m[2]*frame_a.z


"""Linear Classification Models
Author: Rajan Subramanian
Created: May 25, 2020
"""

import numpy as np
from scipy.optimize import minimize
from learning.base import LinearBase
from typing import Union, Dict
from numpy.linalg import norm

class LogisticRegression(LinearBase):
    """
    Implements Logistic Regression via nls
    Args:
    fit_intercept: indicates if intercept is needed or not

    Attributes:
    theta:          Coefficient Weights after fitting
    predictions:    Predicted Values from fitting
    residuals:      Number of incorrect Predictions

    Notes:
    Class uses two estimation methods to estimate the parameters
    of logistic regression
    - A implemention using Newton's Method is given
    - A implemention using Stochastic Gradient Descent is given
    """
    def __init__(self, fit_intercept: bool = True, degree: int = 1):
        self.fit_intercept = fit_intercept
        self.degree = degree
        self.run = False

    def sigm(self, z: np.ndarray) -> np.ndarray:
        """Computes the sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: sigmoid function value
        """
        return 1.0 / (1 + np.exp(-z))

    def sigm_prime(self, z: np.ndarray) -> np.ndarray:
        """computes the first derivative of sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: the first derivative of sigmoid function
        """
        return self.sigm(z) * (1 - self.sigm(z))

    def _jacobian(self,
                  guess: np.ndarray,
                  X: np.ndarray,
                  y: np.ndarray
                  ):
        """Computes the jacobian of likelihood function

        Args:
            guess (np.ndarray): the initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            [np.ndarray]: first partial derivatives wrt weights
        """
        predictions = self.predict(X, guess)
        return -1*(X.T @ (y - predictions))

    def _hessian(self,
                 guess: np.ndarray,
                 X: np.ndarray,
                 y: np.ndarray
                 ):
        """computes the hessian wrt weights

        Args:
            guess (np.ndarray): initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            np.ndarray: second partial derivatives wrt weights
        """
        z = self.net_input(X, thetas=guess)
        prob = self.sigm_prime(z)
        n = prob.shape[0]
        W = np.zeros((n, n))
        np.fill_diagonal(W, prob)
        return X.T @ W @ X

    def _loglikelihood(self, y, z):
        """returns the loglikelihood function of logistic regression

        Args:
            y (np.ndarray): response variable
                            shape = (n_samples,)
            z (np.ndarray): result of net input function
                            shape = (n_samples,)

        Returns:
            np.ndarray: loglikelihood function
        """
        return y @ z - np.log(1 + np.exp(z)).sum()

    def _objective_func(self, guess: np.ndarray, X: np.ndarray, y: np.ndarray):
        """the objective function to be minimized

        Args:
            guess (np.ndarray): initial guess for optimization
            X (np.ndarray): design matrix
                            shape = {n_samples, p_features}
            y (np.ndarray): the response variable
                            shape = {n_samples,}

        Returns:
            float: value from loglikelihood function
        """
        # z = X @ theta
        z = self.net_input(X, thetas=guess)
        f = self._loglikelihood(y, z)
        return -f

    def fit(self,
            X: np.ndarray,
            y: np.ndarray,
            ) -> 'LogisticRegression':
        """fits model to training data and returns regression coefficients
        Args:
        X:
            shape = (n_samples, p_features)
            n_samples is number of instances i.e rows
            p_features is number of features i.e columns
        y:
            shape = (n_samples,)
            Target values

        Returns:
        object
        """
        X = self.make_polynomial(X)
        # generate random guess
        guess_params = np.mean(X, axis=0)
        self.theta = minimize(self._objective_func,
                              guess_params,
                              jac=self._jacobian,
                              hess=self._hessian,
                              method='BFGS',
                              options={'disp': True},
                              args=(X, y))['x']
        return self

    def newton_system(self, X, y, func, jac, x, tol_approx=10E-9, tol_consec=10E-6):
        """Solves N-dimensional newton's problem for F(x) = 0

        Args:
            func (function): given function evaluated at x
            jac (function): jacobian of func as a function of x
            x (np.ndarray): initial guess
            tol_approx (float, optional): largest admissible value of ||F(x)||
                                          when solution is found
                                          Defaults to 10E-9.
            tol_consec (float, optional): largest admissible distance between
                                          two consecutive approximations when
                                          solution is found
                                          Defaults to 10E-6.
        """
        xnew, xold = x, x - 1
        count = 0
        while norm(xnew - xold, ord=2) > tol_consec or count > niter:
            xold = xnew
            z = self.net_input(X, thetas=xold)
            predictions = self.predict(X, xold)
            prob = self.sigm_prime(z)
            n = prob.shape[0]
            W = np.zeros((n, n))
            Winv = np.zeros((n, n))
            np.fill_diagonal(W, prob)
            np.fill_diagonal(Winv, 1/prob)
            zbar = X @ xold - Winv @ (y-predictions)
            xnew = np.linalg.inv(X.T @ W @ X) @ X @ W @ zbar
            count += 1
        return xnew

    def net_input(self, X: np.ndarray, thetas: np.ndarray) -> np.ndarray:
        """Computes linear transformation X*theta

        Args:
            X (np.ndarray): design matrix,
            shape = {n_samples, p_features}
            thetas (np.ndarray): weights of logistic function
            shape = {p_features + intercept}

        Returns:
            np.ndarray: linear transformation
        """
        return X @ thetas

    def predict(self,
                X: np.ndarray,
                thetas: np.ndarray = None,
                ) -> Union[np.ndarray, Dict]:
        """Makes predictions of probabilities

        Args:
            X (np.ndarray): design matrix
            shape = {n_samples, p_features}
            thetas (np.ndarray, optional): estimated weights from fitting
            Defaults to None.
            shape = {p_features + intercept,}

        Returns:
            Union[np.ndarray, Dict]: predicted probabilities
            shape = {n_samples,}
        """
        if thetas is None:
            if isinstance(self.theta, np.ndarray):
                return self.sigm(self.net_input(X, self.theta))
            else:
                return self.sigm(self.net_input(X, self.theta['x']))
        return self.sigm(self.net_input(X, thetas))

class LogisticRegressionGD(LinearBase):
    """Implements Logistic Regression via Gradient Descent

       Args:
       eta:             Learning rate (between 0.0 and 1.0)
       n_iter:          passees over the training set
       random_state:    Random Number Generator seed
                        for random weight initilization

       Attributes:
       theta:           Weights after fitting
       residuals:       Number of incorrect predictions
    """
    def __init__(self,
                 eta: float = 0.001,
                 n_iter: int = 20,
                 random_state: int = 1,
                 fit_intercept: bool = True,
                 degree: int = 1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state
        self.fit_intercept = fit_intercept
        self.degree = degree
        self.run = False

    def fit(self, X: np.ndarray, y: np.ndarray) -> 'LogisticRegressionGD':
        """fits training data
        Args:
        X: shape = {n_samples, p_features}
                    n_samples is number of instances i.e rows
                    p_features is number of features (the dimension of dataset)

        y: shape = {n_samples,}
                    Target values
        Returns:
        object
        """
        n_samples, p_features = X.shape[0], X.shape[1]
        self.theta = np.zeros(shape=1 + p_features)
        self.cost = []
        X = self.make_polynomial(X)

        for _ in range(self.n_iter):
            z = self.net_input(X, self.theta)
            self.theta += self.eta * self._jacobian(self.theta, X, y)
            self.cost.append(self._cost(y, z) / n_samples)
        self.run = True
        return self

    def _jacobian(self,
                  guess: np.ndarray,
                  X: np.ndarray,
                  y: np.ndarray
                  ):
        """Computes the jacobian of likelihood function

        Args:
            guess (np.ndarray): the initial guess for optimizer
            X (np.ndarray): design matrix
                            shape = (n_samples, n_features)
            y (np.ndarray): response variable
                            shape = (n_samples,)

        Returns:
            [np.ndarray]: first partial derivatives wrt weights
        """
        predictions = self.predict(X, guess)
        return (X.T @ (y - predictions))

    def _cost(self, y, z):
        """computes cost of likelihood function

        Args:
            y (np.ndarray): response variable
                            shape = (n_samples,)
            z (np.ndarray): result of net input function
                            shape = (n_samples,)

        Returns:
            np.ndarray: loglikelihood function
        """
        # direction is reversed since we are minimizng cost
        return -1 * (y @ z - np.log(1 + np.exp(z)).sum())

    def net_input(self, X: np.ndarray, thetas: np.ndarray) -> np.ndarray:
        """Computes linear transformation X*theta

        Args:
            X (np.ndarray): design matrix,
            shape = {n_samples, p_features}
            thetas (np.ndarray): weights of logistic function
            shape = {p_features + intercept}

        Returns:
            np.ndarray: linear transformation
        """
        return X @ thetas

    def sigm(self, z: np.ndarray) -> np.ndarray:
        """Computes the sigmoid function

        Args:
            z (np.ndarray): input value from linear transformation

        Returns:
            np.ndarray: sigmoid function value
        """
        return 1.0 / (1 + np.exp(-z))

    def predict(self,
                X: np.ndarray,
                thetas: np.ndarray = None,
                ) -> Union[np.ndarray, Dict]:
        """Makes predictions of probabilities

        Args:
            X (np.ndarray): design matrix
            shape = {n_samples, p_features}
            thetas (np.ndarray, optional): estimated weights from fitting
            Defaults to None.
            shape = {p_features + intercept,}

        Returns:
            Union[np.ndarray, Dict]: predicted probabilities
            shape = {n_samples,}
        """
        if thetas is None:
            if isinstance(self.theta, np.ndarray):
                return self.sigm(self.net_input(X, self.theta))
        return self.sigm(self.net_input(X, thetas))


#!/usr/bin/env python3

import numpy as np

def lowPassFIR(fc, b = 0.08):
  #fc: Cutoff frequency as a fraction of the sampling rate (in (0, 0.5)).
  #b: Transition band, as a fraction of the sampling rate (in (0, 0.5)).

  N = int(np.ceil((4 / b)))
  if not N % 2: N += 1  # Make sure that N is odd.
  n = np.arange(N)

  # Compute sinc filter.
  h = np.sinc(2 * fc * (n - (N - 1) / 2))

  # Compute Blackman window.
  w = 0.42 - 0.5 * np.cos(2 * np.pi * n / (N - 1)) + \
      0.08 * np.cos(4 * np.pi * n / (N - 1))

  # Multiply sinc filter by window.
  h = h * w

  # Normalize to get unity gain.
  h = h / np.sum(h)

  return N, h

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

import matplotlib.pyplot as plt

fs = 1000 # sampling frequency

# generate properly the time vector
t = np.arange(1000)/fs
sga = np.sin(2*np.pi*2*t)   # signal with f = 2
sgb = np.sin(2*np.pi*10*t)  # signal with f = 10
sgc = sga + sgb

N, h = lowPassFIR(fc = 6/fs, b = 2/fs)
sgf = np.convolve(sgc, h)

plt.plot(t, sgc, label = 'original')
plt.plot(t, sgf[int(N/2):1000 + int(N/2)], label = 'low-pass')
plt.plot(t, sga, label = 'f = 2')
plt.legend()
plt.show()


# Author: Eyad Sibai
import numpy as np
from scipy.stats import iqr

def histogram_square_formula(values):
    """ reference https://www.kdnuggets.com/2018/02/histogram-tips-tricks.html
    :param values: the values of the histogram data
    :return: number of bins and width of the bin
    """
    bins = np.sqrt(len(values))
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_sturges_formula(values):
    """
    :param values:
    :return:
    """
    bins = np.ceil(np.log2(len(values))) + 1
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_rice_formula(values):
    """
    :param values:
    :return:
    """
    bins = 2 * np.power(len(values), 1. / 3)
    bin_width = (np.max(values) - np.min(values)) / bins
    return bins, bin_width

def histogram_scott_formula(values):
    """
    :param values:
    :return:
    """
    bin_width = 3.5 * np.std(values) / np.power(len(values), 1 / 3)
    bins = (np.max(values) - np.min(values)) / bin_width
    return bins, bin_width

def histogram_freedman_diaconis_formula(values):
    """
    :param values:
    :return:
    """
    bin_width = 2 * iqr(values) / np.power(len(values), 1 / 3)
    bins = (np.max(values) - np.min(values)) / bin_width
    return bins, bin_width

# Author: Andreas Syrén
def bin_it(values, formula='freedman_diaconis'):
    values = [v for v in values if np.isfinite(v)]
    if formula == 'square':
        bins, bin_width = histogram_square_formula(values)
    elif formula == 'sturges':
        bins, bin_width = histogram_sturges_formula(values)
    elif formula == 'rice':
        bins, bin_width = histogram_rice_formula(values)
    elif formula == 'scott':
        bins, bin_width = histogram_scott_formula(values)
    elif ('freedman' in formula) or ('diaconis' in formula):
        bins, bin_width = histogram_freedman_diaconis_formula(values)
    else:
        raise ValueError("\"%s\" not in implemented formulas." % str(formula))
    return np.linspace(np.min(values), np.max(values), min(500, bins))


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, ln, pprint, oo )
# It is found that the time (in minutes) required by a predator to find a prey is a random variable that is exponentially distributed with

mu = 27

t = symbols( 't' )
a = 1/mu
pdf = a * exp( -a*t )

# According to this distribution, what is the longest time within which the predator will be 85% certain of finding a prey?

pct = .85
x = symbols( 'x' )

prob = solve( Integral( pdf, ( t, 0, x ) ).doit() - pct, x )[ 0 ]
round( prob, 1 )

#  probability that the predator will have to spend more than 1 hour looking for a prey

prob = integrate( pdf, ( t, 60, oo ) ).doit()
round( prob, 4 )


import numpy as np

def mse(targets, outputs):
    return np.mean(np.power(targets - outputs, 2))

def mse_prime(targets, outputs):
    return 2 * (outputs - targets) / targets.size


import networkx as nx
import pandas as pd
import numpy as np
import scipy.integrate

class SI_Model():

    def __init__(self, poblacion, infectados, beta=None):

        self.transitions = nx.MultiDiGraph()
        self.transitions.add_edge('S', 'I', agent='I', rate=beta)
        self.S0 = poblacion-infectados
        self.I0 = infectados
        self.poblacion = poblacion
        self.dataframe = None

    def _new_cases(self, population, time, pos):

        diff = np.zeros(len(pos))
        N = np.sum(population)

        for edge in self.transitions.edges(data=True):
            source = edge[0]
            target = edge[1]
            trans = edge[2]

            rate = trans['rate']*population[pos[source]]

            if 'agent' in trans:
                agent = trans['agent']
                rate *= population[pos[agent]]/N

            diff[pos[source]] -= rate
            diff[pos[target]] += rate

        return diff

    def integrate(self, timesteps):

        pos = {'S':0,
               'I':1}
        population=np.zeros(2)
        population[0] = self.S0
        population[1] = self.I0

        time = np.arange(1, timesteps, 1)

        self.dataframe = pd.DataFrame(scipy.integrate.odeint(self._new_cases, population, time, args=(pos,)), columns=pos.keys(), index=time)
        self.dataframe['tiempo']=time

class SIR_Model():

    def __init__(self, poblacion, infectados, recuperados=0, beta=None, mu=None):

        self.transitions = nx.MultiDiGraph()
        self.transitions.add_edge('S', 'I', agent='I', rate=beta)
        self.transitions.add_edge('I', 'R', rate=mu)
        self.S0 = poblacion-infectados
        self.I0 = infectados
        self.R0 = recuperados
        self.poblacion = poblacion
        self.dataframe = None

    def _new_cases(self, population, time, pos):

        diff = np.zeros(len(pos))
        N = np.sum(population)

        for edge in self.transitions.edges(data=True):
            source = edge[0]
            target = edge[1]
            trans = edge[2]

            rate = trans['rate']*population[pos[source]]

            if 'agent' in trans:
                agent = trans['agent']
                rate *= population[pos[agent]]/N

            diff[pos[source]] -= rate
            diff[pos[target]] += rate

        return diff

    def integrate(self, timesteps):

        pos = {'S':0,
               'I':1,
               'R':2}
        population=np.zeros(3)
        population[0] = self.S0
        population[1] = self.I0
        population[2] = self.R0

        time = np.arange(1, timesteps, 1)

        self.dataframe = pd.DataFrame(scipy.integrate.odeint(self._new_cases, population, time, args=(pos,)), columns=pos.keys(), index=time)
        self.dataframe['tiempo']=time

import numpy,import scipy,import networkx


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 22 16:01:40 2017

@author: picku
"""
"""Linear regression via gradient descent"""

import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
style.use('ggplot')

def total_squared_error(data, m, b):
    """Total squared error between the data points and line
    y = mx + b SE_line = (y_1-(m*x_1+b))^2 + (y_2-(m*x_2)+b)^2 + ...+
    (y_n - (m*x_n + b))^2"""

    total_SE = 0
    for data_point in data:
        SE = (data_point[1] - (m * data_point[0] + b)) ** 2
        total_SE += SE

    return total_SE / len(data)

def partial_difference_quotient(f, points, v, i, h):
    """compute the ith partial difference quotient of function f w.r.t to ith position of v"""

    #add h to just the ith element of v
    w = [v_j + (h if j == i else 0) for j, v_j in enumerate(v)]

    return (f(points, w[0], w[1]) - f(points, v[0], v[1])) / h

def estimate_gradient(points, current_m, current_b):
    """estimate partial derivatives w.r.t. m and b using difference quotients"""
    m = current_m
    b = current_b

    partial_wrt_m = partial_difference_quotient(total_squared_error, points, [m, b], i = 0, h = 0.0001)
    partial_wrt_b = partial_difference_quotient(total_squared_error, points, [m, b], i = 1, h = 0.0001)

    return partial_wrt_m, partial_wrt_b

def step(points, current_m, current_b, learningRate):
    """take step 'downhill' in gradient descent process"""

    #estimate gradient w.r.t. m and w.r.t. b
    partial_wrt_m, partial_wrt_b = estimate_gradient(points, current_m, current_b)

    #update m and b
    next_m = current_m - (learningRate * partial_wrt_m)
    next_b = current_b - (learningRate * partial_wrt_b)

    #update tolerance levels for m and b
    t_val = [next_m - current_m, next_b - current_b]

    return next_m, next_b, t_val

def gradient_descent(points, start_m, start_b, learningRate, numIterations, tolerance):
    """perform gradient descent for specified number of iterations or when specified tolerance
    levels are reached for both m and b"""

    #initialize search
    next_m = start_m
    next_b = start_b

    #keeping track of m and b steps for each iteration
    m_steps = []
    b_steps = []
    total_SE = []

    for i in range(numIterations):

        numIters = 0
        [next_m, next_b, t_val] = step(points, next_m, next_b, learningRate)

        #take step if m and b tolerance are greater than given tolerance value
        if abs(t_val[0]) >= tolerance and abs(t_val[1]) >= tolerance:
            m_steps.append(next_m)
            b_steps.append(next_b)
            total_SE.append(total_squared_error(points, next_m, next_b)) #track total squared error
            numIters = i + 1
        else:
            break

    return next_m, next_b, m_steps, b_steps, total_SE, numIters

def create_line(m, b, x):
    """create line with slope m and y-intercept b using x data points"""
    return m * x + b

def run():

    data = np.load('sampleData.npy')
    x = [row[0] for row in data]
    y = [row[1] for row in data]

    m, b, m_steps, b_steps, SE, numIters = gradient_descent(data, start_m = 0, start_b = 0, learningRate = 0.001, numIterations = 1000, tolerance = 0.0001)

    print('m = {:.2f}, b = {:.2f}'.format(m,b))

    #plot sample data points and line of best fit
    plt.figure(1)
    line = [create_line(m, b, i) for i in range(min(x)-1, max(x)+1)]
    plt.plot(line, label = 'y = {:.2f}x + {:.2f}'.format(m, b))
    plt.scatter(x, y)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.show()
    plt.legend()

    #m and b step progression
    fig = plt.figure(2)
    plt.subplot(311)
    plt.plot(m_steps, b_steps)
    plt.xlabel('m')
    plt.ylabel('b')
    fig.subplots_adjust(bottom = 0.2)

    #Lines created using m and b steps throughout gradient descent procedure
    plt.subplot(312)
    plt.scatter(x, y)
    for i, j in zip(m_steps, b_steps):
        line = [create_line(i, j, k) for k in range(int(round(min(x))-1), int(round(max(x)+1)))]
        plt.plot(line)
    plt.xlabel('x')
    plt.ylabel('y')

    #Total squared error vs. number of iterations
    plt.subplot(313)
    plt.plot(SE)
    plt.ylabel('Total Squared Error')
    plt.xlabel('Number of Iterations')

    #Minimum error found on m,b surface plot
    fig = plt.figure(3)
    ax = fig.gca(projection = '3d')

    m_plot = np.arange(m - 10, m + 10, 0.5)
    b_plot = np.arange(b - 10, b + 10, 0.5)
    m_plot, b_plot = np.meshgrid(m_plot, b_plot)
    SE = np.array(total_squared_error(data, m_plot, b_plot))

    #Set axis limits
    ax.set_zlim(np.min(SE), np.max(SE))
    ax.set_xlim(np.min(m_plot), np.max(m_plot))
    ax.set_ylim(np.min(b_plot), np.max(b_plot))

    #Set axis labels
    ax.set_xlabel('m (slope)')
    ax.set_ylabel('b (y-intercept')
    ax.set_zlabel('SE (Total Squared Error')

    ax.plot_wireframe(m_plot, b_plot, SE, color = 'grey')
    ax.scatter(m, b, total_squared_error(data, m, b), s = 200, c = 'r', marker = '*') #, label = 'm = {}, b = {}'.format(m,b))
    ax.text(m + 0.25, b + 0.25, total_squared_error(data, m, b), '(m = {:.2f}, b = {:.2f})'.format(m,b), size = 12, bbox=dict(facecolor='yellow', alpha=0.5))

    plt.show()

if __name__ == '__main__':
    run()


import numpy as np

##### Base Gratient Descent ####
class GradientDescentRegressor:

    def __init__(self,iterations=100,learning_rate=0.01,weights=np.nan,verbose=False,random_state=100):
        '''
            """ Vanilla Gradient Descent Algorithm

            Read more in the :ref: https://github.com/beyond-papers/beyondpapers

            Parameters
            ----------
            iterations : int , default 100
                Set number of iterations to run.
            learning_rate : float , classifier function, default=0.01
                Set learning rate  of the model.
            weights : array , default np.nan
                weights make gradient descent learn incrementally. Calling fit on the model will reuse the weights.
            random_state : int , default 100
                random state can be change for different random weight initialisation.

            """
        '''
        self.iterations,self.learning_rate,self.weights,self.verbose,self.random_state =\
        iterations,learning_rate,weights,verbose , random_state

    def fit(self,X,y):
        np.random.seed(self.random_state)
        num_features = X.shape[1]
        #Initialisations
        iterations,learning_rate,weights=self.iterations,self.learning_rate,self.weights

        ### Intialise Weights if not exist
        if weights is np.nan:
            bias=np.array(0.0)
            feature_weights=np.random.rand(num_features)
            weights = np.hstack([bias,feature_weights]).T
        ### Transfer Weights
        else:
            weights = self.weights

        ### Add bias col for bias
        bias_col = np.ones(X.shape[0]).reshape(X.shape[0],1)
        X = np.hstack([bias_col,X]).T

        for i in range(iterations):
            ypred=np.dot(weights,X)

            error = ypred - y

            sum_of_squared_error = sum(error**2)
            delta_gradient = learning_rate*np.dot(X,error)/X.shape[1]

            weights =weights-delta_gradient

            self.weights = weights

            if self.verbose == True:
                print('Iterarion error {}: {} '.format(i,sum_of_squared_error))

    def predict(self,X):
        bias_col = np.ones(X.shape[0]).reshape(X.shape[0],1)
        X = np.hstack([bias_col,X]).T
        ypred=np.dot(self.weights,X)
        return ypred


# -*- coding: utf-8 -*-
import numpy as np
from batch_gradient_descent import bgd

# define least square estimation function: Y = aX + b
def lse(X, Y):
    cov_mat = np.cov(X, Y, ddof=0)
    a = cov_mat[0][1] / cov_mat[0][0]
    Y_mean = Y.mean()
    X_mean = X.mean()
    b = Y_mean - a * X_mean

    Y_pred = a * X + b
    Rsquare = np.mean((Y_pred - Y_mean) ** 2) / np.var(Y)

    return a, b, Rsquare

# define gradient descent function: Y = aX + b
def gd(X, Y, alpha=0.01, epsilon=1e-32):
    m = len(X)
    a, b, sse2 = 0, 0, 0
    while True:
        loss = a * X + b - Y
        lossT = loss.T
        sse = np.dot(lossT, loss) / (2 * m)
        if abs(sse2 - sse) < epsilon:
            break
        else:
            sse2 = sse

        grad_a = np.dot(lossT, X) / m
        grad_b = np.dot(lossT, np.ones(m)) / m
        a -= alpha * grad_a
        b -= alpha * grad_b
    return a, b

X = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
Y = np.array([0.199, 0.389, 0.580, 0.783, 0.980, 1.177, 1.380, 1.575, 1.771])

print(lse(X, Y))
print(gd(X, Y, 0.05))
print(bgd(X, Y, 0.05, 1e-32, False))


from numba import njit
import numpy as np

def det1(A):
    """Compute the determinants of a series of 1x1 matrices.

    Parameters
    ----------
    A : nx1x1 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    return A.flatten()

@njit(cache=True)
def det2(A):
    """Compute the determinants of a series of 2x2 matrices.

    Parameters
    ----------
    A : nx2x2 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    n = np.shape(A)[0]
    dets = np.zeros(n)
    for i in range(n):
        dets[i] = A[i, 0, 0] * A[i, 1, 1] - A[i, 0, 1] * A[i, 1, 0]
    return dets

@njit(cache=True)
def det3(A):
    """Compute the determinants of a series of 3x3 matrices.

    Parameters
    ----------
    A : nx3x3 array_like
        Arrays to compute determinants of

    Returns
    -------
    dets : array of length n
        Matrix determinants
    """
    n = np.shape(A)[0]
    dets = np.zeros(n)
    for i in range(n):
        dets[i] = (
            A[i, 0, 0] * (A[i, 1, 1] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 1])
            - A[i, 0, 1] * (A[i, 1, 0] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 0])
            + A[i, 0, 2] * (A[i, 1, 0] * A[i, 2, 1] - A[i, 1, 1] * A[i, 2, 0])
        )
    return dets

def inv1(A):
    """Compute the inverses of a series of 1x1 matrices.

    Parameters
    ----------
    A : nx1x1 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx1x1 array
        Matrix inverses
    """
    return 1.0 / A

@njit(cache=True)
def inv2(A):
    """Compute the inverses of a series of 2x2 matrices.

    Parameters
    ----------
    A : nx2x2 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx2x2 array
        Matrix inverses
    """
    invdets = 1.0 / det2(A)
    n = len(invdets)
    invs = np.zeros((n, 2, 2))
    for i in range(n):
        invs[i, 0, 0] = invdets[i] * A[i, 1, 1]
        invs[i, 1, 1] = invdets[i] * A[i, 0, 0]
        invs[i, 0, 1] = -invdets[i] * A[i, 0, 1]
        invs[i, 1, 0] = -invdets[i] * A[i, 1, 0]
    return invs

@njit(cache=True)
def inv3(A):
    """Compute the inverses of a series of 3x3 matrices.

    Parameters
    ----------
    A : nx3x3 array_like
        Arrays to compute inverses of

    Returns
    -------
    dets : nx3x3 array
        Matrix inverses
    """
    invdets = 1.0 / det3(A)
    n = len(invdets)
    invs = np.zeros((n, 3, 3))
    for i in range(n):
        invs[i, 0, 0] = invdets[i] * (A[i, 1, 1] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 1])
        invs[i, 0, 1] = -invdets[i] * (A[i, 0, 1] * A[i, 2, 2] - A[i, 0, 2] * A[i, 2, 1])
        invs[i, 0, 2] = invdets[i] * (A[i, 0, 1] * A[i, 1, 2] - A[i, 0, 2] * A[i, 1, 1])
        invs[i, 1, 0] = -invdets[i] * (A[i, 1, 0] * A[i, 2, 2] - A[i, 1, 2] * A[i, 2, 0])
        invs[i, 1, 1] = invdets[i] * (A[i, 0, 0] * A[i, 2, 2] - A[i, 0, 2] * A[i, 2, 0])
        invs[i, 1, 2] = -invdets[i] * (A[i, 0, 0] * A[i, 1, 2] - A[i, 0, 2] * A[i, 1, 0])
        invs[i, 2, 0] = invdets[i] * (A[i, 1, 0] * A[i, 2, 1] - A[i, 1, 1] * A[i, 2, 0])
        invs[i, 2, 1] = -invdets[i] * (A[i, 0, 0] * A[i, 2, 1] - A[i, 0, 1] * A[i, 2, 0])
        invs[i, 2, 2] = invdets[i] * (A[i, 0, 0] * A[i, 1, 1] - A[i, 0, 1] * A[i, 1, 0])
    return invs

if __name__ == "__main__":
    np.random.seed(0)
    A2 = np.random.rand(1000, 2, 2)
    A3 = np.random.rand(1000, 3, 3)
    dets2 = det2(A2)
    dets3 = det3(A3)
    invs2 = inv2(A2)
    invs3 = inv3(A3)
    # Check error between this implementation and numpy, use hybrid error measure (f_ref - f_test)/(f_ref + 1) which
    # measures the relative error for large numbers and absolute error for small numbers
    errors = {}
    errors["det2"] = np.linalg.norm((dets2 - np.linalg.det(A2)) / (dets2 + 1.0))
    errors["det3"] = np.linalg.norm((dets3 - np.linalg.det(A3)) / (dets3 + 1.0))
    errors["inv2"] = np.linalg.norm((invs2 - np.linalg.inv(A2)) / (invs2 + 1.0))
    errors["inv3"] = np.linalg.norm((invs3 - np.linalg.inv(A3)) / (invs3 + 1.0))
    for name, error in errors.items():
        print(f"Error norm in {name} = {error:.03e}")


# ========================================
# [] File Name : map.py
#
# [] Creation Date : February 2018
#
# [] Created By : Ali Gholami (aligholami7596@gmail.com)
# ========================================

"""
    Implementation of a linear transformation on a multivariate Gaussian distribution.
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from scipy.stats import multivariate_normal

color_map=[cm.winter,cm.autumn]

MEAN_VECTOR = np.array([0,0])

COV_MATRIX = np.array([
    [1, 0],
    [0, 1]
])

# Transformation configuration
A_T = np.array([
    [-5, 5],
    [1, 1]
])

B_T = np.array([0.5, 1])

# Linear transformation
def linear_map(transformation_matrix, bias_matrix, data):
    '''
        Performs a matrix multiplication as a linear map
        also translates the new mean vector and covariance matrix
    '''
    Y = np.matmul(transformation_matrix, np.transpose(data)) + bias_matrix

    return Y

# Finds the new mean and covariance matrix
def transform_cov_mean(prev_mean, prev_cov, transformation_matrix, bias_matrix):
    '''
        Find the new mean as A*prev_mean + B
        Find the new cov as A^(T) prev_cov A
    '''
    return np.transpose(np.matmul(transformation_matrix, np.transpose(prev_mean))) + bias_matrix, np.matmul(np.matmul(np.transpose(transformation_matrix), prev_cov), transformation_matrix)

# Function to plot each distribution
def plot_dists(samples, mean_vec, cov_mat, i):
    '''
        This will plot normal surface for each sample set
    '''

    x = samples[:, 0]
    y = samples[:, 1]
    x, y = np.meshgrid(x, y)
    xy = np.column_stack([x.flat, y.flat])

    # Use multivariate normal as the result for Z
    z = multivariate_normal.pdf(xy, mean=mean_vec.tolist(), cov=cov_mat.tolist())
    z = z.reshape(x.shape)

    # Plot
    FIG = plt.figure()
    AX = Axes3D(FIG)

    SURF = AX.plot_surface(x, y, z, cmap=color_map[i])
    FIG.colorbar(SURF, shrink=0.5, aspect=5)

    plt.show()

# Generate Gaussian samples and plot the results
GAUSSIAN_SAMPLES = np.random.multivariate_normal(MEAN_VECTOR, COV_MATRIX, size=500)
plot_dists(GAUSSIAN_SAMPLES, MEAN_VECTOR, COV_MATRIX, 1)

# Transform the Gaussian samples
TRANSFORMED_SAMPLES = np.array([])
for point in GAUSSIAN_SAMPLES:
    TRANSFORMED_SAMPLES = np.append(TRANSFORMED_SAMPLES, linear_map(data=point, transformation_matrix=A_T, bias_matrix=B_T))

# Reshape the transformed samples like GAUSSIAN SAMPLES
TRANSFORMED_SAMPLES = TRANSFORMED_SAMPLES.reshape((500, 2))

# Plot the transformed samples
new_mean, new_cov = transform_cov_mean(MEAN_VECTOR, COV_MATRIX, A_T, B_T)
plot_dists(TRANSFORMED_SAMPLES, new_mean, new_cov, 0)


# SmaEmaHelperFunctions.py

#################################################################

## Libraries

import matplotlib.pyplot as plt
import numpy as np

################################################################

## Exponential Moving Average (EMA) Functions

# Calculate the alpha value for a desired period.
def calculateAlpha(ema_period):
    alpha = 2.0 / (ema_period + 1)
    return alpha

# Returns the denominator
def getDenominator(number_of_terms):
    # bottom = 1 + (1-a) + (1-a)^2 + (1-a)^3 + ...
    a = calculateAlpha(number_of_terms)
    i = 0
    total = 0
    while i < number_of_terms:
        term = (1-a)**i
        total = total + term
        i = i + 1
    return total

# Returns the numerator
def getNumerator(price_data, price_data_index, number_of_terms):
    # top = p1 + (1-a)*p2 + (1-a)^2*p3 + (1-a)^3*p4 + ...
    a = calculateAlpha(number_of_terms)
    i = 0
    total = 0
    while i < number_of_terms:
        price = price_data[price_data_index - i]
        cof = (1-a)**i
        term = price * cof
        total = total + term
        i = i + 1
    return total

# Returns a single Exponential Moving Average value.
def getEMA(price_data, price_data_index, number_of_terms):
    if (number_of_terms - price_data_index) > 1:
        # There are too many terms for the given index.
        return 0
    else:
        top = getNumerator(price_data, price_data_index, number_of_terms)
        bottom = getDenominator(number_of_terms)
        EMA = np.array([top / bottom])
        return EMA

# Returns a list of all EMA values.
def getEMAdataset(price_data, number_of_terms):
    ema_data = np.zeros(np.size(price_data))
    i = 0
    while i < np.size(price_data):
        datum = getEMA(price_data, i, number_of_terms)
        ema_data[i] = datum
        i = i + 1
    return ema_data

################################################################

# SMA Functions

# Calculates the average given a list of numbers.
def getAvg(data):
    total = 0
    i = 0
    while i < len(data):
        total = total + data[i]
        i = i + 1
    # Catch divide by zero errors.
    if i == 0:
        return 0
    else:
        return total / i

# Produces a subset list given a list of numbers, the final
#desired index, and the desired subset length.
def getMovingList(data, index, length):
    listEnd = index + 1
    listStart = index - length + 1
    result = data[listStart:listEnd]
    # Note that when index - length < -1, then the result will be [] (empty)
    return result

# Calculates a single average.
def getSingleSMA(data, index, sma_period):
    movingList = getMovingList(data, index, sma_period)
    average = getAvg(movingList)
    return average

# Calculates the entire list of averages.
def getSMAlist(data, sma_period):
    smaList = []
    i = 0
    while i < len(data):
        value = getSingleSMA(data, i, sma_period)
        smaList.append(value)
        i = i + 1
    return smaList

################################################################

# Plotting Functions

# Generates a sine wave.
def generateSineWave(period, amplitude, sigma, end):
    # Equations
    alpha = amplitude / 2.0
    beta = 2.0 * np.pi / period
    frequency = 1.0 / period
    x = np.arange(end + 1)

    # Formula
    y = alpha * np.sin(beta * x) + sigma
    return y


import sys
import numpy as np
from matplotlib import cm
"""
A file for all common functions used in project 1
"""

def FrankeFunction(x,y):
	term1 = 0.75*np.exp(-(0.25*(9*x-2)**2) - 0.25*((9*y-2)**2))
	term2 = 0.75*np.exp(-((9*x+1)**2)/49.0 - 0.1*(9*y+1))
	term3 = 0.5*np.exp(-(9*x-7)**2/4.0 - 0.25*((9*y-3)**2))
	term4 = -0.2*np.exp(-(9*x-4)**2 - (9*y-7)**2)
	return term1 + term2 + term3 + term4

def MSE(y, y_tilde):
	"""
	Function for computing mean squared error.
	Input is y: analytical solution, y_tilde: computed solution.
	"""
	return np.sum((y-y_tilde)**2)/y.size

def R2_Score(y, y_tilde):
	"""
	Function for computing the R2 score.
	Input is y: analytical solution, y_tilde: computed solution.
	"""

	return 1 - np.sum((y[:-2]-y_tilde[:-2])**2)/np.sum((y[:-2]-np.average(y))**2)

def create_X(x, y, n = 5):
	"""
	Function for creating a X-matrix with rows [1, x, y, x^2, xy, xy^2 , etc.]
	Input is x and y mesh or raveled mesh, keyword agruments n is the degree of the polinomial you want to fit.
	"""
	if len(x.shape) > 1:
		x = np.ravel(x)
		y = np.ravel(y)

	N = len(x)
	l = int((n+1)*(n+2)/2)		# Number of elements in beta
	X = np.ones((N,l))

	for i in range(1,n+1):
		q = int((i)*(i+1)/2)
		for k in range(i+1):
			X[:,q+k] = (x**(i-k))*(y**k)

	return X

def plot_surface(x, y, z, title = "", show = False, cmap=cm.coolwarm, figsize = None):
	"""
	Function to plot surfaces of z, given an x and y.
	Input: x, y, z (NxN'Modeler' matrices), and a title (string)
	"""

	from mpl_toolkits.mplot3d import Axes3D
	import matplotlib.pyplot as plt
	from matplotlib.ticker import LinearLocator, FormatStrFormatter

	if figsize:
		fig = plt.figure(figsize = figsize)
	else:
		fig = plt.figure()

	ax = fig.gca(projection='3d')

	# Plot the surface.of the best fit

	surf = ax.plot_surface(x, y, z, cmap=cmap,
				   linewidth=0, antialiased=False)

	# Customize the z axis automatically
	z_min = np.min(z)
	z_min = z_min*1.01 if z_min < 0 else z_min*.99
	z_max = np.max(z)
	z_max = z_max*1.01 if z_max > 0 else z_max*.99

	ax.set_zlim(z_min, z_max)
	ax.zaxis.set_major_locator(LinearLocator(10))
	ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
	ax.view_init(azim=20,elev=45)
	# Add a color bar which maps values to colors.
	fig.colorbar(surf, shrink=0.5, aspect=5)
	ax.set_title(title)

	if show:
		plt.show()

	return fig, ax ,surf

def train_test_data(x_,y_,z_,i):
	"""
	Takes in x,y and z arrays, and a array with random indesies iself.
	returns learning arrays for x, y and z with (N-len(i)) dimetions
	and test data with length (len(i))
	"""
	x_learn=np.delete(x_,i)
	y_learn=np.delete(y_,i)
	z_learn=np.delete(z_,i)
	x_test=np.take(x_,i)
	y_test=np.take(y_,i)
	z_test=np.take(z_,i)

	return x_learn,y_learn,z_learn,x_test,y_test,z_test

def K_fold(x,y,z,k,alpha,model,m=5, ret_std = False):
	"""Function to who calculate the average MSE and R2 using k-fold.
	Takes in x,y and z varibles for a dataset, k number of folds, alpha and which method beta shall use. (OLS,Ridge or Lasso)
	Returns average MSE and average R2"""
	print(m)
	if len(x.shape) > 1:
		x = np.ravel(x)
		y = np.ravel(y)
		z = np.ravel(z)
	n=len(x)
	n_k=int(n/k)
	if n_k*k!=n:
		print("k needs to be a multiple of ", n,k)
	i=np.arange(n)
	np.random.shuffle(i)

	MSE_=0
	R2_=0
	Variance_=0
	Bias_=0
	betas = np.zeros((k,int((m+1)*(m+2)/2)))
	for t in range(k):
		x_,y_,z_,x_test,y_test,z_test=train_test_data(x,y,z,i[t*n_k:(t+1)*n_k])
		X= create_X(x_,y_,n=m)
		X_test= create_X(x_test,y_test,n=m)

		model.fit(X,z_)
		betas[t] = model.beta
		z_predict=model.predict(X_test)

		MSE_+=MSE(z_test,z_predict)
		R2_+=R2_Score(z_test,z_predict)
		Bias_+=bias(z_test,z_predict)
		Variance_+=variance(z_predict)

	return (MSE_/k, R2_/k, Bias_/k, Variance_/k, np.std(betas, axis = 0), np.mean(betas, axis = 0))

def variance(y_tilde):
	"""
	Calculates the variance of the predicted values y_tilde.
	"""
	return np.sum((y_tilde - np.mean(y_tilde))**2)/np.size(y_tilde)

def bias(y, y_tilde):
	"""
	Calculates the bias of the predicted values y_tilde compared to
	the actual data y.
	"""
	return np.sum((y - np.mean(y_tilde))**2)/np.size(y_tilde)

def update_progress(job_title, progress):
	"""
	Shows the progress of an for-loop.
	"""
	length = 20 # modify this to change the length
	block = int(round(length*progress))
	msg = "\r{0}: [{1}] {2}%".format(job_title, "#"*block + "-"*(length-block), round(progress*100, 2))
	if progress >= 1: msg += " DONE\r\n"
	sys.stdout.write(msg)
	sys.stdout.flush()

def savefigure(name, figure = "gcf"):
	"""
	Function for saving figures as a .tex-file for easier integration with latex.
	"""
	try:
		from matplotlib2tikz import save as tikz_save
		tikz_save(name.replace(" ", "_") + ".tex", figure = figure, figureheight='\\figureheight', figurewidth='\\figurewidth')
	except ImportError:
		print("Please install matplotlib2tikz to save figure as a .tex-file.")
		import matplotlib.pyplot as plt
		if figure == "gcf":
			plt.savefig(name+".pdf")
		else:
			fig.savefig(name+".pdf")


import argparse
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.patches import Rectangle, Arc
from matplotlib.collections import PatchCollection
import numpy as np

def fib(n):
    """Short summary.

    Parameters
    ----------
    n : type
        Description of parameter `n`.

    Returns
    -------
    type
        Description of returned object.

    """
    a, b = 1, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

def plot_fibonacci_spiral(
    number_of_squares,
    numbers=True,
    arc=True,
    cmap="Blues",
    alpha=1,
    golden_ratio=False,
    filename="plot.png",
):
    """Short summary.

    Parameters
    ----------
    number_of_squares : type
        Description of parameter `number_of_squares`.
    numbers : type
        Description of parameter `numbers`.
    arc : type
        Description of parameter `arc`.
    cmap : type
        Description of parameter `cmap`.
    alpha : type
        Description of parameter `alpha`.
    golden_ratio : type
        Description of parameter `golden_ratio`.

    Returns
    -------
    type
        Description of returned object.

    """
    fibs = list(fib(number_of_squares))
    x = 0
    y = 0
    angle = 180
    center = (x + fibs[0], y + fibs[0])
    rectangles = []
    xs, ys = [], []

    fig, ax = plt.subplots(1, figsize=(16, 16))
    for i, side in enumerate(fibs):

        rectangles.append(Rectangle([x, y], side, side))
        if numbers and i > number_of_squares - 8:
            ax.annotate(side, xy=(x + 0.45 * side, y + 0.45 * side), fontsize=14)

        if arc:
            this_arc = Arc(
                center,
                2 * side,
                2 * side,
                angle=angle,
                theta1=0,
                theta2=90,
                edgecolor="black",
                antialiased=True,
            )
            ax.add_patch(this_arc)
        angle += 90

        xs.append(x)
        ys.append(y)
        if i == 0:
            x += side
            center = (x, y + side)
        elif i == 1:
            x -= side
            y += side
            center = (x, y)
        elif i in range(2, i + 1, 4):
            x -= side + previous_side
            y -= previous_side
            center = (x + side + previous_side, y)
        elif i in range(3, i + 1, 4):
            y -= side + previous_side
            center = (x + side + previous_side, y + side + previous_side)
        elif i in range(4, i + 1, 4):
            x += side
            center = (x, y + side + previous_side)
        elif i in range(5, i + 1, 4):
            x -= previous_side
            y += side
            center = (x, y)
        previous_side = side

    col = PatchCollection(rectangles, alpha=alpha, edgecolor="black")
    try:
        col.set(array=np.asarray(range(number_of_squares + 1)), cmap=cmap)
    except ValueError:
        print(
            f" '{cmap}' is an invalid colormap, choose a valid one from "
            "https://matplotlib.org/examples/color/colormaps_reference.html"
            " - returning to default 'Blues' colormap"
        )
        col.set(array=np.asarray(range(number_of_squares + 1)), cmap="Blues")

    ax.add_collection(col)
    ax.set_aspect("equal", "box")
    ax.set_xticks([])
    ax.set_yticks([])

    xmin = np.min(xs)
    ymin = np.min(ys)
    xmax = np.max(xs) + fibs[np.argmax(xs)]
    ymax = np.max(ys) + fibs[np.argmax(ys)]
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    gr = str(fibs[i] / fibs[i - 1])
    if golden_ratio:
        plt.title(r"$\varphi$ = " + gr)
    plt.tight_layout()
    plt.savefig(filename)

if __name__ == "__main__":

    description = "Creates a plot for a fibonacci spiral"
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument(
        "-n",
        "--number_of_squares",
        type=int,
        help="number of squares in spiral",
        required=True,
    )

    parser.add_argument(
        "-o",
        "--output",
        type=str,
        help="Plot file name",
        required=False,
        default="plot.png",
    )

    parser.add_argument(
        "--no-label",
        dest="labels",
        help="Remove label showing side lenght at the center of each square",
        action="store_false",
        default=True,
        required=False,
    )

    parser.add_argument(
        "--no-arc",
        dest="arc",
        help="Remove arc of fibonacci spiral",
        action="store_false",
        default=True,
        required=False,
    )

    parser.add_argument(
        "-c",
        "--cmap",
        dest="cmap",
        type=str,
        help="Colormap applied to fibonacci squares",
        default="Blues",
        required=False,
    )

    parser.add_argument(
        "-a",
        "--alpha",
        dest="alpha",
        type=float,
        help="Transparency",
        default=0.95,
        required=False,
    )

    args = parser.parse_args()

    plot_fibonacci_spiral(
        args.number_of_squares,
        numbers=bool(args.labels),
        arc=bool(args.arc),
        cmap=args.cmap,
        alpha=args.alpha,
        filename=args.output,
    )


import numpy as np
import matplotlib.pyplot as plt
import math
import matplotlib.mlab as mlab
import matplotlib.lines as mlines
import scipy.stats as stats

## Background Class H0 Definition

mean0=[0,1]
sigmax0=1.5
sigmay0=1.5
rho=0.7
cov0=[[sigmax0**2,rho*sigmax0*sigmay0],[rho*sigmax0*sigmay0,sigmay0**2]]
n0=1000

## Signal Class H1 Definition

mean1=[4,4]
sigmax1=1
sigmay1=1
cov1=[[sigmax1**2,0],[0,sigmay1**2]]
n1=1000

## Pseudo-Random generation for H0 e H1 gaussians with defined parameters

x0,y0=np.random.multivariate_normal(mean0,cov0,n0).T
T0=np.column_stack((x0,y0))*1.0 ## Data on two columns
m0=np.sum(T0,axis=0)/n0 ## Mean over rows

x1,y1=np.random.multivariate_normal(mean1,cov1,n1).T
T1=np.column_stack((x1,y1))*1.0 ## Data on two columns
m1=np.sum(T1,axis=0)/n1 ## Mean over rows

## Covariance matrices calculation

S_w=(n1)*np.cov(T1,rowvar=False,bias=1)+(n0)*np.cov(T0,rowvar=False,bias=1) ## S_w=S0+S1
S_inv_w=np.linalg.inv(S_w) ## S_w inversion with linear algebra numpy library
S_b=np.outer(m1-m0,m1-m0)

## Fisher vector definition

v=np.matmul(S_inv_w, m1-m0) ## ROWxCOL product by numpy package

## Data projection on \vec{v}

x11=np.matmul(v,T1.transpose()) ## Signal data projection
x00=np.matmul(v,T0.transpose()) ## Background data projection

## Plotting Data, \vec{v} and projections

plt.figure('Data and v')

plt.scatter(x1,y1,color='red', edgecolor='black')
plt.scatter(x0,y0, color='yellow', edgecolor='black')
plt.xlabel('x')
plt.ylabel('y')

def newline(p1, p2): ## Straight line from two given points...
    ax = plt.gca()
    xmin, xmax = ax.get_xbound()

    if(p2[0] == p1[0]):
        xmin = xmax = p1[0]
        ymin, ymax = ax.get_ybound()
    else:
        ymax = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmax-p1[0])
        ymin = p1[1]+(p2[1]-p1[1])/(p2[0]-p1[0])*(xmin-p1[0])

    l = mlines.Line2D([xmin,xmax], [ymin,ymax], color='black', linestyle='-')
    ax.add_line(l)
    return l
newline([0,0], v)

plt.figure('Data projections') ## (bins choosen for graphic-only purposes)

plt.hist(x11, bins=33, color='red', edgecolor='black', alpha=0.8)
plt.hist(x00, bins=66, color='yellow', edgecolor='black', alpha=0.8)
plt.xlabel('Projected Data')
plt.ylabel('Counts')

## Showing plots

plt.show()

## Significance and Specificity optimization and consequent t_cut determination

t=np.concatenate((x11,x00),axis=0)  ## Definition of Fisher Statistic
q=np.linspace(min(t),max(t),len(t)) ## Homogeneuous domain for optimum-value search
alpha=np.zeros(len(t))              ## alpha ~ 1 - E_1 -> #{False Negatives}
beta=np.zeros(len(t))               ## beta  ~ E_0 -----> #{False Positives}

for i in range(0,len(q)):
 alpha[i]=(x11<q[i]).sum()/n1 ## Rejection of H1 when H1 is true
 beta[i]=(x00>q[i]).sum()/n0  ## Acceptance of H1 when H1 is false

ToMinimize=alpha+beta
best=min(ToMinimize)

t_cut=np.mean(q[ToMinimize==best]) ## There can be more than one best-match...
print (round(t_cut,6) , ': t_cut optimum value')

plt.axvline(t_cut)
plt.xlabel('Projected Data')
plt.ylabel('Counts')

## Plotting alpha+beta as a function of trial t_cut (i.e. q)

plt.figure('Minimization Functional')
plt.plot(q,ToMinimize, linewidth = 5)
plt.xlabel('Trial Boundary [t]')
plt.ylabel('(False Positives + False Negatives)')
plt.axvline(np.mean(q[ToMinimize==best]))
plt.show()

## Identified signal purity computation

bestalpha=np.mean(alpha[np.where(((q-t)**2)**0.5==np.min((q-t)**2)**0.5)])
bestbeta=np.mean(beta[np.where(((q-t)**2)**0.5==np.min((q-t)**2)**0.5)])

print (round(bestalpha,6), ': alpha optimum value')
print (round(bestbeta,6), ': beta optimum value')
print (round(1-bestalpha,6)/(round(bestbeta,6)+round(1-bestalpha,6)), ': obtained signal purity')

## ROC curve determination

plt.figure('ROC curve')
plt.plot(beta,1-alpha, linewidth = 5, color='green')
plt.xlabel('False Positive Rate [Background Efficiency]')
plt.ylabel('True Positive Rate [Signal Efficiency]')
plt.title('ROC curve')
plt.show()

## Area under the ROC curve

SamplingPts = beta.shape[0]

# Integrating in [0.1] with "trapezoid-rule"

I = 0.5*alpha[0] + 0.5*alpha[beta.shape[0]-1]
for j in range(1,SamplingPts):
    I += alpha[j]
    dx = beta[j] - beta[j-1]
    I *= dx
I += 1
print('ROC area: ', I)


import matplotlib
matplotlib.use('Agg')

import numpy as np
import matplotlib.pyplot as plt

def prior(x, f="flat"):
    n_points = len(x)
    if f=="flat":
        p = np.ones(n_points)
    return p

def likelihood(s, p_a):
    n_a = s.count('a')
    n_b = s.count('b')
    return (p_a)**n_a * (1-p_a)**n_b

def max_prob(x, pdf_x):
    ii = np.argmax(pdf_x)
    return x[ii]

def mean(x, pdf_x):
    return np.trapz(pdf_x*x, x)

def sigma(x, pdf_x):
    m = mean(x, pdf_x)
    s = np.trapz(pdf_x*x*x, x)
    return np.sqrt(s - m**2)

def posterior(s):
    n_points = 1000
    p_a = np.linspace(0.0, 1.0, n_points)

    p =  prior(p_a)
    l = likelihood(s, p_a)

    post = p * l # Bayes

    norm = np.trapz(post, p_a)
    post = post/norm

    mp = max_prob(p_a, post)
    m = mean(p_a, post)
    sig = sigma(p_a, post)

    values = 'Max Prob = {:.2f}, Mean = {:.2f}, Sigma={:.2f}'.format(mp, m, sig)

    plt.plot(p_a, post , label="data= {}".format(s))
    plt.title(values)
    plt.legend()
    plt.xlabel('$p_a$')
    plt.ylabel('$PDF (p_a|data)$')
    plt.savefig('posterior.pdf')


# search for the roots by secants method

import numpy, matplotlib
import matplotlib.pyplot as plt
from prettytable import PrettyTable
from math import *

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

# def f1(x):
# 	return math.sin(x) * math.cos(x / 20) * math.cos(x / 80)
# 	#return 1 / abs(x) if abs(x) > 1 else 2 - x * x
# 	#return math.e * math.sin(x / 100) / \
# 	#(abs(math.log(abs(math.sin(x) + 1))) + 1) - 1

# def f2(x):
# 	return math.sin(x / 65) * 0.43 - 0.4
# 	return x / 300

def derivative(func, x, power=1):
	dx = 0.001
	derivFunc = lambda x: (func(x + dx) - func(x)) / dx
	if power == 1:
		return derivFunc(x)
	elif power > 1:
		return derivative(derivFunc, x, power - 1)

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

# [ root, f(root), iterations, error ]
def findRootBySecants(func, p1, p2, eps, nMax):
	n = 1
	error = None

	while abs(func(p2)) > eps and abs(p1 - p2) > eps:
		if n > nMax:
			error = "Maximim of n"
			break

		fX1 = func(p1)
		fX2 = func(p2)

		if fX1 == fX2:
			error = "Horisontal"
			break

		if abs(fX1) < abs(fX2):
			fX1, fX2 = fX2, fX1
			p2, p1 = p1, p2

		p1, p2 = p2, p2 - (p2 - p1) * fX2 / (fX2 - fX1)

		n += 1

	return [ p2, func(p2), n, error ]

# [ №, A, B, root, f(root), iterations, error ]
def findRoots(func, a, b, step, eps, nMax):
	result = []
	aNext = min(a + step, b)
	i = 1
	lastRoot = None
	while True:
		if func(a) * func(aNext) <= 0:
			r = findRootBySecants(func, a, aNext, eps, nMax)

			# error handling
			if not r[0] is None:
				if not lastRoot is None and abs(lastRoot - r[0]) < eps:
					r[3] = "Dublicate"
				elif not (a <= r[0] <= aNext):
					r[3] = "Out of interval"
				else:
					lastRoot = r[0]

			if not r[0] is None or not r[3] is None:
				result.append([i, a, aNext] + r)
				i += 1
		a += step;
		if aNext >= b:
			break
		aNext = min(aNext + step, b)

	return result

def findExtremums(func, a, b, step, eps, nMax, power=1):
	result = findRoots(lambda x: derivative(func, x, power), a, b, step, eps, nMax)
	for i in result:
		i[4] = func(i[3])
	return result

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

def findIntersactionPoints(func1, func2, a, b, step, eps, nMax):
	return [ i[3] for i in findRoots(lambda x: func1(x) - func2(x), \
		a, b, step, eps, nMax) ]

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

def getIntegral(func, a, b, intervLength=1e-2):
	result = 0
	while a < b:
		aNext = min(a + intervLength, b)

		result += (func(a) + func(aNext)) * (aNext - a) / 2

		a += intervLength
	return result

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

def drawRoots(roots, col="red"):
	result, = plt.plot([i[3] for i in roots if not i[3] is None], \
		[i[4] for i in roots if not i[3] is None], 'ro', color=col)
	return result

def drawGraph(func, xRange, col="blue"):
	plt.grid(True)

	graph, = plt.plot(xRange, [ func(x) for x in xRange ], \
		linewidth=3, color=col)

	return graph

def drawExtremums(ext, col="green"):
	result, = plt.plot([i[3] for i in ext if not i[3] is None], \
		[i[4] for i in ext if not i[3] is None], 'ro', color=col)
	return result

def drawInflections(infl, col="black"):
	result, = plt.plot([i[3] for i in infl if not i[3] is None], \
		[i[4] for i in infl if not i[3] is None], 'ro', color=col)
	return result

def drawIntervals(func1, func2, intersactPoints, eps):
	if not intersactPoints: return
	last = intersactPoints[0]
	for point in intersactPoints[1:]:
		xRange = numpy.arange(last, point, 0.001)
		plt.fill_between(xRange, [ func1(x) for x in xRange ],\
		 [ func2(x) for x in xRange ], color='blue', alpha=.1)

	result, = plt.plot(intersactPoints, [func1(point) for point in intersactPoints]\
		, 'ro', color="pink")

	return result

def showPlot():
	plt.show()

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

def printRoots(roots):
	table = PrettyTable(["№", "A", "B", "Root", "f(x)",\
	 "Loops Count", "Error"])

	for i in roots:
		if not i[6] is None:
			i[3] = None
			i[4] = None

		n = i[0]
		A = "{:.3f}".format(i[1])
		B = "{:.3f}".format(i[2])
		root = "{:7f}".format(i[3]) if not (i[3] is None) else '-'
		fx = "{:3e}".format(i[4]) if not (i[4] is None) else '-'
		loops = i[5] if not (i[5] is None) else '-'
		error = i[6]

		table.add_row([ n, A, B, root, fx, loops, error])

	print(table)

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

f1Code = input("Enter first  math function: f(x) = ")
f2Code = input("Enter second math function: f(x) = ")

f1 = lambda x: eval(f1Code)
f2 = lambda x: eval(f2Code)

print()

x1 = float(input("Enter the first point  (float): "))
while True:
	x2 = float(input("Enter the second point (float): "))
	if x1 < x2:
		break
	else:
		print("\tError: x1 must be less then x2.")
while True:
	eps = float(input("Enter epsilon (float): "))
	if eps > 0:
		break
	else:
		print("\tError: value must be positive.")
while True:
	step = float(input("Enter step (float): "))
	if 0 < step:
		break
	else:
		print("\tError: value must be positive.")
while True:
	nMax = float(input("Enter max n (integer): "))
	if 0 < nMax:
		break
	else:
		print("\tError: value must be positive.")

xRange = numpy.arange(x1, x2, 0.001)

roots1 = findRoots(f1, x1, x2, step, eps, nMax)
extem1 = findExtremums(f1, x1, x2, step, eps, nMax)
infle1 = findExtremums(f1, x1, x2, step, eps, nMax, 2)

roots2 = findRoots(f2, x1, x2, step, eps, nMax)
extem2 = findExtremums(f2, x1, x2, step, eps, nMax)
infle2 = findExtremums(f2, x1, x2, step, eps, nMax, 2)

intersactions = findIntersactionPoints(f1, f2, x1, x2, step, eps, nMax)
square = sum([ abs(getIntegral(lambda x: f1(x) - f2(x), intersactions[i], \
	intersactions[i + 1])) for i in range(len(intersactions) - 1)] )

graph1Obj = drawGraph(f1, xRange, "blue")
extem1Obj = drawExtremums(extem1)
infle1Obj = drawInflections(infle1)
roots1Obj = drawRoots(roots1)

graph2Obj = drawGraph(f2, xRange, "orange")
extem2Obj = drawExtremums(extem2)
infle2Obj = drawInflections(infle2)
roots2Obj = drawRoots(roots2)

intervalsObj = drawIntervals(f1, f2, intersactions, eps)
print()
printRoots(roots1)

# некрасиво, т.к. использую матплотлиб из вне функций
plt.title("Area: " + str(square))
plt.legend([ graph1Obj, graph2Obj, roots1Obj, extem1Obj, infle1Obj, intervalsObj ], \
	[ "$f1(x) = " + f1Code + "$", "$f2(x) = " + f2Code + "$", "$Roots$", "$Extremums$", "$Inflections$", "$Intersections$"])

showPlot()


from sympy import cos, cosh, diag, exp, sin, sinh, symbols

from einsteinpy.symbolic.metric import MetricTensor

def AntiDeSitter():
    """
    Anti-de Sitter space

    Hawking and Ellis (5.9) p131

    """
    coords = symbols("t chi theta phi")
    t, ch, th, ph = coords
    metric = diag(
        -1,
        cos(t) ** 2,
        cos(t) ** 2 * sinh(ch) ** 2,
        cos(t) ** 2 * sinh(ch) ** 2 * sin(th) ** 2,
    ).tolist()
    return MetricTensor(metric, coords, "ll", name="AntiDeSitterMetric")

def AntiDeSitterStatic():
    """
    Static form of Anti-de Sitter space

    Hawking and Ellis (5.9) p131

    """
    coords = symbols("t r theta phi")
    t, r, th, ph = coords
    metric = diag(-cosh(r) ** 2, 1, sinh(r) ** 2, sinh(r) ** 2 * sin(th) ** 2).tolist()
    return MetricTensor(metric, coords, "ll", name="AntiDeSitterStaticMetric")

def DeSitter():
    """
    de Sitter space

    Hawking and Ellis p125

    """
    coords = symbols("t x y z")
    t = coords[1]
    al = symbols("alpha")
    expr = exp(2 * t / al)
    metric = diag(-1, expr, expr, expr).tolist()
    return MetricTensor(metric, coords, "ll", name="DeSitterMetric")


# -*- coding: utf-8 -*-
"""
Created on Fri Jan 15 16:03:05 2021
@author: MarlaJahari
"""
import numpy as np
import matplotlib.pyplot as ourplot

"PROBLEM: To solve the given diff eqn using finite difference method"
"y2=y'+x(x-4)  p(x)=1 q(x)=0 r(x)=x(x-4) 0<x<4 y(0)=y(4)=0"
'[a,b]=[0,4]'

a=0                   #upper limit
b=4                   #lower limit
n=int(input('no of steps YOU WISH'))
h=(b-a)/n
p=1                   #co-efficient of first derivative
q=0                   #co-efficient of y

'(2-p*h)y[i+1]+ (-4)y[i] + (2+p*h)y[i-1]=2rh^2 (substituting our equation in FDM)'

c=[]                 #vector to store the constants on right

for i in range(1,n,1):
    r=2*i*h*((i*h)-4)#since x=0+i*h
    C= r*h**2        #the constant part of equation
    c.append(C)      #producing elements in loop and adding them to c

A=-4                 #(-2*q*h**2-4) co-efficient of y[i] throughout the loop
B=2-p*h              #co-efficient of y[i+1] "
E=2+p*h              #co-efficient of y[i-1] "
x=[]                 #for storing A for diff values of i
y=[]                 #for storing B for "
z=[]                 #for storing E for "

for i in range(n-1):
    x.append(A)      #adding B to vector x for diff i's

for i in range(n-2):
    y.append(B)      #adding A to vector y for diff i's
    z.append(E)

def tridiag(x,y,z, k1=-1,k2=0,k3=1): #creating a function for triD matrix

     return np.diag(x,k2) + np.diag(z,k1)+ np.diag(y,k3) #main+off+off

M=tridiag(x, y, z)                   #built matrix
P=np.array([[c]]).T                  #converting const array to soln column vector
print("Our co-efficient matrix:")
print(M)                             #printing matrix (*not printed with the command for spacing)
print("Our constant matrix:")
print(P)                             #printing soln vector

'Algorithm for Thomas method to solve tri-diagonal matrixes'

"""
here consider our reference matrix is of form

   [b1 c1 0
   a2 b2 c2
   0  a3 b2]

"""

v=len(c)                   #extracting the length of const mtrx
f=np.zeros(v-1)            #creating an array of zeros length v-1
g=np.zeros(v)
d=np.zeros(v)

'~Matrix Operations~'

# for an eq b1x1+ c1x2=r1, div throughout by b1(1st element of main D)>>

f[0]=y[0]/x[0] #dividing 1st elm(Up-D) by 1st elm(M-D)
g[0]=c[0]/x[0]

#row2- eqn a2x1+b2x2+c2x3, to eliminate a2x1>>
#take the 1st element of off-D below(a2) and multiply by the 1st row
#repeat it down the rows until  all the MD elms=1, lD elms=0, uD elms=k
#we divide throughout by b2-a2*(k) k=previous uD modified elm>>

for i in range(1,v-1):

      f[i]=y[i]/(x[i]-z[i-1]*f[i-1]) #co-efficients of xi throughout

for i in range(1,v):

      #now we modify const Vectr in sync with the rest of our operations
      #modifying constant Vectr>> c1=r1/b1, c2=(r2-a2r1)/(b2-a2k1),c3=..etc

      g[i]=(c[i]-z[i-1]*g[i-1])/(x[i]-z[i-1]*f[i-1])

      #equating modified const vector w/ final soln vector

      d[v-1]=g[v-1]

#d stores the final solution for each elm simultaneously by backward subst

'Final Backward Substitution'

for i in range(v-1,0,-1):

      d[i-1]=g[i-1]-f[i-1]*d[i]      #backw subst (x1=r1-x2*k1, x2=r2-""..)

T=d                                  #soln by Thomas' algorithm
W=np.linalg.solve(M,c)               #soln by in-built funcn

print("The solution vector by inbuilt numpy func: ",W)

print("The solution vector by Thomas' Algorithm:",d)

'PLOTTING'

#for plotting Y, we create a zero vectr w/ soln values in between

Y=np.zeros(n+1)
for i in range(1,n):
    Y[i]=d[i-1]

#for plotting X, we create a vector to store subsequent values in a loop

X=[]
for i in range(n+1):
        x=i*h
        X.append(x)

ourplot.title("FINITE DIFFERENCE GRAPH")
ourplot.xlabel("X")
ourplot.ylabel("Plotting discrete y-values")
ourplot.plot(X,Y)
ourplot.show()


# imports
import math
import scipy.stats as st

def conf_int(mean, var, n, p=0.95):
    """
    Calculate a confidence interval
    :param mean: mean of simulations
    :param var: variance of simulations
    :param n: amount of simulations
    :param p: certainty percentage
    :return:
    """
    pnew = (p+1)/2
    zval = st.norm.ppf(pnew)
    sigma = math.sqrt(var)
    alambda = (zval*sigma)/math.sqrt(n)
    min_lambda = mean - alambda
    plus_lambda = mean + alambda
    return f"Confidence interval: [{min_lambda:.4f} < X < {plus_lambda:.4f}] with p = {p}"

def calc_rho(mu, lbda, c):
    return lbda/(c*mu)

def pwait(c, rho):
    """
    Probability that a job has to wait

    :param c: amount of servers
    :param rho: occupation rate per server
    :return: probability
    """
    fixed = ((c*rho)**c)/math.factorial(c)

    sum = 0
    for n in range(c):
        sum += ((c*rho)**n)/math.factorial(n)

    return fixed/((1-rho)*sum + fixed)

def expw(mu, c, rho):
    """
    Expected waiting time

    :param mu: server processing time
    :param c: amount of servers
    :param rho: occupation rate per server
    :return: Exp. waiting time
    """

    piw = pwait(c, rho)
    return piw * (1/(1-rho)) * 1/(c*mu)

def expquel(c, rho):
    """
    Expected queue length

    :param c: amount of servers
    :param rho: occupation rate per server
    :return: Exp. queue length
    """
    piw = pwait(c, rho)
    return piw * (rho/(1-rho))

# print(f'Probability that a job has to wait: {pwait(c, rho):.2f}')
# print(f'Expected waiting time E(W): {expw(mu, c, rho):.2f} time units')
# print(f'Expected queue length E(Lq): {expquel(c, rho):.2f} customers')
# print dataframe with data
# print(data)
# run_data.loc[round(SIM_TIME/5)] = [rho, SIM_TIME, data["AVG_WAITING"].mean()]
# print(f'Expected waiting time E(W): {expw(MU, SERVERS, RHO):.3f} time units')
# print(f'AVG waiting: {data["AVG_WAITING"].mean():.3f} time units')
# print(conf_int(data["AVG_WAITING"].mean(), data["AVG_WAITING"].var(), SIMULATIONS, p=0.95))
# print()
# print(f'AVG arriving: {data["AVG_ARRIVING"].mean():.3f} per time unit')
# print(conf_int(data["AVG_ARRIVING"].mean(), data["AVG_ARRIVING"].var(), SIMULATIONS, p=0.95))
# print()
# print(f'AVG leaving: {data["AVG_LEAVING"].mean():.3f} per time unit')
# print(conf_int(data["AVG_LEAVING"].mean(), data["AVG_LEAVING"].var(), SIMULATIONS, p=0.95))
# print(f'Simulation {s+1}')
# print(f'Average waiting time: {avg_waiting:.3f} time units')
# print(f'Avg customers arriving per time unit: {avg_arrivals:.3f} time units')
# print(f'Avg customers leaving per time unit: {avg_leavers:.3f} time units\n')

# print("EXPECTED VALUES AND PROBABILITIES")
#
# print(f'Rho: {RHO}\nMu: {MU}\nLambda: {LAMBDA}\nExpected interarrival time: {1 / LAMBDA:.2f} time units')
# print(f'Expected processing time per server: {1 / MU:.2f} time units\n')
# print(f'Probability that a job has to wait: {pwait(SERVERS, RHO):.2f}')
# print(f'Expected waiting time E(W): {expw(MU, SERVERS, RHO):.2f} time units')
# print(f'Expected queue length E(Lq): {expquel(SERVERS, RHO):.2f} customers\n')


# blackbox_function.py
"""Volume 2: Optimization Packages I (scipy.optimize). Auxiliary File."""

import numpy as np
from scipy import linalg as la

def blackbox(y_free):
    """
    Finds the length of a curve approximated piece-wise by a set of points.
    Accepts:
        y_free (1xn ndarray): the non-endpoint y-values of the curve.

    Returns:
        total_length (float): the length of the approximated curve.
    """
    # Initialize local constants.
    m = len(y_free) + 2 # Number points: free variables, origin, and endpoint.
    a, b = 40, 30       # Coordinates of endpoint.

     # Generate the evenly-spaced x-values of the curve.
    x = np.linspace(0,a,m)

     # Pad the free variables with the fixed endpoint values, 0 and b.
    y = np.hstack((0,y_free, b))

     # Calculate and return the line integral of the approximated curve.
    partial_norms = []
    for i,item in enumerate(y[:-1]):
        partial_norms.append(la.norm(np.array([x[i+1]-x[i],y[i+1] - item])))
    return np.sum(partial_norms)


from __future__ import division
import numpy as np
'''-----------------------------------------------------------------------------
Newton diferences class
    Calculates the divided diferences
    Calculates the polinomial aproximation for a value
-----------------------------------------------------------------------------'''
class NewtonDiferences():
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def dividedDiferences(self):
        #Calculates all the divided diferences for the list of points
        x = self.x
        y = self.y
        n = len(x)
        a = []
        for i in range(n):
            a.append(y[i])
        for j in range(1,n):
            for i in range(n-1,j-1,-1):
                a[i] = (a[i]-a[i-1])/(x[i]-x[i-j])

        return np.array(a)
    def calInterpolation(self,a,x,value_to_interpolate):
        #given a value_to_interpolate aproximates the value through the polinomial
        n = len(a)-1
        tmp = a[n]
        for i in range(1,n+1):
            tmp = tmp*(value_to_interpolate-x[n-i])+a[n-i]
        return tmp


import numpy as np

'''def corall(src, dst):
    "Correlation Alignment"
    src_flat = src.reshape(-1, 3)
    src_flat_mean = np.mean(src_flat, 0, keepdims=True)
    src_flat_std = np.std(src_flat, 0, keepdims=True)
    src_flat_norm = (src_flat - src_flat_mean) / src_flat_std
    src_flat_cov_eye = np.matmul(src_flat_norm.T, src_flat_norm) + np.eye(3)

    dst_flat = dst.reshape(-1, 3)
    dst_flat_mean = np.mean(dst_flat, 0, keepdims=True)
    dst_flat_std = np.std(dst_flat, 0, keepdims=True)
    dst_flat_norm = (dst_flat - dst_flat_mean) / dst_flat_std
    dst_flat_cov_eye = np.matmul(dst_flat_norm.T, dst_flat_norm) + np.eye(3)

    src_flat_norm_transfer = np.matmul(src_flat_norm, np.matmul(
        np.linalg.inv(_mat_sqrt(src_flat_cov_eye)),
        _mat_sqrt(dst_flat_cov_eye)
    ))
    src_flat_transfer = src_flat_norm_transfer * dst_flat_std + dst_flat_mean
    return src_flat_transfer.reshape(src.shape)

def _mat_sqrt(m):
    u, s, v = np.linalg.svd(m)
    return np.matmul(np.matmul(u, np.diag(np.sqrt(s))), v)'''

def matSqrt_numpy(x):
    U,D,V = np.linalg.svd(x)
    result = U.dot(np.diag(np.sqrt(D))).dot(V.T)
    return result

def corall(source, target):
    n_channels = source.shape[-1]

    source = np.moveaxis(source, -1, 0)  # HxWxC -> CxHxW
    target = np.moveaxis(target, -1, 0)  # HxWxC -> CxHxW

    source_flatten = source.reshape(n_channels, source.shape[1]*source.shape[2])
    target_flatten = target.reshape(n_channels, target.shape[1]*target.shape[2])

    source_flatten_mean = source_flatten.mean(axis=1, keepdims=True)
    source_flatten_std = source_flatten.std(axis=1, keepdims=True)
    source_flatten_norm = (source_flatten - source_flatten_mean) / source_flatten_std

    target_flatten_mean = target_flatten.mean(axis=1, keepdims=True)
    target_flatten_std = target_flatten.std(axis=1, keepdims=True)
    target_flatten_norm = (target_flatten - target_flatten_mean) / target_flatten_std

    source_flatten_cov_eye = source_flatten_norm.dot(source_flatten_norm.T) + np.eye(n_channels)
    target_flatten_cov_eye = target_flatten_norm.dot(target_flatten_norm.T) + np.eye(n_channels)

    source_flatten_norm_transfer = matSqrt_numpy(target_flatten_cov_eye).dot(np.linalg.inv(matSqrt_numpy(source_flatten_cov_eye))).dot(source_flatten_norm)
    source_flatten_transfer = source_flatten_norm_transfer * target_flatten_std + target_flatten_mean

    coraled = source_flatten_transfer.reshape(source.shape)
    coraled = np.moveaxis(coraled, 0, -1)  # CxHxW -> HxWxC
    return coraled


import numpy as np

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

def J(yhat, y):
    return sum(0.5*(y - yhat)**2)

def y_estim(x_i, w_hat, b_hat):
    return sigmoid(np.dot(w_hat, x_i) + b_hat)

def y_estim(x_i, w_hat, b_hat):
    return sigmoid(np.dot(w_hat, x_i) + b_hat)

def y_estim_all(x_data, w_hat, b_hat):
    y_estim = np.zeros(shape=(100, 1))
    for i in range(x_data.shape[0]):
        i_data = x_data[i]
        y_estim[i] = sigmoid(np.dot(i_data, w_hat) + b_hat)
    return y_estim

def log_regression(x_data, y_data, **kwargs):
    N = x_data.shape[0]
    w = kwargs.get('w0', np.zeros(shape=(x_data.shape[1], 1)))
    b = kwargs.get('b0', 0)
    alpha = kwargs.get('alpha', 1)
    epsilon = kwargs.get('epsilon', 1e-6)

    # w_hist = np.arr
    # b_hist = []
    # w_hist.append(w.flatten())
    # b_hist.append(b.flatten())

    y_hat = y_estim_all(x_data, w, b)
    err = np.sum((y_hat - y_data)**2)/N
    prev_err = 1000
    delta_err = prev_err - err

    while delta_err > epsilon or delta_err < 0:

        prev_err = err

        dw = np.sum((y_hat-y_data)*y_hat*(np.ones(shape=y_data.shape) - y_hat)*x_data, axis=0)/N
        db = np.sum((y_hat-y_data)*y_hat*(np.ones(shape=y_data.shape) - y_hat), axis=0)/N

        dw = np.array([[dw[0]], [dw[1]]])
        db = np.array([db])

        # print(dw, w, db, b)

        w -= alpha*dw
        b -= alpha*db

        # w_hist.append(w)
        # b_hist.append(b)

        y_hat = y_estim_all(x_data, w, b)
        err = np.sum((y_hat - y_data)**2)/N
        delta_err = prev_err - err

    return w, b