#
# Copyright 2018 Manish Kushwaha
#  Contact manish.k79@gmail.com for usage other than personal use.
#
# MyGeometry.py
#
# Python Geometry Utility Functions, including,
#  getDistanceFromLine
#  pointOfReflection
#  points2path
#  intersectLines

import math
import numpy as np

def getDistanceFromLine(a,b,p):
    """ returns distance of a point(p) from a Line(a,b)

        returns dist between point (p) from a Line (a,b).
        returns NaN if points a,b are co-incident.
    """

    dx = np.linalg.norm(b-a)
    if dx<1e-6:  return np.nan

    n = (b-a)/dx
    dist = np.linalg.norm((a-p)-(np.dot(a-p,n))*n)
    return dist

def pointOfReflection(m1,m2,t,p):
    """ returns point of reflection from point t --> p, reflecting
        on surface/Line (m1,m2)

        returns a tuple: (xi, yi, valid, r), where
        (xi, yi) is the point of reflection
        r is the scalar multiple such that (xi,yi) = m1 + r*(m2-m1)
            valid == 0 if there is no reflection (invalid)
            valid == 1 if it has a unique reflection ON the segment  """
    d1 = getDistanceFromLine(m1,m2,t)
    d2 = getDistanceFromLine(m1,m2,p)
    if (d2==0 or d1==0):
        return (0.,0.,0,0.)
    alp = (d1/d2)
    beta = alp**2 - 1
    r = (alp**2)*p - t
    delta = (alp**2)*np.dot(p,p) - np.dot(t,t)

    c = np.array([0,0,0],dtype=float)
    c[0] = beta*(np.dot(m1,m1) + np.dot(m2,m2) - 2*np.dot(m1,m2))
    c[1] = 2*beta*np.dot(m1,m2-m1) - 2*np.dot(r,m2-m1)
    c[2] = beta*np.dot(m1,m1) - 2*np.dot(m1,r) + delta

    t_sol = np.roots(c)

    mu = (m2-m1)/np.linalg.norm(m2-m1)
    nu = (0.55)*(m1+m2)
    nu = (m1-nu)-(np.dot(m1-nu,mu))*mu
    nu = nu/np.linalg.norm(nu)

    valid = 0
    q_ret = np.array([0.,0.])
    x_ret = 0.

    for x_sol in t_sol:
        if (x_sol.dtype==np.complex):
            if (x_sol.imag/x_sol.real)<100.0:
                x_sol = x_sol.real
            else:
                continue
        q = (1-x_sol)*m1 + x_sol*m2
        qtu = (q - t)/np.linalg.norm(q-t)
        pqu = (p - q)/np.linalg.norm(p-q)
        v1 = np.dot(qtu,mu)
        v2 = np.dot(pqu,mu)
        w1 = np.dot(qtu,nu)
        w2 = np.dot(pqu,nu)

        if ( (abs(v1)<1e-5 or abs(v2)<1e-5 or (abs(v1-v2)/abs(v1) < 1)) and
             (abs(w1)<1e-5 or abs(w2)<1e-5 or (abs(w1+w2)/abs(w1) < 1)) ):
            x_ret = x_sol
            q_ret = q
            valid = 1

    return (q_ret[0],q_ret[1],valid,x_ret)

def points2path(wxy, speed, verb=1):
    """ returns a path from a numpy array of waypoints, and speed

        returns a numpy array of points on the interpolated waypoint path """

    t1 = -1
    pathxy = np.array([])
    for w1,w2 in zip(wxy[:-1], wxy[1:]):
        dist = (sum((w2-w1)**2))**(1/2)
        interval = math.floor(dist/speed)
        t2 = t1 + 1 + interval
        #print(w1,w2,dist,interval,t1+1,t2+0.1)
        dt = np.arange(t1+1,t2+0.1)
        dt = (dt - t1 - 1) / (t2 - t1 - 1)
        pxy_ = np.zeros((len(dt),2))
        for i,t in zip(range(len(dt)),dt):
            pxy_[i,:] = (1-t)*w1 + t*w2

        t1 = t2
        print("Append to path array of size " + str(len(pathxy)) + ", new path of size " + str(len(pxy_)))
        if (pathxy.size==0):
            pathxy = pxy_
        else:
            pathxy = np.append(pathxy, pxy_, axis=0)

    print("\nNumber of points of generated path: " + str(len(pathxy)))
    return pathxy

def intersectLines( a, b, p, q ):
    """ returns the intersection of Line(a,b) and Line(p,q)

        returns a tuple: (xi, yi, valid, r, s), where
        (xi, yi) is the intersection
        r is the scalar multiple such that (xi,yi) = a + r*(b-a)
        s is the scalar multiple such that (xi,yi) = p + s*(q-p)
            valid == 0 if there are 0 or inf. intersections (invalid)
            valid == 1 if it has a unique intersection ON the segment    """

    DET_TOLERANCE = 0.00000001

    # the first line is a + r*(b-a)
    # in component form:
    x1, y1 = a;   x2, y2 = b
    dx1 = x2 - x1;  dy1 = y2 - y1

    # the second line is x + s*(y-x)
    x, y = p;   xB, yB = q;
    dx = xB - x;  dy = yB - y;

    # we need to find the (typically unique) values of r and s
    # that will satisfy
    #
    # (x1, y1) + r(dx1, dy1) = (x, y) + s(dx, dy)
    #
    # which is the same as
    #
    #    [ dx1  -dx ][ r ] = [ x-x1 ]
    #    [ dy1  -dy ][ s ] = [ y-y1 ]
    #
    # whose solution is
    #
    #    [ r ] = _1_  [  -dy   dx ] [ x-x1 ]
    #    [ s ] = DET  [ -dy1  dx1 ] [ y-y1 ]
    #
    # where DET = (-dx1 * dy + dy1 * dx)
    #
    # if DET is too small, they're parallel
    #
    DET = (-dx1 * dy + dy1 * dx)

    if math.fabs(DET) < DET_TOLERANCE: return (0,0,0,0,0)

    # now, the determinant should be OK
    DETinv = 1.0/DET

    # find the scalar amount along the "self" segment
    r = DETinv * (-dy  * (x-x1) +  dx * (y-y1))

    # find the scalar amount along the input line
    s = DETinv * (-dy1 * (x-x1) + dx1 * (y-y1))

    # return the average of the two descriptions
    xi = (x1 + r*dx1 + x + s*dx)/2.0
    yi = (y1 + r*dy1 + y + s*dy)/2.0
    return ( xi, yi, 1, r, s )

def rayIntersectWObstacle(t,p,vobs,verb=1):
    """ this function checks if direct line-of-sight (LOS) ray from source (t)
        to destination (p) has clear path in a field of obstructions (xyobs).

        returns a tuple (rayClear, ob, ip) where
         rayClear True if LOS exists, False otherwise
         ob is obstruction that intersects the LOS path
         ip is the point of intersection  """
    rayClear = True
    ip = np.array([0.,0.])
    ob = np.array([])
    for ob in vobs:
        (_,_, valid, r, s) = intersectLines(ob[:2],ob[2:],t,p)
        if ( valid and r>=0 and r<=1 and s>=0 and s<=1):
            ip = (1-r)*ob[:2] + r*ob[2:]
            rayClear=False
            # todo: what is there are more than one
            #  obstacles in the direct path
            break
    return (rayClear,ob,ip)

def findAllPathBetween(t,p,xyobs,verb=1):
    """ this function finds all paths, i.e., direct line-of-sight (LOS) ray, and
        all indirect non-line-of-sight (NLOS) rays, from a source (t) to a destination (p)
        in a field of obstructions (xyobs).
        The NLOS is calculated using only a single-reflection model, i.e., any complex
        ray with reflections off of multiple obstacles that may be present are not considered.

        returns a tuple (los,nlosxy) where
         los True if LOS exists, False otherwise
         nlosxy is numpy array of size (N,2) where N is the number of obstruction causing NLOS """

    c = (p+t)/2
    r = np.linalg.norm(p-t)

    if verb>3:
        print("Center {!s}, Radius {!s}".format(c,r))
        fig, ax = plt.subplots()
        ax.scatter(p[0],p[1],s=40,color='red')
        ax.scatter(t[0],t[1],s=40,color='green')

    # find all obstacles in the visible range
    vobs = np.array([])
    for ob in xyobs:
        if ( (np.linalg.norm(ob[:2]-c) < r) or (np.linalg.norm(ob[2:]-c) < r) ):
            if vobs.size==0:
                vobs = ob
            else:
                vobs = np.vstack((vobs,ob))
            if verb>3:
                l1 = lines.Line2D([ob[0],ob[2]],[ob[1],ob[3]],color='black',axes=ax)
                ax.add_line(l1)

    if vobs.size>0 and len(vobs.shape)==1:
        v2 = vobs.copy()
        v2.resize((1,4))
        vobs = v2

    if verb>2:
        print("Number of visible Obstables {!s}, Visible objects {!s}".format(vobs.shape[0],vobs))

    # check if LOS is clear
    los,ob,ip = rayIntersectWObstacle(t,p,vobs,verb)
    if verb>3:
        l1 = lines.Line2D([t[0],ip[0]],[t[1],ip[1]],lw=1,ls='-',color='blue',axes=ax)
        ax.add_line(l1)
        l2 = lines.Line2D([p[0],ip[0]],[p[1],ip[1]],lw=1,ls='dashed',color='blue',axes=ax)
        ax.add_line(l2)
    if verb>2:
        if los==True:
            print(" * LOS present...")
        else:
            print(" * LOS NOT present, intersects with {!s} at {!s}".format(ob,ip))

    # Check for NLOS paths
    nlosxy = np.array([])
    for i,ob in enumerate(vobs):
        (xi,yi, valid, r) = pointOfReflection(ob[:2],ob[2:],t,p)
        if ( valid and r>=0 and r<=1 ):
            vidx = list(set(range(vobs.shape[0]))-set([i]))
            rayClear,ob1,ip1 = rayIntersectWObstacle(t,np.array([xi,yi]),vobs[vidx,:],verb)
            if rayClear==False:
                if verb>3:
                    l1 = lines.Line2D([t[0],ip1[0]],[t[1],ip1[1]],lw=1,ls='-',color='blue',axes=ax)
                    ax.add_line(l1)
                    l2 = lines.Line2D([xi,ip1[0]],[yi,ip1[1]],lw=1,ls='dashed',color='blue',axes=ax)
                    ax.add_line(l2)
                continue
                #print(" * NLOS NOT present, intersects with " + str(ob) + " at " + str(ip))
            if verb>3:
                l2 = lines.Line2D([t[0], xi], [t[1], yi], lw=1, ls='dashed', color='red', axes=ax)
                ax.add_line(l2)

            rayClear,ob1,ip1 = rayIntersectWObstacle(np.array([xi,yi]),p,vobs[vidx,:],verb)
            if rayClear==False:
                if verb>3:
                    l1 = lines.Line2D([xi,ip1[0]],[yi,ip1[1]],lw=1,ls='-',color='blue',axes=ax)
                    ax.add_line(l1)
                    l2 = lines.Line2D([p[0],ip1[0]],[p[1],ip1[1]],lw=1,ls='dashed',color='blue',axes=ax)
                    ax.add_line(l2)
                continue
                #print(" * NLOS NOT present, intersects with " + str(ob) + " at " + str(ip))
            if verb>3:
                l2 = lines.Line2D([p[0], xi], [p[1], yi], lw=1, ls='dashed', color='green', axes=ax)
                ax.add_line(l2)

            if nlosxy.size==0:
                nlosxy = np.array([xi,yi])
            else:
                nlosxy = np.vstack((nlosxy,np.array([xi,yi])))
            if verb>2:
                print(" * NLOS present with reflection from {!s} at {!s}".format(ob,[xi,yi]))

    if verb>3:
        ax.grid()
        ax.set_xlim(0,1000)
        ax.set_ylim(0,1000)
        plt.show()

    if nlosxy.size>0 and len(nlosxy.shape)==1:
        v2 = nlosxy.copy()
        v2.resize((1,2))
        nlosxy = v2

    return (los,nlosxy)


