# Imports
import numpy as np
import pandas as pd
import polynomial_regression as plr
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt

# Function to plot data and results
def plot_fit(model, features, output):
    plt.plot(features['power_1'], output, '.', label='output')
    plt.plot(features['power_1'], model.predict(features), '-', label='regression line')
    plt.title('Price vs Sq.ft.')
    plt.ylabel('Price')
    plt.xlabel('Sq.ft.')
    plt.legend(loc='upper left')
    plt.show()

# Read training and test data
dtype_dict = {'bathrooms':float, 'waterfront':int,
              'sqft_above':int, 'sqft_living15':float,
              'grade':int, 'yr_renovated':int,
              'price':float, 'bedrooms':float,
              'zipcode':str, 'long':float,
              'sqft_lot15':float, 'sqft_living':float,
              'floors':str, 'condition':int,
              'lat':float, 'date':str,
              'sqft_basement':int, 'yr_built':int,
              'id':str, 'sqft_lot':int, 'view':int}

train_data = pd.read_csv('../data/Week03/wk3_kc_house_train_data.csv', dtype=dtype_dict)
val_data = pd.read_csv('../data/Week03/wk3_kc_house_valid_data.csv', dtype=dtype_dict)
test_data = pd.read_csv('../data/Week03/wk3_kc_house_test_data.csv', dtype=dtype_dict)

train_data = train_data.sort(['sqft_living'])
val_data = val_data.sort(['sqft_living'])
test_data = test_data.sort(['sqft_living'])

# Create order 1 polynomial feature, train and predict
poly1_data = plr.polynomial_dataframe(train_data['sqft_living'], 1)
output = train_data['price']
model1 = LinearRegression()
model1.fit(poly1_data, output)

# Plot order 1 fit
plot_fit(model1, poly1_data, output)

# Read other dataset
set_1 = pd.read_csv('../data/Week03/wk3_kc_house_set_1_data.csv', dtype=dtype_dict)
set_2 = pd.read_csv('../data/Week03/wk3_kc_house_set_2_data.csv', dtype=dtype_dict)
set_3 = pd.read_csv('../data/Week03/wk3_kc_house_set_3_data.csv', dtype=dtype_dict)
set_4 = pd.read_csv('../data/Week03/wk3_kc_house_set_4_data.csv', dtype=dtype_dict)
set_1 = set_1.sort(['sqft_living', 'price'])
set_2 = set_2.sort(['sqft_living', 'price'])
set_3 = set_3.sort(['sqft_living', 'price'])
set_4 = set_4.sort(['sqft_living', 'price'])

poly_set_1 = plr.polynomial_dataframe(set_1['sqft_living'], 15)
poly_set_2 = plr.polynomial_dataframe(set_2['sqft_living'], 15)
poly_set_3 = plr.polynomial_dataframe(set_3['sqft_living'], 15)
poly_set_4 = plr.polynomial_dataframe(set_4['sqft_living'], 15)
output_set_1 = set_1['price']
output_set_2 = set_2['price']
output_set_3 = set_3['price']
output_set_4 = set_4['price']

model_set_1 = LinearRegression()
model_set_2 = LinearRegression()
model_set_3 = LinearRegression()
model_set_4 = LinearRegression()
model_set_1.fit(poly_set_1, output_set_1)
model_set_2.fit(poly_set_2, output_set_2)
model_set_3.fit(poly_set_3, output_set_3)
model_set_4.fit(poly_set_4, output_set_4)

# Plot order 15 fits
plot_fit(model_set_1, poly_set_1, output_set_1)
plot_fit(model_set_2, poly_set_2, output_set_2)
plot_fit(model_set_3, poly_set_3, output_set_3)
plot_fit(model_set_4, poly_set_4, output_set_4)

# Cross validation to select order
MAX_POLYNOMIAL_DEGREE = 15
RSS = np.zeros((MAX_POLYNOMIAL_DEGREE))
val_output = val_data['price']
test_output = test_data['price']

print 'Cross validation over multiple polynomial fits'
for degree in range(1, MAX_POLYNOMIAL_DEGREE+1):
    train_features = plr.polynomial_dataframe(train_data['sqft_living'], degree)
    val_features = plr.polynomial_dataframe(val_data['sqft_living'], degree)
    model = LinearRegression()
    model.fit(train_features, output)
    RSS[degree-1] = plr.get_residual_sum_of_squares(model, val_features, val_output)
    print 'Fitting degree: ' + str(degree) + ' polynomial' + ' (RSS: ' + str(RSS[degree-1]) + ')'

best_degree = np.argmin(RSS) + 1
print 'Minimum Validation RSS: ' + str (np.min(RSS)) + ' for degree: ' + str(np.argmin(RSS)+1)

model = LinearRegression()
train_features = plr.polynomial_dataframe(train_data['sqft_living'], best_degree)
test_features = plr.polynomial_dataframe(test_data['sqft_living'], best_degree)
model.fit(train_features, output)
RSS_test_best = plr.get_residual_sum_of_squares(model, test_features, test_output)

print 'Test RSS with degree ' + str(best_degree) + ': ' + str(RSS_test_best)
plot_fit(model, train_features, output)


import matplotlib.pyplot as plt
import numpy as np

def newton_raphson(function, function1, x_initial, iteration, e):

###   -------Only for function  : (x**3-20)------

##  In this code we can give a prompt value for initial guess
## and also number of iterations
#e=10**(-4)
# def function(x):
#      return x**3-20
# def function1(x):
#      return 3*x**2
# x_initial =int(input("Enter Initial Value: "))
# iteration = int(input("Enter number of iteration: "))
# x_old=100
# for val in range(0,iteration):
#     if function(x_initial)-function(x_old)<e:
#          x_initial=x_initial-function(x_initial)/function1(x_initial)
#          print("%.5f" % round(x_initial,5))
#     elif function1(x_initial)==0:
#          print("roots cannot be found")
#     else:
#          print("Exceeded number of iterations")

   x =  x_initial
   for val in range(0,iteration):

        func=function(x)
        if abs(func)< e:
            print("Found solution after", val, "iterations")
            return  x
        Df= function1(x)
        if Df ==0:  # if derivative is zero
            print("No solution, Derivative is zero")
            return  None
        x=x-func/Df

   else:
        print("exceeded maximum iterations")
   return None
### Choices for functions

print("(a): x^(1/3)")
print("(b): x^3-20 ")
print("(c): 1-x**2 ")

## ask for choice of function (choices are (a), (b), (c))

choice =input("Choose function whose root you want to find: ")

##--------------First function----------
if choice== "a":
  #print("(a): x^(1/3)")
  function =lambda x:  x**(1/3)
  function1 = lambda x :(1/3)*x**(-2/3)
  result= newton_raphson(function, function1,1,12,10**(-4))
  print(result)

  ##-------------plotting-------------
  x = np.linspace(0,5,50)
  f= x**(1/3)

  fig = plt.figure()
  ax = fig.add_subplot(1, 1, 1)
  ax.spines['left'].set_position('center')

  ax.spines['bottom'].set_position('zero')
  ax.spines['right'].set_color('none')
  ax.spines['top'].set_color('none')
  ax.xaxis.set_ticks_position('bottom')
  ax.yaxis.set_ticks_position('left')
#plot the function
  plt.plot(x, f,'g', label = 'f(x)= x^(1/3)')

  plt.xlabel('x') # naming the x axis
  plt.ylabel('f(x)')# naming the y axis

# giving a title to my graph
  plt.legend(loc='upper left')
    #plt.title('f(x)=x^3-20')

  plt.show()

##--------------Second function----------

elif choice=="b":
  #print("(b): x**3-20 ")
  function =lambda x: x**3-20
  function1 = lambda x :3*x**2
  result= newton_raphson(function, function1,2,12,10**(-4))
  print("Root for the function x^3-20 is " +"%.6f" % round(result,6))

  ##-------------plotting-------------
  x = np.linspace(-5,5,50)
  f=x**3-20

  fig = plt.figure()
  ax = fig.add_subplot(1, 1, 1)
  ax.spines['left'].set_position('center')
  ax.spines['bottom'].set_position('zero')
  ax.spines['right'].set_color('none')
  ax.spines['top'].set_color('none')
  ax.xaxis.set_ticks_position('bottom')
  ax.yaxis.set_ticks_position('left')
#plot the function
  plt.plot(x, f,'b', label = 'f(x)=x^3-20')

  plt.xlabel('x') # naming the x axis
  plt.ylabel('f(x)')# naming the y axis

# giving a title to my graph
  plt.legend(loc='upper left')
    #plt.title('f(x)=x^3-20')

  plt.show()

  ##--------------Third function------------
else:
  #print("(c): 1-x**2 ")
  function =lambda x:  1-x**2
  function1 = lambda x :-2*x
  result= newton_raphson(function, function1,0,12,10**(-4))
  print(result)

   ##-------------plotting-------------

  x = np.linspace(-5,5,50)
  f=1-x**2

  fig = plt.figure()
  ax = fig.add_subplot(1, 1, 1)
  ax.spines['left'].set_position('center')
  ax.spines['bottom'].set_position('zero')
  ax.spines['right'].set_color('none')
  ax.spines['top'].set_color('none')
  ax.xaxis.set_ticks_position('bottom')
  ax.yaxis.set_ticks_position('left')
#plot the function
  plt.plot(x, f,'c', label = 'f(x)=1-x**2')

  plt.xlabel('x') # naming the x axis
  plt.ylabel('f(x)')# naming the y axis

# giving a title to my graph
  plt.legend(loc='upper left')
    #plt.title('f(x)=x^3-20')

  plt.show()


"""
    Projects the given Hermitian matrix on the set of density matrices (positive, trace one operators).

    Author: Akshay Seshadri
"""

import numpy as np

import project_root # noqa
from src.optimization.project_simplex import project_on_simplex_bisection

