"""
lineshapes1d.py One-dimensional lineshape functions and classes
"""

import numpy as np
import scipy.special    # needed for complex error function (wo
pi = np.pi

####################################
# 1D lineshape simulator functions #
####################################

# Gaussian (normal) lineshape simulator functions
def sim_gauss_sigma(x, x0, sigma):
    """
    Simulate a Gaussian (normal) lineshape with unit height at the center.

    Simulate discrete points of a continuous Gaussian (normal) distribution
    with unit height at the center.  Sigma (the standard deviation of the
    distribution) is used as the distribution scale parameter.

    Functional form:

        f(x; x0, sigma) = exp(-(x-x0)^2  / (2 * sigma^2))

    Parameters:

    * x     Array of values at which to evaluate distribution.
    * x0    Center (mean) of Gaussian distribution.
    * sigma Scale (variance) of the Gaussian distribution.

    """
    return np.exp(-(x-x0)**2 / (2.0*sigma**2) )

def sim_gauss_fwhm(x, x0, fwhm):
    """
    Simulate a Gaussian (normal) lineshape with unit height at the center.

    Simulate discrete points of a continuous Gaussian (normal) distribution
    with unit height at the center.  FWHM (full-width at half-maximum ) is used
    as the distribution scale parameter.

    Functional form:

        f(x; x0, fwhm) = exp( -(x-x0)^2 * 4 * ln(2)  / (fwhm^2))

    Parameters:

    * x     Array of values at which to evaluate distribution.
    * x0    Center (mean) of Gaussian distribution.
    * fwhm  Full-width at half-maximum of distribution.

    """
    return np.exp(-(x-x0)**2 * 4 * np.log(2) / (fwhm**2))

# Lorentzian lineshape simulator functions
def sim_lorentz_gamma(x, x0, gamma):
    """
    Simulate a Lorentzian lineshape with unit height at the center.

    Simulates discrete points of the continuous Cauchy-Lorentz (Breit-Wigner)
    distribution with unit height at the center.  Gamma (the half-width at
    half-maximum, HWHM) is used as the scale parameter.

    Functional form:

        f(x; x0, gamma) = g^2 / ((x-x0)^2 + g^2)

    Parameters:

    * x     Array of values at which to evaluate distribution.
    * x0    Center of the distribution.
    * gamma Scale parameter, half-width at half-maximum, of distribution.

    """
    return gamma**2 / (gamma**2 + (x-x0)**2)

def sim_lorentz_fwhm(x, x0, fwhm):
    """
    Simulate a Lorentzian lineshape with unit height at the center.

    Simulates discrete points of the continuous Cauchy-Lorentz (Breit-Wigner)
    distribution with unit height at the center.  FWHM (full-width at
    half-maximum) is used as the scale parameter.

    Functional form:

        f(x; x0, fwhm) = (0.5 * fwhm)^2 / ((x-x0)^2 + (0.5 * fwhm)^2)

    Parameters:

    * x     Array of values at which to evaluate distribution.
    * x0    Center of the distribution.
    * fwhm  Full-width at half-maximum of distribution.

    """
    return (0.5 * fwhm)**2 / ((0.5 * fwhm)**2 + (x-x0)**2)

# Voigt lineshape simulator functions

def sim_voigt_fwhm(x, x0, fwhm_g, fwhm_l):
    """
    Simulate a Voigt lineshape with unit height at the center.

    Simulates discrete points of the continuous Voigt profile with unit height
    at the center.  Full-width at half-maximum (FWHM) of each component are used
    as the scale parameters for the Gaussian and Lorentzian distribution.

    Functional Form:

        f(x; x0, fwhm_g, fwhm_l) = Re[w(z)] / Re[(w(z0)]

    Where:

        z = sqrt(ln(2)) * (2 * (x - x0) + 1j * fwhm_l) / fwhm_g
        z0 = sqrt(ln(2)) * 1j * fwhm_l / fwhm_g
        w(z) is the complex error function of z

    Parameters

    * x         Array of values at which to evalutate distribution.
    * x0        Center of the distribution.
    * fwhm_g    Full-width at half-maximum of the Gaussian component.
    * fwhm_l    Full-width at half-maximum of the Lorentzian component.

    """
    z = np.sqrt(np.log(2)) * (2.0 * (x - x0) + 1.j * fwhm_l) / fwhm_g
    z0 = np.sqrt(np.log(2)) * 1.j *  fwhm_l / fwhm_g
    return scipy.special.wofz(z).real / scipy.special.wofz(z0).real

def sim_voigt_sigmagamma(x, x0, sigma, gamma):
    """
    Simulate a Voigt lineshape with unit height at the center.

    Simulates discrete points of the continuous Voigt profile with unit height
    at the center.  Sigma and gamma are used as the Gaussian and Lorentzian
    scaler parameters.

    Functional Form:

        f(x; x0, sigma, gamma) = Re[w(z)] / Re[(w(z0)]

    Where:

        z = ((x - x0) + 1j * gamma) / (sigma * sqrt(2))
        z0 = (1j * gamma) / (sigma * sqrt(2))
        w(z) is the complex error function of z

    Parameters

    * x     Array of values at which to evalutate distribution.
    * x0    Center of the distribution
    * sigma Gaussian scale component of Voigt profile.  Variance of the Gaussian
            distribution.
    * gamma Lorentzian scale component of Voigt profile.  Half-width at
            half-maximum of the Lorentzian component.

    """
    z = (x - x0 + 1j * gamma) / ( sigma * np.sqrt(2))
    z0 = (1j * gamma) / (sigma * np.sqrt(2))
    return scipy.special.wofz(z).real / scipy.special.wofz(z0).real

# Pseudo Voigt linehspae simulator functions
def sim_pvoigt_fwhm(x, x0, fwhm, eta):
    """
    Simulate a Pseudo Voigt lineshape with unit height at the center.

    Simulates discrete points of the continuous Pseudo Voigt profile with unit
    heigh at the center.  Full-width at half-maximum (FWHM) of the Gaussian and
    Lorentzian distribution are used as the scale parameter as well as eta, the
    mixing factor.

    Functional Form:

        f(x; x0, fwhm, eta) = (1-eta) * G(x; x0, fwhm) + eta * L(x; x0, fwhm)

    Where:

        G(x; x0, fwhm) = exp( -(x-x0)^2 * 4 * ln(2)  / (fwhm^2))
        L(x; x0, fwhm) = (0.5 * fwhm)^2 / ((x-x0)^2 + (0.5 * fwhm)^2)

    Parameters

    * x     Array of values at which to evalutate distribution.
    * x0    Center of the distribution.
    * fwhm  Full-width at half-maximum of the Pseudo Voigt profile.
    * eta   Lorentzian/Gaussian mixing parameter.

    """
    G = sim_gauss_fwhm(x, x0, fwhm)
    L = sim_lorentz_fwhm(x, x0, fwhm)
    return (1.0 - eta) * G + eta * L

########################
# 1D Lineshape classes #
########################

# A lineshape class defines methods used to fit and simulate one dimensional
# lineshapes, which can be used to build multidimensinal lineshapes.  These
# classes should have the following 6 methods:

# sim(self, M, p)   - Using parameters in p simulate a lineshape of length M.
# nparams(self, M)  - Determind the number of parameters needed for a length M
#                     lineshape.
# guessp(self, sig) - Estimate parameters of signal sig, these should be
#                     parameter which might be used for initial least-squares
#                     fitting.
# pnames(self, M)   - Give names to the parameters of a lineshape of length M.
#
# add_edge(self, p, (min,max))  - take into account region limits at min,max
#                                 for parameters in p.
# remove_edge(self, p, (min,max))   - remove the effects of region limits
#                                   min, max for parameters in p.

# location-scale lineshapes

class location_scale():
    """
    Class for building a 2 parameter location scale lineshape class
    """
    def nparam(self,M):
        return 2

    def add_edge(self,p,(min,max)):
        if p[0] is None:
            return p
        return p[0]-min,p[1]

    def remove_edge(self,p,(min,max)):
        if p[0] is None:
            return p
        return p[0]+min,p[1]

class gauss_sigma(location_scale):
    """
    Gaussian (normal) lineshape class with unit height at the mean and sigma
    scale parameter. See sim_gauss_sigma for functional form and parameters.
    """
    name = "guassian"

    def sim(self,M,p):
        x = np.arange(M)
        x0, sigma = p
        return sim_gauss_sigma(x, x0, sigma)

    def guessp(self,sig):
        c,fwhm = center_fwhm(sig)
        return (c,fwhm/2.35482004503)

    def pnames(self,M):
        return ("x0","sigma")

class gauss_fwhm(location_scale):
    """
    Gaussian (normal) lineshape class with unit height at the mean and fwhm
    scale parameter. See sim_gauss_fwhm for functional form and parameters.
    """
    name = "guassian"

    def sim(self,M,p):
        x = np.arange(M)
        x0, fwhm = p
        return sim_gauss_fwhm(x, x0, fwhm)

    def guessp(self,sig):
        c,fwhm = center_fwhm(sig)
        return (c,fwhm)

    def pnames(self,M):
        return ("x0","fwhm")

class lorentz_gamma(location_scale):
    """
    Lorentzian lineshape class with unit height at the center and gamma scale
    parameter.  See sim_lorentz_gamma for functional form and parameters.
    """
    name = "lorentz"

    def sim(self,M,p):
        x = np.arange(M)
        x0, gamma = p
        return sim_lorentz_gamma(x, x0, gamma)

    def guessp(self,sig):
        c,fwhm = center_fwhm(sig)
        return (c,fwhm/2.)

    def pnames(self,M):
        return("x0","gamma")

class lorentz_fwhm(location_scale):
    """
    Lorentzian lineshape class with unit height at the center and gamma scale
    parameter.  See sim_lorentz_fwhm for functional form and parameters.
    """
    name = "lorentz"

    def sim(self,M,p):
        x = np.arange(M)
        x0, fwhm = p
        return sim_lorentz_fwhm(x, x0, fwhm)

    def guessp(self,sig):
        c,fwhm = center_fwhm(sig)
        return (c,fwhm)

    def pnames(self,M):
        return("x0","fwhm")

# Voigt (location, 2 scale-like parameters) lineshapes.

class location_2params():
    """
    Class for building a 3 parameter location, scale, other lineshape class
    """
    def nparam(self,M):
        return 3

    def add_edge(self,p,(min,max)):
        if p[0] is None:
            return p
        return p[0]-min,p[1]

    def remove_edge(self,p,(min,max)):
        if p[0] is None:
            return p
        return p[0]+min,p[1]

class voigt_fwhm(location_2params):
    """
    Voigt lineshape class with unit height at the center and full-width
    half-maximum scale parameters.  See sim_voigt_fwhm for functional form and
    parameters.
    """
    name = "voigt"

    def sim(self, M, p):
        x = np.arange(M)
        x0, fwhm_g, fwhm_l = p
        return sim_voigt_fwhm(x, x0, fwhm_g, fwhm_l)

    def guessp(self, sig):
        c, fwhm = center_fwhm(sig)
        return (c, fwhm*0.5, fwhm*0.5)

    def pnames(self, M):
        return ("x0", "fwhm_gauss", "fwhm_lorentz")

class voigt_sigmagamma(location_2params):
    """
    Voigt lineshape class with unit height at the center and sigma, gamma scale
    parameters.  See sim_voigt_sigmagamma for functional form and parameters.
    """
    name = "voigt"

    def sim(self, M, p):
        x = np.arange(M)
        x0, sigma, gamma = p
        return sim_voigt_sigmagamma(x, x0, sigma, gamma)

    def guessp(self, sig):
        c, fwhm = center_fwhm(sig)
        return (c, fwhm/2.35482004503 * 0.5  , fwhm * 0.5 * 0.5)

    def pnames(self, M):
        return ("x0", "fwhm_gauss", "fwhm_lorentz")

class pvoigt_fwhm(location_2params):
    """
    Pseudo-Voigt lineshape class with unit height at the center and full-width
    half-maximum scale parameter.  See sim_pvoigt_fwhm for functional form and
    parameters.
    """
    name = "pvoigt"

    def sim(self, M, p):
        x = np.arange(M)
        x0, fwhm, eta = p
        return sim_pvoigt_fwhm(x, x0, fwhm, eta)

    def guessp(self, sig):
        c, fwhm = center_fwhm(sig)
        return (c, fwhm, 0.5)

    def pnames(self, M):
        return ("x0", "fwhm", "eta")

# misc lineshape classes
class scale():
    """
    Scale lineshape class

    Simulates a lineshape with functional form:

    1.0, a0, a1, a2, ....

    Where a0, a1, ... are the parameters provided.

    """
    name = "scale"

    def sim(self,M,p):
        l = np.empty(M,dtype='float')
        l[0] = 1
        l[1:] = p
        return l

    def nparam(self,M):
        return int(M-1)

    def guessp(self,sig):
        return sig[1:]/sig[0]

    def pnames(self,M):
        return tuple(["a%i"%i for i in range(1,M)])

    def add_edge(self,p,(min,max)):
        return p

    def remove_edge(self,p,(min,max)):
        return p

# lineshape convience
gauss = gauss_fwhm
lorentz = lorentz_fwhm
voigt = voigt_fwhm
pvoigt = pvoigt_fwhm

# lineshape class router

def ls_str2class(l):
    """ Convert lineshape string to lineshape class """
    if l == "gauss" or l == "g":
        return gauss()
    elif l == "lorentz" or l == "l":
        return lorentz()
    elif l == "scale" or l == "s":
        return scale()
    elif l == "voigt" or l == "v":
        return voigt()
    elif l == "pvoigt" or l == "pv":
        return pvoigt()
    else:
        raise ValueError("Unknown lineshape %s",(l))