###########################################################################################
#
#  hernquist.py
#     Compute analytic Hernquist models
#
# 29 Oct 2017: First construction
# 11 Jul 2021: Much more usable (read:correct) now!
#
# Generalization is (BT 2.64)
#
#                           rho0
#   rho(r) = -------------------------------
#            (r/a)^alpha (1+r/a)^(beta-alpha)
#
# where alpha=1, beta=4 for the Hernquist model
#
# (beta=4 is the dehnen family of models. dehnen models with alpha in [0.6,2] reasonably describe elliptical galaxy centers)
# (alpha=2, beta=4 is Jaffe model)
# (alpha=1, beta=3 is NFW model)
#
# Enclosed mass is then
#
#                                               s^{2-\alpha}
#    M(r) = 4\pi \rho_0 a^3 \int_0^{r/a} ds --------------------
#                                            (1+s)^{\beta-alpha}
#

'''

hernquist (part of exptool.models)
    Implementation of the analytic Hernquist models

#H = Hernquist(np.linspace(0.001,1.,300))

# how does one construct a R D M P table if they want to from this?

'''
from __future__ import absolute_import, division, print_function, unicode_literals

# general python imports
import numpy as np
import math
import time
import sys
import os

class Hernquist():
    """The classic Hernquist model.
    Hernquist has a gravitational radii of 6rscl.
    """
    def __init__(self,rscl=1.0,G=1,M=1):
        self.rscl = rscl
        self.G    = G
        self.M    = M
        self.rho0 = self.get_rho0()
    def get_rho0(self):
        """solving BT08, eq. 2.66 at r==1000a"""
        rs = 1000.
        return self.M*(2*(1+rs)*(1+rs))/(rs*rs)/(4*np.pi*np.power(self.rscl,3))
    def get_mass(self,r):
        """BT08, eq. 2.66"""
        rs = r/self.rscl
        return 4*np.pi*self.rho0*np.power(self.rscl,3)*(rs*rs)/(2*(1+rs)*(1+rs))
    def get_dens(self,r):
        """BT08, eq. 2.64, with alpha=1, beta=4"""
        alpha = 1
        beta  = 4
        return self.rho0 * (r/self.rscl)**(-alpha) * (1. + r/self.rscl)**(-beta+alpha)
    def get_pot(self,r):
        """BT08, eq. 2.67"""
        return -4*np.pi*self.G*self.rho0 *self.rscl*self.rscl * ( (2*(1.+r/self.rscl))**-1.)


import numpy as np
import matplotlib.pyplot as plt

# To initialize the internal grid points with zero and boundary condition
def initialize_matrix(N):
	grid_points = np.zeros((N+1, N+1))
	# grid poinnts go from 0 to N
	for i in range(N+1):
		for j in range(N+1):
			x = i/N
			y = j/N

			if i == 0 or j == 0 or i == N or j == N:
				# Uncomment the below line and comment the enxt line for 2nd boundary conndition
				# grid_points[i][j] = np.exp(x-y)
				grid_points[i][j] = x**2 - y**2

	return grid_points

# SOR relaxation scheme
# iterates until error = 2*machine_epsilon or num_iterations <= max_num_iterations
def solve_SOR(grid_points, w, max_num_iterations = 10000):

	error_values = []
	num_iteration_values = []

	error_allowed = 2 * np.finfo(np.float64).eps
	error = 100
	N = np.shape(grid_points)[0] - 1
	new_grid_points = np.array(grid_points)
	old_grid_points = np.array(grid_points)
	num_iteration = 0
	# while error >= error_allowed and num_iteration < max_num_iterations:
	while error>=error_allowed and num_iteration < max_num_iterations:
		for i in range(N+1):
			for j in range(N+1):
				if i == 0 or j == 0 or i == N or j == N:
					continue
				else:
					GS_next_iterate = 0.25*(new_grid_points[i-1][j] + old_grid_points[i+1][j] + new_grid_points[i][j-1] + old_grid_points[i][j+1])
					new_grid_points[i][j] = w*GS_next_iterate + (1-w)*old_grid_points[i][j] #linear combination of new iterate (as per Gauss Seidel) and old grid value.

		error_matrix = (old_grid_points - new_grid_points)**2
		error = (np.sum(error_matrix)**0.5)/N
		old_grid_points = np.array(new_grid_points)
		# print("w:",w, "num_iteration:", num_iteration, "error:", error)
		error_values.append(np.log(error))
		num_iteration_values.append(num_iteration)
		num_iteration += 1
	return error_values

if __name__ == '__main__':
	w_range = np.linspace(0,2,21)
	max_num_iteration_values = [20,40,50,60,100]
	grid_points1 = initialize_matrix(50)

	for max_num_iteration in max_num_iteration_values:
		error_value1 = []
		for w in w_range[1:-1]:
			error_range = solve_SOR(grid_points1, w, max_num_iteration)
			error_value1.append(error_range[-1])

		label = "Number of iterations = " + str(max_num_iteration)
		plt.plot(w_range[1:-1], error_value1, label = label)

	plt.legend()
	plt.title("Error vs w for different grid size (number of iterations = 20)")
	plt.xlabel("w")
	plt.ylabel("Error after iteration")

	plt.show()


from collections import Callable
from typing import List
import numpy as np
from numpy import ndarray as vec, mat

def rk4(A: mat, x: vec, h: float) -> vec:
    """
    This is an implementation of Runge-Kutta 4th order method
    A: square matrix encapsulating the dynamics dx/dt = A * x. A is assumed to be constant during the step.
    x: state variable at time t
    return x at t+h
    """
    k1 = A * x
    k2 = A * (x + h * k1 / 2)
    k3 = A * (x + h * k2 / 2)
    k4 = A * (x + h * k3)
    return x + h/6 * (k1 + 2*k2 + 2*k3 + k4)

def adams_moulton(As: List[mat], xs: List[vec], h: float) -> vec:
    """
    Adams-Moulton is a linear multistep method which uses the information from the previous steps
    https://en.wikipedia.org/wiki/Linear_multistep_method#Adams%E2%80%93Moulton_methods
    """
    a = np.eye(len(xs[0])) - h * 251/720 * As[4]
    b = xs[3] + h * (646 * As[3] * xs[3]
                 - 264 * As[2] * xs[2]
                 + 106 * As[1] * xs[1]
                 - 19 * As[0] * xs[0]) / 720
    return np.linalg.solve(a, b)


import numpy as np
from src.test_functions.test_function import Function

class Michalewicz(Function):
    """
    https://www.sfu.ca/~ssurjano/michal.html
    """
    def __init__(self, m=10):
        self.m = m

    def minimal_value(self, nb_dimensions):
        """
        https://www.sfu.ca/~ssurjano/michal.html
        different with each dimensionality, not easy to determine
        :param nb_dimensions:
        """
        if nb_dimensions==2:
            return -1.8013
        else:
            return None

    def optimal_solution(self, nb_dimensions):
        """
        https://www.sfu.ca/~ssurjano/michal.html
        different with each dimensionality, not easy to determine
        """
        if nb_dimensions==2:
            return np.asarray([[2.20, 1.57]])
        else:
            return None

    def fitness_function_implementation(self, x, axis=1):
        x = np.asarray(x)
        dim = x.shape[axis]
        i = np.arange(1, dim+1, 1)
        sin_2m = np.power(np.sin(i * np.square(x)/np.pi), 2*self.m)
        return - (np.sin(x) * sin_2m).sum(axis=axis)


import numpy as np

class CrossEntropyLoss:
    def forward_pass(self, predicted, labels):
        predicted = np.clip(predicted, 1e-10, 1 - 1e-10)  # probabilities for all classes
        predicted_probabilities = predicted[range(len(predicted)), labels]  # probabilities of classes equal to labels
        log_likelihoods = -np.log(predicted_probabilities)  # cross entropy loss
        data_loss = np.mean(log_likelihoods)  # mean of loss values
        self.batch_losses_sum += np.sum(log_likelihoods)  # batch loss values
        self.batch_losses += len(log_likelihoods)  # number of samples in batch
        return data_loss

    def backward_pass(self, grad_inputs, labels):
        self.grad_output = grad_inputs.copy()  # make safe copy
        self.grad_output[range(len(grad_inputs)), labels] -= 1  # Calculating gradient
        layer_output = self.grad_output / len(grad_inputs)  # Normalizing
        return layer_output

    def epoch_loss(self):
        loss = self.batch_losses_sum / self.batch_losses  # calculate epoch loss
        return loss

    def new_epoch(self):
        self.batch_losses_sum = 0  # reset batch loss variables
        self.batch_losses = 0


import numpy as np

class ClosedFormRegression(object):
    def __init__(self, order, lambda_var):
        self.order = order
        self.lambda_var = lambda_var

    def fit(self, X_train, y_train):
        H = self.calculate_H(X_train)
        # l2 regularization
        G = np.eye(H.shape[1])
        # ignore theta[0]
        G[0, 0] = 0
        self.theta = np.linalg.inv(H.T.dot(H) + self.lambda_var * G).dot(H.T).dot(y_train)

    def mse(self, X, y):
        y_pred = self.predict(X)
        return np.sum((y - y_pred) ** 2) / (2 * y_pred.shape[0])

    def calculate_H(self, X):
        H = np.c_[np.ones((X.shape[0], 1)), X]
        for i in range(2, self.order + 1):
            H = np.c_[H, np.power(H[:, 1], i)]
        return H

    def predict(self, X_unseen):
        H_unseen = self.calculate_H(X_unseen)
        return H_unseen.dot(self.theta)

    def description(self):
        return "ord=%d, lambda=%d" % (self.order, self.lambda_var)


'''
Lattice paths
'''

import numpy as np

map_len = 20

f = np.zeros(shape=(map_len + 1, map_len + 1), dtype=np.float)

f[:, 0] = 1
f[0, :] = 1

for i in range(1, map_len + 1):
    for j in range(1, map_len + 1):
        f[i][j] += f[i - 1][j] + f[i][j - 1]
'''
for i in range(map_len + 1):
    str_row = ''
    for j in range(map_len + 1):
        str_row += ' ' + str(f[i][j])
    print(str_row)
'''
print(f[map_len][map_len])