def project_on_density_matrices_flattened(V):
    """
        Projects a Hermitian matrix V on the set of density matrices X = {A \in C^{N x N} | A^\dagger = A, A >=0, tr(A) = 1}.

        Note that a Hermitian matrix must be provided. Further, the input can be the matrix or its flattened vector (row-major/C-style flattening
        assumed, which is the default of numpy). Checks are not done to ensure this is the case.

        The output is a (row-major) flattened vector of the projection.
    """
    n = int(np.sqrt(V.size))                                        # dimension of V

    V = V.reshape((n, n))                                           # V as a matrix

    V_eigvals, V_eigvecs = np.linalg.eigh(V)                        # get the spectral decomposition of V

    # project on to the density matrices by projecting the eigenvalues of V on the probability simplex
    Pi_V_eigvals = project_on_simplex_bisection(V_eigvals)

    # construct the projection form the eigevectors of V and the projected eigenvalues: Pi_V = U D_Pi U^\dagger
    Pi_V = V_eigvecs.dot( np.diag(Pi_V_eigvals).dot( V_eigvecs.T.conj() ) )

    # return the (row-major) flattened output
    return Pi_V.ravel()


"""
This script provides functions for linear least square fitting
"""

import numpy as np
import sys
from matplotlib import pyplot

def lls_fit(X, Y):
    """Given X and Y, solve A that optimizes the 2-norm difference between
    A.T @ X and Y.

    Attributes:
        X (np.ndarray):
            Dimension: n-by-N. X = [x^1, x^2, \cdots{}, x^N] where x^i is the
            i-th data point vector.
        Y (np.ndarray):
            Dimension: m-by-N. Y = [y^1, y^2, \cdots{}, y^N] where y^i is the
            i-th target vector.

    Returns:
        A (np.ndarray):
            Dimension: n-by-m.
    """
    if X.ndim == 1:
        A = X@Y / (X@X)
    else:
        A = np.linalg.pinv(X@X.T) @ X@Y.T
    return A

def lls_predict(A, X):
    """Given X and A, compute predicted value Y = A.T@X
    """
    if X.ndim == 1:
        return A * X
    else:
        return A.T @ X

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: dataX dataY")
        sys.exit(1)

    # read data
    X = np.loadtxt(sys.argv[1])
    Y = np.loadtxt(sys.argv[2])

    # sort data
    if X.ndim == 1:
        inds = np.argsort(X)
        Xnew, Ynew = [], []
        for i in inds:
            Xnew.append(X[i])
            Ynew.append(Y[i])
        X = np.array(Xnew)
        Y = np.array(Ynew)

    # put data into the desired format
    if X.ndim != 1:
        X = X.T
        Y = Y.T

    # fit
    A = lls_fit(X, Y)

    # predict
    x = np.arange(0, 20, 0.5)
    pred_Y = lls_predict(A, x)

    # plot and compare
    if X.ndim == 1:
        pyplot.plot(X, Y, 'ko', label="data")
        pyplot.plot(x, pred_Y, 'r', label='fitted')
        pyplot.show()


import numpy as np
import math

def get_scaling_factor(n):
    k = n // 2
    if n % 2 == 0:
        scaling_factor = np.sqrt(2 / (np.pi * (2 * k - 1))) \
                * (2 ** (2 * k - 2) * math.factorial(k-1)**2) / math.factorial(2 * k - 2)
    else:
        scaling_factor = np.sqrt(np.pi / k) * math.factorial(2 * k - 1) / \
                (2 ** (2 * k - 1) * math.factorial(k - 1)**2)
    return scaling_factor

def ustd(x):
    """
    See https://en.wikipedia.org/wiki/Unbiased_estimation_of_standard_deviation#Bias_correction
    """
    # Reshape to handle both 1d and 2d arrays
    if x.ndim == 1:
        n = x.size
        unbiased_variance = np.var(x, ddof=1)
    else:
        n = x.shape[1]
        unbiased_variance = np.var(x, ddof=1, axis=1)

    unbiased_std = np.sqrt(unbiased_variance) / get_scaling_factor(n)

    return unbiased_std

if __name__ == "__main__":
    assert abs(get_scaling_factor(2) - 0.7978845608) < 1e-6
    assert abs(get_scaling_factor(3) - 0.8862269255) < 1e-6
    assert abs(get_scaling_factor(4) - 0.9213177319) < 1e-6
    # test that this actually works
    x = np.random.normal(size=(100000,4))
    sigma = ustd(x)
    assert abs(np.mean(sigma) - 1) < 0.005


import numpy as np

def energy(X,W_fixed):
    """
    Input X is a vector (N by 1), and the shape of W_fixed is N by N.\n
    Return a 2D array E with shape (1,1)
    """
    return (-1/2) * np.dot(np.dot(np.transpose(X),W_fixed),X)

def f(W,X):
    """
    W : W_fixed after storaging phase with shape (N,N) \n
    X : testing pattern X with shape (N,1) \n
    Return X_new with shape (N,1)
    """
    i = 0
    v = np.dot(W,X)
    for value in v :
        if value > 0 :
            v[i,0] = 1
        elif value < 0 :
            v[i,0] = -1
        else :
            v[i,0] = X[i,0]
        i += 1
    return v

X0 = np.array([ 1,-1, 1,-1, 1,-1])
X2 = np.array([-1, 1,-1, 1,-1, 1])
X4 = np.array([ 1, 1, 1, 1, 1, 1])
X6 = np.array([-1,-1,-1,-1,-1,-1])

X0      = X0.reshape(1,X0.size)
X2      = X2.reshape(1,X2.size)
X4      = X4.reshape(1,X4.size)
X6      = X6.reshape(1,X6.size)
X       = np.array([X0,X2,X4,X6])
W_fixed = np.zeros([X0.shape[1],X0.shape[1]])

for p in range(X.shape[0]):
    W_fixed = W_fixed + np.dot(X[p,:,:].reshape(-1,1),X[p,:,:].reshape(1,-1))
for i in range(X.shape[2]):
    for j in range(X.shape[2]):
        if i==j:
            W_fixed[i,j]=0

X_test = X0.reshape(-1,1)                    #-- change label index
E = energy(X_test,W_fixed)
#------------  obtain new X  -------------
E_cycle = [E]
print('-------------------------------------')
print('E(0) : ',E[0,0])
print('X(0) : ',X_test[:,0])
X_new = X_test

for cycle in range(100):
    X_new = f(W_fixed,X_new)
    E = energy(X_new,W_fixed)
    E_cycle.append(E[0,0])

    print('-------------------------------------')
    print('E({}) : {}'.format(cycle+1,E[0,0]))
    print('X({}) : {}'.format(cycle+1,X_new[:,0]))

    if E_cycle[cycle+1] == E_cycle[cycle] :
        print('-------------------------------------')
        print('cycle :',cycle+1)
        print('E converges : {}'.format(E[0,0]))
        print('X final : {}'.format(X_new[:,0]))
        break


from scipy.linalg import lstsq
import pandas as pd
import numpy as np
import argparse

def landmark_registration_ls(pts_f1, pts_f2, out_f, delimiter="\t"):

    points1 = pd.read_csv(pts_f1, delimiter=delimiter)
    points2 = pd.read_csv(pts_f2, delimiter=delimiter)

    src = np.concatenate([np.array(points1['x']).reshape([-1,1]),
                          np.array(points1['y']).reshape([-1,1])],
                         axis=-1)
    dst = np.concatenate([np.array(points2['x']).reshape([-1,1]),
                          np.array(points2['y']).reshape([-1,1])],
                         axis=-1)

    A = np.zeros((src.size,6))
    A[0:src.shape[0],[0,1]] = src
    A[0:src.shape[0],2] = 1
    A[src.shape[0]:,[3,4]] = src
    A[src.shape[0]:,5] = 1
    b = dst.T.flatten().astype('float64')
    x = lstsq(A,b)

    tmat = np.eye(3)
    tmat[0,:] = x[0].take([0,1,2])
    tmat[1,:] = x[0].take([3,4,5])
    pd.DataFrame(tmat).to_csv(out_f, header=None, index=False, sep="\t")

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description="Estimate transformation from points using least squares")

    parser.add_argument("fn_pts1", help="File name src points")
    parser.add_argument("fn_pts2", help="File name dst points")
    parser.add_argument("fn_tmat", help="File name transformation matrix")
    args = parser.parse_args()

    landmark_registration_ls(args.fn_pts1, args.fn_pts2, args.fn_tmat)


from tb3ms import TB3MS_Data
from stockExpectedReturn import Average_StdDev_Data, average
import numpy as np
import pandas as pd
from stockCovariance import stockCovariance
import matplotlib.pyplot as plt

tickers = ["AAPL","GOOG","AMZN","MSFT","INTC","IBM","ORCL","CSCO","NVDA"]

p_ret = [] # Define an empty array for portfolio returns
p_vol = [] # Define an empty array for portfolio volatility
p_weights = [] # Define an empty array for asset weights
ind_er = average(tickers)
cov_matrix = stockCovariance(tickers)
df = Average_StdDev_Data(tickers)
rf = TB3MS_Data().get("Last Effective Monthly Rate")

num_assets = len(tickers)
num_portfolios = 10000
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)
    p_weights.append(weights)
    returns = np.dot(weights, ind_er)
    p_ret.append(returns)
    var = cov_matrix.mul(weights, axis=0).mul(weights, axis=1).sum().sum()# Portfolio Variance
    sd = np.sqrt(var) # Daily standard deviation
    p_vol.append(sd)

data = {'Returns':np.array(p_ret), 'Volatility':np.array(p_vol)}

for counter, symbol in enumerate(df.columns.tolist()):
    data[symbol+' weight'] = [w[counter] for w in p_weights]
portfolios  = pd.DataFrame(data)

lenght = len(portfolios["Returns"])
sharpe_ratios = []

for i in range(lenght):
    sharpe_ratios.append((portfolios["Returns"][i]-rf)/portfolios["Volatility"][i])

portfolios["Sharpe Ratio"] = sharpe_ratios
min_vol_port = portfolios.iloc[portfolios["Volatility"].idxmin()]
optimal_risky_port = portfolios.iloc[((portfolios["Returns"]-rf)/portfolios["Volatility"]).idxmax()]