# basic lineshape analysis

def center_fwhm(signal):
    """
    Estimate the center and full-width half max of a signal.
    """

    # negate the signal if it appears to be a negative peak
    if -signal.min() > signal.max():
        signal = -signal

    # the center is the highest point in the signal
    center = signal.argmax()

    # find the points that bracket the first and last crossing of the
    # half max, then use linear extrapolation to find the location of the
    # half max on either side of the maximum.  The difference between these
    # two values is a good approximation of the full width at half max
    max = signal.max()
    hmax = max/2.

    top_args = np.nonzero(signal > hmax)[0]     # all points above half-max
    l_idx = top_args[0]     # index of left hand side above half-max
    r_idx = top_args[-1]    # index of right hand side above half-max

    # solve hmax = mx+b => x = y-b/m
    # for two points x_0 and x_1 this becomes y = (hmax-x_0)/(x_1-x_0)
    # to this value add the index of x_0 to get the location of the half-max.

    # left side
    if l_idx==0:
        left = l_idx    # this is a bad guess but the best we can do
    else:
        x_0,x_1 = signal[l_idx-1],signal[l_idx]
        left = l_idx-1+(hmax-x_0)/(x_1-x_0)

    # right side
    if r_idx == len(signal)-1:
        right = r_idx   # this is a bad guess but the best we can do
    else:
        x_0,x_1 = signal[r_idx],signal[r_idx+1]
        right = r_idx+(hmax-x_0)/(x_1-x_0)

    return center,right-left

def center_fwhm_bymoments(signal):
    """
    Estimate the center and full-width half max of a signal using moments
    """

    # calculate the zeroth, first and second moment
    x = np.arange(signal.size)
    m0 = signal.sum()
    m1 = (x*signal).sum()
    m2 = (x**2.*signal).sum()

    # mu (the center) is the first over the zeroth moment
    mu = m1/m0
    # sigma (the variance) is  sqrt( abs(m2)-mu**2)
    sigma = np.sqrt(np.abs(m2)-mu**2)

    return mu,sigma*2.3548


import numpy as np
from numpy import array,kron

def HeisenbergHamiltonian(N, J, h, PBC=False):
#     # Some error checking
    if (len(J) != 3):
        print("error: Vector containing the couplings has to have length 3")
    if (len(h) != 3):
        print("error: Vector containing the field strengths has to have length 3")
#     # Provide the Pauli matrices
    s0 = array([[1+0.0j,0+0.0j],[0+0.0j,1+0.0j]])
    sx = array([[0+0.0j,1+0.0j],[1+0.0j,0+0.0j]])
    sy = array([[0+0.0j,-1.0j],[0.0+1.0j,0.0+0.0j]])
    sz = array([[1+0.0j,0+0.0j],[0+0.0j,-1+0.0j]])
    H = np.zeros(shape=(2**N,2**N))
    for i in range(1,N+1):
        id_left = array(np.identity(2**(i - 1)), copy=False)
        id_right = array(np.identity(2**(N - i)), copy=False)
        H = (
            H
            + kron(kron(id_left,sx),id_right)
            + h[1]*kron(kron(id_left,sy),id_right)
            + h[2]*kron(kron(id_left,sz),id_right)
            )
#         print('H = ',H)
        if (i<N):
            id_right = array(np.identity(2**(N-i-1)), copy=False)
            H = (
                H
                + J[0]*kron(kron(kron(id_left,sx),sx),id_right)
                + J[1]*kron(kron(kron(id_left,sy),sy),id_right)
                + (J[2]/h[0])*kron(kron(kron(id_left,sz),sz),id_right)
                )
    if PBC==True:
        idx = array(np.identity(2**(N-2)), copy=False)
        H = (
            H + J[0]*kron(kron(sx,idx),sx)
                + J[1]*kron(kron(sy,idx),sy)
                + (J[2]/h[0])*kron(kron(sz,idx),sz)
            )
    return(H)

def getT(N):
    swap = array([[1., 0., 0., 0.],
       [0., 0., 1., 0.],
       [0., 1., 0., 0.],
       [0., 0., 0., 1.]])
    res = array(np.identity(2**N), copy=False)
    for i in range(1,N):
        id_left = array(np.identity(2**(i - 1)), copy=False)
        id_right = array(np.identity(2**(N - i - 1)), copy=False)
        tmp = kron(kron(id_left,swap),id_right)
        res = np.dot(tmp,res)
#         print("id_left = {}\nid_right = {}\ntmp = {}\nres = {}".format(repr(id_left),repr(id_right),repr(tmp),repr(res)))
    return(res)


from __future__ import division
from sympy import MatrixSymbol, Matrix,diff,init_printing,symbols,sqrt,pprint,simplify,exp

x = MatrixSymbol('x', 1, 2)

x = symbols('x')
y = symbols('y')
c = symbols('c')
g = sqrt(x**2 + y**2 + c**2)
gx = diff(g,x)
gy = diff(g,y)
gxx = diff(gx,x)
gyy = diff(gy,y)

print 'MQ'
print '---------------------------------------------'
pprint(g)
print '---------------------------------------------'
pprint(gx)
print '---------------------------------------------'
pprint(gy)
print '---------------------------------------------'
pprint(gxx)
print '---------------------------------------------'
pprint(gyy)
print '---------------------------------------------'
pprint(simplify(gxx+gyy))

g = exp(-(x**2 + y**2)/c**2)
gx = diff(g,x)
gy = diff(g,y)
gxx = diff(gx,x)
gyy = diff(gy,y)

print 'GAUSSIAN'
print '---------------------------------------------'
pprint(g)
print '---------------------------------------------'
pprint(gx)
print '---------------------------------------------'
pprint(gy)
print '---------------------------------------------'
pprint(gxx)
print '---------------------------------------------'
pprint(gyy)
print '---------------------------------------------'
pprint(simplify(gxx+gyy))


import numpy as np
import scipy.constants as constants

def calculate_phonon_heat_capacity(temperatures,v_dos):
    # This function calculates the phonon heat capacity from the phonon density of states.
    # INPUTS:
    # IMPORTANT: All inputs need to be in the right units! (see information below)
    # temperatures: array of shape (N,) corresponding to the temperatures for the heat capacity calculation, in K.
    # v_dos: array of shape (M,2) which contains information on the phonon density of states. The first column is
    # energy in eV and the second column is the corresponding phonon density of states in states/eV.
    #
    # OUTPUT:
    # phonon_heat_capacity: array of shape(N-1,2) which contains information on the phonon heat capacity. The first
    # column is temperature (the temperature sampling is slightly different due to the numerical differentiation).
    # The second column is heat capacity. The units depend on the units of the phonon density of states:
    # If it is in states/eV/unit cell, the heat capacity is in J/unit cell. In other words, the volume to which
    # v_dos and phonon_heat_capacity refer is the same (here: one unit cell).

    # constants
    K_B = constants.Boltzmann*constants.physical_constants['joule-electron volt relationship'][0]  # Boltzmann constant, in eV/K
    ELEMENTARY_CHARGE = constants.e # elementary charge in C

    # ensure that v_dos has only positive energy entries (zero energy causes numerical problems and the v_dos
    # should anyway be zero there.)
    v_dos = v_dos[v_dos[:,0]>0,:]

    # step 1: calculate electron energy as a function of temperature
    phonon_energy = np.empty(np.shape(temperatures))
    phonon_heat_capacity = np.empty([np.shape(temperatures)[0]-1,2])
    for i in range(0,len(temperatures)):
        if temperatures[i] == 0:
            phonon_energy[i] = 0
        else:
            bose_einstein_distribution = 1./(np.exp(v_dos[:,0]/(K_B*temperatures[i]))-1)
            thermal_occupation_number = bose_einstein_distribution*v_dos[:,1] # occupation number=distribution function*DOS
            phonon_energy[i] = np.trapz(thermal_occupation_number*v_dos[:,0],v_dos[:,0]) # energy stored in phonon system
    # convert phonon energy from eV to J:
    phonon_energy = phonon_energy*ELEMENTARY_CHARGE

    # step 2: calculate numerical derivative of phonon energy with respect to temperature to obtain heat capacity
    # temperatures:
    phonon_heat_capacity[:,0] = (temperatures[0:-1]+temperatures[1:])/2
    # heat capacity in J/(unit cell K) (assuming that the DOS is per unit cell):
    phonon_heat_capacity[:,1] = np.diff(phonon_energy)/(temperatures[1]-temperatures[0])

    return phonon_heat_capacity


import numpy as np
import matplotlib.pyplot as plt
import time
x0 = 0
xn = 1
dx = 0.1
dx = 0.01
# n = 10 #Choose when dx = 0.1
n = 100 #Choose when dx = 0.01
x = np.linspace(x0,xn,n+1)
a = np.zeros(n+1)
b = np.zeros(n+1)
c = np.zeros(n+1)
d = np.zeros(n+1)
phi = np.zeros(n+1)
#boundary conditions
phi[0] = 1.0
phi[n] = 0.0
#Calculating coefficients for the first and last node
b[1] = -0.2/(dx*dx) - 1
c[1] = 0.1/(dx*dx) + 1/(2*dx)
d[1] = phi[0]*(1/(2*dx)-0.1/(dx*dx))
a[n-1] = 0.1/(dx*dx) - 1/(2*dx)
b[n-1] = -0.2/(dx*dx) - 1
d[n-1] = phi[n]*(-1/(2*dx)-0.1/(dx*dx))
#Calculating coefficients for the rest
for i in range(2,n-1):
    a[i] = 0.1/(dx*dx) - 1/(2*dx)
    b[i] = -0.2/(dx*dx) - 1
    c[i] = 0.1/(dx*dx) + 1/(2*dx)
#Thomas algorithm
for i in range(2,n):
    a[i] = a[i]/b[i-1]
    b[i] = b[i] - a[i]*c[i-1]
    d[i] = d[i] - a[i]*d[i-1]

phi[n-1] = d[n-1] / b[n-1]
for i in range(n-2,0,-1):
    phi[i]= (d[i] - c[i]*phi[i+1])/b[i]

print(phi)
print(x)
plt.plot(x, phi)
plt.title("phi vs. x : dx = 0.01")
plt.ylabel('phi')
plt.xlabel('x')
plt.show()


import numpy as np

def calc_angle(u: np.ndarray, v: np.ndarray):
    return np.arccos(np.dot(u / np.linalg.norm(u), v / np.linalg.norm(v))) * 57.2958

def check_right_angle(angle: float, epsilon: float):
    return abs(angle - 90) < epsilon

def side_lengths_are_too_different(u: np.array, v: np.array, t: np.array, n: np.array, eps_scale: float):
    uv = np.dot(u - v, u - v)
    un = np.dot(u - n, u - n)
    vt = np.dot(v - t, v - t)
    tn = np.dot(t - n, t - n)

    shortest = min(uv, un, vt, tn)
    longest = max(uv, un, vt, tn)
    return longest > eps_scale * shortest

def check_rect(rect: np.ndarray):
    ab = rect[1] - rect[0]
    ad = rect[3] - rect[0]
    bc = rect[2] - rect[1]
    dc = rect[2] - rect[3]

    if not (check_right_angle(calc_angle(ab, ad), 20)):
        if check_right_angle(calc_angle(ab, bc), 20):
            if check_right_angle(calc_angle(bc, dc), 20):
                if check_right_angle(calc_angle(ad, dc), 20):
                    return False
    # check if it is a square grid ..
    if side_lengths_are_too_different(rect[0], rect[1], rect[2], rect[3], 1.44):
        return False

    return True

def detect_rect_corners(corners: np.array):
    rect = np.zeros((4, 2), dtype="float32")
    corners = corners.reshape(4, 2)

    corners_sum = np.sum(corners, axis=1)

    min_val_idx = np.argmin(corners_sum)

    rect[0] = corners[min_val_idx]
    corners = np.delete(corners, min_val_idx, 0)

    max_val_idx = np.argmax(corners_sum)

    if max_val_idx <= 1:
        rect[2] = corners[max_val_idx]
        corners = np.delete(corners, max_val_idx, 0)

    if corners[0][0] > corners[1][0]:
        rect[1] = corners[0]
        rect[3] = corners[1]
    else:
        rect[1] = corners[1]
        rect[3] = corners[0]

    return rect.reshape(4, 2)

def calc_dimensions(rect):
    u, v, t, n = rect
    width = max(int(np.linalg.norm(t - n)), int(np.linalg.norm(u - v)))

    height = max(int(np.linalg.norm(v - t)), int(np.linalg.norm(u - n)))

    return np.array([
        [0, 0],
        [width - 1, 0],
        [width - 1, height - 1],
        [0, height - 1]], dtype="float32"), width, height


from typing import List

import numpy as np

def random_start_weights(list_of_layer_dimensions):
    """
    creates random weights for each edge in computational graph of size 'list_of_layer_dimensions'

    :param list_of_layer_dimensions: array of dimensions of particular layer starting with input layer,
    through hidden layers, out with output layer.
    :return: list of ndarrays - list of 2n ndarrays - weights from neurons in layer a to a + 1, where a starts with 0
    (input layer) and goes through hidden layers until output layer a = L
    """
    weights = []
    for idx in range(len(list_of_layer_dimensions) - 1):
        first_layer_dim = list_of_layer_dimensions[idx]
        second_layer_dim = list_of_layer_dimensions[idx + 1]
        weights.append(np.random.rand(second_layer_dim, first_layer_dim))
    return weights