import numpy as np

def cross_entropy_error(y, t):
    delta = 1e-7  # to avoid log(0)
    return - np.sum(t * np.log(y + delta))

if __name__ == '__main__':
    t = np.array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0])

    y1 = np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y2 = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
    y3 = np.array([0.1, 0.9, 0.3, 0.3, 0, 0, 0.2, 0, 0, 0.6])

    print('t = {0}'.format(t))
    print('y1 = {0}, cross_entropy_error = {1}'.format(
        y1, cross_entropy_error(y1, t)))
    print('y2 = {0}, cross_entropy_error = {1}'.format(
        y2, cross_entropy_error(y2, t)))
    print('y3 = {0}, cross_entropy_error = {1}'.format(
        y3, cross_entropy_error(y3, t)))
    print('t = {0}, cross_entropy_error = {1}'.format(
        t, cross_entropy_error(t, t)))


from __future__ import absolute_import
from __future__ import print_function
from __future__ import division

import pandas as pd
import numpy as np

from sklearn.metrics import mean_absolute_error, mean_squared_error

def rmse(y_true, y_pred):
    """
    Calculates RMSE
    :param y_true: actual values
    :param y_pred: predicted values
    :return: RMSE
    """
    return np.sqrt(mean_squared_error(y_true, y_pred))

def mse(y_true, y_pred):
    """
    Calculates MSE
    :param y_true: actual values
    :param y_pred: predicted values
    :return: MSE
    """
    return mean_squared_error(y_true, y_pred)

def mae(y_true, y_pred):
    """
    Calculates MAE
    :param y_true: actual values
    :param y_pred: predicted values
    :return: MAE
    """
    return mean_absolute_error(y_true, y_pred)

def mape(y_true, y_pred):
    """
    Calculates MAPE
    :param y_true: actual values
    :param y_pred: predicted values
    :return: MAPE
    """
    y_true = np.reshape(y_true, (-1,))
    y_pred = np.reshape(y_pred, (-1,))
    return 100*np.mean(np.abs((y_true - y_pred)/y_true))

def smape(y_true, y_pred):
    """
    Calculates sMAPE
    :param y_true: actual values
    :param y_pred: predicted values
    :return: sMAPE
    """
    y_true = np.reshape(y_true, (-1,))
    y_pred = np.reshape(y_pred, (-1,))
    return np.mean(2.0 * np.abs(y_true - y_pred) / (np.abs(y_true) + np.abs(y_pred))).item()

def sign(y_true, y_pred):
    raise(NotImplementedError)

def tic(y_true, y_pred):
    """
    Calculates TIC metric
    :param y_true: actual values
    :param y_pred: predicted values
    :return: TIC
    """
    raise(NotImplementedError)

def mase(insample, y_true, y_pred, freq):
    """
    Calculates MAsE
    :param insample: insample data
    :param y_test: out of sample target values
    :param y_hat_test: predicted values
    :param freq: data frequency
    :return: MAsE
    """
    y_hat_naive = []
    for i in range(freq, len(insample)):
        y_hat_naive.append(insample[(i - freq)])

    masep = np.mean(abs(insample[freq:] - y_hat_naive))

    return np.mean(abs(y_true - y_pred)) / masep

def mutual_info():
    raise(NotImplementedError)

def maek(y_true, y_pred, stds, kr):
    def check_threshold(a, kr):
        vf= a<kr
        return vf.astype(int)
    y_true = np.reshape(y_true, (-1,))
    y_pred = np.reshape(y_pred, (-1,))
    stds = np.array(stds)
    print('stds: {}'.format(stds))
    dif = y_true - y_pred
    thres = check_threshold(stds,kr)
    print('thres: {}'.format(thres))
    return np.sum(np.abs(dif)*thres)/np.sum(thres)


import numpy as np
import numpy.linalg as LA

class LeastSquaresL2Reg:
    def __init__(self, A, y, lmbda=0.1):
        self.A = A
        self.y = y
        self.n, self.d = A.shape
        self.lmbda = lmbda
        self.strong_cvx = lmbda
        self.singular_values = LA.svd(A, compute_uv=False)

    def _loss(self, a, y, x):
        return (1 / (2*self.n)) * ((a@x - y)**2 + self.lmbda * LA.norm(x)**2)

    def loss(self, A, y, x, return_list=False):
        if return_list:
            return [self._loss(a, yi, x) for a, yi in zip(A, y)]
        return (1 / (2*self.n)) * (LA.norm(A@x - y)**2 + self.lmbda * LA.norm(x)**2)

    def grad(self, A, y, x):
        nb_eg = A.shape[0] if len(A.shape) > 1 else 1
        shape = (nb_eg, -1) if nb_eg > 1 else (1, -1)
        A = A.reshape(*shape)
        r = A@x - y
        return A.T@r/self.n + self.lmbda * x * (nb_eg / self.n)

    def predict(self, A, x):
        return A @ x

    @property
    def lipschitz(self):
        return self.lmbda + max(self.singular_values)**2 / self.n

    @property
    def PL_modulus(self):
        return self.strong_cvx / (2 * self.lipschitz)

class LogisticLoss:
    def __init__(self, A, y):
        self.A = A
        self.y = y
        self.n, self.d = A.shape
        assert set(np.unique(y)) == {-1, 1}

    def _loss(self, a, y, x):
        return np.log(1 + np.exp(-y * np.inner(a, x)))

    def loss(self, A, y, x):
        loss = (1/self.n) * np.array([self._loss(a, yi, x)
                                      for a, yi in zip(A, y)])
        return loss

    def grad(self, A, y, x):
        yAx = y * (A @ x)
        C = -y * np.exp(-yAx) / (1 + np.exp(-yAx))
        # could use numpy broadcasting here; (C * A.T).sum(axis=?)
        return sum(c * a for a, c in zip(A, C))

    def predict(self, A, x):
        return np.sign(A @ x)


import numpy as np

def softmax(x):
    '''
    http://cs231n.github.io/linear-classify/#softmax
    https://stackoverflow.com/questions/34968722/how-to-implement-the-softmax-function-in-python

    f = np.array([123, 456, 789]) # example with 3 classes and each having large scores
    p = np.exp(f) / np.sum(np.exp(f)) # Bad: Numeric problem, potential blowup

    # instead: first shift the values of f so that the highest number is 0:
    f -= np.max(f) # f becomes [-666, -333, 0]
    p = np.exp(f) / np.sum(np.exp(f)) # safe to do, gives the correct answer

    '''
    e = np.exp(x - np.amax(x, axis=1, keepdims=True))
    return e / np.sum(e, axis=1, keepdims=True)


#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

G = 6.67e-11
M = 5.97e24
R = 6371000

class SatAlt:
    def __init__(self):

        # arrays for variables to draw graph
        self.time_values = []
        self.altt_values = []

        # input period time to get sat altitude
        self.period = float(input("Enter Period Time of Satellite: "))
        self.print_results("Satellite", self.period, self.get_altitude(self.period))

        # geosynchronous sats
        tt = 86400
        self.print_results("Geosynchronous Satellites", tt, self.get_altitude(tt))

        # geo-sat using sidereal day
        tt = 23.98 * 3600
        self.print_results("Geosynchronous Satellites Using Sidereal Day", tt, self.get_altitude(tt))

        # 90 minute sats
        tt = 90 * 60
        self.print_results("90 Minute Period Satellites", tt, self.get_altitude(tt))

        # 45 minute sats
        tt = 45 * 60
        self.print_results("45 Minute Period Satellites", tt, self.get_altitude(tt))

        # graph window
        win = plt.figure()

        # sort the plotting values from the lowest to the highest
        self.time_values.sort()
        self.altt_values.sort()

        # draw the plot
        graph = plt.plot(self.time_values, self.altt_values, "ro-", color="black")
        plt.title("Satellite Period Time vs Satellite Altitude")
        plt.xlabel("Period Time (s)")
        plt.ylabel("Altitude (m)")
        plt.grid()
        plt.show()

    def get_altitude(self, t):
        coeff = (G*M) / (4*np.pi**2)
        coeff = pow(coeff, 1/3)

        h = ( coeff * pow(t**2, 1/3) ) - R

        return h

    def print_results(self, category, time, altitude):
        print("{}:".format(category))
        print("\tPeriod Time\t: {:,} [s]\t:= {:,.3f} [hrs]".format( time, time/3600 ))
        print("\tAltitude\t: {:,.3f} [m]".format(altitude)) if (altitude > 0) else print("\t[-] Period Time too short to maintain a satellite above the Earth's surface")
        print("\n\r")

        # fill in array
        self.time_values.append(time)
        self.altt_values.append(altitude)

SatAlt()


"""
Problem Statement

The NumPy module also comes with a number of built-in routines for linear
algebra calculations. These can be found in the sub-module linalg.

linalg.det

The linalg.det tool computes the determinant of an array.

print numpy.linalg.det([[1 , 2], [2, 1]])       #Output : -3.0
linalg.eig

The linalg.eig computes the eigenvalues and right eigenvectors of a square
array.

vals, vecs = numpy.linalg.eig([[1 , 2], [2, 1]])
print vals                                      #Output : [ 3. -1.]
print vecs                                      #Output : [[ 0.70710678
-0.70710678]
                                                #          [ 0.70710678
                                                0.70710678]]
linalg.inv

The linalg.inv tool computes the (multiplicative) inverse of a matrix.

print numpy.linalg.inv([[1 , 2], [2, 1]])       #Output : [[-0.33333333
0.66666667]
                                                #          [ 0.66666667
                                                -0.33333333]]
Other routines can be found here

Task

You are given a square matrix A with dimensions NXN. Your task is to find
the determinant.

Input Format

The first line contains the integer N.
The next N lines contains the N space separated elements of array A.

Output Format

Print the determinant of A.

Sample Input

2
1.1 1.1
1.1 1.1
Sample Output

0.0
"""
import numpy

n = int(raw_input())
a = numpy.array([map(float, raw_input().split()) for i in range(n)],
                dtype=numpy.float)
print numpy.linalg.det(a)


# -*- coding: utf-8 -*-
"""
A simple mathematical description of the spread of a flu in a company is the
so-called the flu model, which divides the (fixed) population of N individuals
into three "compartments" which may vary as a function of time, t:

V(t) are those vulnerable but not yet infected with the flu;

S(t) is the number of sick individuals;

RI(t) are those individuals who either are immune or have recovered from the
flu and now have immunity to it.

The model describes the change in the population of each of these compartments
in terms of two parameters, β and γ.

β describes the effective contact rate of the flu: an infected individual comes
into contact with βN other individuals per unit time (of which the fraction
that are susceptible to contracting the flu is V/N).

γ is the mean recovery rate: that is, 1/γ is the mean period of time during
which a sick individual can pass it on.

The differential equations describing this model were first derived by Kermack
and McKendrick [Proc. R. Soc. A, 115, 772 (1927)]:

dV / dt = -βVS / N

dS / dt = βVS / N - γS

dRI / dt = γS

@author: Andres Peñuela
"""
from scipy.integrate import odeint
import numpy as np
from numba import njit # the function jit allows to compile the code and reduced