plt.subplots(figsize=[30,20])
main = plt.scatter(portfolios["Volatility"], portfolios["Returns"], c=sharpe_ratios)
plt.scatter(min_vol_port[1],min_vol_port[0], color="r", marker="o", s=100, label="Minimum Volatility Portfolio")
plt.scatter(optimal_risky_port[1], optimal_risky_port[0], color='g', marker='o', s=100, label="Optimal Portfolio")
plt.title("Efficient Frontier", fontsize=18)
plt.xlabel("Volatility", fontsize=14)
plt.ylabel("Returns", fontsize=14)

index = sharpe_ratios.index(max(sharpe_ratios))

plt.plot([0, portfolios["Volatility"][index]],[rf, portfolios["Returns"][index]], "-", linewidth=3, color ="green", label = "Capital Market Line")

plt.grid()
plt.colorbar(main)
plt.legend()

print("Lowest Risk Portfolio:\n")
print(min_vol_port)
print("\nHighest Sharpe Ratio Portfolio:\n")
print(optimal_risky_port)
plt.show()


import numpy as np
import scipy.linalg
import unittest

from utils import *

def time_evo(H, t: float):
    """
    Returns the solution to the Schrodinger equation for Hamiltonian H:
    U = exp(-itH/h) where h is Planck's reduced constant.
    The exponential is a matrix function that takes the exponential of (-itv/h)
    where v are the eigenvalues of H and multiplies by the outer product of the
    corresponding eigenvectors.

    We absorb h into t. I think.

    Parameters:
        H : (n, n) matrix
            Hamiltonian matrix. Must be Hermitian.
        t : float
            Time to evolve for.
    Returns:
        u : u = exp(-itH) as described above. u is a unitary matrix.
    """
    prc = 1.054571817e-34 # Planck's reduced constant in Joule seconds
    # TODO: Does including prc matter? Can I assume it's folded into t?
    # No, I don't think I can. I should test incorporating prc here. But I
    # can't just divide it because it's too small and Python won't handle it
    # well. Instead I need to use logarithms!
    return scipy.linalg.expm(-1j * t * H)

def prod_time_evo(Hs: list, ts: float):
    """
    The product of each Hamiltonian H in Hs evolved for correspoidning time t
    in ts. This can be used for Trotterization if the ts are all the same.
    We use this for each step in the Variational Hamiltonian Ansatz (VHA).

    Parameters:
        Hs: list of (n, n) matrices (sub-matrices of Hamiltonian)
            A list of the Hamiltonian sub-matrices. The sum of the Hamiltonians
            in Hs is the actual Hamiltonian of the system. Each H in Hs must
            be Hermitian.
        ts: list of ints (times)
            A list of the times to evolve the sub-matrices in Hs by.
            Hs[i] is evolved for ts[i] seconds.
    Returns:
        u: (n, n) unitary matrix.
            The product of each Hamiltonian sub-matrix evolved for its
            corresponding time. Since we're multiplying k unitaries, our result
            must also be unitary.
    """
    result = 1
    for (H, t) in zip(Hs, ts):
        result = NDot(result, time_evo(H, t))
    return result

# TESTS
tol = 0.005
herms = [np.eye(4), X, Y, Z, I, NKron(X, Y), NKron(Y, Z, Z, I)]

# Test scipy.linalg.expm() to make sure it does what I expect. Use X.
class TestExpm(unittest.TestCase):
    def test_X(self):
        # Making sure expm doesn't just exponentiate elements
        expm_X = scipy.linalg.expm(X)
        exp_X = np.exp(X)
        # I worked it out on paper and these should be different
        self.assertFalse(array_eq(expm_X, exp_X, tol))
        # Make sure expm_X has the correct values
        e = 2.718281
        corr_expm_X = .5 * np.array([[e + 1/e, e - 1/e],
                                     [e - 1/e, e + 1/e]], dtype='complex128')
        self.assertTrue(array_eq(expm_X, corr_expm_X, tol))

class TestUnitary(unittest.TestCase):
    def test_eig_mod_1(self):
        # Making sure eigenvalues of time_evo() have modulus 1 since it
        # returns a unitary
        def check_if_eigs_mod_1(u):
            eig = np.linalg.eig(u)
            mag = np.abs(eig[0])
            self.assertTrue(array_eq(mag, np.ones(mag.shape), tol))

        for H in herms:
            u = time_evo(H, np.random.uniform(0, 3))
            check_if_eigs_mod_1(u)

    def test_prod_identity(self):
        # U*U = I should be true
        def adjoint_prod(u):
            prod = NDot(adjoint(u), u)
            inv_prod = NDot(u, adjoint(u))
            self.assertTrue(array_eq(prod, np.eye(prod.shape[0]), tol))
            self.assertTrue(array_eq(inv_prod, np.eye(prod.shape[0]), tol))

        for H in herms:
            u = time_evo(H, np.random.uniform(0, 1))
            adjoint_prod(u)

# Check that time_evo() is continuous: t=3 * t=1 should equal t=4 for same H.
class TestContinuous(unittest.TestCase):
    def test_continuous(self):
        for H in herms:
            u_1 = time_evo(H, 1)
            u_3 = time_evo(H, 3)
            u_4 = time_evo(H, 4)
            self.assertTrue(array_eq(NDot(u_1, u_3), u_4, tol))

# TODO: Test prod_time_evo()

if __name__ == '__main__':
    unittest.main()


from IMLearn.learners import UnivariateGaussian, MultivariateGaussian
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = "simple_white"
from matplotlib import pyplot as plt

def test_univariate_gaussian():
    # Question 1 - Draw samples and print fitted model
    mu = 10
    sigma = 1
    num = 1000
    X = np.random.normal(mu,sigma,num)
    uni_gauss = UnivariateGaussian()
    uni_gauss.fit(X)
    print(uni_gauss.mu_, uni_gauss.var_)

    # Question 2 - Empirically showing sample mean is consistent
    _, (ax1, ax2) = plt.subplots(2,constrained_layout=True,figsize=(8,8))
    diff = [abs(uni_gauss.fit(X[:i]).mu_ - mu) for i in range(10,num+10,10)]
    ax1.set(xlabel='Sample size',ylabel='Absolute distance')
    ax1.set_title('Distance between estimated expected value and actual expected value')
    ax1.scatter(range(10,num+10,10),diff,s=5)

    # Question 3 - Plotting Empirical PDF of fitted model
    pdfs = uni_gauss.pdf(X)
    ax2.set(xlabel='Sample',ylabel='pdf')
    ax2.set_title('Empirical pdf')
    ax2.scatter(X,pdfs,s=7)
    plt.show()

def test_multivariate_gaussian():
    # Question 4 - Draw samples and print fitted model
    mu = np.array([0,0,4,0])
    cov = np.array([[1,0.2,0,0.5],
                    [0.2,2,0,0],
                    [0,0,1,0],
                    [0.5,0,0,1]])
    X = np.random.multivariate_normal(mu,cov,1000)
    multi_gauss = MultivariateGaussian()
    multi_gauss.fit(X)
    print(multi_gauss.mu_)
    print(multi_gauss.cov_)

    # Question 5 - Likelihood evaluation
    # calculate heatmap
    _x, _y = np.linspace(-10,10,200), np.linspace(-10,10,200)
    x, y = np.meshgrid(_x,_y,sparse=True) # note that meshgrid reverses order- y is the first feature, x is the third
    func = np.vectorize(lambda u,v: multi_gauss.log_likelihood(np.array([u,0,v,0]),cov,X))
    heatmap = func(y,x)
    # plot heatmap
    plt.figure()
    plt.xlabel('Third mean feature')
    plt.ylabel('First mean feature')
    plt.title('Likelihood heatmap with respect to different first and third mean features')
    plt.pcolormesh(x, y, heatmap)
    plt.colorbar()
    plt.show()

    # Question 6 - Maximum likelihood
    ymax, xmax = np.unravel_index(heatmap.argmax(), heatmap.shape)
    print(_y[ymax], _x[xmax])

if __name__ == '__main__':
    np.random.seed(0)
    test_univariate_gaussian()
    test_multivariate_gaussian()


#!/usr/bin/env python3

"""
    Title:  ML Workshop: Multiple Linear Regression (MLR)
    Date:   May 15, 2021
    Author: Mario Raciti
"""

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_validate

np.random.seed(0)

# Create a dataset
# Vars
obs_n = 200
b0, b1, b2, b3 = 5, 8, -5, 0.1
fluctuations = 1
# Features
X1 = 1 + 2 * np.random.random(size=(obs_n, 1))  # in (1, 3)
X2 = 2 * np.random.random(size=(obs_n, 1))      # in (0, 2)
X3 = 4 + 3 * np.random.random(size=(obs_n, 1))  # in (4, 7)
X = np.concatenate((X1, X2, X3), axis=1)    # Gather features
# # Plot features to chart
# plt.plot(X1.squeeze(), "r+")
# plt.plot(X2.squeeze(), "b*")
# plt.plot(X3.squeeze(), "go")
# plt.suptitle('X1, X2, X3', fontsize=16)
# plt.show()
# Targets
Y = b0 + b1 * X1.squeeze() + b2 * X2.squeeze() + b3 * X3.squeeze() + \
    fluctuations * np.random.randn(obs_n)
# # Plot dataset to chart
# plt.plot(X1.squeeze(), Y, 'b+')
# plt.suptitle('Y vs X1', fontsize=16)
# plt.show()
# plt.plot(X2.squeeze(), Y, 'b+')
# plt.suptitle('Y vs X2', fontsize=16)
# plt.show()
# plt.plot(X3.squeeze(), Y, 'b+')
# plt.suptitle('Y vs X3', fontsize=16)
# plt.show()

# Create an MLR model
mlr_model = LinearRegression()
# Training
mlr_model.fit(X, Y)
print(mlr_model.coef_)
print(mlr_model.intercept_)
# Results
print(f'Source b0 = {b0}\t Model b0 = {mlr_model.intercept_:.2f}\tRelative difference {100*(b0-mlr_model.intercept_)/mlr_model.intercept_:.2f}%')
print(f'Source b1 = {b1}\t Model b1 = {mlr_model.coef_[0]:.2f}\tRelative difference {100*(b1-mlr_model.coef_[0])/mlr_model.coef_[0]:.2f}%')
print(f'Source b2 = {b2}\t Model b2 = {mlr_model.coef_[1]:.2f}\tRelative difference {100*(b2-mlr_model.coef_[1])/mlr_model.coef_[1]:.2f}%')
print(f'Source b3 = {b3}\t Model b3 = {mlr_model.coef_[2]:.2f}\tRelative difference {100*(b3-mlr_model.coef_[2])/mlr_model.coef_[2]:.2f}%')