def random_start_biases(list_of_layer_dimensions):
    """
    creates random start biases for given layers of neural network

    :param list_of_layer_dimensions: array of dimensions of particular layer starting with input layer,
    through hidden layers, out with output layer.
    :return: list of ndarrays -  biases for each dim except in input layer
    """
    biases = []
    for dim in list_of_layer_dimensions[1:]:
        biases.append(np.array(np.random.rand(dim), dtype=np.float))
    return biases

def sigmoid(x):
    """
    activation function - simple and classical sigmoid
    """
    return 1 / (1 + np.exp(-x))

def dsigmoid(x):
    """
    Derivative of sigmoid function
    """
    return sigmoid(x) * (1 - sigmoid(x))

class FeedForwardNN:
    """
    Feed forward neural network with:
    - cost function c(y*, y) = 0.5 * ||y* - y||^2
    - constant minibatch size of 1
    - default learning rate of 0.01
    """

    def __init__(self,
                 layer_dimensions: List[int],
                 activation_function=sigmoid,
                 activation_derivative=dsigmoid,
                 learning_rate=0.01) -> None:
        super().__init__()
        self.layer_dimensions = layer_dimensions
        self.w = random_start_weights(layer_dimensions)
        self.b = random_start_biases(layer_dimensions)
        self.activations = []
        self.z = []
        self.activation_function = activation_function
        self.activation_derivative = activation_derivative
        self.eta = learning_rate

    def forward(self, input_vector: np.array) -> np.array:
        """
    Executes a forward step.

    Given input vector, computes activation values of all neurons and memoizes
    them, next returns activation vector of neurons in the last layer

    :param input_vector: input vector of shape(0, layer_dimensions[0])
    :return: returns activation vector of output layer
    """
        for idx, layer in enumerate(self.layer_dimensions):
            if idx is 0:
                self.activations.append(input_vector)
            else:
                previous_a = self.activations[-1]
                zeta = np.dot(self.w[idx - 1], previous_a) + self.b[idx - 1]
                self.z.append(zeta)
                a = self.activation_function(zeta)
                self.activations.append(a)
        return self.activations[-1]

    def fit(self, xs, ys):
        """
        Fits the model against the learning set
        :param xs: data
        :param ys: labels
        :return: nothing, the function changes parameters (biases and weights)
        to fit the network against train data
        """
        for x, y in zip(xs, ys):
            self._fit_single(x, y)

    def _fit_single(self, x, y):
        predicted = self.forward(x)
        gradient_weights, gradient_biases = self.backward(y, predicted)
        self.update_weights_and_biases(gradient_biases, gradient_weights)

    def update_weights_and_biases(self, grad_biases, grad_weights):
        for i in range(len(self.w)):
            self.w[i] = self.w[i] - (self.eta * grad_weights[i])
        for i in range(len(self.b)):
            self.b[i] = self.b[i] - (self.eta * grad_biases[i])

    def backward(self, real, predicted):
        """
        Executes backpropagation algorithm fitting weights and biases against predicted value
        """
        gradient_weights = [np.zeros(weight.shape) for weight in self.w]
        gradient_biases = [np.zeros(bias.shape) for bias in self.b]

        # weights and biases from layer L-1 to layer L
        partial_cost_last_a = predicted - real
        delta = self.activation_derivative(self.z[-1])
        last_layer_neuron_factor = partial_cost_last_a * delta
        last_layer_weights_gradient = np.dot(last_layer_neuron_factor.reshape((-1, 1)),
                                             self.activations[-2].reshape((1, -1)))
        gradient_weights[-1] = last_layer_weights_gradient
        gradient_biases[-1] = last_layer_neuron_factor

        # weights for other hidden layers
        for i in range(len(self.activations) - 2, 0, -1):
            z_and_old_delta = self.z[i] * delta
            delta = np.dot(self.w[i].transpose(), z_and_old_delta)
            squashed_derivative = self.activation_derivative(self.z[i - 1])
            neuron_error = squashed_derivative * delta
            gradient_weights[i - 1] = np.dot(neuron_error.reshape((-1, 1)), self.activations[i - 1].reshape((1, -1)))
            gradient_biases[i - 1] = neuron_error
        return gradient_weights, gradient_biases


import math
import numpy as np
from numba import njit
from numba import cuda

"""
Loss = sum_i(sum_j(A[i,j] * abs(i-j)/(m^2)))
"""
def loss(matrix):
    # return loss_gpu(matrix) # use GPU
    return loss_cpu(matrix) # use CPU

@njit
def loss_cpu(elements):
    """
    This file directly optimize this loss function.
    calculate the whole matrix
    @return at the scale: Loss / 2
    """
    ret = 0
    l = elements.shape[0]
    _l_1 = 1.0/(l*l)
    for i in range(l):
        for j in range(i):
            if elements[i, j] > 0:
                ret += (i-j) * _l_1 * elements[i, j]  # here because j<i, we can safely ommit abs() for speed.
    return ret

@njit
def loss_gradient_if_swap(elements, target_i, target_j):
    """
    This file directly optimize this loss function.
    only calculate i and j-th row and col
    @return at the scale: Loss * l
    """
    ret = 0  # loss gained by swapping
    l = elements.shape[0]

    for m, m_inv in [[target_i, target_j], [target_j, target_i]]:
        for n in range(l):
            if elements[m, n] > 0 or elements[m_inv, n] > 0:
                if m != n and m_inv != n:
                    ret += (abs(m-n)-abs(m_inv-n)) * (elements[m, n] - elements[m_inv, n])
    return ret

@cuda.jit
def _loss_gpu(matrix, ret):
    """ CUDA kernel function for loss_gpu() """
    x, y = cuda.grid(2)
    if x<matrix.shape[0] and y<matrix.shape[1]: # Important: Don't use early return in CUDA functions, will cause Memory Error. Use cuda-memcheck to check memory.
        if y<x: # only compute half of the matrix
            if matrix[x, y] > 0:
                loss = (x-y) * ret[1] * matrix[x, y]
                cuda.atomic.add(ret, 0, loss)

def loss_gpu(matrix):
    """ same as loss(), but run on GPU """
    ret = np.array([0.0, 1/(matrix.shape[0]*matrix.shape[0])], dtype=np.float64)
    threadsperblock = (16, 16)
    blockspergrid_x = math.ceil(matrix.shape[0] / threadsperblock[0])
    blockspergrid_y = math.ceil(matrix.shape[1] / threadsperblock[1])
    blockspergrid = (blockspergrid_x, blockspergrid_y)
    _loss_gpu[blockspergrid, threadsperblock](matrix, ret)
    return ret[0]

@njit
def swap_inplace(elements, indices, i, j):
    """ swap the matrix and indices inplace at position i and j """
    _tmp = elements[i, :].copy()
    elements[i, :] = elements[j, :]
    elements[j, :] = _tmp

    _tmp = elements[:, i].copy()
    elements[:, i] = elements[:, j]
    elements[:, j] = _tmp

    # also swap indices to keep track of the direct way from original matrix to final matrix.
    if indices is not None:
        _tmp = indices[i]
        indices[i] = indices[j]
        indices[j] = _tmp


"""ETS Prediction Model"""
__docformat__ = "numpy"

import logging
from typing import Union

import numpy as np
import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing

from openbb_terminal.decorators import log_start_end

logger = logging.getLogger(__name__)

TRENDS = ["N", "A", "Ad"]
SEASONS = ["N", "A", "M"]

@log_start_end(log=logger)
def get_exponential_smoothing_model(
    data: Union[pd.Series, np.ndarray],
    trend: str = "N",
    seasonal: str = "N",
    seasonal_periods: int = 5,
    n_predict: int = 5,
):
    """
    Perform exponential smoothing
    Parameters
    ----------
    data: Union[pd.Series, np.ndarray]
        Series of closing values
    trend: str
        Trend component.  One of [N, A, Ad]
    seasonal: str
        Seasonal component.  One of [N, A, M]
    seasonal_periods: int
        Number of seasonal periods in a year
    n_predict: int
        Number of days to forecast

    Returns
    -------
    model:
        Exponential smoothing model
    title: str
        String describing selected model
    forecast : List[float]
        List of predictions
    """
    if trend == "N":  # None
        if seasonal == "N":  # None
            title = "Trend='N',  Seasonal='N': Simple Exponential Smoothing"
            ETS = ExponentialSmoothing(
                data,
                trend=None,
                damped_trend=False,
                seasonal=None,
            )
            model = ETS.fit(
                smoothing_level=None, smoothing_trend=None, damping_trend=None
            )

        elif seasonal == "A":  # Additive
            title = "Trend='N',  Seasonal='A': Exponential Smoothing"
            ETS = ExponentialSmoothing(
                data,
                trend=None,
                damped_trend=False,
                seasonal="add",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(smoothing_level=None, smoothing_seasonal=None)

        elif seasonal == "M":  # Multiplicative
            title = "Trend='N',  Seasonal='M': Exponential Smoothing"
            ETS = ExponentialSmoothing(
                data,
                trend=None,
                damped_trend=False,
                seasonal="mul",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(smoothing_level=None, smoothing_seasonal=None)

    elif trend == "A":  # Additive
        if seasonal == "N":  # None
            title = "Trend='A',  Seasonal='N': Holt’s linear method"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=False,
                seasonal=None,
            )
            model = ETS.fit(
                smoothing_level=None, smoothing_trend=None, damping_trend=None
            )

        elif seasonal == "A":  # Additive
            title = "Trend='A',  Seasonal='A': Additive Holt-Winters’ method"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=False,
                seasonal="add",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(
                smoothing_level=None, smoothing_trend=None, smoothing_seasonal=None
            )

        elif seasonal == "M":  # Multiplicative
            title = "Trend='A',  Seasonal='M': Multiplicative Holt-Winters’ method"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=False,
                seasonal="mul",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(
                smoothing_level=None, smoothing_trend=None, smoothing_seasonal=None
            )

    elif trend == "Ad":  # Additive damped
        if seasonal == "N":  # None
            title = "Trend='Ad', Seasonal='N': Additive damped trend method"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=True,
                seasonal=None,
            )
            model = ETS.fit(
                smoothing_level=None, smoothing_trend=None, damping_trend=None
            )

        elif seasonal == "A":  # Additive
            title = "Trend='Ad', Seasonal='A': Exponential Smoothing"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=True,
                seasonal="add",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(
                smoothing_level=None,
                smoothing_trend=None,
                damping_trend=None,
                smoothing_seasonal=None,
            )

        elif seasonal == "M":  # Multiplicative
            title = "Trend='Ad', Seasonal='M': Holt-Winters’ damped method"
            ETS = ExponentialSmoothing(
                data,
                trend="add",
                damped_trend=True,
                seasonal="mul",
                seasonal_periods=seasonal_periods,
            )
            model = ETS.fit(
                smoothing_level=None,
                smoothing_trend=None,
                damping_trend=None,
                smoothing_seasonal=None,
            )

    if model.mle_retvals.success:
        forecast = [max(i, 0) for i in model.forecast(n_predict)]

    else:
        forecast = []

    return model, title, forecast


# Polynomial Regression

# Importing the libraries
# Importando as bibliotecas
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
# Importando os dados
dataset = pd.read_csv('Position_Salaries.csv')
X = dataset.iloc[:, 1:2].values
y = dataset.iloc[:, 2].values

# Fitting Linear Regression to the Dataset (for comparisson)
# Ajustando os dados para regressão linear (para comparação)
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)

# Fitting Polynomial Regression to the Dataset
# Ajustando os dados para regressão polinomial
from sklearn.preprocessing import PolynomialFeatures
poly_reg = PolynomialFeatures(degree = 4)
X_poly = poly_reg.fit_transform(X)
lin_reg_2 = LinearRegression()
lin_reg_2.fit(X_poly, y)

# Visualising the Linear Regression results
# Visualizando os resultados da Regressão Linear
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg.predict(X), color = 'blue')
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position leve')
plt.ylabel('Salary')
plt.show()