class population:

    def __init__(self, N = 100, I_0 = 10, S_0 = 1):
        # Total population, N.
        self.N = N
        # Initial number of sick individuals
        self.S_0 = S_0
        # Initial number of immune individuals
        self.I_0 = I_0
        # Everyone else, S_0, is susceptible to infection initially.
        self.V_0 = N - S_0 - I_0

def simulation(t,population, contact, contagion, recovery, vaccination):

    y_0 = population.S_0, population.I_0, population.V_0

    def deriv(y, t, N, contact, contagion, recovery, vaccination):
        S, RI, V = y
        dVdt = -contact*contagion * V * S / N - np.min([vaccination, vaccination*V])
        dSdt = contact*contagion * V * S / N - recovery * S #- sigma * S
        dRIdt = recovery * S + np.min([vaccination, vaccination*V])

        return dSdt, dRIdt, dVdt

    ret = odeint(deriv, y_0, t, args=(population.N, contact, contagion, recovery, vaccination))
    S, RI, V = ret.T

    return S, RI, V

@njit(parallel = False) # Numba decorator to speed-up the function below
def model(param,t,N):
    RI_0 = param[0]
    S_0 = 1
    contact = param[1]
    contagion = param[2]
    recovery = 1/param[3]
    vaccination = param[4]

    V_0 = N - S_0 - RI_0

    T = len(t)
    S  = np.zeros(T)
    RI = np.zeros(T)
    V  = np.zeros(T)
    vaccination_num = np.zeros(T)

    S[0] = S_0
    RI[0] =  RI_0
    V[0] = V_0

    for i in np.arange(T-1):
        V[i+1] = np.array([V[i] - contact*contagion * S[i] * V[i] / N - vaccination_num[i],0]).max()
        RI[i+1] = RI[i] + recovery * S[i] + vaccination_num[i]
        S[i+1] = np.array([S[i] + contact*contagion * S[i] * V[i] / N - recovery * S[i],N]).min()
        vaccination_num[i+1] = np.array([V[i+1], vaccination]).min()

    max_value = np.array(np.max(S))
    vaccine_price = 7 # £ per vaccine
    vaccination_cost =  np.sum(vaccination_num) * vaccine_price# + RI_0 * vaccine_price
    social_dist_price = 200 # Price of implementing social distancing and isolation measures
    social_dist_cost = (2 - contact) * social_dist_price
    masks_price = 50 # Price of the distribution of face masks
    masks_cost = (1 - contagion) * masks_price
    treatment_price = 0 # we set it to zero to reflect that it is not an action but an unknown variable
    treatment_cost = (21 - 1/recovery) * treatment_price
    total_cost = np.array(vaccination_cost + \
                          social_dist_cost + masks_cost + \
                          treatment_cost)

    return S, RI, V,max_value,total_cost

@njit(parallel = False) # Numba decorator to speed-up the function below
def function(param,t,N,output):
    RI_0 = param[0]
    S_0 = 1
    contact = param[1]
    contagion = param[2]
    recovery = 1/param[3]
    vaccination = param[4]

    V_0 = N - S_0 - RI_0

    T = len(t)
    S  = np.zeros(T)
    RI = np.zeros(T)
    V  = np.zeros(T)
    vaccination_num = np.zeros(T)

    S[0] = S_0
    RI[0] =  RI_0
    V[0] = V_0

    for i in np.arange(T-1):
        V[i+1] = np.array([V[i] - contact*contagion * S[i] * V[i] / N - vaccination_num[i],0]).max()
        vaccination_num[i+1] = np.array([V[i+1], vaccination]).min()
        RI[i+1] = RI[i] + recovery * S[i] + vaccination_num[i+1]
        S[i+1] = np.array([S[i] + contact*contagion * S[i] * V[i] / N - recovery * S[i],N]).min()

    if output == 0: # max number of sick individuals in a day
        out = np.array(np.max(S))
    elif output == 1: # total cost of the measures
        vaccine_price = 7 # £ per vaccine. Cost before the outbreak
        vaccination_cost = np.sum(vaccination_num) * vaccine_price# + RI_0 * vaccine_price
        social_dist_price = 200 # Price of implementing social distancing and isolation measures
        social_dist_cost = (2 - contact) * social_dist_price
        masks_price = 50 # Price of the distribution of face masks
        masks_cost = (1 - contagion) * masks_price
        treatment_price = 0 # we set it to zero to reflect that it is not an action but an unknown variable
        treatment_cost = (21 - 1/recovery) * treatment_price
        out = np.array(vaccination_cost + \
                       social_dist_cost + masks_cost + \
                       treatment_cost)

#    elif output == 2:
#        out = np.array(RI[-1])
#    elif output == 3:
#        out = np.array(V[-1])

#    out = np.array(np.max(S))

    return out


import numpy as np
import pandas as pd

__all__ = ['volParkinson', 'volVanilla']

def difs ( close ):
    return np.log(close).diff()

def volParkinson ( highs, lows ):
    """Estimates the historical volatility for series using the Parkinson method.

	Arguments:
		highs:
			numpy array or pandas series of daily high values
		lows:
			numpy array or pandas series of daily low values

	Returns:
		float, Parkinson volatility estimate

	Example:

	.. code-block:: python

	   # Use the daily high's and low's
	   v = rraider.estimate.volParkinson( highs = df['High'], lows  = df['Low'] )

	   v
	   # 0.42135

    """

    xt = np.log ( highs / lows )
    xt = xt**2 / (4 * np.log(2))

    res = np.mean(xt) ** 0.5
    return res

def volVanilla ( close, ewm:bool = False, halflife:float = 60 ):
    """Estimates the historical volatility for series using the standard 'close' method.

	Arguments:
		close:
			numpy array or pandas series of daily close values
		ewm:
			boolean, whether to use exponential-moving avererage, or to use flat variance
		halflife:
			float, rate parameter for ewm, if applicable

	Returns:
		float, Historical volatility estimate

	Example:

	.. code-block:: python

	   v = rraider.estimate.volVanilla( close = df['Close'], ewm = True, halflife = 25 )

	   v
	   # 0.389
    """
    x = pd.Series( difs(close), index=close.index)
    if ewm:
        x = x.ewm(halflife)
    j = x.std()
    return j.iloc[-1]


import numpy as np
import math
from scipy.special import comb,perm
import matplotlib.pyplot as plt

class CheckMaximumL(object):
    def __init__(self):
        self.p = 0
        self.l = 0

    def set(self, p, l):
        if (l > self.l):
            self.p = p
            self.l = l

    def get(self):
        return self.p, self.l

def strf(prefix, f):
    return str.format("{0}={1:2f}", prefix, f)

def BernoulliDist():
    L = []
    N = 10
    for x in range(N+1):
        l = comb(N,x) * math.pow(0.6,x) * math.pow(0.4,N-x)
        L.append(l)
        plt.text(x,l,np.around(l, 3))
    print(np.sum(L))

    plt.plot(L, '-o')
    plt.xlabel("x")
    plt.ylabel("probability")
    plt.title("Bernoulli Distribution")
    plt.grid()
    plt.show()

def Likelihood_bernoulli(N,X,title="Bernoulli Likelihood"):
    checker = CheckMaximumL()
    L = []
    P = np.linspace(0,1,num=101)
    for p in P:
        l = math.pow(p,X) * math.pow(1-p,N-X)
        L.append(l)
        checker.set(p, l)
    #endfor
    plt.plot(P, L)
    x,y = checker.get()
    plt.plot(x,y,'x')
    plt.text(x,y,strf(r'$\theta$', x))
    plt.xlabel(r'$\theta$')
    plt.ylabel("likelihood")
    plt.title(title)
    plt.grid()
    plt.show()

def likelihood_exp(X, style):
    checker = CheckMaximumL()
    L = []
    P = np.linspace(0.01,3,num=100)
    n = X.shape[0]
    mean = np.mean(X)
    print(mean)
    for p in P:
        l = np.exp(-np.sum(X)/p) / np.power(p,n)
        l = l * np.power(X[0],3)    # 不是公式的一部分，是为了让两条线的高度差不多
        L.append(l)
        checker.set(p,l)
    ll, = plt.plot(P, L, linestyle=style)
    x,y = checker.get()
    plt.plot(x,y,'x')
    plt.text(x,y,strf(r'$\theta$', mean))
    plt.xlabel(r'$\theta$')
    plt.ylabel("likelihood")
    return ll

def exp_fun(p):
    n = 10
    x = np.linspace(0,1,num=n)
    X = 1 / p * np.exp(-1 * x / p)
    return X

def likelihood_exps():
    lines = []
    labels = []
    styles = ['-', '--','-.',':']
    p = [0.5, 0.3]
    for i in range(2):
        X = exp_fun(p[i])
        print(X)
        print(np.mean(X))
        ll = likelihood_exp(X, styles[i])
    plt.title("Exponential Distribution")
    plt.grid()
    plt.show()

def likelihood_norm_mu(style):
    checker = CheckMaximumL()
    L = []
    P = np.linspace(0,1,num=101)
    n=10
    X = np.random.normal(0.5,0.5,n)
    mu_max = np.mean(X)
    std = np.std(X)
    for mu in P:
        sum = np.sum(np.square(X - mu))
        l = np.exp(-0.5 * sum / np.square(std)) * np.power(np.sqrt(2*np.pi*std), n)
        L.append(l)
        checker.set(mu, l)
    ll, = plt.plot(P, L, linestyle=style)
    x,y = checker.get()
    plt.plot(x,y,'x')
    plt.text(x,y,strf(r'$\mu$', mu_max))
    plt.xlabel(r'$\mu$')
    plt.ylabel("likelihood")
    return ll

def likelihood_norm_sigma(style):
    checker = CheckMaximumL()
    L = []
    P = np.linspace(0.01,1,num=100)
    n=10
    X = np.random.normal(0.5,0.5,n)
    mu = np.mean(X)
    std_max = np.sum(np.square(X-mu))/n
    for sigma2 in P:
        l = np.exp(-0.5 * np.sum(np.square(X-mu)) / sigma2)  / np.power(np.sqrt(sigma2*2*np.pi),n)
        L.append(l)
        checker.set(sigma2, l)
    ll, = plt.plot(P, L, linestyle=style)
    x,y = checker.get()
    plt.plot(x,y,'x')
    plt.text(x,y,strf(r'$\sigma^2$', std_max))
    plt.xlabel(r'$\sigma^2$')
    plt.ylabel("likelihood")
    #plt.show()
    return ll

def likelihood_norms():
    styles = ['-', '--','-.',':']
    for i in range(2):
        likelihood_norm_mu(styles[i])
    plt.title("Normal Distribution")
    plt.grid()
    plt.show()

    styles = ['-', '--','-.',':']
    for i in range(2):
        likelihood_norm_sigma(styles[i])
    plt.title("Normal Distribution")
    plt.grid()
    plt.show()