# Predictions
new_obs = 100
i = 0
feature_n = np.shape(X)[1]
X_pred = np.empty([new_obs, feature_n])
for feature in [X1, X2, X3]:
    first, last = feature.min(), feature.max()
    X_pred[:, i] = np.linspace(first, last, new_obs)
    i += 1

# Predict
Y_pred = mlr_model.predict(X=X_pred)

# Plot predictions to chart
plt.plot(X, Y, '+')
plt.plot(X_pred, Y_pred, 'r-')
plt.show()

# Model Evaluation
# K-fold Cross Validation
K = 5
print(f'Number of folds = {K}\n')
error = 'neg_mean_squared_error'    # NOTE: MSE = RSS/N
results = cross_validate(mlr_model, X, Y, cv=K, scoring=error)

# Error for each fold and average error over the folds:
errors = -results['test_score']
print(f'Error for each fold: {errors}')
print(f'Error averaged on all folds: {errors.mean()}')


"""
Test for the measure module.
"""

#imports

import pytest
import molecool
import numpy as np

def test_calculate_distance():

    r1 = np.array([0,0,0])
    r2 = np.array([0,1,0])

    expected_distance = 1

    calculated_distance = molecool.calculate_distance(r1,r2)

    assert expected_distance == calculated_distance

def test_calculate_angle():

    rA = np.array([0,0,-1])
    rB = np.array([0,0,0])
    rC = np.array([1,0,0])

    expected_angle = 90

    calculated_angle = molecool.calculate_angle(rA,rB,rC, degrees=True)

    assert pytest.approx(expected_angle) == calculated_angle

@pytest.mark.parametrize("p1,p2,p3, expected_angle", [
    (np.array([np.sqrt(2)/2, np.sqrt(2)/2, 0]), np.array([0, 0, 0]), np.array([1, 0, 0]), 45),
    (np.array([0, 0, -1]), np.array([0, 1, 0]), np.array([1, 0, 0]), 60  ),
    (np.array([np.sqrt(3)/2, (1/2), 0]), np.array([0, 0, 0]), np.array([1, 0, 0]), 30),
])
def test_calculate_angle_many(p1,p2,p3, expected_angle):

    calculated_value = molecool.calculate_angle(p1, p2, p3, degrees=True)

    assert pytest.approx(expected_angle) == calculated_value


from math import sin, pi
import numpy as np
from sympy import Symbol, integrate
import matplotlib.pyplot as plt
# function for the Rectangular rule
def Rectangular(a,b,f,n):
   h = (b-a)/float(n)
   s = 0
   for i in range(0,n,1):
       x = (i+0.5)*h
       s = s+ f(x)
   return h*s
#  function to compute pi
def function(x):
    return sin(2*pi*x)
# define integration limits and integration points
a = 0.0; b = 0.5;
n = 100
Exact = 1./pi
print("Relative error= ", abs( (Rectangular(a,b,function,n)-Exact)/Exact))


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Apr 25 10:49:26 2019

@author: self-driver
"""

import numpy as np
from scipy.misc import comb

def bernstein_poly(i, n, t):
    """
     The Bernstein polynomial of n, i as a function of t
     (i)
     (n) * t^i * (1-t)^(n-i)
    """
    return comb(n, i) * ( (1-t)**(n-i) ) * (t)**i

def bezier_curve(points,nTimes=1000):
    """
       Given a set of control points, return the
       bezier curve defined by the control points.

       points should be a list of lists, or list of tuples
       such as [ [1,1], [2,3], [4,5], ..[Xn, Yn] ]
        nTimes is the number of time steps, defaults to 1000

        See http://processingjs.nihongoresources.com/bezierinfo/
    """
    npoints = len(points)
    xPoints = np.array([p[0] for p in points])
    yPoints = np.array([p[1] for p in points])

    t = np.linspace(0.0, 1.0, nTimes)

    polynomial_array = np.array([ bernstein_poly(i,npoints-1, t) for i in range(0, npoints)])

    xvals = np.dot(xPoints, polynomial_array)
    yvals = np.dot(yPoints, polynomial_array)

    return xvals, yvals

def bezier_curve_D(points,n,nTimes=1000):
    """
       Given a set of control points, return the
       bezier curve defined by the control points.

       points should be a list of lists, or list of tuples
       such as [ [1,1], [2,3], [4,5], ..[Xn, Yn] ]
        nTimes is the number of time steps, defaults to 1000

        See http://processingjs.nihongoresources.com/bezierinfo/
    """
    npoints = len(points)
    xPoints = np.array([p[0] for p in points])
    yPoints = np.array([p[1] for p in points])

    t = np.linspace(0.0, 1.0, nTimes)

    polynomial_array = np.array([ bernstein_poly(i,npoints-1, t) for i in range(0, npoints)])

    xvals = np.dot(xPoints, polynomial_array)
    yvals = np.dot(yPoints, polynomial_array)

    return xvals, yvals


import numpy as np

def SS(data:np.ndarray, eq_params:tuple|list) -> float:
    """
    Calculate the sum of sqared error for a given dataset and line.

    Args:
        data: Row major numpy array with target variable in last column
        eq_params: coefficients for slope-intercept form of line, ex: if eq is y = 1 + 2x -> eq_params is (1, 2)
                   Importantly, order of the coefficients in eq_params must be the same as the variables in data

    Returns:
        sum_squared_diffs: Float value, sum of squared error from data to prediction line
    """
    # calculate the predicted values of the target variable using the equation parameters and all non-target dimensions
    y_intercept, *dim_coefficients = eq_params
    coefficients_applied_to_data = data[:, :-1] * dim_coefficients
    predicted_target_values = np.sum(coefficients_applied_to_data, axis=1) + y_intercept

    # calculate, square, and sum the distances from each point to its position predicted by the line
    diffs = predicted_target_values - data[:, -1]
    squared_diffs = np.square(diffs)
    sum_squared_diffs = np.sum(squared_diffs)

    return sum_squared_diffs

# def determine_line_of_best_fit_for_vis():
#     pass

def determine_LBF(data: np.array) -> tuple:
    """
    Iteratively calculate the line of best fit (LBF) for given data

    Args:
        data: Row major representation of data in np.array w/ target variable in last column
        precision: # of decimal places to calculate the paramaters for LBF to

    Returns:
        line_params: tuple of parameters for LBF, ex: if line is y = 1 + 2x -> line_params is (1, 2)
    """
    pass

if __name__ == "__main__":
    points = np.array([[0, 0], [1, 2], [2, 4], [3, 6]])
    line_params = [0, 2]
    result = SS(points, line_params)
    print(result)
    points_t = np.transpose(points)

    print(np.matmul(points_t,points))


# -*- coding: utf-8 -*-
"""
Created on Sat Apr 18 13:03:24 2020

@author: Mohammad Asif Zaman

Code for testng differentiation matrix operators