# Visualising the Polynomial Regression results
# Visualizando os resultados da Regressão Polinomial
X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape((len(X_grid), 1))
plt.scatter(X, y, color = 'red')
plt.plot(X_grid, lin_reg_2.predict(poly_reg.fit_transform(X_grid)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position leve')
plt.ylabel('Salary')
plt.show()

# Predicting a new result with Linear Regression
# Predizendo os resultados com Regressão Linear
lin_reg.predict([[6.5]])

# Predicting a new result with Polynomial Regression
# Predizendo os resultados com Regressão polinomial
lin_reg_2.predict(poly_reg.fit_transform([[6.5]]))


#coding: utf-8

from numpy import *

def loadDataSet(fileName, delim='\t'):
	fr = open(fileName)
	stringArr = [line.strip().split(delim) for line in fr.readlines()]
	datArr = [map(float,line) for line in stringArr]
	return mat(datArr)

#topNfeat是可选参数，即选择N个特征。如果不设置，则默认返回全部特征
def pca(dataMat, topNfeat=9999999):
	meanVals = mean(dataMat, axis=0)
	meanRemoved = dataMat - meanVals
	covMat = cov(meanRemoved, rowvar=0)
	eigVals, eigVects = linalg.eig(mat(covMat))
	eigValInd = argsort(eigVals)
	eigValInd = eigValInd[:-(topNfeat+1):-1]
	redEigVects = eigVects[:, eigValInd]
	lowDDataMat = meanRemoved * redEigVects
	reconMat = (lowDDataMat * redEigVects.T) + meanVals
	return lowDDataMat, reconMat

def replaceNanWithMean():
	datMat = loadDataSet('secom.data', ' ')
	numFeat = shape(datMat)[1]
	for i in range(numFeat):
		meanVal = mean(datMat[nonzero(~isnan(datMat[:,i].A))[0], i])
		datMat[nonzero(isnan(datMat[:,i].A))[0],i] = meanVal
	return datMat


#! /usr/bin/env python

"""
File: centered_diff.py

Copyright (c) 2016 Taylor Patti

License: MIT

Takes the first derivative of functions numerically and demonstrates this with a set of
functions and a resulting table.
"""

from math import *
from sympy import diff, lambdify, symbols

def numdiff(f, x, h=1E-5):
    """takes the first derivative of function f"""
    return (f(x + h) - f(x - h)) / (2 * float(h))

def quadratic(x):
    return x**2 + x + 1

def test_quaddiff():
    apt = (abs(numdiff(quadratic, 2) - 5) < 1E-10)
    msg = "Derivatives do not match."
    assert apt, msg

def application():
    """makes list of the difference between the estimated derivative for h=0.01 and
    the analytical derivative"""
    application_list = []
    x = symbols('x')
    print
    print '%20s %20s' % ('Function', 'Error')
    print
    funcvar_tuples = [(lambda t: exp(t), 'exp(x)', float(0)), (lambda t : exp(-2*t**2), \
    'exp(-2*x**2)', float(0)), (lambda t: cos(t), 'cos(x)', 2*pi), (lambda t: log(t), \
    'log(x)', float(1))]
    for i in funcvar_tuples:
        prime = diff(i[1], x)
        exact = lambdify([x], prime)
        error = numdiff(i[0], i[2], 0.01) - exact(i[2])
        print '%20s %21.2E' % (i[1], error)
        print
        application_list.append(error)
    return application_list

def test_accuracycheck():
    """ensures that each item in application is bellow a certain threshold
    and thus above a certain accuracy"""
    f = application()
    apt = True
    for i in f:
        if abs(i) > 1E-3:
            apt = False
    msg = "Too large of an error."
    assert apt, msg


import numpy as np

class SimpleLinearRegression():
    def __init__(self):
        self.coefficient = None
        self.intercept = None

    def fit(self, x, y):
        '''
            Given a dataset with 1 input feature x and output feature y,
            estimates the coefficient and compute the intercept.
        '''
        self.coefficient = self._coefficient_estimate(x, y)
        self.intercept = self._compute_intercept(x, y)

    def predict(self, x):
        '''
            y = b_0 + b_1*x
        '''
        x_times_coeff = np.multiply(x, self.coefficient)
        return np.add(x_times_coeff, self.intercept)

    def r2_score(self, y_true, y_pred):
        '''
            r2 = 1 - (rss/tss)
            rss = sum_{i=0}^{n} (y_i - y_hat)^2
            tss = sum_{i=0}^{n} (y_i - y_bar)^2
        '''
        y_average = np.average(y_true)

        residual_sum_of_squares = 0
        total_sum_of_squares = 0

        for i in range(len(y_true)):
            residual_sum_of_squares += (y_true[i] - y_pred[i])**2
            total_sum_of_squares += (y_true[i] - y_average)**2

        return 1 - (residual_sum_of_squares/total_sum_of_squares)

    def _compute_intercept(self, x, y):
        '''
            intercept = y_bar - coefficient*x_bar

            WHERE:  y_bar = average(y),
                    x_bar = average(x),
                    coefficient = coefficient already estimated
        '''
        # find the average of the array x
        x_average = np.average(x)

        # multiply the coefficient and the average of the x values
        mul = self.coefficient*x_average

        return np.average(y) - mul

    def _coefficient_estimate(self, x, y):
        '''
            coefficient_{x,y} = ∑_{i=0}^{n} (x_i - x_bar) * (y_i - y_bar)
                                _________________________________________
                                ∑_{i=0}^{n} (x_i - x_bar)^2
        '''
        numerator = 0
        denominator = 0

        for i in range(len(x)):
            x_value = x[i]
            y_value = y[i]
            x_average = np.average(x)
            y_average = np.average(y)

            numerator += (x_value - x_average) * (y_value - y_average)
            denominator += (x_value - x_average)**2

        return numerator / denominator


import sys
import numpy as np
import matplotlib.pyplot as plt
#from matplotlib.patches import Circle
import math

def W(x, y):
    """Return the wind vector given a wind speed."""
    r = np.sqrt(x*x+y*y)
    v = V(r)
    if r>0:
        costheta = x/r
        sintheta = y/r
        return [-sintheta*v,costheta*v]
    else:
        return [0,0]

def V(r):
    return 2*r*r*np.exp(-r)

def example(n):
    # Grid of x, y points
    nx, ny = n, n
    x = np.linspace(-2, 2, nx)
    y = np.linspace(-2, 2, ny)

    # Wind field vector components U,V
    U, V = np.zeros((ny, nx)), np.zeros((ny, nx))
    for j in range(ny-1,-1,-1):
        for i in range(0,nx):
            vv = W(x[i],y[j])
            U[j,i]=vv[0]
            V[j,i]=vv[1]

    fig = plt.figure()
    ax1 = fig.add_subplot(1,1,1)

    # Plot the streamlines.
    ax1.streamplot(x, y, U, V, color=np.sqrt(U*U+V*V), cmap='Spectral')
    ax1.set_xlabel('$x$')
    ax1.set_ylabel('$y$')
    ax1.set_xlim(-2,2)
    ax1.set_ylim(-2,2)
    ax1.set_aspect('equal')
    plt.title('Tangential Wind Vectors')
    plt.show()

if __name__=='__main__':
    example(8)


"""
# !/usr/bin/env python
# -*- coding: utf-8 -*-
@Time        : 2022/3/26 13:25
@File        : cross-entropy.py
"""

import numpy as np

# Write a function that takes as input two lists Y, P,
# and returns the float corresponding to their cross-entropy.
def cross_entropy(Y, P):
    """
    cross_entropy
    """
    Y = np.float_(Y)
    P = np.float_(P)

    return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))

udacity-program_self_driving_car_engineer_v1.0/part01-computer vision and deep learning/module03-deep learning/lesson01-introduction to neural networks/exercise04-cross entropy/cross-entropy.py


import numpy as np
from matplotlib import pyplot as plt
from random import randint
from math import sqrt

class RidgeRegression :

	def fit(self, X, y, regularizationParam=0):
		B = regularizationParam * np.eye(X.shape[1])
		B[0, 0] = 0
		self.w = np.dot(np.linalg.pinv(np.dot(X.T, X) + np.dot(B.T, B)),np.dot(X.T, y))

	def predict(self, X):
		h= np.dot(X, self.w)
		return h

	def rmse(self,y,h):

		Ermse=0.00
		for i in range(len(y)):
			Ermse= Ermse+(y[i]-h[i])**2
		Ermse=Ermse*1/len(y)
		Ermse=sqrt(Ermse)
		return Ermse

	def mad(self,y,h):

		Emad=0.00
		for i in range(len(y)):
			Emad=Emad+abs(y[i]-h[i])
		Emad=Emad*1/len(y)
		return Emad

	def rec(self,E):

		Eprev=0.00
		correct=0.00
		acc=0.00
		X_axis,Y_axis=[],[]
		for i in range(len(E)):
			if E[i] > Eprev:
				acc=correct/len(E)
				X_axis.append(Eprev)
				Y_axis.append(acc)
				Eprev=E[i]
			correct+=1
		acc=correct/len(E)
		X_axis.append(E[len(E)-1])
		Y_axis.append(acc)
		plt.plot(X_axis, Y_axis, 'g')
		plt.show()

	def ImportanceOfFeature(self, feature, label):
		return np.corrcoef(feature, label)[0,1]

	def ImportantFeatureLeast(self):
		W= abs(self.w)
		return np.argmin(W)


"""
https://www.numpy.org/
https://stackabuse.com/numpy-tutorial-a-simple-example-based-guide/
"""
import numpy as np

print('\n\n\nCreate\n')
array1 = np.array((1, 2, 3, 4, 5, 6, 7))
array2D = np.array([[2, 4, 6], [8, 10, 12], [14, 16, 18]])  # create 2D array
array3D = np.array([
    [
        [0, 1, 2],
        [0, 1, 2],
        [0, 1, 2],
    ],
    [
        [0, 1, 2],
        [0, 1, 2],
        [0, 1, 2],
    ],
    [
        [0, 1, 2],
        [0, 1, 2],
        [0, 1, 2],
    ],
])
ranged_array = np.arange(2, 9)  # create array using np.arange(start, stop, step, dataType)
ranged_array2 = np.arange(2, 10, 2)
zeros = np.zeros(5)  # create array of 5 zeros
zeros2D = np.zeros((5, 4))
array5D = np.zeros((2, 3, 4, 2, 3))
ones = np.ones(5)  # create array of 5 ones
ones2D = np.ones((5, 4))
linear_array = np.linspace(1, 10, 10)  # create array of linearly-spaced numbers that you want between the specified range
linear_array2 = np.linspace(1, 10, 20)
identity_matrix = np.eye(4)  # create an identity matrix
uniform_distribution = np.random.rand(2, 3)  # create 2d array with random numbers between 0-1 (uniform distribution)
normal_distribution = np.random.rand(2, 3)  # create 2d array with random numbers between 0-1 (Gaussian distribution)
random = np.random.randint(50, 100, 5)  # create an array of 5 random integers between 50 and 100
print('array2D', array2D)
print('array3D', array3D)
print('ranged_array', ranged_array)
print('ranged_array2', ranged_array2)
print('zeros', zeros)
print('zeros2D', zeros2D)
print('array5D', array5D)
print('ones', ones)
print('ones2D', ones2D)
print('linear_array', linear_array)
print('linear_array2', linear_array2)
print('identity_matrix', identity_matrix)
print('uniform_distribution', uniform_distribution)
print('normal_distribution', normal_distribution)
print('random', random)

print('\n\n\nSimple Math\n')
array2 = array1 + 1  # increment all points by 1
array3 = array1 * 2  # multiply all points by 2
array4 = np.array((-1, -2, -3, -4, -5, -6, -7))
array5 = array1 + array2  # join points of arrays
print('array1 type', type(array1))
print('array2', array2)
print('array3', array3)
print('array5', array5)

print('\n\n\nReshape\n')
array1D = np.arange(1, 11)
array2D = array1D.reshape(5, 2)
print('array1D', array1D)
print('array2D', array2D)

print('\n\n\nFind Min/Max Values\n')
random5 = np.random.randint(1, 100, 5)
random5_min = random.min()
random5_max = random.max()
random5_min_index = random.argmin()
random5_max_index = random.argmax()
print('random5', random5)
print('minimum value & index: ', random5_min, random5_min_index)
print('maximum value & index: ', random5_max, random5_max_index)

print('\n\n\nIndexing\n')
sliced_array = random5[1:3]
print('array1[0]', array1[0])
print('random5[4]', random5[4])
print('random5[-1]', random5[-1])
print('random5[1:3]', random5[1:3])
print('sliced_array = random5[1:3]', sliced_array)
print('array2D[1]', array2D[1])
print('array2D[1, 0]', array2D[1, 0])
print('array2D[0, 2]', array2D[1, 0])
print('retrieve the first column: array2D[:, 0]', array2D[:, 0])
print('retrieve the second column: array2D[:, 1]', array2D[:, 0])
print('retrieve the elements from the first two rows and first two columns: array2D[:2, :2]', array2D[:2, :2])

print('\n\n\nArithmetic Operations\n')
array2pw4 = np.arange(1, 16)
array2pw4x2 = array2pw4 + array2pw4
logn = np.lib.scimath.logn
print('array2pw4 = np.arange(1, 16)', array2pw4)
print('array2pw4x2 = array2pw4 + array2pw4', array2pw4 + array2pw4)
print('add scalar: array2pw4 + 10', array2pw4 + 10)
print('sub scalar: array2pw4 - 10', array2pw4 - 10)
print('div scalar: array2pw4 / 5', array2pw4 / 5)
print('mlt scalar: array2pw4 * 5', array2pw4 * 5)
print('np.ln(a)=np.log(a) -> np.log(array2pw4)', np.log(array2pw4))
print('np.log10(array2pw4)', np.log10(array2pw4))
print('np.log2(array2pw4)', np.log2(array2pw4))
print('np.log10(array2pw4)', np.log10(array2pw4))
print('logn(100, array2pw4)', logn(100, array2pw4))
print('np.log(array2pw4)/np.log(100)', np.log(array2pw4)/np.log(100))
print('np.exp(array2pw4)', np.exp(array2pw4))
print('np.sqrt(array2pw4)', np.sqrt(array2pw4))
print('np.square(array2pw4)', np.square(array2pw4))
print('np.sin(array2pw4)', np.sin(array2pw4))
print('np.sinh(array2pw4)', np.sinh(array2pw4))
print('np.sinh(array2pw4)', np.arcsin(array2pw4))
print('np.cos(array2pw4)', np.cos(array2pw4))
print('np.cos(array2pw4)', np.tan(array2pw4))

print('\n\n\nLinear Algebra Operations\n')
x = np.array([2, 4])
y = np.array([1, 3])
# The dot product of the above two vectors is (2 x 1) + (4 x 3) = 14

# without numpy
dot_product = 0
for a, b in zip(x, y):
    dot_product += a * b
print(dot_product)