def BetaDist(a, b, style):
    c = math.factorial(a+b-1)/(math.factorial(a-1)*math.factorial(b-1))
    F = []
    X = np.linspace(0,1,num=101)
    for x in X:
        f = c * math.pow(x, a-1) * math.pow(1-x, b-1)
        F.append(f)
    #endfor
    l = str.format(r"$\alpha={0}, \beta={1}$", a, b)
    #print(l)
    plt.plot(X,F,label=l,linestyle=style)

def likelihood_Beta(alpha, beta, a, b):
    checker = CheckMaximumL()
    c = math.factorial(alpha+beta-1)/(math.factorial(alpha-1)*math.factorial(beta-1))
    F = []
    X = np.linspace(0,1,num=101)
    for x in X:
        f = c * math.pow(x, a) * math.pow(1-x, b)
        F.append(f)
        checker.set(x, f)
    #endfor
    x,y = checker.get()
    plt.plot(x,y,'x')
    plt.text(x,y,strf(r'$\theta$', x))
    plt.plot(X,F)
    plt.grid()
    plt.title("Beta Distribution")
    plt.show()

def BetaDists():
    BetaDist(3,3,'-')
    BetaDist(5,1,'-.')
    BetaDist(2,2,'--')
    BetaDist(1,3,':')
    plt.grid()
    plt.ylim(ymax=2.5)
    plt.xlabel("x")
    plt.ylabel("PDF")
    plt.title("Beta Distribution")
    plt.legend()
    plt.show()

if __name__ == "__main__":

    BernoulliDist()
    Likelihood_bernoulli(N=10,X=7)
    likelihood_exps()
    likelihood_norms()

    BetaDists()

    likelihood_Beta(3, 3, 9, 5)


import numpy as np
from itertools import product
from tfnumpy.tensor import rank3tensor_decomposition_ALS

def decompose_tensor_jennrich(rank3tensor):
    # initialize two random variables
    a = np.random.uniform(size=rank3tensor.shape[2])
    b = np.random.uniform(size=rank3tensor.shape[2])

    # T_a and T_b
    Ta = sum(rank3tensor[:, :, i ] *a[i] for i in range(rank3tensor.shape[2]))
    Tb = sum(rank3tensor[:, :, i ] *b[i] for i in range(rank3tensor.shape[2]))

    # eigenvalues of various auxilliary matrices
    eigvals_u, eigvecs_u = np.linalg.eig(np.matmul(Ta, np.linalg.pinv(Tb)))
    eigvals_v, eigvecs_v = np.linalg.eig(np.transpose(np.matmul(np.linalg.pinv(Ta), Tb)))

    # pair up reciprocal eigenvalues
    # pair up eigenvalues of Ta and Tb
    idx_pairs = []
    tol = 1e-5

    for i, eigval_u in enumerate(eigvals_u):
        for j, eigval_v in enumerate(eigvals_v):
            if abs(eigval_u - 1/ eigval_v) < tol:
                idx_pairs += [(i, j)]
                break

    # solving for third eigenvectors
    nbcomp = len(idx_pairs)
    solved = False
    while not solved:
        try:
            A = np.zeros((nbcomp * rank3tensor.shape[2], nbcomp * rank3tensor.shape[2]))
            B = np.zeros(nbcomp * rank3tensor.shape[2])
            eqidx = 0
            ij_combs = list(tuple(product(range(rank3tensor.shape[0]), range(rank3tensor.shape[1]))))
            for k in range(rank3tensor.shape[2]):
                for ij_comb_idx in np.random.choice(range(len(ij_combs)), size=nbcomp, replace=False):
                    i, j = ij_combs[ij_comb_idx]
                    B[eqidx] = rank3tensor[i, j, k]
                    for ck in range(nbcomp):
                        A[eqidx, ck * rank3tensor.shape[2] + k] = eigvecs_u[i, idx_pairs[ck][0]] * eigvecs_v[j, idx_pairs[ck][1]]
                    eqidx += 1

            sol = np.linalg.solve(A, B)
            solved = True   # exception is not caught at this point
        except np.linalg.LinAlgError:
            solved = False
    eigvecs_w = sol.reshape((nbcomp, rank3tensor.shape[2]), order='F')

    # rearranging eigenvectors
    rearranged_eigvecs_u = np.zeros(shape=eigvecs_u.shape)
    rearranged_eigvecs_v = np.zeros(shape=eigvecs_v.shape)
    for i, (u_idx, v_idx) in enumerate(idx_pairs):
        rearranged_eigvecs_u[:, i] = eigvecs_u[:, u_idx]
        rearranged_eigvecs_v[:, i] = eigvecs_v[:, v_idx]
    rearranged_eigvecs_w = eigvecs_w

    # return values
    return rearranged_eigvecs_u, rearranged_eigvecs_v, rearranged_eigvecs_w

def decompose_tensor_ALS(rank3tensor, k, *args, **kwargs):
    return rank3tensor_decomposition_ALS(rank3tensor, k, *args, **kwargs)


import matplotlib.pyplot as plt
import numpy as np

plt.figure(1)

plt.plot([-2, 0],[0, 0])
plt.plot([0, 0],[0, 1])
plt.plot([0, 2],[1, 1])
plt.xlabel("x")
plt.ylabel("heaviside(x)")

plt.figure(2)
x = np.linspace(-2, 2, 100)
y = np.tanh(x)
plt.plot(x, y)
plt.plot([-2, 2],[0, 0])
plt.plot([0, 0],[-1, 1])
plt.xlabel("x")
plt.ylabel("tanh(x)")

plt.figure(3)
x = np.linspace(-5, 5, 100)
y = np.tanh(x)
y2 = 1.0/(1.0 + np.exp(-x))
plt.plot(x, y2)
plt.plot([-10, 10],[0, 0])
plt.plot([0, 0],[-1, 1])
plt.xlabel("x")
plt.ylabel("sigmoid(x)")

plt.figure(4)
x = np.linspace(-5, 5, 100)
y = np.tanh(x)
y2 = 1.0/(1.0 + np.exp(-x))
y3 = y2 * (1 - y2)
plt.plot(x, y2, label=r'$\sigma(x)$')
plt.plot(x, y3, label=r'$\sigma(x)/dx$')
plt.plot([-5, 5],[0, 0])
plt.plot([0, 0],[-1, 1])
plt.xlabel("x")
plt.ylabel("$\sigma$(x)")
plt.legend()
plt.show()


import sympy as sp
import numpy as np
import pickle

class SymbolicRateMatrixArrhenius(sp.Matrix):
    """
    Symbolic representation of Arrhenius process rate matrix.
    """
    class Symbols:
        @classmethod
        def _barrier_element_symbol(cls, i, j):
            if i == j:
                return 0
            return sp.symbols('B_%d%d' % (i + 1, j + 1), real=True)

        def __init__(self, N):
            self.E_i = sp.symbols('E_1:%d' % (N + 1), real=True)
            self.B_ij = sp.Matrix(N, N, self._barrier_element_symbol)
            self.T = sp.symbols('T', real=True)

    @classmethod
    def _create_elements(cls, N):
        symbols = cls.Symbols(N)

        def create_symbolic_rate_matrix_element(i, j):
            if i == j:
                return 0
            return sp.exp(- (symbols.B_ij[i, j] - symbols.E_i[j]) / symbols.T)

        rate_matrix_symbolic = sp.Matrix(N, N, create_symbolic_rate_matrix_element)

        # Set each diagonal element as minus the sum of the other elements in its column (ensures Detailed Balance)
        rate_matrix_symbolic -= sp.diag(*np.sum(rate_matrix_symbolic, axis=0))

        return rate_matrix_symbolic, symbols

    def __new__(cls, N):
        """
        Parameters
        ----------
        N : int
            Number of states.
        """
        elements, symbols = cls._create_elements(N)
        self = super().__new__(cls, elements)
        self.symbols = symbols
        return self

    def subs_symbols(self, energies=None, barriers=None, temperature=None):
        """
        Return a new rate matrix with subs applied to each entry.

        Parameters
        ----------
        energies : 1-D array or sequence of float
            Energies of the states of the arrhenius, ordered in ascending order.
        barriers : 2-D array
            Matrix of energy barriers between states.
        temperature : float
            Temperature.

        Returns
        -------
        new : SymbolicRateMatrixArrhenius
            New instance of RateMatrixArrhenius with subs applied.
        """
        subs_dict = {}
        if energies is not None:
            subs_dict.update(zip(self.symbols.E_i, energies))
        if barriers is not None:
            subs_dict.update(zip(np.ravel(self.symbols.B_ij), np.ravel(barriers)))
            del subs_dict[0]
        if temperature is not None:
            subs_dict.update({self.symbols.T: temperature})
        expr = self.subs(subs_dict)
        if not expr.free_symbols:
            expr = np.array(expr).astype(np.float64)
        return expr

    def lambdify(self, symmetric_barriers=False):
        params = (self.symbols.T,) + self.symbols.E_i

        if symmetric_barriers:
            barriers_subs = dict(zip(np.ravel(np.triu(self.symbols.B_ij.T)),
                                     np.ravel(np.triu(self.symbols.B_ij))))

            barriers_free_symbols = set(barriers_subs.values())
            expr = self.subs(barriers_subs)
        else:
            barriers_free_symbols = set(self.symbols.B_ij.values())
            expr = self

        params += tuple(filter(lambda b: b in barriers_free_symbols, self.symbols.B_ij.values()))

        return sp.lambdify(params, expr)

class _SymbolicThreeStateEigensystem:
    FILE_NAME_EIGENSYSTEM = 'three_state_eigensystem_symbolic.pickle'

    @classmethod
    def _file_path(cls):
        import os
        __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
        return os.path.join(__location__, cls.FILE_NAME_EIGENSYSTEM)

    @classmethod
    def _save_eigensystem(cls):
        r_sym = SymbolicRateMatrixArrhenius(3)
        eigensystem_right = r_sym.eigenvects()
        eigensystem_left = r_sym.T.eigenvects()

        eigenvalues, _, V = zip(*eigensystem_right)
        _, _, U = zip(*eigensystem_left)

        # The returned eigenvalues (from sympy) is ordered as: lam1, lam3, lam2 (seen in numerical checks)
        u1, u3, u2 = [sp.Matrix(U[i][0]) for i in [0, 1, 2]]
        lam1, lam3, lam2 = eigenvalues
        v1, v3, v2 = [sp.Matrix(V[i][0]) for i in [0, 1, 2]]

        # Normalization of left eigenvectors by their sum of their components
        u1 = sp.simplify(u1 / (np.sum(u1) / 3.))
        u2 = u2 / (np.sum(u2) / 3.)
        u3 = u3 / (np.sum(u3) / 3.)

        # Normalization of right eigenvectors by the inner product with the left eigenvectors
        v1 = v1 / u1.dot(v1)
        v2 = v2 / u2.dot(v2)
        v3 = v3 / u3.dot(v3)

        es = (u1, u2, u3), (lam1, lam2, lam3), (v1, v2, v3)

        pickle.dump(es, open(cls._file_path(), 'wb'))

    @classmethod
    def load_eigensystem(cls):
        return pickle.load(open(cls._file_path(), 'rb'))

def symbolic_three_state_eigensystem():
    return _SymbolicThreeStateEigensystem.load_eigensystem()