"""

from __future__ import print_function

import time
import math
import numpy as np
import pylab as py
import scipy.sparse as sp                 # import sparse matrix library

py.rcParams.update({'font.size': 14})

# import the file where the differentiation matrix operators are defined
from diff_matrices import Diff_mat_1D, Diff_mat_2D

def my_contourf(x,y,F,ttl):
    py.contourf(x,y,F,41,cmap = 'inferno')
    py.colorbar()
    py.xlabel('x'); py.ylabel('y'); py.title(ttl)
    return 0

# Define independent variables
Nx = 80                         # No. of grid points along x direction
Ny = 70                         # No. of grid points along y direction
x = np.linspace(-3,3,Nx)        # x variables in 1D
y = np.linspace(-3,3,Ny)        # y variable in 1D

dx = x[1] - x[0]                # grid spacing along x direction
dy = y[1] - y[0]                # grid spacing along y direction

X,Y = np.meshgrid(x,y)          # 2D meshgrid

# Test funcitons
F = np.exp(-X**2-3*Y**2)        # 2D Gaussian test function
Fx = np.multiply(X,F)*(-2)      # Analytical value of dF/dx
Fy = np.multiply(Y,F)*(-6)      # Analytical value of dF/dy

F2x = np.multiply(2*X**2-1,F)*(2)      # Analytical value of d2F/dx2
F2y = np.multiply(6*Y**2-1,F)*(6)      # Analytical value of d2F/dy2

G = np.exp(-x**2)
Gx = np.multiply(x,G)*(-2)
G2x = np.multiply(2*x**2-1,G)*(2)

Dx_1d, D2x_1d = Diff_mat_1D(Nx)                     # Calling 1D matrix operators from function
Dx_2d, Dy_2d, D2x_2d, D2y_2d = Diff_mat_2D(Nx,Ny)   # Calling 2D matrix operators from funciton

# Differentiation using matrix operators. The division by 2dx and 2dy are needed as they are not defined within the funcitons

dGx =  Dx_1d*G/(2*dx)     # dF/dx from matrix operator
d2Gx =  D2x_1d*G/(dx**2)     # dF/dx from matrix operator

dFx = (Dx_2d*F.ravel()).reshape([Ny,Nx])/(2*dx)     # dF/dx from matrix operator
dFy = (Dy_2d*F.ravel()).reshape([Ny,Nx])/(2*dy)     # dF/dy from matrix operator

d2Fx = (D2x_2d*F.ravel()).reshape([Ny,Nx])/(dx**2)     # d2F/dx2 from matrix operator
d2Fy = (D2y_2d*F.ravel()).reshape([Ny,Nx])/(dy**2)     # d2F/dy2 from matrix operator

# Plotting 1D differntiations
py.close('all')
#py.contourf(x,y,F,41,cmap = 'plasma')

py.figure(figsize = (14,4.8))
py.subplot(1,2,1)
py.plot(x,Gx,'o',label = 'Analytical')
py.plot(x,dGx,label = 'Matrix')
py.legend()
py.xlabel('x'); py.ylabel(r'$dG/dx$'); py.title('First derivative')

py.subplot(1,2,2)
py.plot(x,G2x,'o',label = 'Analytical')
py.plot(x,d2Gx,label = 'Matrix')
py.legend()
py.xlabel('x'); py.ylabel(r'$d^2G/dx^2$'); py.title('Second derivative')

# Plotting 2D differentiations

# First partial derivatives
py.figure(figsize = (14,4.8))
py.subplot(1,2,1); my_contourf(x,y,Fx,r'Analytical $dF/dx$')
py.subplot(1,2,2); my_contourf(x,y,dFx,r'Matrix operator $dF/dx$')

py.figure(figsize = (14,4.8))
py.subplot(1,2,1); my_contourf(x,y,Fy,r'Analytical $dF/dy$')
py.subplot(1,2,2); my_contourf(x,y,dFy,r'Matrix operator $dF/dy$')

# Second partial derivatives
py.figure(figsize = (14,4.8))
py.subplot(1,2,1); my_contourf(x,y,F2x,r'Analytical $d^2F/dx^2$')
py.subplot(1,2,2); my_contourf(x,y,d2Fx,r'Matrix operator $d^2F/dx^2$')

py.figure(figsize = (14,4.8))
py.subplot(1,2,1); my_contourf(x,y,F2y,r'Analytical $d^2F/dy^2$')
py.subplot(1,2,2); my_contourf(x,y,d2Fy,r'Matrix operator $d^2F/dy^2$')


import numpy as np
import matplotlib.pyplot as plt

'''
Adam Bashforth m step method:
    Function Name: adam_bashforth

    Inputs: f: defining function ( from y' = f(t,y) )
            a: starting Time
            b: ending Time
            alpha: Initial Condition
            h: stepsize
            type: # of steps (the 'm' in m-step scheme)
                                m = 2,3,4 or 5
            N = (optional) to override the number of iterations
                default: N = 0 for normal Adam Bashforth explicit
                    reset N to any finite number i to get i iterations.
                        (i >= type-1, for obvious reasons)
            plot: Binary input
                Default plot = 0: No plotting
                        plot = 1: plots the solution

    Outputs: [t, y]: where
                t: time axis
                y: solution array
'''

#--------------------- RK 4th order method ---------------------------

def RK4_method(f, a, b, t, y, h, N=0):
    if(N == 0):
        N = int((b-a)/h)

    for i in range(N):
        k1 = h*f(t[-1], y[-1])
        yp2 = y[-1] + k1/2
        k2 = h*f(t[-1]+h/2, yp2)
        yp3 = y[-1] + k2/2
        k3 = h*f(t[-1]+h/2, yp3)
        yp4 = y[-1] + k3
        k4 = h*f(t[-1]+h, yp4)
        val = (1/6)*(k1 + 2*k2 + 2*k3 + k4)
        y.append(y[-1] + val)
        t.append(t[-1] + h)
    return t, y

#----------------- Adam Bashforth explicit methods --------------------

#Adam Bashforth handler
def adam_bashforth(f, a, b, alpha, h, type, N = 0, plot = 0):
    t = [a]
    y = [alpha]
    t, y = RK4_method(f, a, b, t, y, h, type - 1)
    if(N == 0):
        N = int(((b-a)/h) - (type-1))

    if(type == 2):
        t, y = abf2(f, y, t, h, N)
        if(plot == 1):
            plt.plot(t, y, label = "AB %s step solution"%type)
            plt.legend()
            plt.show()

    if(type == 3):
        t, y = abf3(f, y, t, h, N)
        if(plot == 1):
            plt.plot(t, y, label = "AB %s step solution"%type)
            plt.legend()
            plt.show()

    if(type == 4):
        t, y = abf4(f, y, t, h, N)
        if(plot == 1):
            plt.plot(t, y, label = "AB %s step solution"%type)
            plt.legend()
            plt.show()

    if(type == 5):
        t, y = abf5(f, y, t, h, N)
        if(plot == 1):
            plt.plot(t, y, label = "AB %s step solution"%type)
            plt.legend()
            plt.show()

    return [t, y]

#2 step Adam Bashforth
def abf2(f, y, t, h, N):
    for i in range(N):
        y.append(y[-1] + (h/2)*(3*f(t[-1], y[-1]) - f(t[-2], y[-2])))
        t.append(t[-1] + h)
    return t, y

#3 step Adam Bashforth
def abf3(f, y, t, h, N):
    for i in range(N):
        y.append(y[-1] + (h/12)*(23*f(t[-1], y[-1]) - 16*f(t[-2], y[-2]) \
                                + 5*f(t[-3], y[-3])))
        t.append(t[-1] + h)
    return t, y

#4 step Adam Bashforth
def abf4(f, y, t, h, N):
    for i in range(N):
        y.append(y[-1] + (h/24)*(55*f(t[-1], y[-1]) - 59*f(t[-2], y[-2]) \
                                    + 37*f(t[-3], y[-3]) - 9*f(t[-4], y[-4])))
        t.append(t[-1] + h)
    return t, y

#5 step Adam bashforth
def abf5(f, y, t, h, N):
    for i in range(N):
        y.append(y[-1] + (h/720)*(1901*f(t[-1], y[-1]) - 2774*f(t[-2], y[-2]) \
                                + 2616*f(t[-3], y[-3]) - 1274*f(t[-4], y[-4]) \
                                +251*f(t[-5], y[-5])))
        t.append(t[-1] + h)
    return t, y


#!/usr/bin/env python

__author__ = "Adeel Ahmad"
__email__ = "adeelahmad14@hotmail.com"
__status__ = "Production"

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import skimage.io as io

def edgesMarrHildreth(img, sigma):
	"""
		finds the edges using MarrHildreth edge detection method...
		:param im : input image
		:param sigma : sigma is the std-deviation and refers to the spread of gaussian
		:return:
		a binary edge image...
	"""
	size = int(2*(np.ceil(3*sigma))+1)

	x, y = np.meshgrid(np.arange(-size/2+1, size/2+1), np.arange(-size/2+1, size/2+1))

	normal = 1 / (2.0 * np.pi * sigma**2)

	kernel = ((x**2 + y**2 - (2.0*sigma**2)) / sigma**4) * np.exp(-(x**2+y**2) / (2.0*sigma**2)) / normal # LoG filter

	kern_size = kernel.shape[0]
	log = np.zeros_like(img, dtype=float)

	# applying filter
	for i in range(img.shape[0]-(kern_size-1)):
		for j in range(img.shape[1]-(kern_size-1)):
			window = img[i:i+kern_size, j:j+kern_size] * kernel
			log[i,j] = np.sum(window)

	log = log.astype(np.int64, copy=False)

	zero_crossing = np.zeros_like(log)

	# computing zero crossing
	for i in range(log.shape[0]-(kern_size-1)):
		for j in range(log.shape[1]-(kern_size-1)):
			if log[i][j] == 0:
				if (log[i][j-1] < 0 and log[i][j+1] > 0) or (log[i][j-1] < 0 and log[i][j+1] < 0) or (log[i-1][j] < 0 and log[i+1][j] > 0) or (log[i-1][j] > 0 and log[i+1][j] < 0):
					zero_crossing[i][j] = 255
			if log[i][j] < 0:
				if (log[i][j-1] > 0) or (log[i][j+1] > 0) or (log[i-1][j] > 0) or (log[i+1][j] > 0):
					zero_crossing[i][j] = 255

	# plotting images
	fig = plt.figure()
	a =fig.add_subplot(1,2,1)
	imgplot = plt.imshow(log, cmap='gray')
	a.set_title('Laplacian of Gaussian')
	a = fig.add_subplot(1,2,2)
	imgplot = plt.imshow(zero_crossing, cmap='gray')
	string = 'Zero Crossing sigma = '
	string += (str(sigma))
	a.set_title(string)
	plt.show()

	return zero_crossing


# solutions.py
"""Volume 1A: SVD and Image Compression. Solutions file."""

# solutions.py
from scipy import linalg as la
import numpy as np
from matplotlib import pyplot as plt

# Problem 1
def truncated_svd(A,k=None,tol=10**-6):
    """Computes the truncated SVD of A. If r is None or equals the number
        of nonzero singular values, it is the compact SVD.
    Parameters:
        A: the matrix
        k: the number of singular values to use
        tol: the tolerance for zero
    Returns:
        U - the matrix U in the SVD
        s - the diagonals of Sigma in the SVD
        Vh - the matrix V^H in the SVD
    """
    m,n = A.shape
    eigs,evecs = la.eig(A.conj().T.dot(A))

    s = np.sqrt(eigs)
    sort_indices = np.argsort(s)[::-1] #Sort from greatest to least
    # Sort singular values
    s = s[sort_indices]
    # Sort eigenvectors the same way
    evecs = evecs[:,sort_indices]
    if k is not None:
        if k > len(s):
            raise ValueError("k is too large")
        else:
            # Keep only the largest k singular values
            s = s[:k]
    # Remove all zero singular values
    s = s[s>tol]

    #calculate V
    V = evecs[:,:len(s)]

    #calculate U
    U = np.empty((m,len(s)))
    for i in xrange(len(s)):
        U[:,i] = (1./s[i])*A.dot(V[:,i])

    return U, s, V.conj().T

# Problem 2
def visualize_svd():
    S = np.load("circle.npz")["circle"]
    vec = np.load("circle.npz")["unit_vectors"]

    A = np.array([3,1,1,3]).reshape((2,2))
    U,s,Vh = la.svd(A)

    VhS = Vh.dot(S)
    SigVhS = np.diag(s).dot(VhS)
    USigVhS = U.dot(SigVhS)

    Vhvec = Vh.dot(vec)
    SigVhvec = np.diag(s).dot(Vhvec)
    USigVhvec = U.dot(SigVhvec)

    border1 = [-1.1, 1.1, -1.1, 1.1]
    border2 = [-4.5, 4.5, -4.5, 4.5]
    plt.subplot(2,2,1)
    plt.plot(S[0],S[1],vec[0],vec[1])
    plt.axis(border1)
    plt.axis("equal")
    plt.subplot(2,2,2)
    plt.plot(VhS[0],VhS[1],Vhvec[0],Vhvec[1])
    plt.axis(border1)
    plt.axis("equal")
    plt.subplot(2,2,3)
    plt.plot(SigVhS[0],SigVhS[1],SigVhvec[0],SigVhvec[1])
    plt.axis(border2)
    plt.axis("equal")
    plt.subplot(2,2,4)
    plt.plot(USigVhS[0],USigVhS[1],USigVhvec[0],USigVhvec[1])
    plt.axis(border2)
    plt.axis("equal")
    plt.show()

# Problem 3
def svd_approx(A, k):
    """Returns best rank k approximation to A with respect to the induced 2-norm.

    Inputs:
    A - np.ndarray of size mxn
    k - rank

    Return:
    Ahat - the best rank k approximation
    """
    U,s,Vt = la.svd(A, full_matrices=False)
    S = np.diag(s[:k])
    u1,s1,vt1 = U[:,:k],S[:k,:k],Vt[:k,:]
    diff = (u1.nbytes + np.diag(s1).nbytes + vt1.nbytes) - A.nbytes
    if diff > 0:
        print "WARNING: Given parameters do not result in compressed data."
    Ahat = U[:,:k].dot(S).dot(Vt[:k,:])
    return Ahat

# Problem 4
def lowest_rank_approx(A,e):
    """Returns the lowest rank approximation of A with error less than e
    with respect to the induced 2-norm.

    Inputs:
    A - np.ndarray of size mxn
    e - error

    Return:
    Ahat - the lowest rank approximation of A with error less than e.
    """
    U,s,Vt = la.svd(A, full_matrices=False)
    if e<=s[-1]:
        print "Warning: Matrix cannot be approximated below this error bound"
        return A
    k = np.where(s<e)[0][0]
    Ahat = U[:,:k].dot(S).dot(Vt[:k,:])
    return Ahat

# Problem 5
def compress_image(filename,k):
    """Plot the original image found at 'filename' and the rank k approximation
    of the image found at 'filename.'

    filename - jpg image file path
    k - rank
    """
    orig_img = plt.imread(filename).astype(float)
    red = orig_img[:,:,0]
    green = orig_img[:,:,1]
    blue = orig_img[:,:,2]

    img = np.zeros(orig_img.shape)
    img[:,:,0] = svd_approx(red,k)
    img[:,:,1] = svd_approx(green,k)
    img[:,:,2] = svd_approx(blue,k)

    img = np.round(img)/255.
    orig_img = np.round(orig_img)/255.
    img[img<0] = 0.
    img[img>1] = 1.

    plt.subplot(1,2,1)
    plt.title("Original Image")
    plt.imshow(orig_img)
    plt.subplot(1,2,2)
    plt.title("Rank " + str(k) + " Approximation")
    plt.imshow(img)
    plt.show()


import numpy as np
import pandas as pd
import math

import random

def noisy_sin(steps_per_cycle = 50,
              number_of_cycles = 500,
              random_factor = 0.4):
    # Ref:
    '''
    random_factor    : amount of noise in sign wave. 0 = no noise
    number_of_cycles : The number of steps required for one cycle

    Return :
    pd.DataFrame() with column sin_t containing the generated sin wave
    '''
    random.seed(0)
    df = pd.DataFrame(np.arange(steps_per_cycle * number_of_cycles + 1), columns=["t"])
    df["sin_t"] = df.t.apply(lambda x: math.sin(x * (2 * math.pi / steps_per_cycle)+ random.uniform(-1.0, +1.0) * random_factor))
    df["sin_t_clean"] = df.t.apply(lambda x: math.sin(x * (2 * math.pi / steps_per_cycle)))
    return(df)

def load_data():
    steps_per_cycle = 10
    df = noisy_sin(steps_per_cycle=steps_per_cycle,
              random_factor = 0.02)

#     test_size = 0.25
#     ntr = int(len(df) * (1 - test_size))
#     df_train = df[["sin_t"]].iloc[:ntr]
#     df_test  = df[["sin_t"]].iloc[ntr:]
#     X_train = df_train.iloc[0:3749].values.reshape(-1,1,1)
#     y_train = df_train.iloc[1:3750].values.reshape(-1,1,1)

#     X_test = df_test.iloc[0:1250].values.reshape(-1,1,1)
#     y_test = df_test.iloc[1:1251].values.reshape(-1,1,1)

    def _load_data(data, n_prev = 100):
        """
        data should be pd.DataFrame()
        """
        docX, docY = [], []
        for i in range(len(data)-n_prev):
            docX.append(data.iloc[i:i+n_prev].values)
            docY.append(data.iloc[i+n_prev].values)
        alsX = np.array(docX)
        alsY = np.array(docY)

        return alsX, alsY

    length_of_sequences = 2
    test_size = 0.25
    ntr = int(len(df) * (1 - test_size))
    df_train = df[["sin_t"]].iloc[:ntr]
    df_test  = df[["sin_t"]].iloc[ntr:]
    (X_train, y_train) = _load_data(df_train, n_prev = length_of_sequences)
    (X_test, y_test)   = _load_data(df_test, n_prev = length_of_sequences)

    return X_train, y_train, X_test, y_test


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

# import sklearn

"""Math behind Large Margin Classification"""

u = np.array([10, 20])
v = np.array([30, 70])
np.dot(u, v)

np.transpose(u)

# Gaussian Kernel
from mpl_toolkits.mplot3d import Axes3D

x_lin = np.array([1,2,3,4,5,6,7,8,9,10])
y_lin = np.array([2,2,3,2,2,9,6,8,8,9])
label_lin = np.array([0,0,0,0,0,1,1,1,1,1])

fig = plt.figure()
ax=fig.add_subplot(111)
plt.scatter(x_lin, y_lin, c=label_lin, s=60)
plt.plot([-2.5, 10], [12.5, -2.5], 'k-', lw=2)
ax.set_xlim([-5,15])
ax.set_ylim([-5,15])
plt.show()

# In the figure below, we plot a dataset which is not linearly separable. If we draw a straight line, most of the points will be not be classified in the correct class.
#
# One way to tackle this problem is to take the dataset and transform the data in another feature map. It means, you will use a function to transform the data in another plan, which should be linearable.

x = np.array([1,1,2,3,3,6,6,6,9,9,10,11,12,13,16,18])
y = np.array([18,13,9,6,15,11,6,3,5,2,10,5,6,1,3,1])
label = np.array([1,1,1,1,0,0,0,1,0,1,0,0,0,1,0,1])

fig = plt.figure()
plt.scatter(x, y, c=label, s=60)
plt.show()

### illustration purpose
def mapping(x, y):
	x = np.c_[(x, y)]
	if len(x) >	2:
		x_1 = x[:,0]**2
		x_2 = np.sqrt(2)*x[:,0]*x[:,1]
		x_3 = x[:,1]**2
	else:
		x_1 = x[0]**2
		x_2 = np.sqrt(2)*x[0]*x[1]
		x_3 = x[1]**2
	trans_x = np.array([x_1, x_2, x_3])
	return trans_x

x_1  = mapping(x, y)
x_1.shape

# Let's make a new plot with 3 axis, x, y and z respectively.

# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x_1[0], x_1[1], x_1[2], c=label, s=60)
ax.view_init(30, 185)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()

# We see an improvement but if we change the orientation of the plot, it is clear that the dataset is now separable

# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x_1[0], x_1[1], x_1[1], c=label, s=60)
ax.view_init(0, -180)
ax.set_ylim([150,-50])
ax.set_zlim([-10000,10000])
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.show()

"""What is a Kernel in machine learning?