# with numpy
print((x * y).sum())
print(x.dot(y))

# Matrix Multiplication
X = np.array(([1, 2, 3], [4, 5, 6]))
Y = np.array(([1, 2], [4, 5], [7, 8]))
Z = np.dot(X, Y)
print(Z)
# also if the dimensions of the two matrices match
Z = np.multiply(X, X)
print(Z)

# Finding the Inverse of a Matrix
Y = np.array(([1, 2], [3, 4]))
Z = np.linalg.inv(Y)
print(Z)

# in order to verify if the inverse has been calculated correctly,
# we can take the dot product of a matrix with its inverse,
# which should yield an identity matrix
W = Y.dot(Z)
# the result as expected, Ones in the diagonal and zeros (or very close to zero) elsewhere
print(W)

# Finding the Determinant of a Matrix
X = np.array(([1, 2, 3], [4, 5, 6], [7, 8, 9]))
Z = np.linalg.det(X)
print(Z)  # output should be "6.66133814775094e-16"

# Finding the Trace of a Matrix
X = np.array(([1, 2, 3], [4, 5, 6], [7, 8, 9]))
# The trace of a matrix is the sum of all the elements in the diagonal of a matrix
Z = np.trace(X)
print(Z)  # output should be "15" since the sum of the diagonal elements of the matrix X is 1 + 5 + 9 = 15


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import Dict, Iterator, List, Optional, Union, Literal, Tuple
import numpy as np
from numpy.polynomial.polynomial import polyval as np_polyval
from sklearn import linear_model
from sklearn.preprocessing import PolynomialFeatures

def polyfit(x: List[float], y: List[float], degree: int, weight: List[float] = None):
    """
    least squares polynomial fit
    when degree ==n, y = c0 + c1 * x + c2 * x**2 + ... + cn * x**n

    :param x:
    :param y:
    :param degree:
    :param weight:
    :return: coeff: array, np.array([c0, c1, ... , cn])
             score: int
    """
    skx = list(zip(x))
    skv = list(y)

    skx = np.array(skx).astype(np.float64)
    skv = np.array(skv).astype(np.float64)

    poly = PolynomialFeatures(degree)
    skx_ = poly.fit_transform(skx)
    clf = linear_model.LinearRegression(fit_intercept=False)
    clf.fit(skx_, skv, sample_weight=weight)
    return clf.coef_, clf.score(skx_, skv)

def polyval_derivative(x: Union[float, np.ndarray, List[float]],
                       coeff: List[float]) -> Tuple[Union[float, np.ndarray], Union[float, np.ndarray]]:
    '''
    when degree == 2, len(coeff) = 3,
        y = c0 + c1 * x + c2 * xx
        dy/dx = c1 + 2*c2 * x
    when degree == 3, len(coeff) = 4,
        y = c0 + c1 * x + c2 * xx + c3 * xxx
        dy/dx = c1 + 2*c2 * x + 3*c3 * xx

    :param x:
    :param coeff: [c0, c1, c2, ...]
    :return: y: float
             dy/dx: float
    '''
    x = np.asarray(x)
    y = np_polyval(x, coeff)

    degree = len(coeff) - 1
    dydx = 0.
    for i in range(degree):
        dydx += (i + 1) * coeff[i + 1] * x ** i

    return y, dydx


"""
Модель корректировки размера дивидендов Линтера:
1) D^*_t = g * P_t
2) D_t - D_t-1 = a + b * (D^*_t - D_t-1) + e_0

P_t - текущая прибыль фирмы после уплаты налогов
D_t - дивиденды, которые фирма выплачивает своим акционерам в текущем периоде
D^*_t - целевые дивиденды
g - доля прибыли, направляемая на дивиденды
"""

from pathlib import Path

from sympy import Matrix, solve, Eq

from . import utils

# =========================== ДАНО ===========================

endos = 'D_t D^*_t'
endos = utils.get_symbols_dict(endos)

predeters = 'P_t D_t-1'
predeters = utils.get_symbols_dict(predeters)

params = 'g a b'
params = utils.get_symbols_dict(params)

devs = 'e_0'
devs = utils.get_symbols_dict(devs)

# =========================== СТРУКТУРНАЯ ФОРМА (система) ===========================

eq_system = [
    Eq(endos['D^*_t'],
       params['g'] * predeters['P_t']),
    Eq(endos['D_t'] - predeters['D_t-1'],
       params['a'] + params['b'] * (endos['D^*_t'] - predeters['D_t-1']) + devs['e_0']),
]

# =========================== ПРИВЕДЕННАЯ ФОРМА (система) ===========================

reduced_form_system = solve(eq_system, *endos.values())

# =========================== КОМПОНЕНТЫ ФОРМЫ (матрица) ===========================

Y = utils.create_endos_v(endos)
X = utils.create_predeters_v([1], predeters)

# матрица коэффициентов при ЭНДОГЕННЫХ переменных
A = Matrix([
    [0, 1],
    [1, -params['b']],
])

# матрица коэффициентов при ПРЕДОПРЕДЕЛЕННЫХ (и мнимых) переменных
B = Matrix([
    [0, -params['g'], 0],
    [-params['a'], 0, params['b'] - 1],
])

U = Matrix([
    [0],
    [devs['e_0']],
])

# =========================== ПРИВЕДЕННАЯ ФОРМА (матрица) ===========================

M = utils.calc_M(A, B)
reduced_form_matrix = utils.get_reduced_form(X, Y, A, M, U)

# =========================== ПРОВЕРКА ===========================

assert reduced_form_system == solve(reduced_form_matrix, *endos.values())

# =========================== СОХРАНЕНИЕ PNG ===========================

if __name__ == '__main__':
    OUTPUT_FOLDER = f'../resources/{"_".join(Path(__file__).stem.split("_")[-2:])}'

    utils.save_reduced_system(reduced_form_system, OUTPUT_FOLDER)

    utils.save_components(OUTPUT_FOLDER, A=A, Y=Y, B=B, X=X, U=U)
    utils.save_reduced_matrix(reduced_form_matrix, OUTPUT_FOLDER, dvioptions=['-T', '9cm,3cm', '-O', '3cm,3cm'])


'''
Perceptron Implementation
Steps:
1. Initialize
2. Compute activation u = w.T*x
3. Compute output y = signum(u + b)
4. adapt weights w(n+1) = w(n) + lr*(d(n) - y)*x(n)

This code also includes prediction steps to visualize the perceptron output

Author: Wesin Alves
'''
import numpy as np
import matplotlib.pyplot as plt
# set variables
global x_n,x_tn, line, line_test
from sklearn.model_selection import train_test_split

# Generate dataset
# data has gaussian distribution
number_of_samples = 100
mean_a = -0.5
mean_b = 0.5
sigma_a = 0.1
sigma_b = 0.1
timesteps = 15