import numpy as np

def l2_regularization(W, reg_strength):
    '''
    Computes L2 regularization loss on weights and its gradient

    Arguments:
      W, np array - weights
      reg_strength - float value

    Returns:
      loss, single value - l2 regularization loss
      gradient, np.array same shape as W - gradient of weight by l2 loss
    '''
    # TODO: Copy from previous assignment
    loss = reg_strength * np.sum(W ** 2)
    grad = 2 * reg_strength * W

    return (loss, grad)

def softmax(predictions):
    '''
    Computes probabilities from scores

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output

    Returns:
      probs, np array of the same shape as predictions -
        probability for every class, 0..1
    '''
    # TODO implement softmax
    # Your final implementation shouldn't have any loops
    probs = predictions.copy()

    if predictions.ndim == 1:
        probs -= np.max(probs)
        probs = np.exp(probs)
        probs = probs / np.sum(probs)
    else:
        probs -= np.max(probs,
                        axis=1).reshape((probs.shape[0], 1))

        probs = np.exp(probs)
        probs = probs / np.sum(probs, axis=1).reshape((probs.shape[0], 1))

    return probs

def cross_entropy_loss(probs, target_index):
    '''
    Computes cross-entropy loss

    Arguments:
      probs, np array, shape is either (N) or (batch_size, N) -
        probabilities for every class
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss: single value
    '''
    # TODO implement cross-entropy
    # Your final implementation shouldn't have any loops
    if type(target_index) is int:
        loss = -np.log(probs[target_index])
    else:
        loss = -np.sum(np.log(
            probs[np.arange(target_index.size), target_index]
        )) / target_index.size

    return loss

def softmax_with_cross_entropy(predictions, target_index):
    '''
    Computes softmax and cross-entropy loss for model predictions,
    including the gradient

    Arguments:
      predictions, np array, shape is either (N) or (batch_size, N) -
        classifier output
      target_index: np array of int, shape is (1) or (batch_size) -
        index of the true class for given sample(s)

    Returns:
      loss, single value - cross-entropy loss
      dprediction, np array same shape as predictions - gradient of predictions by loss value
    '''
    # TODO copy from the previous assignment
    dprediction = softmax(predictions)
    loss = cross_entropy_loss(dprediction, target_index)

    if type(target_index) is int:
        dprediction[target_index] -= 1
    else:
        dprediction[np.arange(target_index.size), target_index] -= 1
        dprediction /= target_index.size

    return (loss, dprediction)

class Param:
    '''
    Trainable parameter of the model
    Captures both parameter value and the gradient
    '''
    def __init__(self, value):
        self.value = value
        self.grad = np.zeros_like(value)

class ReLULayer:
    def __init__(self):
        pass

    def forward(self, X):
        # TODO copy from the previous assignment
        self.X = X
        return np.where(X > 0, X, 0)

    def backward(self, d_out):
        # TODO copy from the previous assignment
        d_result = d_out * np.where(self.X > 0, 1, 0)

        return d_result

    def params(self):
        return {}

class FullyConnectedLayer:
    def __init__(self, n_input, n_output):
        self.W = Param(0.001 * np.random.randn(n_input, n_output))
        self.B = Param(0.001 * np.random.randn(1, n_output))
        self.X = None

    def forward(self, X):
        # TODO copy from the previous assignment
        self.X = X
        result = np.dot(self.X, self.W.value) + self.B.value
        return result

    def backward(self, d_out):
        # TODO copy from the previous assignment
        d_input = np.dot(d_out, np.transpose(self.W.value))
        self.W.grad += np.dot(np.transpose(self.X), d_out)
        self.B.grad += np.dot(np.ones((1, d_out.shape[0])), d_out)

        return d_input

    def params(self):
        return {'W': self.W, 'B': self.B}

class ConvolutionalLayer:
    def __init__(self, in_channels, out_channels,
                 filter_size, padding):
        '''
        Initializes the layer

        Arguments:
        in_channels, int - number of input channels
        out_channels, int - number of output channels
        filter_size, int - size of the conv filter
        padding, int - number of 'pixels' to pad on each side
        '''

        self.filter_size = filter_size
        self.in_channels = in_channels
        self.out_channels = out_channels

        self.W = Param(
            np.random.randn(filter_size, filter_size,
                            in_channels, out_channels))
        self.B = Param(np.zeros(out_channels))

        self.padding = padding

    def forward(self, X):
        (batch_size, height, width, channels) = X.shape
        self.X = X
        self.X_pad = np.pad(X, ((0, 0),
                                (self.padding, self.padding),
                                (self.padding, self.padding),
                                (0, 0)),
                            'constant', constant_values=(0))

        # TODO: Implement forward pass
        # Hint: setup variables that hold the result
        # and one x/y location at a time in the loop below
        out_height = height - self.filter_size + 2 * self.padding + 1
        out_width = width - self.filter_size + 2 * self.padding + 1

        result = np.zeros((batch_size, out_height, out_width,
                           self.out_channels))

        # It's ok to use loops for going over width and height
        # but try to avoid having any other loops
        nsize = self.filter_size * self.filter_size * self.in_channels

        for y in range(out_height):
            for x in range(out_width):
                # TODO: Implement forward pass for specific location
                inp = self.X_pad[
                        :,
                        y:y + self.filter_size,
                        x:x + self.filter_size,
                        :].reshape((batch_size, nsize))

                w = self.W.value.reshape((nsize, self.out_channels))

                result[:, y, x, :] = np.dot(inp, w) + self.B.value

        return result

    def backward(self, d_out):
        # Hint: Forward pass was reduced to matrix multiply
        # You already know how to backprop through that
        # when you implemented FullyConnectedLayer
        # Just do it the same number of times and accumulate gradients
        (batch_size, height, width, channels) = self.X.shape
        (_, out_height, out_width, out_channels) = d_out.shape

        # TODO: Implement backward pass
        # Same as forward, setup variables of the right shape that
        # aggregate input gradient and fill them for every location
        # of the output
        d_input = np.zeros_like(self.X_pad)
        nsize = self.filter_size * self.filter_size * self.in_channels

        # Try to avoid having any other loops here too
        for y in range(out_height):
            for x in range(out_width):
                # TODO: Implement backward pass for specific location
                # Aggregate gradients for both the input and
                # the parameters (W and B)
                X_slice = self.X_pad[:,
                                     y:y + self.filter_size,
                                     x:x + self.filter_size,
                                     :,
                                     np.newaxis]

                grad = d_out[:, y, x, np.newaxis, np.newaxis, np.newaxis, :]

                self.W.grad += np.sum(grad * X_slice, axis=0)

                d_input[:, y:y + self.filter_size,
                        x:x + self.filter_size, :] += np.sum(
                            self.W.value * grad, axis=-1)

        self.B.grad += np.sum(d_out, axis=(0, 1, 2))

        d_input = d_input[:, self.padding:self.padding + height,
                          self.padding:self.padding + width, :]

        return d_input

    def params(self):
        return {'W': self.W, 'B': self.B}

class MaxPoolingLayer:
    def __init__(self, pool_size, stride):
        '''
        Initializes the max pool

        Arguments:
        pool_size, int - area to pool
        stride, int - step size between pooling windows
        '''
        self.pool_size = pool_size
        self.stride = stride
        self.X = None

    def forward(self, X):
        (batch_size, height, width, channels) = X.shape
        self.X = X.copy()

        # TODO: Implement maxpool forward pass
        # Hint: Similarly to Conv layer, loop on
        # output x/y dimension
        out_height = (height - self.pool_size) // self.stride + 1
        out_width = (width - self.pool_size) // self.stride + 1

        result = np.zeros((batch_size, out_height, out_width,
                           channels))

        for y in np.arange(0, out_height, self.stride):
            for x in np.arange(0, out_height, self.stride):
                result[:, y, x, :] = np.amax(
                    X[:, y:y+self.stride, x:x+self.stride, :],
                    axis=(1, 2))

        return result

    def backward(self, d_out):
        # TODO: Implement maxpool backward pass
        (batch_size, height, width, channels) = self.X.shape
        (_, out_height, out_width, _) = d_out.shape

        d_input = np.zeros_like(self.X)

        for y in np.arange(0, out_height, self.stride):
            for x in np.arange(0, out_height, self.stride):
                x_pool = self.X[
                    :,
                    y:y+self.stride,
                    x:x+self.stride,
                    :]
                mask = (x_pool == np.amax(x_pool, axis=(1, 2))[:,
                                                               np.newaxis,
                                                               np.newaxis,
                                                               :])

                d_input[:,
                        y:y+self.pool_size,
                        y:y+self.pool_size,
                        :] += mask * d_out[:, y, x, :][:, np.newaxis,
                                                       np.newaxis, :]

        return d_input

    def params(self):
        return {}

class Flattener:
    def __init__(self):
        self.X_shape = None

    def forward(self, X):
        (batch_size, height, width, channels) = X.shape
        self.X_shape = X.shape

        # TODO: Implement forward pass
        # Layer should return array with dimensions
        # [batch_size, hight*width*channels]
        result = X.reshape((batch_size, height * width * channels))
        return result

    def backward(self, d_out):
        # TODO: Implement backward pass
        d_input = d_out.reshape(self.X_shape)
        return d_input

    def params(self):
        # No params!
        return {}


# packages used

import numpy as np
import scipy.signal as sig
import matplotlib.pyplot as plt
import sys
import pprint as pp
import numpy.random as random

sys.path.append("../")
import custom_tools.fftplot as fftplot

import control as con
import control.matlab as ctrl
import custom_tools.handyfuncs as hf

# set the continuous sysc in either vector form or s form
# set the c2d_method
# returns impulse response, freq response, and pz map of both cont and discrete systems

z = con.tf('z')
zm1 = 1 / z
s = con.tf('s')

# Fs = 10 / np.pi
Fs = 1e4
print('Sampling rate is {}'.format(Fs))
T = 1 / Fs

# create continuous time transfer function (following presentation example in back-up slides)
# sysc = con.tf(1, [1, 2, 2, 0])
# sysc = con.tf(1, [1, 0, 1])

K = 1
# sysc = K * 1 / (s ** 3 + 2 * s ** 2 + 2 * s + 0)
# sysc = K * 1 / ((s + 1j) * (s - 1j))
sysc = s / (s + 1000)

# remove imag parts in coeffs of sysc.den and num
# imag 0j present if using symbolic expression and root_locus function stuck in complex warning:
# "Casting complex values to real discards the imaginary part"
# using squeeze will remove more brackets than necessary so just use [0][0]
sysc_den = sysc.den[0][0].real
sysc.den = [[sysc_den]]
sysc_num = sysc.num[0][0].real
sysc.num = [[sysc_num]]

# 2nd order system with wn and zeta
fn = 1
wn = 2 * np.pi * fn
zeta = 0.2
# sysc = wn ** 2 / (s ** 2 + 2 * zeta * wn * s + wn ** 2)

print(sysc)