The idea is to use a higher-dimension feature space to make the data almost linearly separable as shown in the figure above.

There are plenty of higher dimensional spaces to make the data points separable. For instance, we have shown that the polynomial mapping is a great start.

We have also demonstrated that with lots of data, these transformation is not efficient. Instead, you can use a kernel function to modify the data without changing to a new feature plan. """

"""Let's see an example to understand the concept of Kernel. You have two vectors, x1 and x2. The objective is to create a higher dimension by using a polynomial mapping. The output is equal to the dot product of the new feature map. From the method above, you need to:

    1.Transform x1 and x2 into a new dimension
    2.Compute the dot product: common to all kernels
    3.Transform x1 and x2 into a new dimension

You can use the function created above to compute the higher dimension.
"""
## Kernel
x1 = np.array([3,6])
x2 = np.array([10,10])

x_1 = mapping(x1, x2)
print(x_1)
## Compute the dot product

# You can use the object dot from numpy to compute the dot product between the first and second vector stored in x_1.

print(np.dot(x_1[:,0], x_1[:,1]))

# Instead, you can use the polynomial kernel to compute the dot product without transforming the vector. This function computes the dot product of x1 and x2 as if these two vectors have been transformed into the higher dimension. Said differently, a kernel function computes the results of the dot product from another feature space.

# You can write the polynomial kernel function in Python as follow.

def polynomial_kernel(x, y, p=2):
	return (np.dot(x, y)) ** p

polynomial_kernel(x1, x2, p=2)

# Type of Kernel Methods

# There are lots of different kernels available. The simplest is the linear kernel. This function works pretty well for text classification. The other kernel is:

    # Polynomial kernel
    # Gaussian Kernel


from IMLearn.learners import UnivariateGaussian, MultivariateGaussian
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
pio.templates.default = "simple_white"
MU_UNI = 10
SIGMA_UNI = 1
SAMPLE_SIZE = 1000

def test_univariate_gaussian():
    # Question 1 - Draw samples and print fitted model
    X = np.random.normal(MU_UNI, SIGMA_UNI, SAMPLE_SIZE)
    data = UnivariateGaussian()
    data.fit(X)
    print((data.mu_, data.var_))

    # Question 2 - Empirically showing sample mean is consistent
    slicing_samples = np.arange(10, 1010, 10)
    calculate_y_values = np.vectorize(lambda slice: np.abs(data.fit(X[:slice]).mu_-MU_UNI))
    y_values = calculate_y_values(slicing_samples)

    fig1 = go.Figure(
        data=[go.Bar(x=slicing_samples, y=y_values)])

    fig1.update_layout(title="distance between the estimated and true value of the expectation,"
                             "as a function of the sample size",
                      xaxis_title ="sample size",
                      yaxis_title ="distance between the estimated and true value of the expectation")
    fig1.show()

    # Question 3 - Plotting Empirical PDF of fitted model
    pdf_values = data.pdf(X)
    fig2 = go.Figure(data=go.Scatter(x=X, y=pdf_values, mode='markers'))

    fig2.update_layout(title="PDF",
                       xaxis_title="sample value",
                       yaxis_title="PDF value")

    fig2.show()

def test_multivariate_gaussian():
    # Question 4 - Draw samples and print fitted model
    mu = np.array([0, 0, 4, 0])
    sigma = np.array([[1, 0.2, 0, 0.5], [0.2, 2, 0, 0], [0, 0, 1, 0], [0.5, 0, 0, 1]])
    data = MultivariateGaussian()
    X = np.random.multivariate_normal(mu, sigma, SAMPLE_SIZE)
    data.fit(X)
    print(data.mu_)
    print(data.cov_)

    # Question 5 - Likelihood evaluation
    lin_range = np.linspace(-10, 10, 200)
    likelihood_values = []
    likelihood_args_dict = {}
    for f1 in lin_range:
        res = []
        for f3 in lin_range:
            log_likelihood = data.log_likelihood(np.array([f1, 0, f3, 0]), sigma, X)
            res.append((data.log_likelihood(np.array([f1, 0, f3, 0]), sigma, X)))
            likelihood_args_dict[log_likelihood] = (f1, f3)
        likelihood_values.append(res)

    fig3 = go.Figure(
        data=[go.Heatmap(x=lin_range, y=lin_range, z=likelihood_values)])

    fig3.update_layout(title="Likelihood evaluation as a function of f1,f3 values when mu = [f1,0,f3,0]",
                       xaxis_title="f3 values",
                       yaxis_title="f1 values")
    fig3.show()

    # Question 6 - Maximum likelihood
    print(np.round(likelihood_args_dict[np.max(likelihood_values)], 3))

if __name__ == '__main__':
    np.random.seed(0)
    test_univariate_gaussian()
    test_multivariate_gaussian()


from scipy import signal

from nnlib.layers import *

def rel_error(x, y):
    """ returns relative error """
    return np.max(np.abs(x - y) / (np.maximum(1e-8, np.abs(x) + np.abs(y))))

x = [1, 2, 3, 5, 1, -1, 2, 3, 5, 8, 3, 9, 1, 2, 5, 1]
x = np.array(x)
print("input x shape: ", x.shape)
filters = [4, 5, 3, 4]
filters = np.array(filters)
b = np.array([0])

stride = 1

mode = "full"
if mode == "valid":
    padding = 0
elif mode == "full":
    padding = len(filters) - 1

scipy_correlate = signal.correlate(x, filters, mode=mode)
print("correlate scipy:", scipy_correlate)
print("correlate scipy shape:", scipy_correlate.shape)

scipy_fft = signal.correlate(x, filters, mode=mode, method="fft")
print("fft scipy:", scipy_fft)
print("fft scipy shape:", scipy_fft.shape)

np_correlate = np.correlate(x, filters, mode=mode)
print("correlate numpy:", np_correlate)
print("correlate numpy shape:", np_correlate.shape)

print("x: ", x)
print("filters: ", filters)
xfft = np.fft.fft(x)
filterfft = np.conj(np.fft.fft(filters, len(xfft)))
# element-wise multiplication in the frequency domain
out = xfft * filterfft
# take the inverse of the output from the frequency domain and return the modules of the complex numbers
out = np.fft.ifft(out)
output = np.real(out)[:xfft.shape[-1]]
out_W = np.int(((len(x) + 2 * padding - len(filters)) / stride) + 1)
# output = np.absolute(out)
output = output[:out_W]

print("output of cross-correlation via fft: ", output)
print("output of cross-correlation via fft shape: ", output.shape)

x = x.reshape(1, 1, -1)
filters = filters.reshape(1, 1, -1)

print("size of padding: ", padding)
conv_param = {'stride': stride, 'pad': padding}
outnaive, _ = conv_forward_naive_1D(x, filters, b, conv_param)
print("out naive conv: ", outnaive)
print("out naive conv shape: ", outnaive.shape)

print("x: ", x)
print("filters: ", filters)

outfft, _ = conv_forward_fft_1D(x, filters, b, conv_param)
print("outfff: ", outfft)
print("outfff shape: ", outfft.shape)

print("is the fft cross_correlation for convolution correct with respect to naive: ",
      np.allclose(outfft, outnaive, atol=1e-12))
print("relative error fft naive: ", rel_error(outfft, outnaive))
print("absolute error fft: ", np.sum(np.abs(outfft - outnaive)))
print("absolute error scipy: ", np.sum(np.abs(scipy_correlate - outnaive)))
print("absolute error numpy: ", np.sum(np.abs(np_correlate - outnaive)))
print("absolute error scipy fft: ", np.sum(np.abs(scipy_fft - outnaive)))


import numpy as np
import pylab as pl
import time

def plot_exact_solution():
    # Advection velocity:
    a=1

    x=np.linspace(-1,1,1000)
    pl.clf()
    pl.hold(False)
    for t in np.linspace(0,1,100):
        pl.plot(x,eta(x-a*t))
        pl.draw()
        time.sleep(0.0001)

def eta(x,IC='gaussian'):
    # Initial condition:
    x=np.remainder(x,1.)
    if IC=='gaussian':
        return np.exp(-100*(x-0.5)**2)
    elif IC=='square':
        return 1.0*(x>0.05)*(x<0.25)

def FD_solution(method='Lax-Friedrichs',a=1.,cflnum=0.9,m=50,IC='gaussian'):
    method=method.lower()

    x=np.linspace(0,1,m+1);
    u=eta(x,IC)

    h=x[1]-x[0]
    k=cflnum*h/a

    if method=='leapfrog':
        # Take one step with Euler:
        uold=u.copy()
        u=eta(x-a*k,IC); u[0]=eta(1.-a*k,IC)
        u[1:-1]=u[1:-1]-0.5*cflnum*(u[2:]-u[:-2])
        u[-1]=u[-1]-0.5*cflnum*(u[0]-u[-2])
        u[0] =u[0] -0.5*cflnum*(u[1]-u[-1])
        unew=np.zeros(np.size(u))

    pl.clf()
    pl.hold(False)
    T=2.
    N=int(round(T/k))

    for n in xrange(N+1):
        t=n*k
        pl.plot(x,u,'-o',x,eta(x-t*a,IC),'-k')
        pl.ylim([-0.1,1.])
        pl.legend(['computed','exact'],loc=2)
        pl.draw()
        time.sleep(0.0001)
        if method=='centered':
            u[1:-1]=u[1:-1]-0.5*cflnum*(u[2:]-u[:-2])
            u[0] = u[0] -0.5*cflnum*(u[1]-u[-1])
            u[-1]= u[-1]-0.5*cflnum*(u[0]-u[-2])
        elif method=='leapfrog':
            unew[1:-1]=uold[1:-1]-cflnum*(u[2:]-u[:-2])
            unew[0]=uold[0]-cflnum*(u[1]-u[-1])
            unew[-1]=uold[-1]-cflnum*(u[0]-u[-2])
            uold=u.copy()
            u=unew.copy()
        elif method=='lax-friedrichs':
            u[1:-1]=0.5*(u[2:]+u[:-2])-0.5*cflnum*(u[2:]-u[:-2])
            u[0] =  0.5*(u[1] +u[-1]) -0.5*cflnum*(u[1]-u[-1])
            u[-1]=  0.5*(u[0] +u[-2]) -0.5*cflnum*(u[0]-u[-2])
        elif method=='lax-wendroff':
            u[1:-1]=u[1:-1]-0.5*cflnum*(u[2:]-u[:-2])+0.5*cflnum**2 * (u[2:]-2*u[1:-1]+u[:-2])
            u[0]=   u[0]   -0.5*cflnum*(u[1]-u[-1])+0.5*cflnum**2 * (u[1]-2*u[0]   +u[-1])
            u[-1]=  u[-1]  -0.5*cflnum*(u[0]-u[-2])+0.5*cflnum**2 * (u[0]-2*u[-1]   +u[-2])
        elif method=='beam-warming':
            u[2:]=u[2:]-0.5*cflnum*(3*u[2:]-4*u[1:-1]+u[:-2])+0.5*cflnum**2 * (u[2:]-2*u[1:-1]+u[:-2])
            #u[0]=u[0]-0.5*cflnum*(3*u[0]-4*u[-1]+u[-2])+0.5*cflnum**2 * (u[0]-2*u[-1]+u[:-2])
        elif method=='upwind':
            u[1:]=u[1:]-cflnum*(u[1:]-u[:-1])
            u[0]= u[0] -cflnum*(u[0] -u[-1])
        else:
            raise Exception('unrecognized method')

    t=(N+1)*k
    #pl.plot(x,u)
    pl.plot(x,u,'-o',x,eta(x-t*a,IC),'-k')
    pl.ylim([-0.1,1.])
    pl.legend(['computed','exact'],loc=2)
    pl.draw()


from typing import Union

from scipy.stats import binom, rv_discrete

from probability.custom_types.external_custom_types import FloatArray1d
from probability.distributions.mixins.attributes import NIntDMixin, PFloatDMixin
from probability.distributions.mixins.calculable_mixin import CalculableMixin
from probability.distributions.mixins.rv_discrete_1d_mixin import \
    RVDiscrete1dMixin
from probability.utils import num_format, is_scalar

class Binomial(
    RVDiscrete1dMixin,
    NIntDMixin,
    PFloatDMixin,
    CalculableMixin,
    object
):
    """
    The binomial distribution with parameters n and p is the discrete
    probability distribution of the number of successes in a sequence of n
    independent experiments, each asking a yes–no question, and each with its
    own boolean-valued outcome: success/yes/true/one (with probability p) or
    failure/no/false/zero (with probability q = 1 − p)

    https://en.wikipedia.org/wiki/Binomial_distribution
    """
    def __init__(self, n: int, p: float):
        """
        Create a new binomial distribution.

        :param n: Number of trials.
        :param p: Probability of success in an individual trial.
        """
        self._n: int = n
        self._p: float = p
        self._reset_distribution()

    def _reset_distribution(self):

        self._distribution: rv_discrete = binom(self._n, self._p)

    def mode(self) -> int:

        return int(self._n * self._p)

    @property
    def lower_bound(self) -> int:
        return 0

    @property
    def upper_bound(self) -> int:
        return self._n

    @staticmethod
    def fit(data: FloatArray1d, **kwargs) -> 'Binomial':
        """
        Fit a Binomial distribution to the data from a single experiment using
        the maximum likelihood estimate for p.

        https://en.wikipedia.org/wiki/Binomial_distribution
        #Estimation_of_parameters

        :param data: Iterable of data of to fit to. Each value should be 0 or 1.
        """
        n = len(data)
        k = sum(data)
        return Binomial(n=n, p=k / n)

    @staticmethod
    def fits(data: FloatArray1d, n: int) -> 'Binomial':
        """
        Fit a Binomial distribution to the distribution of results of a series
        of experiments using the maximum likelihood estimate for p.

        :param data: Iterable of data to fit to.
        :param n: Number of trials.
        """
        N = n * len(data)
        k = sum(data)
        return Binomial(n=n, p=k / N)

    def __str__(self):

        return (
            f'Binomial('
            f'n={num_format(self._n, 3)}, '
            f'p={num_format(self._p, 3)})'
        )

    def __repr__(self):

        return f'Binomial(n={self._n}, p={self._p})'

    def __eq__(
            self, other: Union['Binomial', int, float]
    ) -> Union[bool, float]:

        if is_scalar(other):
            return self.pmf().at(other)
        else:
            return (
                self._n == other._n and
                abs(self._p - other._p) < 1e-10
            )

    def __ne__(
            self, other: Union['Binomial', int, float]
    ) -> Union[bool, float]:

        if is_scalar(other):
            return 1 - self.pmf().at(other)
        else:
            return not self.__eq__(other)


#!/usr/bin/python3
import scipy.fftpack
import numpy as np
import matplotlib.pyplot as plt

# Number of samplepoints -- FFT size
N = 512
# sampling frequency : fs
# maximum frequency that can be measured "fs/2" Nyquist's theorem
fs= 1000 #Hz
# sampling time period = 1/fs
T = 1/fs
# length of the signal in time
L = N*T
# Vector with sampling time-stamps
t = np.linspace(0.0,L,N)
# input signal
# test signal -- replace this --
y = 0.5 * np.sin(150 * 2.0 * np.pi * t) + np.sin(400 * 2.0 * np.pi * t)

fig, (datain,dataout) = plt.subplots(nrows=1,ncols=2,sharex=False,sharey=False)

yf = scipy.fftpack.fft(y)

# yfabs[0] gives the DC component of the input signal
# If the FFT is input is just real numbers (true for all real world apps.)
#   second half of fft-out is complex conjugate of first half of the fft-out
#<VVI> Why are we scaling the output ??
yfabs = np.abs(yf[0:int(N/2)]) * 2/N
fd = np.array(list(range(0,int(N/2)))).astype(float) * fs/N
print(y)
print(yfabs)

datain.plot(t,y)
dataout.plot(fd,yfabs)
#yinv = scipy.fftpack.ifft(yf)
#dataout.plot(t,yinv)

#xf = np.linspace(0.0, 1.0/(2.0*T), N/2)

#fig, ax = plt.subplots()
#ax.plot(xf[1:], 2.0/N * np.abs(yf[1:N/2]))
plt.show()


"""
Created on Mon Feb  1 10:08:31 2016
"""
#------------------------------------------------------------------------------
#CHAPTER 6:  The Finite-Element Method
#------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt

#%% CODE 07: Listing 6.1 1D static elasticity - Pag 140
#------------------------------------------------------------------------------
# FINITE ELEMENTS SOLUTION
#------------------------------------------------------------------------------
# [...]
# Basic parameters
nx = 20             # Number of boundary points
u = np.zeros(nx)    # Solution vector
f = np.zeros(nx)    # Source vector
mu = 1              # Constant shear modulus
# Element boundary points
x = np.linspace(0, 1, nx) # x in [0,1]
h = x[2] - x[1] # Constant element size
# Assemble stiffness matrix K_ij
K = np.zeros((nx, nx))
for i in range(1, nx-1):
    for j in range(1, nx-1):
        if i == j:
            K[i, j] = 2*mu/h
        elif i == j + 1:
            K[i, j] = -mu/h
        elif i + 1 == j:
            K[i, j] = -mu/h
        else:
            K[i, j] = 0
# Souce term is a spike at i = 15
f[15] = 1
# Boundary condition at x = 0
u[0] = 0.15 ; f[1] = u[0]/h
# Boundary condition at x = 1
u[nx-1] = 0.05 ; f[nx-2] = u[nx-1]/h
# finite element solution
u[1:nx-1] = np.linalg.inv(K[1:nx-1, 1:nx-1]) @ f[1:nx-1].T
# [...]

# Plotting
plt.plot(x,u, color='r', lw=3.)
plt.xlabel('x')
plt.ylabel('u(x)')
plt.axis([0, 1, 0.04, .28])

#%% CODE 08: Listing 6.2 1D static elasticity with finite differences - Pag 141
#------------------------------------------------------------------------------
# FINITE DIFFERENCES SOLUTION
#------------------------------------------------------------------------------
# Poisson's equation with relaxation method
# non-zero boundary conditions
u  = np.zeros(nx) # set u to zero
du = np.zeros(nx) # du/dx
f  = np.zeros(nx) # forcing

nt = 500
isnap = 25

# [...]
# Forcing
f[15] = 1/h  # force vector
for it in range(nt):
    # Calculate the average of u (omit boundaries)
    for i in range(1, nx-1):
        du[i] =u [i+1] + u[i-1]
    u = 0.5*( f*h**2/mu + du )
    u[0] = 0.15    # Boundary condition at x=0
    u[nx-1] = 0.05 # Boundary condition at x=1
# [...]

    # visualization
    fd = u
    xfd = np.arange(0, nx)*h
    if np.mod(it+1, isnap) == 0:
        plt.plot(xfd, fd, color='k', ls='-.')

plt.savefig('Fig_6.7.png')
plt.show()


from typing import Tuple

import numpy
from scipy.special import expit
from sklearn.linear_model import LogisticRegression

from assignment_2_3_4.oracles import AbstractOracle
from assignment_2_3_4.oracles.abstract_oracle import Matrix

class LogisticRegressionOracle(AbstractOracle):
    _eps = numpy.finfo(numpy.float64).eps
    name: str = "logistic regression"

    def __init__(self, x: Matrix, y: numpy.ndarray):
        super().__init__(x, y)
        self._x_t = self._x.T

    def get_true_minimum(self, tol: float, max_iter: float) -> float:
        sklearn_logit_reg = LogisticRegression(
            tol=tol, max_iter=max_iter, random_state=7, fit_intercept=False, penalty="none"
        )
        sklearn_logit_reg.fit(self._x, self._y)
        minimum_point = sklearn_logit_reg.coef_[0]
        return self.value(minimum_point)

    # ========== Logistic Regression Calculations ==========

    def _get_loss(self, logit: numpy.ndarray) -> numpy.float:
        return -numpy.mean(numpy.log(numpy.where(self._y != 0, logit + self._eps, 1 - logit + self._eps)))

    def _get_grad(self, logit: numpy.ndarray) -> numpy.ndarray:
        return (logit - self._y) @ self._x / self._n_samples

    def _get_hessian(self, logit: numpy.ndarray) -> numpy.ndarray:
        logit = (logit * (1 - logit)).reshape(-1, 1) / self._n_samples
        if isinstance(self._x_t, numpy.ndarray):
            return self._x_t @ (self._x * logit) / self._n_samples
        else:
            return (self._x_t @ (self._x.multiply(logit))).toarray()

    def _get_hessian_dot_vector(self, logit: numpy.ndarray, d: numpy.ndarray) -> numpy.ndarray:
        logit = logit * (1 - logit) / self._n_samples
        return ((self._x @ d) * logit) @ self._x

    # ========== Oracle Interface ==========

    def value(self, weights: numpy.ndarray) -> numpy.float:
        self._call_counter += 1
        return self._get_loss(expit(self._x.dot(weights)))

    def grad(self, weights: numpy.ndarray) -> numpy.ndarray:
        self._call_counter += 1
        return self._get_grad(expit(self._x.dot(weights)))

    def hessian(self, weights: numpy.ndarray) -> numpy.ndarray:
        self._call_counter += 1
        return self._get_hessian(expit(self._x.dot(weights)))

    def hessian_vec_product(self, weights: numpy.ndarray, d: numpy.ndarray) -> numpy.ndarray:
        self._call_counter += 1
        return self._get_hessian_dot_vector(expit(self._x.dot(weights)), d)

    def fuse_value_grad(self, weights: numpy.ndarray) -> Tuple[numpy.float, numpy.ndarray]:
        self._call_counter += 1
        logit = expit(self._x.dot(weights))
        return self._get_loss(logit), self._get_grad(logit)

    def fuse_value_grad_hessian(self, weights: numpy.ndarray) -> Tuple[numpy.float, numpy.ndarray, numpy.ndarray]:
        self._call_counter += 1
        logit = expit(self._x.dot(weights))
        return self._get_loss(logit), self._get_grad(logit), self._get_hessian(logit)

    def fuse_value_grad_hessian_vec_product(
        self, weights: numpy.ndarray, d: numpy.ndarray
    ) -> Tuple[numpy.float, numpy.ndarray, numpy.ndarray]:
        self._call_counter += 1
        logit = expit(self._x.dot(weights))
        return (
            self._get_loss(logit),
            self._get_grad(logit),
            self._get_hessian_dot_vector(logit, d),
        )