x_1a = sigma_a * np.random.randn(number_of_samples//2) + mean_a
x_1b = sigma_b * np.random.randn(number_of_samples//2) + mean_b
x_1 = np.concatenate((x_1a,x_1b), axis=None)

x_2a = sigma_a * np.random.randn(number_of_samples//2) + mean_a
x_2b = sigma_b * np.random.randn(number_of_samples//2) + mean_b
x_2 = np.concatenate((x_2a,x_2b), axis=None)

x_t1 = sigma_a * np.random.randn(number_of_samples//3) + mean_a
x_t2 = sigma_b * np.random.randn(number_of_samples//3) + mean_b

x_3 = x_1 + x_2

b = np.ones(number_of_samples)

x_n = np.array([x_1,x_2, b])

# usefull functions
def quantizer(vector, threshold):
	x = []
	for val in vector:
		if val > threshold:
			x.append(1.0)
		else:
			x.append(-1.0)

	return np.array(x)

def sigum(value):
	val = value[0]
	if val > 0:
		val = 1.0
	else:
		val = -1.0

	return val

# initialization
w_n = np.zeros((3,1))
d_n = quantizer(x_3, 0)
lr = 0.01

X_train, X_test, d_train, d_test = train_test_split(x_n.T,d_n.T, test_size=0.3, random_state=42)
train_samples = X_train.shape[0]
test_sample = X_test.shape[0]

for t in range(timesteps):
	mse = 0
	errors = []
	# compute activation
	u = w_n.T.dot(X_train[t,:])
	# compute response
	y_n = sigum(u)
	# adapt weights
	deltaW = lr * (d_train[t] - y_n) * X_train[t,:]
	w_n = w_n + deltaW
	errors.append(d_train[t] - y_n)
	print(d_train[t] - y_n)
	print(w_n)

mse = np.mean(np.array(errors)**2)
print('MSE: ',mse)

# predictions
test = np.array([1, 1, 1])
y_test = sigum(w_n.T.dot(test))
print(y_test)


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

datos = pd.read_csv("world.csv")

# Desigualdad de Chebyshev
x = np.log10(datos['Population'])
k = 1.5
s = x.std()
m = x.mean()

# valores alrededor de la media
ii = np.abs(x-m) < k*s
n_in = len(x[ii])

# valores en la cola de la derecha
ii = x-m > k*s
n_one_side = len(x[ii])
n = len(x)

#Cotas de la desigualdad
f_che = 1.0 - 1/(k*k)
f_che_one_side = 1/(1+k**2)

titulo = "m = {:.2f}, s={:.2f}, k={:.2f}\n".format(m, s, k)
titulo += "N(m+/-ks)/n = {:.2f} ".format(n_in/n)
titulo += "vs. Chebyshev: N(m+/-ks)/n > {:.2f}\n".format(f_che)
titulo += "N(k)/n = {:.2f}, ".format(n_one_side/n)
titulo += "vs. One Side Chebyshev: N(k)/n < {:.2f}".format(f_che_one_side)

plt.figure(figsize=(8,6))
plt.hist(x, bins=20)
plt.title(titulo)
plt.xlabel('Log10 Population')
plt.ylabel('Histograma')
plt.savefig("distribucion.png")

# Correlaciones

def coef_corr(x, y):
    n = len(x)
    r = np.sum((x-x.mean())*(y-y.mean()))/((n-1) * x.std() * y.std())
    return r

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

r = coef_corr(np.log10(datos['Population']), np.log10(datos['Area (sq. mi.)']))
plt.scatter(np.log10(datos['Population']), np.log10(datos['Area (sq. mi.)']), label="r={:.2f}".format(r))
plt.legend()
plt.xlabel("log10 Population")
plt.ylabel("log10 Area")

plt.savefig("correlaciones.png", bbox_inches='tight')


"""
Bayesian analysis of multiple populations characterized by a J (mean, sample standard
error mean_j, sigma_j) pairs, or alternatively J sets {yi}j of N_j size raw data sets
using a hierarchicaly gaussian model characterized by hyper-parameters
mu, tau govering gaussian prior distribution of population means
using the approach of gelman et al, DBA3 chapter 5, e.g. the famous 8-schools case
"""
import random as rn
import numpy as np
import matplotlib.pyplot as plt
import kimpy_utilities as ku
import arviz as az
from math import *
percentsign = '%'
#-------------------------------------
def mu_params(tau,means_data,sterr_data):
  """ calculates  global mean, spread for know tau
    DBA3 eq. 5.20"""
  nset = len(means_data)
  mu_prec = 0.
  mu_av = 0.
  for j in range(nset):
    mu_precj = 1./(sterr_data[j]**2 + tau**2)
    mu_prec += mu_precj
    mu_av += mu_precj*means_data[j]
  mu_av /= mu_prec
  return mu_av, mu_prec

def tau_post(tau,means_data,sterr_data):
  """ calculate posterior marginal for hyperparamter tau conditioned
  on data set means, std. errs according to DBA3 eqs. 5.20, 5.21 """
  nset = len(means_data)
  mu_av, mu_prec = mu_params(tau,means_data,sterr_data)
  mu_stdev = sqrt(1./mu_prec)
  logprob = 0.
  for j in range(nset):
    mu_precj = 1./(sterr_data[j]**2 + tau**2)
    logprob += 0.5*log(mu_precj) - 0.5*(means_data[j] - mu_av)**2*mu_precj
  tau_prob = mu_stdev*exp(logprob)
  return tau_prob
#
#-------------------------------------
print('\nBayesian hierarchical multiple means analysis')
print(' using Gaussian likelihood model\n')
input_type = int(input('Input data as (1) pairs of mean, std. error (2) J sets of raw data {y_i}j >> '))
#input_type = 1 # debug
means_data = []
sterr_data = []
if(input_type == 1):
  data_file = input('name of file containing one (mean, std. err) per line>> ')
  #data_file = 'EightSchools.dat' # debug
  print('reading mean, std. err data from file ',data_file)
  ku.read_xy(means_data,sterr_data,data_file)
  nset = len(means_data)
else:
  data_raw = []
  while(1):
    data_file = input('name of file  1 set of raw data, one float per line (exit to end input)>> ')
    if(data_file == 'exit'): break
    yj = []
    ku.read_x(yj,data_file)
    data_raw.append(yj)
  # done with file reads,  convert raw data to mean, std errors
  nset = len(data_raw)
  print('# of data sets read: ',nset )
  for j in range(nset):
    muj = ku.average_x(data_raw[j])
    nj = len(data_raw[j])
    mu2j = ku.average_xy(data_raw[j],data_raw[j])
    varj = mu2j - muj*muj
    sterrj = sqrt(varj/(nj - 1))
    means_data.append(muj)
    sterr_data.append(sterrj)
#
print('\ninput data mean    stderr: ')
for j in range(nset):
  print('%12.5f %12.5f ' % (means_data[j],sterr_data[j]))
#
#==============================================================
# compute mean of means, and stdev of means to get overall location, and
# scale range for hyperparameter tau
#==============================================================
mu_mu = ku.average_x(means_data)
mu2 = ku.average_xy(means_data,means_data)
mu_var = mu2 - mu_mu*mu_mu
mu_stdev = sqrt(mu_var)
print('\nglobal mean, stdev of means: %12.5f %12.5f '% (mu_mu,mu_stdev))
tau_range = 8.*mu_stdev
#
#==============================================================
# compute posterior marginal distbn. for hyperparameter mu, tau, the center, spread of means
# assuming a uniform prior for tau and mu
#==============================================================
#NPOINT = 2501 # debug
NPOINT = ku.NPOINT
dtau = tau_range/(NPOINT - 1.)
tau_axis = np.zeros(NPOINT)
tau_prob = np.zeros(NPOINT)
tau_val = 0.
for i in range(NPOINT):
  tau_axis[i] = tau_val
  tau_prob[i] = tau_post(tau_val,means_data,sterr_data)
  tau_val += dtau
tau_prob /= np.max(tau_prob)
tau_cdf = ku.pdf_to_cdf(tau_axis,tau_prob)
ndim = 99
quantile_axis = np.zeros(ndim) # values for inverse cdf of p(tau) for sampling
tau_quantile = np.zeros(ndim) # value of tau for each percentile
for i in range(ndim):
  quantile_axis[i] = 1.*(i+1)
  tau_quantile[i] = ku.quantile(tau_axis,tau_cdf,quantile_axis[i])
tau_up = tau_quantile[95]
print('tau 95{:1s} limits: ({:12.5f} to {:12.5f})'.format(percentsign,0.,tau_up))
#print(tau_axis)
#print(tau_quantile)

plt.figure(1)
plt.title('posterior marginal for hyperparameter tau (spread of means)')
plt.plot(tau_axis,tau_prob,'g-')
plt.plot(tau_axis,tau_cdf,'r-')
plt.xlim(0.,tau_axis[-1])
plt.ylim(0.,1.1)
plt.xlabel('tau ')
plt.ylabel('p(tau|data) ')
plt.show()
#
"""
plt.figure(2)
plt.title(' inverse cdf for p(tau|data')
plt.plot(quantile_axis,tau_quantile,'g-')
plt.xlim(0.,100.)
plt.xlabel('%')
plt.ylabel('tau ')
plt.show()
"""
#
# sample global spread paramter tau from p(tau|data)
# then sample global location mu from p(mu|tau,data) = Normal(mu_mu,mu_stdev)
# (DBA3 eq. between 5.19 and 5.20)
# then for each data set, sample its location parameter theta_j
# from p(theta_j | mu, tau, data) = Normal(theta_av_j, stdev_j)
# (DBA3 eq. 5.17)
rn.seed(123)
tau_sample = []
mu_check = 0.
nsample = 5000
theta_sample = np.zeros((nset,nsample))
#ts = np.zeros((nsample,nset))
print('\nsampling randomly from posterior...')
for i in range(nsample):
  i1 = rn.randint(0,ndim-1)
  tau_val = tau_quantile[i1]
  tau_sample.append(tau_val)
  mu_av, mu_prec = mu_params(tau_val,means_data,sterr_data)
  mu_stdev = sqrt(1./mu_prec)
  mu_val = rn.normalvariate(mu_av,mu_stdev)
  mu_check += mu_val
  #print(i1,tau_val,mu_val)
  for j in range(nset):
    theta_prec = 1./sterr_data[j]**2 + 1./tau_val**2
    theta_stdev = sqrt(1./theta_prec)
    theta_av = (means_data[j]/sterr_data[j]**2 + mu_val/tau_val**2)/theta_prec
    theta_val = rn.normalvariate(theta_av,theta_stdev)
    theta_sample[j][i] = theta_val
    #ts[i][j] = theta_val
mu_check /= nsample
print('mean of means from %d samples: %12.5f   ' % (nsample,mu_check))
#
# extract median, 95% credible interval ranges
for j in range(nset):
  theta_sample[j].sort()
  im = nsample//2
  il = int(nsample*0.025)
  iu = int(nsample*0.975)
  print('set %3d median %12.5f 95%s CI (%12.5f , %12.5f) ' \
  %(j+1,theta_sample[j][im],percentsign,theta_sample[j][il],theta_sample[j][iu]))
#print(theta_sample)
nbins = 20
plt.figure()
#n, bins, patches = plt.hist(theta_sample[0], nbins)
az.plot_forest(theta_sample,quartiles=True) # better than box plot for large data sets
#plt.boxplot(ts)
plt.show()
#
"""
nbins = 20
plt.figure(3)
n, bins, patches = plt.hist(tau_sample, nbins)
plt.show()
"""


# -*- coding:utf-8 -*-
"""
Project   : numpy
File Name : 7_to_8_svd
Author    : Focus
Date      : 8/21/2021 9:29 PM
Keywords  : 1. Singular Value Decomposition
            2.TODO(Yu) eigenvalue decompositon.
            3. Orthogonal Matric: A*Transpose(A) == E or Transpose(A) * A == E
            4. Eigen Decomposition(=Spectral Decomposition)
Abstract  : M = U * Sigma * HermitianConjugate(V)
Param     :
Usage     : py 7_to_8_svd
Reference :
"""
import numpy as np

A = np.mat("4 11 14; 8 7 -2")
print("A \n", A)
U, Sigma, V = np.linalg.svd(A, full_matrices=False)
print("----------\n""U: \n", U)
print("----------\n""Sigma:\n", Sigma)
print("----------\n""V: \n", V)

print("Product: \n", U * np.diag(Sigma) * V)


# Digital Signal Processing - hwk2 - Part 2.2
# Christos Dimopoulos - 03117037

# Exercise 2.2: Linear Prediction Covariance and Autocorrelation Method vs DFT
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
plt.close('all')
counter = 0;

# Part (a): DFT Calculation
n = np.arange(0,101,1);
s = 10*np.cos(0.24*np.pi*n+0.2*np.pi) + 12*np.sin(0.26*np.pi*n - 0.8*np.pi)

# Zero-padding
N = 1024
x = np.zeros(N)
x[0:101] = s

# Plot DFT X[k]
counter = counter+1
plt.figure(counter)
fs = 1
DFT = np.fft.rfft(x)
f = np.fft.rfftfreq(np.size(x))*fs
plt.plot(f,20*np.log10(np.abs(DFT)),'b', label='N = 1024 samples')
plt.xlabel('Frequency (Hertz)')
plt.ylabel('Magnitude (dB)')
plt.title('DFT of signal x[n] after zero-paading')
plt.legend()

# Part (b): Linear Prediction Covariance Method
p = 4
n = np.arange(-p,101,1);
s = 10*np.cos(0.24*np.pi*n+0.2*np.pi) + 12*np.sin(0.26*np.pi*n - 0.8*np.pi)

def cross_correlate(i,k,sig, N,n):
    corr = 0
    for m in range(101):
        a = sig[n+m-i]
        b = sig[n+m-k]
        corr = corr + a*b
    return corr

covariance_matrix = np.zeros((p,p))
for i in range(1,p+1):
    for j in range(1,p+1):
        covariance_matrix[i-1,j-1] = cross_correlate(i,j,s,np.size(s),p)

psi_matrix = np.zeros((p,1))
for i in range(1,p+1):
    psi_matrix[i-1,0] = cross_correlate(i,0,s,np.size(s),p)

LPC = np.matmul(np.linalg.inv(covariance_matrix),psi_matrix)
print("Covariance Method: \n", np.transpose(LPC))

# Part (c): Autocorrelation Method
n = np.arange(0,101,1);
s = 10*np.cos(0.24*np.pi*n+0.2*np.pi) + 12*np.sin(0.26*np.pi*n - 0.8*np.pi)

win_hamming = np.zeros(101)
win_hamming[:] = sp.signal.get_window("hamming", 101, 'true')
v = s*win_hamming
counter = counter+1
plt.figure(counter)
plt.stem(n,v,'b',label='w[n]: Hamming Window', use_line_collection=True)
plt.xlabel('Discrete Time n')
plt.ylabel('Amplitude (V)')
plt.title('Windowed Signal v[n] = s[n]w[n]')
plt.legend()

def autocorrelation(k,N,sig):
    result = 0
    for m in range(p,N-k):
        result = result + sig[m]*sig[m+k]
    return result

Emin0 = autocorrelation(0,np.size(v), v)

# Level 1
k1 = -(autocorrelation(1,np.size(v),v))/Emin0
LPC1 = np.zeros(1)
LPC1[0] = -k1
Emin1 = (1-k1**2)*Emin0

def levinson(sig, prevLPC, prevEmin, level):
    temp = 0
    newLPC = np.zeros(level)
    for j in range(1,level):
        temp = temp+prevLPC[j-1]*autocorrelation(level-j,np.size(sig),sig)
    k = -(autocorrelation(level,np.size(sig),sig)-temp)/prevEmin
    newLPC[level-1] = -k
    for j in range(1,level):
        newLPC[j-1] = prevLPC[j-1]+k*prevLPC[level-j-1]
    newEmin = (1-k**2)*prevEmin
    return (newEmin,newLPC,k)

# Level 2
(Emin2,LPC2,k2) = levinson(v,LPC1,Emin1,2)
# Level 3
(Emin3,LPC3,k3) = levinson(v,LPC2,Emin2,3)
# Level 4
(Emin4,LPC4,k4) = levinson(v,LPC3,Emin3,4)

print("\nAutocorrelation Method: \n", LPC4)

# Part (d): Time for some plotting
win_hamming = np.ones(101)
win_hamming = sp.signal.get_window("hamming", 101, 'true')
x[0:101] = x[0:101]*win_hamming

LPCcov = np.ones(p+1)
LPCcov [1:] = -LPC[:,0]
predict1 = sp.signal.lfilter(LPCcov,1,x)
ws1, Acov = sp.signal.freqz(LPCcov, 1, fs = 1)

LPCaut = np.ones(p+1)
LPCaut [1:] = -LPC4
predict2 = sp.signal.lfilter(LPCaut,[1],x)
ws2, Aaut = sp.signal.freqz(LPCaut, 1, fs = 1)

counter = counter+1
plt.figure(counter)
plt.plot(f,20*np.log10(np.abs(DFT)),'b', label='Spectrum of windowed signal v[n]')
plt.plot(ws1,20*np.log10(1/np.abs(Acov)),'r', label='1/|Acov| - Covariance Method')
plt.plot(ws2,20*np.log10(np.abs(1/Aaut)),'--g', label='1/|Aaut| - Autocorrelation Method')
plt.xlabel('Frequency (Hertz)')
plt.ylabel('Magnitude (dB)')
plt.title('Spectrum of signal s[n] and LPC Analysis Amplitude Responses')
plt.legend()
plt.show()

print("\n")
roots_cov = np.roots(LPCcov)
roots_aut = np.roots(LPCaut)
print("Zeros of Acov:\n ",roots_cov)
print("Zeros of Aaut:\n ",roots_aut)


import networkx as nx
import numpy as np
import scipy.sparse
import scipy.sparse.linalg

def SpringRank(A,alpha=0.,l0=1.0,l1=1.0):
    """
    Main routine to calculate SpringRank by solving linear system
    Default parameters are initialized as in the standard SpringRank model

    INPUT:

        A=network adjacency matrix (can be weighted)
        alpha: controls the impact of the regularization term
        l0: regularization spring's rest length
        l1: interaction springs' rest length

    OUTPUT:

        rank: N-dim array, indeces represent the nodes' indices used in ordering the matrix A

    """
    N=A.shape[0]
    k_in=np.sum(A,0)
    k_out=np.sum(A,1)
    One=np.ones(N)

    C= A+A.T
    D1 = np.zeros(A.shape)
    D2 = np.zeros(A.shape)

    for i in range(A.shape[0]):
        D1[i,i]=k_out[i,0]+k_in[0,i]
        D2[i,i]=l1*(k_out[i,0]-k_in[0,i])

    if alpha!=0.:
        print('Using alpha!=0: matrix is invertible')

        B=One*alpha*l0+np.dot(D2,One)
        A=alpha*np.eye(N)+D1-C
        A=scipy.sparse.csr_matrix(np.matrix(A))

        try:
            print('Trying scipy.sparse.linalg.spsolve(A,B)')
            rank = scipy.sparse.linalg.spsolve(A,B)
            # rank=np.linalg.solve(A,B)  # cannot use it with sparse matrices
            return np.transpose(rank)
        except:
            print('Switched to scipy.sparse.linalg.bicgstab(A,B)[0]')
            rank=scipy.sparse.linalg.bicgstab(A,B)[0]
            return np.transpose(rank)

    else:
        print('Using faster computation: fixing a rank degree of freedom')

        C= C+np.repeat(A[N-1,:][None],N,axis=0)+np.repeat(A[:,N-1].T[None],N,axis=0)
        D3 = np.zeros(A.shape)
        for i in range(A.shape[0]):D3[i,i]=l1*(k_out[N-1,0]-k_in[0,N-1])

        B=np.dot(D2,One)+np.dot(D3,One)
        # A=D1-C
        A=scipy.sparse.csr_matrix(np.matrix(D1-C))
        rank = scipy.sparse.linalg.spsolve(A,B)
        return np.transpose(rank)

def SpringRank_planted_network(N,beta,alpha,K,prng,l0=0.5,l1=1.):
    '''

    Uses the SpringRank generative model to build a directed, possibly weigthed and having self-loops, network.
    Can be used to generate benchmarks for hierarchical networks

    Steps:
        1. Generates the scores (default is factorized Gaussian)
        2. Extracts A_ij entries (network edges) from Poisson distribution with average related to SpringRank energy

    INPUT:

        N=# of nodes
        beta= inverse temperature, controls noise
        alpha=controls prior's variance
        K=E/N  --> average degree, controls sparsity
        l0=prior spring's rest length
        l1=interaction spring's rest lenght

    OUTPUT:
        G: nx.DiGraph()         Directed (possibly weighted graph, there can be self-loops)

    '''
    G=nx.DiGraph()

    scores=prng.normal(l0,1./np.sqrt(alpha*beta),N)  # planted scores ---> uses factorized Gaussian
    for i in range(N):G.add_node(i,score=scores[i])

    #  ---- Fixing sparsity i.e. the average degree  ----
    Z=0.
    for i in range(N):
        for j in range(N):
            Z+=np.exp(-0.5*beta*np.power(scores[i]-scores[j]-l1,2))
    c=float(K*N)/Z
    #  --------------------------------------------------

    # ----  Building the graph   ------------------------
    for i in range(N):
        for j in range(N):

            H_ij=0.5*np.power((scores[i]-scores[j]-l1),2)
            lambda_ij=c*np.exp(-beta*H_ij)

            A_ij=prng.poisson(lambda_ij,1)[0]

            if A_ij>0:G.add_edge(i,j,weight=A_ij)

    return G

import numpy,import scipy,import networkx


import numpy as np
import matplotlib.pyplot as plt
DEBUG = False

def sigmoid(Xm):
    Xmcp = np.copy(Xm)
    for x in np.nditer(Xmcp, op_flags=['readwrite']):
        x[...] = 1/(1 + np.exp(-x))
    return Xmcp

# Inputs:
# W1m = [[w10,w11,w12]
#        [w20,w21,w22]]
def y_fw_propagate(W1m,W2m,Xm):
    Bm = np.matmul(W1m,Xm.transpose())
    Zm = sigmoid(Bm)
    #Zpm =  (np.concatenate((np.array([[1,1,1,1]]),Zm),axis=0))
    Zpm =  (np.concatenate((np.ones((1,Xm.shape[0])),Zm),axis=0))
    if(DEBUG):
        print("Zpm:")
        print(Zpm)
    A1m = np.matmul(W2m,Zpm)
    return sigmoid(A1m),Zm

def stepGradient(Xm,Y1m,Tm,Zm,W1m,W2m,gamma):
    #compute W^(2) gradient
    #W10,W11,W12

    #remove W10
    #W2pm = [[W11,W12]]
    W2pm = np.delete(W2m,0).reshape(1,2)

    Zpm =  (np.concatenate((np.array([[1,1,1,1]]),Zm),axis=0))
    DT2m = (Y1m - Tm)*Y1m*(1 - Y1m)
    gradient_W2m = DT2m*Zpm
    if(DEBUG):
        print("gradient_W2m")
        print(gradient_W2m)
    gradient_W2m = gradient_W2m.sum(axis=1) #returns row vector

    #compute W^(1) gradient
    # Wji, ji = 10,20,11,21,12,22
    # 4 columns  for n = 1,2,3,4
    # 2 rows for W(j=1), W(j=2)
    gradient_W1i0m = DT2m*W2pm.transpose()*Zm*(1-Zm)*Xm[:,[0]].transpose() #W(j=1,2)(i=0), n=1,2,3
    gradient_W1i1m = DT2m*W2pm.transpose()*Zm*(1-Zm)*Xm[:,[1]].transpose() #W(j=1,2)(i=1), n=1,2,3
    gradient_W1i2m = DT2m*W2pm.transpose()*Zm*(1-Zm)*Xm[:,[2]].transpose() #W(j=1,2)(i=2), n=1,2,3
    gradient_W1m = np.concatenate((gradient_W1i0m,gradient_W1i1m),axis=0)
    gradient_W1m = np.concatenate((gradient_W1m,gradient_W1i2m),axis=0)

    if(DEBUG):
        print("gradient_W1m")
        print(gradient_W2m)
    gradient_W1m = gradient_W1m.sum(axis=1) # sum and return Wji only
    #At this point, returns gradient_W1m row vector 10,20,11,21,12,22
    gradient_W1m = gradient_W1m.reshape(3,2).transpose()

    W1m_next = W1m - gamma*gradient_W1m
    W2m_next = W2m - gamma*gradient_W2m
    return W1m_next,W2m_next

def main():
    Xm = [
         [1,0,0],
         [1,0,1],
         [1,1,0],
         [1,1,1]
        ]
    Xm = np.array(Xm)
    Tm = [[0,1,1,0]]
    Tm = np.array(Tm)
    W2m = np.random.randn(1,3)*np.sqrt(2.0/3.0)
    W2m = np.array(W2m)
    W1m = np.random.randn(2,3)*np.sqrt(2.0/6.0)
    W1m = np.array(W1m)

    Ym = None
    Zm = None
    gamma = 0.25
    epochs = 10000

    for i in range(epochs):
        Ym,Zm = y_fw_propagate(W1m,W2m,Xm)
        W1m,W2m = stepGradient(Xm,Ym,Tm,Zm,W1m,W2m,gamma)

    #training complete
    stdout = "W1_ji = \n" +\
    "[[W10,W11,W12],\n" +\
    "[W20,W21,W22]]"
    print(stdout)
    print(W1m)
    stdout = "W2_kj = \n" +\
    "[[W10,W11,W12]]"
    print(stdout)
    print(W2m)

    #Apply inputs and print output
    Ym,Zm = y_fw_propagate(W1m,W2m,Xm)
    stdout = "\ny(%s) =\n %s\n"%(Xm,Ym.transpose())
    print(stdout)

    #Apply inputs and print output
    X2m = [
            [1,0,0],
            [1,0,0],
            [1,0,0],
            [1,1,1],
            [1,1,0],
            [1,1,0],
            [1,0,1],
            [1,1,1]
          ]
    X2m = np.array(X2m)
    Ym,Zm = y_fw_propagate(W1m,W2m,X2m)
    stdout = "y(%s) =\n %s\n"%(X2m,Ym.transpose())
    print(stdout)

if __name__ == "__main__":
    main()


# Naive implementation of::
# Basu, SIAM, 1973: 10.1137/0710045

import numpy as np

def TCheb(n, x):
    if n == 0:
        return 1.0
    elif n == 1:
        return x
    else:
        return 2.0*x*TCheb(n-1, x) - TCheb(n-2, x)

def get_coeff_mat(f, *, m, n):

    xroots = np.cos((2*np.arange(0, m+1, 1) + 1)*np.pi/(2*(m+1)))
    yroots = np.cos((2*np.arange(0, n+1, 1) + 1)*np.pi/(2*(n+1)))

    a = np.zeros((m+1, n+1))
    for i in range(m+1):
        for j in range(n+1):
            dsum = 0
            for r in range(0, m+1):
                for s in range(0, n+1):
                    xr = xroots[r]
                    ys = yroots[s]
                    dsum += f(xr, ys)*TCheb(i, xr)*TCheb(j, ys)
            a[i,j] = dsum
    return 4.0/((m+1)*(n+1))*a

def eval_naive(*, amat, x, y):
    m, n = amat.shape
    m -= 1
    n -= 1

    dsum = 0
    for i in range(m+1):
        for j in range(n+1):
            contrib = amat[i, j]*TCheb(i, x)*TCheb(j, y)
            if i == j == 0:
                contrib /= 4
            elif i == 0 and j > 0:
                contrib /= 2
            elif j == 0 and i > 0:
                contrib /= 2
            dsum += contrib
    return dsum

def eval_Clenshaw(*, amat, x, y):
    m, n = amat.shape
    m -= 1
    n -= 1

    def Clenshaw(c, ind):
        N = len(c) - 1
        u_k = 0; u_kp1 = 0; u_kp2 = 0
        k = 0
        for k in range(N, -1, -1):
            # Do the recurrent calculation
            u_k = 2.0*ind*u_kp1 - u_kp2 + c[k]
            if k > 0:
                # Update the values
                u_kp2 = u_kp1; u_kp1 = u_k
        return (u_k - u_kp2)/2

    b = np.zeros((m+1))
    for i in range(len(b)):
        b[i] = Clenshaw(amat[i,:], y)
    return Clenshaw(b, x)


import numpy as np
import pandas as pd
from random import uniform

class Kmeans:
    def __init__(self):
        self.data = None
        self.centroid_centers = None
        self.classes = None

    @staticmethod
    def __euclidian_distance(p1: np.ndarray, p2: np.ndarray) -> float:
        return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5

    def __get_random_centroid_centers(self, k: int) -> np.array:
        describe = self.data.describe()

        # Define space to create a random centroid center
        lim_x_min = describe['x'].min()
        lim_x_max = describe['x'].max()
        lim_y_min = describe['y'].min()
        lim_y_max = describe['y'].max()

        centroid_centers = np.zeros([k, 2])
        for i in range(k):
            centroid_centers[i, 0] = uniform(lim_x_min, lim_x_max)
            centroid_centers[i, 1] = uniform(lim_y_min, lim_y_max)

        return centroid_centers

    def __get_data_classes(self, k:int, centroid_centers: np.array) -> np.array:
        data_length = len(self.data)
        np_classes = np.zeros([data_length])
        for i in range(data_length):
            point = self.data.iloc[i].values
            centers_distance = np.zeros([k])
            for j in range(k):
                centers_distance[j] = self.__euclidian_distance(p1=centroid_centers[j], p2=point)
            np_classes[i] = np.where(centers_distance == np.amin(centers_distance))[0][0]
        return np_classes

    def __update_centroid_centers(self, centroid_centers: np.array, np_classes: np.array):
        for update_class in np.unique(np_classes).astype(int):
            centroid_centers[update_class][0] = self.data.iloc[np.where(np_classes == update_class)[0]].describe()['x']['mean']
            centroid_centers[update_class][1] = self.data.iloc[np.where(np_classes == update_class)[0]].describe()['y']['mean']
        return centroid_centers

    def fit(self, k: int, data: pd.DataFrame, centroid_centers: np.array, random: bool = False, n: int = 100):
        self.data = data
        if random:
            centroid_centers = self.__get_random_centroid_centers(k=k)
        count = 0
        while count < n:
            self.classes = self.__get_data_classes(k=k, centroid_centers=centroid_centers)
            centroid_centers = self.__update_centroid_centers(centroid_centers=centroid_centers, np_classes=self.classes)
            count += 1

        self.centroid_centers = centroid_centers


# Code is taken from https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def wrap_np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    result = np_seg_intersect(np.array([np.array(a[0]), np.array(a[1])]), np.array([np.array(b[0]), np.array(b[1])]), considerCollinearOverlapAsIntersect)

    if type(result) == np.ndarray:
        return result.tolist()
    else:
        return result

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None

def does_intersect(a, b):
    result = wrap_np_seg_intersect(a, b, True)
    if not (type(result) == bool or result is None):
        return True
    elif result == True:  # co linear overlap
        return True
    return False


from collections import namedtuple
import numpy as np
import matplotlib.pyplot as plt
from numba import jit

Electrode = namedtuple("Electrode", ["x", "y", "r", "V"])

@jit
def setup(electrodes, dx, N):
    is_boundary = np.empty((N, N), dtype="bool")
    V = np.empty((N, N))
    for i in range(N):
        for j in range(N):
            boundary_value = None
            if i == 0 or j == 0 or i == N - 1 or j == N - j:
                is_boundary[i][j] = True
                boundary_value = 0.0
            else:
                is_boundary[i][j] = False
                for e in electrodes:
                    x = i * dx
                    y = j * dx
                    if (x - e.x)**2 + (y - e.y)**2 < e.r**2:
                        is_boundary[i][j] = True
                        boundary_value = e.V
                        break
            V[i][j] = boundary_value if is_boundary[i][j] else 0
    return V, is_boundary

@jit
def SOR(V, is_boundary, eps, alpha, dx):
    N = V.shape[0]
    steps = 0
    while True:
        for i in range(1, N - 1):
            for j in range(1, N - 1):
                if not is_boundary[i][j]:
                    V_star = (V[i + 1][j] + V[i - 1][j] + V[i][j - 1] + V[i][j + 1]) / 4.0
                    Delta_V = V_star - V[i][j]
                    V[i][j] += alpha * Delta_V
        bad = False
        for i in range(1, N - 1):
            for j in range(1, N - 1):
                if not is_boundary[i][j]:
                    laplace = (V[i - 1][j] - 2 * V[i][j] + V[i + 1][j]) / dx**2 + (V[i][j - 1] - 2 * V[i][j] + V[i][j + 1]) / dx**2
                    if abs(laplace) >= eps:
                        bad = True
                        break
            if bad: break
        steps += 1
        if not bad: return steps

N = 100
L = 1.0
dx = L / (N - 1)
eps = 1e-5
alpha = 1.7

electrodes = [
    Electrode(0.2, 0.2, 0.1, +1),
    Electrode(0.4, 0.2, 0.1, -1),
    Electrode(0.7, 0.3, 0.1, +1),
    Electrode(0.5, 0.8, 0.1, +1),
]

V, is_boundary = setup(electrodes, dx, N)
steps = SOR(V, is_boundary, eps, alpha, dx)
print(steps, "steps needed")

x = y = np.linspace(0, L, N)
plt.pcolormesh(x, y, V)
plt.xlabel("x")
plt.ylabel("y")
plt.title("V")
plt.colorbar()
plt.show()


import numpy as np
from nptyping import NDArray

def _RotZ(roll: float) -> NDArray[(4,4), float]:
    c = np.cos(roll)
    s = np.sin(roll)
    return np.array([
        [c, -s, 0, 0],
        [s,  c, 0, 0],
        [0,  0, 1, 0],
        [0,  0, 0, 1]
        ])

def _RotY(pitch: float) -> NDArray[(4,4), float]:
    c = np.cos(pitch)
    s = np.sin(pitch)
    return np.array([
        [ c, 0, s, 0],
        [ 0, 1, 0, 0],
        [-s, 0, c, 0],
        [ 0, 0, 0, 1]
        ])
def _RotX(yaw: float) -> NDArray[(4,4), float]:
    c = np.cos(yaw)
    s = np.sin(yaw)
    return np.array([
        [1, 0,  0, 0],
        [0, c, -s, 0],
        [0, s,  c, 0],
        [0, 0,  0, 1]
        ])

def Rot(roll:float, pitch:float, yaw:float) -> NDArray[(4,4), float]:
    tmp = np.dot(_RotZ(roll), _RotY(pitch))
    return np.dot(tmp, _RotX(yaw))

def Trans(x:float,y:float,z:float)->NDArray[(4,4), float]:
    return np.array([
        [1, 0, 0, x],
        [0, 1, 0, y],
        [0, 0, 1, z],
        [0, 0, 0, 1]
    ])

def homogeneous_transformation_matrix(a:float, alpha:float, d:float, theta:float)->NDArray[(4,4),float]:
    """Compute homogeneous transformation matrix based on Modified DH comvention.
    In this function, homogeneous transformation matrix is computed bellow following
    T^{i-1}_{i} = Trans(a,0,0)*RotX(alpha)*Trans(0,0,d)*RotZ(theta)

    Parameters
    --------
    a : float
        The length of straight lines perpendicular
        from the z_{i-1} axis to the z_{i} axis.
    alpha : float
        The angle between the z_{i-1} axis and the z_{i} axis.
    d : float
        The length between i-1 coordinate origin O_{i-1} and i coordinate origin O_{i}
        when translate direction z axis.
    theta : float
        The angle at which x_{i-1} axis and x_{i} axis match
        when rotated around z_{i-1} axis

    Returns
    --------
    numpy.ndarray(4,4) : homogeneous transformation matrix

    Raises:
    --------
    ValueError
    """
    ta = Trans(a, 0, 0)
    rx = _RotX(alpha)
    td = Trans(0, 0, d)
    rz = _RotZ(theta)

    ret = np.eye(4)
    ret = np.dot(ret, ta)
    ret = np.dot(ret, rx)
    ret = np.dot(ret, td)
    ret = np.dot(ret, rz)
    return ret


"""
Author: Xenon Han
Last modified date: 30/11/2021
"""
import numpy as np

# Give me the adjacency matrix first, from left to right, up to down, each separate by space
# Actually the hub matrix and the authority matrix is transport relation, but no sure which one the user want to use, so
# simple ask for all
hub_matrix = list(
    input("Give me the adjacency matrix for the hub, from left to right, up to down, each separate by space: ").split(
        " "))
authority_matrix = list(
    input(
        "Give me the adjacency matrix for the authority, from left to right, up to down, each separate by space: ").split(
        " "))
v = int(input("Input the number of vertices: "))
hub = [1] * v  # initialize to 1, you can change to other value
authority = [1] * v  # initialize to 1, you can change to other value

hub_matrix = list(map(float, hub_matrix))  # convert from string to float
authority_matrix = list(map(float, authority_matrix))  # convert from string to float

# transform the list the adjacency matrix
M = np.array(hub_matrix).reshape(v, int(len(hub_matrix) / v))
M_t = np.array(authority_matrix).reshape(v, int(len(authority_matrix) / v))

print("adjacency matrix for hub:\n", M)
print("\nadjacency matrix for authority:\n", M_t)

MM_t = np.dot(M, M_t)  # h = MM_T * h
M_tM = np.dot(M_t, M)  # a = M_tM * a
print("\nMM_t for hub:\n", MM_t)
print("\nM_tM for authority:\n", M_tM)

vector_sum = 0
iteration = 0
old_hub = hub
old_authority = authority

# calculate hub till convergence
while True:
    hub = np.dot(MM_t, hub)
    vector_sum = np.sum(hub, axis=0)
    hub = hub / vector_sum * v  # normalization
    if np.all(hub - old_hub < 0.00001):  # vector with 0's
        break
    print("\niteration", iteration, "hub value:\n", hub)
    old_hub = hub
    iteration += 1

print("Hub vector convergence at iteration", iteration, ", FINAL hub value:\n", hub)

# calculate authority till convergence
iteration = 0
while True:
    authority = np.dot(M_tM, authority)
    vector_sum = np.sum(authority, axis=0)
    authority = authority / vector_sum * v  # normalization
    if np.all(authority - old_authority < 0.00001):  # vector with 0's
        break
    print("\niteration", iteration, "authority value:\n", authority)
    old_authority = authority
    iteration += 1

print("Authority vector convergence at iteration", iteration, ", FINAL authority value:\n", authority)

np.set_printoptions(suppress=True)
print("\n\nNormalized Hub Weight: ", np.round(hub, 4))
print("Normalized Authority Weight: ", np.round(authority, 4))


"""
Created on Fri Sep  6 11:33:15 2019
@author: Emerson Boeira
"""
#%% Header: import python packages and libraries

import numpy as np  # important package for scientific computing
from scipy import signal  # signal processing library
import matplotlib.pyplot as plt  # library to plot graphics
import pysid.correlation as corr # import pysid.correlation modules and functions

#%% Example

Nn = 100000
sigma2e = 5
e = np.random.normal(0, np.sqrt(sigma2e), Nn)

#B = np.array([1])
#A = np.array([1])

A = np.array([1, -1.7, 0.72])
B = np.array([1, -0.95])

D = np.array([1])
C = np.array([1])

S1 = signal.TransferFunction(B, A, dt=1)
S2 = signal.TransferFunction(D, C, dt=1)

_, y = signal.dlsim(S1, e)
_, w = signal.dlsim(S2, e)

y = y[:,0]
w = w[:,0]

maxlag = 15

#%% Calculating theoretical cross covariance function

ryw, tau = corr.arma_ccorr(B, A, D, C, sigma2e, maxlag)

#%% Calculating sample-based cross covariance with np.cov()

ryw2, tau2 = corr.smpl_ccorr(y, w, maxlag)

#%% Graphics

plt.figure()
plt.stem(tau, ryw)
plt.stem(tau2, ryw2, 'r--')
plt.grid(True)
plt.xlabel(r'$\tau$ (samples)')
plt.ylabel(r'$r_{yw}(\tau)$')
plt.legend(('Red = Theoretical','Blue = Sampled-based'))
#plt.savefig('ex_ccorr.eps')
plt.show()


import numpy as np

a = (45/180) * np.pi
R = np.array(
        [[np.cos(a), -np.sin(a), 0],
          [np.sin(a), np.cos(a), 0],
          [0, 0, 1]])

print("\nR = ")
print(R)

S = np.diag([1.0, 2.0, 3.0])
A = np.dot(np.dot(R, S), R.T)
print("\nA = ")
print(A)

evals, evecs = np.linalg.eig(A)
idx = np.argsort(np.abs(evals)) # smallest first
U = evecs[:, idx] # sort columns
D = np.diag(evals[idx])

assert np.allclose(np.abs(R), np.abs(U))
assert np.allclose(D, S)
assert np.allclose(A, np.dot(U, np.dot(D, U.T)))

a = -a
RR = np.array([[np.cos(a), -np.sin(a), 0],
     [np.sin(a), np.cos(a), 0],
     [0, 0, 1]])

assert np.allclose(R.T, RR)


import numpy as np
import math

def gauss(stdev, rank):
    """Creates Gauss average mask given stdev and rank"""

    def gaussian_f(r):
        """Gaussian function"""
        num = math.e ** (- (r ** 2) / (2 * (stdev ** 2)))
        den = stdev * math.sqrt(2 * math.pi)

        return num / den

    mask = np.fromfunction(
        lambda x, y: gaussian_f(abs(x - rank // 2) + abs(y - rank // 2)),
        (rank, rank),
        dtype=float)

    # Normalize mask to have unitary sum of elements
    return mask / np.sum(mask)

def avg(rank):
    """Creates Average mask given rank"""
    return np.ones((rank, rank)) / (rank ** 2)

# Commonly used masks for convenience
sharpen = [
    np.array([[0,  -1,  0],
              [-1,  5, -1],
              [0,  -1,  0]]),

    np.array([[-1, -1, -1],
              [-1,  9, -1],
              [-1, -1, -1]]),

    np.array([[1, -2,  1],
              [-2, 5, -2],
              [1, -2,  1]])
]

prewitt = [
    np.array([[-1, -1, -1],
              [0,   0,  0],
              [1,   1,  1]]),

    np.array([[-1, 0, 1],
              [-1, 0, 1],
              [-1, 0, 1]])
]

sobel = [
    np.array([[-1, -2, -1],
              [0,   0,  0],
              [1,   2,  1]]),

    np.array([[-1, 0, 1],
              [-2, 0, 2],
              [-1, 0, 1]])
]


# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as ply
from PIL import Image
#import scipy.signal as spy
import functions as func

Img = Image.open('..\data_src\img_src\SAR_Troms.tiff')

width, height = Img.size

img = np.array(Img).astype(np.uint8)

ply.imshow(img, cmap = 'Greys_r')
ply.title('Original')
ply.colorbar()
ply.show()

# a)
h_val = np.zeros(256, dtype = int)

#Computing histogram
for y in range(0, height):
  for x in range(0, width):
    h_val[img[y, x]] += 1

#Plotting histogram
ply.bar(range(len(h_val)), h_val)
ply.title("Histogram")
ply.show()

# b )
# Extracting two 100x100 images
oya = img[625:725, 790:890]#nordspissen
ocean = img[790:890, 1200:1300]#hav sør for øyen

h_val2 = np.zeros(256, dtype = int)#oya histogram values
h_val3 = np.zeros(256, dtype = int)#ocean histogram values

#Computing histogram of oya and ocean
for y in range(0, 100):
  for x in range(0, 100):
    h_val2[oya[y, x]] += 1
    h_val3[ocean[y, x]] += 1
  #end for x
#end for y

#Displaying histogram for oya
ply.bar(range(len(h_val2)), h_val2)
ply.title('Histogram of Tromsoya')
ply.show()

ply.imshow(oya, cmap = 'Greys_r')
ply.title('100x100 cutout of Tromsoya')
ply.colorbar()
ply.show()

#Displaying histogram for ocean
ply.bar(range(len(h_val3)), h_val3, align = 'center')
ply.title('Histogram of ocean')
ply.show()

ply.imshow(ocean, cmap = 'Greys_r')
ply.title('100x100 cutout of ocean')
ply.colorbar()
ply.show()

print('Mean, oya:', np.mean(oya)                   )
print('Standard deviation, oya:',  np.std(oya)     )
print('Mean, ocean:', np.mean(ocean)               )
print('Standard deviation, ocean:', np.std(ocean)  )

# c)
# Apply smoothing filter
smoothmask = 1/49 * np.ones((7, 7), dtype=int)#Averaging

smoothedimg = func.myconv2(img, smoothmask).astype(np.uint8)#Convolving with mask

#smoothedimg = (smoothedimg**1.06).astype(np.uint8) #Gamma correction

ply.imshow(smoothedimg, cmap='Greys_r')
ply.title('Smoothed, 7x7 averaging mask')
ply.colorbar()
ply.show()

# d)
#Computing new histograms
h_val = np.zeros(256, dtype = int)

#Computing histogram
for y in range(0, height):
  for x in range(0, width):
    h_val[smoothedimg[y, x]] += 1

#Plotting histogram
ply.bar(range(len(h_val)), h_val)
ply.title("Histogram, smoothed")
ply.show()

#Looking at histogram of ocean and island
oya = smoothedimg[625:725, 790:890]#nordspissen
ocean = smoothedimg[790:890, 1200:1300]#hav sør for øyen

h_val2 = np.zeros(256, dtype = int)#oya histogram values
h_val3 = np.zeros(256, dtype = int)#ocean histogram values

#Computing histogram of oya and ocean
for y in range(0, 100):
  for x in range(0, 100):
    h_val2[oya[y, x]] += 1
    h_val3[ocean[y, x]] += 1
  #end for x
#end for y

#Displaying histogram for oya
ply.bar(range(len(h_val2)), h_val2)
ply.title('Histogram of Tromsoya, smoothed')
ply.show()

ply.imshow(oya, cmap = 'Greys_r')
ply.title('100x100 cutout of Tromsoya, smoothed')
ply.colorbar()
ply.show()

#Displaying histogram for ocean
ply.bar(range(len(h_val3)), h_val3)
ply.title('Histogram of ocean, smoothed')
ply.show()

ply.imshow(ocean, cmap = 'Greys_r')
ply.title('100x100 cutout of ocean, smoothed')
ply.colorbar()
ply.show()

print('Mean, oya, smoothed:', np.mean(oya)                   )
print('Standard deviation, oya, smoothed:',  np.std(oya)     )
print('Mean, ocean, smoothed:', np.mean(ocean)               )
print('Standard deviation, ocean, smoothed:', np.std(ocean)  )