c2d_method = 'zoh'
# method (string, optional) – Method to be applied,
# ‘zoh’ Zero-order hold on the inputs (default)
# ‘foh’ First-order hold, currently not implemented
# ‘impulse’ Impulse-invariant discretization, currently not implemented
# ‘tustin’ Bilinear (Tustin) approximation, only SISO ‘matched’ Matched pole-zero method, only SISO

# plot impulse response
tc, youtc = con.impulse_response(sysc)
# plt.figure()
# plt.plot(tc, youtc)
# plt.title("Impulse Response - Continuous System")
# plt.xlabel("Time [s]")
# plt.ylabel("Magnitude")
# plt.grid()

# plot step response
tc_s, youtc_s = con.step_response(sysc)
# plt.figure()
# plt.plot(tc_s, youtc_s)
# plt.title("Step Response - Continuous System")
# plt.xlabel("Time [s]")
# plt.ylabel("Magnitude")
# plt.grid()

# plot frequency response
w = 2 * np.pi * np.logspace(-3, np.log10(0.5 * Fs), 100)
magc, phasec, w = con.freqresp(sysc, w)
# freq response returns mag and phase as [[[mag]]], [[[phase]]]
# squeeze reduces this to a one dimensional array, optionally can use mag[0][0]
magc = np.squeeze(magc)
phasec = np.squeeze(phasec)
# plt.figure()
# plt.semilogx(w / (2 * np.pi), hf.db(magc))
# plt.grid()
# plt.xlabel('Frequency [Hz]')
# plt.ylabel('Magnitude [dB]')
# plt.title('Frequency Response - Continuous System')

#######################################################################################
# # doing impulse invariance the long way...
# # partial fraction expansion
# [r, p, k] = sig.residue([1], [1, 2, 2, 0])
# print([r, p, k])
# print('f is {}'.format(1 / T))
#
# # combine terms
# GF = con.tf([r[0], 0], [1, -np.exp(p[0] * T)], T)
# print(GF)
# GFB = con.tf([r[1], 0], [1, -np.exp(p[1] * T)], T)
# print(GFB)
# sys3 = con.tf([r[2], 0], [1, -np.exp(p[2] * T)], T)
# print(sys3)
#
# # combine systems
# sysd = (GF + GFB + sys3)
# print("Transfer Function for Discrete System (using Method of Impulse Invariance):")
# print(sysd)
#######################################################################################

# construct discrete system
sysd1 = con.c2d(sysc, T, method=c2d_method)
print('Using sample system')
print(1 / T * sysd1)
print(sysd1)
sysd = 1 / T * sysd1

# plot impulse response

# create time vector as multiples of the sampling time
# from 0 to 7 seconds to match analog impulse response
nsamps = 7 // T
td = np.arange(nsamps) * T

td, youtd = con.impulse_response(sysd, td)
youtd = np.squeeze(youtd)
# plt.figure()
# plt.plot(td, youtd)
# plt.plot(td, youtd, 'o', label='Sample Locations')
# plt.title("Impulse Response - Discrete System")
# plt.xlabel("Time [s]")
# plt.ylabel("Magnitude")
# plt.grid()
# plt.legend()

td_s, youtd_s = con.step_response(T * sysd, td)
youtd_s = np.squeeze(youtd_s)
# plt.figure()
# plt.plot(td, youtd_s)
# plt.plot(td, youtd_s, 'o', label='Sample Locations')
# plt.title("Step Response - Discrete System")
# plt.xlabel("Time [s]")
# plt.ylabel("Magnitude")
# plt.grid()
# plt.legend()

# plot frequency response
w = 2 * np.pi * np.logspace(-3, np.log10(0.5 * Fs), 100)
magd, phased, w = con.freqresp(T * sysd, w)

# freq response returns mag and phase as [[[mag]]], [[[phase]]]
# squeeze reduces this to a one dimensional array, optionally can use mag[0][0]
magd = np.squeeze(magd)
phased = np.squeeze(phased)
# plt.figure()
# plt.semilogx(w / (2 * np.pi), hf.db(magd))
# plt.grid()
# plt.xlabel('Frequency [Hz]')
# plt.ylabel('Magnitude [dB]')
# plt.title('Frequency Response - Discrete System')

# comparing continuous to discrete
plt.figure()
plt.plot(tc, youtc, label='Continous')
plt.plot(td, youtd, 'o', label='Discrete')
plt.title("Impulse Response - Comparison")
plt.xlabel("Time [s]")
plt.ylabel("Magnitude")
plt.grid()
plt.legend()

plt.figure()
plt.plot(tc_s, youtc_s, label='Continous')
plt.plot(td_s, youtd_s, 'o', label='Discrete')
plt.title("Step Response - Comparison")
plt.xlabel("Time [s]")
plt.ylabel("Magnitude")
plt.grid()
plt.legend()

plt.figure()
plt.subplot(2, 1, 1)
plt.semilogx(w / (2 * np.pi), hf.db(magc), label='Continous')
plt.semilogx(w / (2 * np.pi), hf.db(magd), '--', label='Discrete')
plt.grid(which='major', linestyle='-', linewidth='0.5', color='gray')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='gray')
plt.xlabel('Frequency [Hz]')
plt.ylabel('Magnitude [dB]')
plt.title('Frequency Response - Comparison')
plt.legend()

plt.subplot(2, 1, 2)
plt.semilogx(w / (2 * np.pi), np.unwrap(phasec) * 180 / np.pi, label='Continous')
plt.semilogx(w / (2 * np.pi), np.unwrap(phased) * 180 / np.pi, '--', label='Discrete')
plt.grid(which='major', linestyle='-', linewidth='0.5', color='gray')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='gray')
plt.xlabel('Frequency [Hz]')
plt.ylabel('Phase [Deg]')
# plt.title('Frequency Response - Comparison')
plt.legend()

plt.figure()
plt.subplot(2, 1, 1)
plt.semilogx(w, hf.db(magc), label='Continous')
plt.semilogx(w, hf.db(magd), '--', label='Discrete')
plt.grid(which='major', linestyle='-', linewidth='0.5', color='gray')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='gray')
plt.xlabel('Frequency [rad/s]')
plt.ylabel('Magnitude [dB]')
plt.title('Frequency Response - Comparison - rad/s')
plt.legend()

plt.subplot(2, 1, 2)
plt.semilogx(w, np.unwrap(phasec) * 180 / np.pi, label='Continous')
plt.semilogx(w, np.unwrap(phased) * 180 / np.pi, '--', label='Discrete')
plt.grid(which='major', linestyle='-', linewidth='0.5', color='gray')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='gray')
plt.xlabel('Frequency [rad/s]')
plt.ylabel('Phase [Deg]')
# plt.title('Frequency Response - Comparison')
plt.legend()

con.pzmap(sysc, title='Cont System Laplace plane')

con.pzmap(sysd, title='Discrete System Z plane')
if sysd.isdtime():
    cir_phase = np.linspace(0, 2 * np.pi, 500)
    plt.plot(np.real(np.exp(1j * cir_phase)), np.imag(np.exp(1j * cir_phase)), 'r--')
    plt.axis('equal')

plt.figure()
real, imag, freq = con.nyquist_plot(sysc)
scale = K / 2 + 1
plt.axis([-scale, scale, -scale, scale])
plt.title('Nyquist plot discrete')

# plt.figure()
rlist, klist = con.root_locus(sysc, grid=True)
plt.title('Root Locus cont')

plt.figure()
real, imag, freq = con.nyquist_plot(T * sysd, omega=w)
scale = K / 2 + 1
plt.axis([-scale, scale, -scale, scale])
plt.title('Nyquist plot discrete')

# plt.figure()
rlist, klist = con.root_locus(T * sysd, xlim=(-2, 2), ylim=(-2, 2), grid=True)
plt.title('Root Locus discrete')
if sysd.isdtime():
    cir_phase = np.linspace(0, 2 * np.pi, 500)
    plt.plot(np.real(np.exp(1j * cir_phase)), np.imag(np.exp(1j * cir_phase)), 'r--')
    # plt.axis('equal')

plt.show()


'''
Author: Kris Gonzalez
Date Created: 180511
Objective: quick demo on some of the basic numpy matrix operations

KJGNOTE: this module is pretty badass, not gonna lie.
'''

import sys
assert sys.version_info[0]>2,'Please do not use py2.'

import numpy as np

x=np.linspace(0,6,100) # default gives vector (no orientation, 1D)
y=np.sin(x)
y=y+1
k=np.array([[1,2,3]]) # with single brackets, have a vector with no orientation
k.shape
c = np.array( [ [1,2], [3,4] ], dtype=complex )
z=np.zeros((3,4))

I1=np.array([[1,0,0],[0,1,0],[0,0,1]])
I2=np.identity(3)
a=np.arange(3)
k=np.matmul(a,a.transpose())

a=np.arange(10).reshape(5,2)

a=np.arange(3)
b=np.row_stack([a,a]) # puts values vertically on top of one another. need list/tuple argument
c=np.column_stack((a,a)) # puts rows horizontally next to each other
# note: stacking involves having matching dimensions!!!

d=np.row_stack([a,a,a])
print(d)
d[1][2] = 7
print(d)

# how to sort a 2D array by a given column, both in ascending and descending order
print('\nwill show how to sort by a given column')
x=np.round(np.random.rand(10,3)*10,1)
print('orig: \n',x)
x_sort=x[x[:,1].argsort()]
print('sorted by column1:\n',x_sort)

x_revsort=x[x[:,1].argsort()[::-1]]
print('reverse sorted by column1:\n',x_revsort)

print('''kjg quick note on matrix multiplication: please note that the following 3
    methods for multiplication are similar, but not 100% identical. in some very
    rare cases, this may cause some numerical errors.''')
a=np.random.rand(3,3)
b=np.random.rand(3,3)
c=np.random.rand(3,4)
mm=np.matmul

ans1=a.dot(b).dot(c) # better
ans2=mm(a,mm(b,c))   # good
ans3=a@b@c           # best, but only for 3.5+. note: equivalent to ans1

print('full precision')
print('1&2: ',(ans1==ans2).all(),end=' | ') # false
print('1&3: ',(ans1==ans3).all(),end=' | ') # true
print('2&3: ',(ans2==ans3).all())           # false

print('with rounding:')
a1=np.round(ans1,6)
a2=np.round(ans2,6)
a3=np.round(ans3,6)
print('1&2: ',(a1==a2).all(),end=' | ') # true
print('1&3: ',(a1==a3).all(),end=' | ') # true
print('2&3: ',(a2==a3).all())           # true

print(''' random items ========================================================== ''')
# np.rint: rounded integers
a=np.random.rand(5)*10
print('original:\n ',a)
print('rounded to nearest int:\n ',np.rint(a))

# np.unique: unique values in array
a=np.array([4,3,1,1,5,4,1,3,2,5,1,2])
print('raw:',a)
print('unique values:',np.unique(a,return_counts=True))

# np.interp: linear interpolation
xref = np.linspace(0,6, 50) # a few ref values, slow data rate
yref = np.sin(xref)
# Changing "10" to larger number than "50" causes downsampling to 50
# Changing "0" or "6" below lead to truncation / extension
xout = np.linspace(-1,5.5,10) # many out values, to be scaled to ref.
yout = np.cos(xout)-.5 # y data as well
yout_adj = np.interp(xref,xout,yout)-0.25 # (x: desired resolution, xp: current resolution, fp: data to change)
# f,p = plt.subplots()
# p.plot(xref,yref,'-o',label='ref')
# p.plot(xout,yout,':s',label='out')
# p.plot(xref,yout_adj,':x',label='out_adj')
# p.legend()
# plt.show()
# eof


#%%

import numpy as np
from numpy import pi
from numpy import linalg as LA
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

##################################
      #Define Basics
##################################

# single qubit basis states |0> and |1>
q0 = np.array([[1],[0]])
q1 = np.array([[0],[1]])

# Pauli Matrices
I  = np.array([[ 1, 0],[ 0, 1]])
X = np.array([[ 0, 1],[ 1, 0]])
Y = np.array([[ 0,-1j],[1j, 0]])
Z = np.array([[ 1, 0],[ 0,-1]])
HG = (1/np.sqrt(2))*np.array([[1,1],[1,-1]])

#Creates the all zero input state.
def all_Zero_State(n_qubits):

   if n_qubits < 2:
       return 'Invalid Input : Specify at least 2 qubits'

   else:
       #Init State
       init_all_zero = np.kron(q0,q0)

       for t in range(n_qubits - 2):
           init_all_zero = np.kron(init_all_zero,q0)

       return init_all_zero

#Creates the equally superimposed product state from all zero state.
def equal_Superposition(n_qubits, init_all_zero):

    if n_qubits < 2:
        return 'Invalid Input : Specify at least 2 qubits'

    else:

        all_H = np.kron(HG,HG)

        for t in range(n_qubits - 2):
            all_H = np.kron(all_H,HG)

        equal_Superpos = all_H@init_all_zero

        return equal_Superpos

#Analytical Ground State using Numpy's inbuilt eigh function.
def get_analytical_ground_state(H):
  e, v = LA.eigh(H)
  return np.min(e), v[:,np.argmin(e)]

#Create Unitary
def CU(Q, theta, n_qubits):
  Id = np.eye(2**n_qubits)
  if np.sum(Q@Q - Id) == 0.0:
    return np.cos(theta)*Id - 1j*np.sin(theta)*Q
  else:
    return 'Input Matrix is not involutary'

#%%


"""The functions to be tested according to lecture 7"""

from functools import lru_cache
from numpy import sqrt
__all__ = ['my_sum', 'factorial', 'solve_quadratic']

def my_sum(iterable):
    """Calculating a sum"""
    tot = 0
    for i in iterable:
        tot += i
    return tot

@lru_cache(maxsize=None)  # Note: -> @cache in python >= 3.9
def factorial(number):
    """Calculating a factorial"""
    return number * factorial(number-1) if number else 1

def solve_quadratic(a, b, c):
    """Solve a quadratic equation ax^2+bx+c=0 in the reals"""
    if b**2 - 4.0*a*c > 0:
        # two solutions
        return (
            (-b - sqrt(b**2 - 4.0*a*c))/(2.0*a),
            (-b + sqrt(b**2 - 4.0*a*c))/(2.0*a)
        )
    elif b**2 - 4.0*a*c == 0:
        # one solution
        return -b/(2.0*a)
    else:
        # no solutions
        return None


import numpy as np

def hadamard(u: np.ndarray, v: np.ndarray) -> np.ndarray:
    """Multiply each element with the same index from both
    arrays and return an array of the same size.

    Args:
        (numpy.ndarray) u: First operand.
        (numpy.ndarray) v: Second operand.

    Returns:
        (np.ndarray): Binary operation results.
    """

    return u * v

def l1(u: np.ndarray, v: np.ndarray) -> np.ndarray:
    """Subtract each element with the same index from both
    arrays and return an array of the same size and absolute values.

    Args:
        (numpy.ndarray) u: First operand.
        (numpy.ndarray) v: Second operand.

    Returns:
        (np.ndarray): Binary operation results.
    """

    return np.abs(u - v)

def l2(u: np.ndarray, v: np.ndarray) -> np.ndarray:
    """Subtract each element with the same index from both
    arrays and return an array of the same size and squared values.

    Args:
        (numpy.ndarray) u: First operand.
        (numpy.ndarray) v: Second operand.

    Returns:
        (np.ndarray): Binary operation results.
    """

    return (u - v) ** 2

def avg(u: np.ndarray, v: np.ndarray) -> np.ndarray:
    """Add each element with the same index from both
    arrays and return an array of the same size and halved values.

    Args:
        (numpy.ndarray) u: First operand.
        (numpy.ndarray) v: Second operand.

    Returns:
        (np.ndarray): Binary operation results.
    """

    return (u + v) / 2.0


#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2016 Pi-Yueh Chuang <pychuang@gwu.edu>
#
# Distributed under terms of the MIT license.

"""
Definition of Gauss-Jacobi quadrature class. It's also the base class for all
Gauss-based quadrature methods.
"""

import numpy
from utils.poly.Jacobi import Jacobi
from utils.misc import factorial_division

class GaussJacobi(object):
    """Definition of Gauss-Jacobi quadrature class

    Usage:

        q = GaussJacobi(7, 0, 0)
        f = lambda x: x**6
        print(q(f, -1., 2.))

        >> 18.42857....

    Attributes:
        nodes: quadrature points
        weights: weights for quadrature points
    """

    def __init__(self, _n, _alpha=0, _beta=0):
        """Initialization of Gauss-Jacobi quafrature instance

        Args:
            _n: the order of Gauss-Jacobi quadrature
            _alpha: the alpha for the Jacobi polynomial
            _beta: the beta for the Jacobi polynomial
        """

        self.n = _n
        self._check_order()

        self.alpha = _alpha
        self.beta = _beta

        self._quad_points()

    def __call__(self, f, xmin=-1, xMax=1):
        """Carry out quadrature integration on the one-variable function f

        Linear shape function is assumed.

        Args:
            f: the one-variable function
            xmin: the lower limit of the integral
            xMax: the upper limit of the integral

        Returns:
            the result of integration
        """

        dx_dxi = (xMax - xmin) / 2
        x = (self.nodes + 1.) * dx_dxi + xmin

        if callable(f):
            return numpy.sum(f(x) * dx_dxi * self.weights)
        else:
            # TODO: check the type & size of f is a list/ndarray
            return numpy.sum(f * dx_dxi * self.weights)

    def __repr__(self):
        """__repr__"""

        return "{0}({1}, {2}, {3})".format(
            self.__class__, self.n, self.alpha, self.beta)

    def __str__(self):
        """__str__"""

        s = "Nodes  : " + str(self.nodes) + "\n"
        s += "Weights: " + str(self.weights)

        return s

    def _quad_points(self):
        """_quad_points

        Calculate the locations and weights of quadrature points
        """

        def H():
            p = Jacobi(self.n, self.alpha, self.beta)

            ans = numpy.power(2, self.alpha+self.beta+1)
            ans *= factorial_division(self.n, self.n+self.alpha)
            ans /= factorial_division(
                self.n+self.beta, self.n+self.alpha+self.beta)

            ans /= (1. - numpy.power(self.nodes, 2))
            ans /= numpy.power(p.derive()(self.nodes), 2)

            return ans

        # quadrature points
        p = Jacobi(self.n, self.alpha, self.beta)
        self.nodes = p.roots

        # weights
        self.weights = H()

    def _check_order(self):
        """Check the order of quadrature"""

        if self.n < 1:
            raise ValueError(
                "Requires the order to be greater than 0. " +
                "Current input n: {0}".format(self.n))


# -*- coding: utf-8 -*-
"""
Created on Wed Jan 22 15:32:30 2020

Script to demonstrate small angles approximation sin(theta) = theta

@author: https://www.linkedin.com/in/gabrieltribeiro/
"""

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

theta = np.linspace(0, 45, 46)
y_sin = np.sin(theta*math.pi/180)

err_sin = abs(100*(y_sin[1:] - theta[1:]*math.pi/180) / y_sin[1:])

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10,5))

axes[0].plot(theta, theta*math.pi/180)
axes[0].plot(theta, y_sin)
axes[0].legend(['theta', 'sin(theta)'])
axes[0].set_xlabel('Angle (deg)', style='italic', fontsize='large')
axes[0].set_ylabel('Function value', style='italic', fontsize='large')
axes[0].set_xticks(ticks=np.linspace(0, 45, 10))
axes[0].grid()

axes[1].bar(theta[1:16], err_sin[0:15])
axes[1].hlines(y=1, xmin=theta[1], xmax=theta[16], linestyles='dashed')
axes[1].set_xlabel('Angle (deg)', style='italic', fontsize='large')
axes[1].set_ylabel('% of error', style='italic', fontsize='large')
axes[1].set_xticks(ticks=np.linspace(0, 16, 15))


"""
randmat.py:  Random matrix methods
"""
from __future__ import division

import numpy as np

def rand_rot_invariant_mat(nz1,nz0,cond_num=10,is_complex=False):
    """
    Creates a rotationally invariant random matrix.

    A rotationally invariant matrix is of the form :math:`A=USV^*` where
    :math:`U` and :math:`V` are uniformly distributed on the unitaries
    (for complex matrices) and orthogonal matrices (for real matrices).
    The singular values :math:`S=diag(s_1,\ldots,s_r)` are logarithmically
    spaced from :math:`1/cond_num` to 1.  The singular values are then
    scaled to have an average magnitude squared of one.
    """

    # Generate a random Gaussian matrix
    if is_complex:
        A = np.random.randn(nz1,nz0) + 1j*np.random.randn(nz1,nz0)
    else:
        A = np.random.randn(nz1,nz0)

    # Take the SVD
    U,s,V = np.linalg.svd(A,full_matrices=0)

    # Reset the singular values
    r = len(s)
    s = np.logspace(-np.log10(cond_num),0,r)
    s = s / np.sqrt(np.mean(s**2))

    # Rebuild the matrix
    A = (U*s[None,:]).dot(V)

    return A


import numpy as np
import numpy.random

rng = np.random.default_rng()

def monte_carlo_definite_integral(f, low, high, n):
    xs = rng.uniform(low, high, n)
    f_v = np.vectorize(f)
    ys = f_v(xs)
    mean = np.mean(ys)
    std = np.std(ys, ddof=1)
    range_size = high - low
    return range_size*mean, range_size*std/np.sqrt(n-1)


import numpy as np

'''
This method is used to get the KL-Transform of the image
The function requires an image as argument
It returns the transform, the transformation matrix, and the eigen values
in descending order.
'''
def kl_transform(img):
  '''Finding the eigen vector and value of covariance matrix of the image'''
  eigenVal, eigenVec = np.linalg.eig(np.cov(img))

  '''Sorting the eigen vector in descending order of thier values'''
  idx = eigenVal.argsort()[::-1]
  eigenVal = eigenVal[idx]
  eigenVec = eigenVec[:,idx]

  '''Finding the transform'''
  # klt = np.dot(eigenVec[:100,:512], img)
  klt = np.dot(eigenVec, img)

  return klt, eigenVal, eigenVec