from copy import deepcopy
import numpy as np
from PIL import Image
from scipy.special import binom
from scipy.ndimage import convolve
import matplotlib.pyplot as plt

def loadimg(path):
    """ Load image file
    Args:
        path: path to image file
    Returns:
        image as (H, W) np.array normalized to [0, 1]
    """

    #
    # You code here
    #
    img = Image.open(path)
    # why not normalized?
    img = np.array(img)
    return img

def gauss2d(sigma, fsize):
    """ Create a 2D Gaussian filter

    Args:
        sigma: width of the Gaussian filter
        fsize: (W, H) dimensions of the filter
    Returns:
        *normalized* Gaussian filter as (H, W) np.array
    """

    #
    # You code here
    #

    m, n = fsize
    # Just make matrixes
    x = np.arange(-m/2+0.5,m/2)
    y = np.arange(-n/2+0.5,n/2)
    # https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html
    # Expand rank
    X,Y = np.meshgrid(x, y, sparse = True)
    g = np.exp(-(X**2 + Y**2)/(2*sigma**2))
    # Normalization
    return g/np.sum(g)

def binomial2d(fsize):
    """ Create a 2D binomial filter

    Args:
        fsize: (W, H) dimensions of the filter
    Returns:
        *normalized* binomial filter as (H, W) np.array
    """

    #
    # You code here
    #

    def binomialfactors(n):
        a = np.array([[binom(n-1,i) for i in range(0, n)]])
        return a

    x = binomialfactors(fsize[0])
    y = binomialfactors(fsize[1])
    f = y.T * x

    return f/np.sum(f)

def downsample2(img, f):
    """ Downsample image by a factor of 2
    Filter with Gaussian filter then take every other row/column

    Args:
        img: image to downsample
        f: 2d filter kernel
    Returns:
        downsampled image as (H, W) np.array
    """

    #
    # You code here
    #

    # filter with Gaussian filter
    img_result = convolve(img,f,mode = "mirror")

    # take every other row/column
    return img_result[::2,::2]

def upsample2(img, f):
    """ Upsample image by factor of 2

    Args:
        img: image to upsample
        f: 2d filter kernel
    Returns:
        upsampled image as (H, W) np.array
    """

    #
    # You code here
    #

    up = np.zeros(2 * np.array(img.shape))
    up[::2,::2] = img
    # a scale factor is 4
    up = 4 * convolve(up, f, mode="mirror")

    return up

def gaussianpyramid(img, nlevel, f):
    """ Build Gaussian pyramid from image

    Args:
        img: input image for Gaussian pyramid
        nlevel: number of pyramid levels
        f: 2d filter kernel
    Returns:
        Gaussian pyramid, pyramid levels as (H, W) np.array
        in a list sorted from fine to coarse
    """

    #
    # You code here
    #

    pyramid = [img]
    for i in range(0, nlevel-1):
        pyramid.append(downsample2(pyramid[i],f))

    return pyramid

def laplacianpyramid(gpyramid, f):
    """ Build Laplacian pyramid from Gaussian pyramid

    Args:
        gpyramid: Gaussian pyramid
        f: 2d filter kernel
    Returns:
        Laplacian pyramid, pyramid levels as (H, W) np.array
        in a list sorted from fine to coarse
    """

    #
    # You code here
    #

    # reverse order of gaussian pyramid
    gpyramid = gpyramid[::-1]

    # build lapacian pyramid from coarse to fine
    lpyramid = [gpyramid[0]]
    for i in range(1, len(gpyramid)):
        # Vorlesung l3-edges_pyramids Seite 62
        lpyramid.append(gpyramid[i] - upsample2(gpyramid[i-1], f))

    # reverse lapalacian pyramid to correct order
    return lpyramid[::-1]

def reconstructimage(lpyramid, f):
    """ Reconstruct image from Laplacian pyramid

    Args:
        lpyramid: Laplacian pyramid
        f: 2d filter kernel
    Returns:
        Reconstructed image as (H, W) np.array clipped to [0, 1]
    """

    #
    # You code here
    #

    # start with coarsest level of Laplacian pyramid
    img = lpyramid[-1]

    # upsample and add next finer level
    for l in lpyramid[-2::-1]:
        img = l + upsample2(img, f)

    # make sure values of reconstructed image are inside bounds
    img = img.clip(0, 1)

    return img

def amplifyhighfreq(lpyramid, l0_factor=1.0, l1_factor=1.0):
    """ Amplify frequencies of the finest two layers of the Laplacian pyramid

    Args:
        lpyramid: Laplacian pyramid
        l0_factor: amplification factor for the finest pyramid level
        l1_factor: amplification factor for the second finest pyramid level
    Returns:
        Amplified Laplacian pyramid, data format like input pyramid
    """

    #
    # You code here
    #
    lpyramid = deepcopy(lpyramid)
    lpyramid[0] += l0_factor
    lpyramid[1] += l1_factor

    return lpyramid

def createcompositeimage(pyramid):
    """ Create composite image from image pyramid
    Arrange from finest to coarsest image left to right, pad images with
    zeros on the bottom to match the hight of the finest pyramid level.
    Normalize each pyramid level individually before adding to the composite image

    Args:
        pyramid: image pyramid
    Returns:
        composite image as (H, W) np.array
    """

    #
    # You code here
    #

    def normalize(img):
        return (img - img.min())/(img.max() - img.min())

    h = pyramid[0].shape[0]
    composite = [
        # array of rank 2 expands array
        np.pad(normalize(img), ((0, h - img.shape[0]), (0,0))) for img in pyramid
    ]
    composite = np.concatenate(composite, axis=1)

    return composite


# -*- coding: utf-8 -*-
"""
Ejercicios 1 y 2: Cálculo del polinomio de aproximación (caso discreto).
Utilizamos la matriz de Vandermonde (ver ejercicio1.py del Interpolación).

-----------------------------------------------------------------------------
Función Vandermonde_g: Halla la matriz de Vandermonde a partir
de un vector x (nodos) para construir un polinomio de aproximación de grado g.

Argumentos de entrada:
    x:  vector que contiene los nodos (array numpy).
    g:  grado del polinomio de aproximación (número entero).

Argumentos de salida:
    V:  matriz de Vandermonde con g+1 columnas (array numpy).

Ejemplos:
    x = np.array([1,2,3,4,5.])
    print('Matriz de Vandermonde con 4 columnas =\n', Vandermonde_g(x,3))
    Salida:
        Matriz de Vandermonde con 4 columnas =
        [[  1.   1.   1.   1.]
        [  1.   2.   4.   8.]
        [  1.   3.   9.  27.]
        [  1.   4.  16.  64.]
        [  1.   5.  25. 125.]]
"""

import numpy as np
import matplotlib.pyplot as plt
np.set_printoptions(precision = 2)   # solo dos decimales
np.set_printoptions(suppress = True) # no usar notación exponencial

# Función Vandermonde_g
#--------------------------------------------------------
def Vandermonde_g(x,g):
    n = len(x)        # dimensión de x
    V = np.ones((n, g+1)) # la matriz de Vandermonde tiene g+1 columnas
    for i in range(1, g+1):
        V[:,i] = V[:,i-1]*x # multiplicamos cada colunma por el vector x para
                            # generar la siguiente columna
    return V

#--------------------------------------
# Ejemplos
#--------------------------------------

# Ejemplo de prueba
x = np.array([1,2,3,4,5.])
print('Matriz de Vandermonde con 4 columnas =\n', Vandermonde_g(x,3))

#------------------------------------------------------------------------------
# Ejercicio1
#------------------------------------------------------------------------------
# Datos
f = lambda x : np.cos(x)         # función
x = np.array([-1,-0.5,0,0.5,1])  # nodos
y = f(x)  # valores en los nodos

# Calculamos el polinomio de aproximación de grado 2

# Calculamos la matriz de Vandermonde
V = Vandermonde_g(x,2)
# La traspuesta de V es V.T
A = np.dot(V.transpose(),V)       # multiplicamos V traspuesa por V (matriz del sistema)
b = np.dot(V.transpose(),y.transpose())       # multiplicamos V traspuesa por y traspuesto (vector de términos independientes)
print('Matriz del sistema =\n', A)    # mostramos la matriz A
print('Término independiente =', b)   # mostramos el vector b

# Resolvemos el sistema
p = np.linalg.solve(A, b)
print('Solución del sistema =',p) # p es el polinomio de aproximación

# Reordenamos los coeficientes del polinomio p
# para que parezcan de mayor a menor potencia de x
p = p[::-1]

# Para hacer la representación gráfica del polinomio
plt.close('all')

xx = np.linspace(-1,1)   # para hacer la gráfica del polinomio p  en [-1,1]
yy = np.polyval(p,xx) # evaluamos p en xx

plt.figure(1)
plt.plot(x,y,'ro')      # dibujamos los puntos (en rojo con o)
plt.plot(xx,yy, label = 'p')      # dibujamos el polinomio
plt.grid()
plt.legend()
plt.show()

#------------------------------------------------------------------------------
# Ejercicio 2 (se resuelve como el caso anterior pero con los datos del ejercicio 2)
#------------------------------------------------------------------------------
# Datos
f = lambda x : np.cos(np.arctan(x)) - np.exp(x**2)*np.log(x+2)
x = np.linspace(-1,1,10)
y = f(x)

# Calculamos el polinomio de aproximación de grado 4

# Calculamos la matriz de Vandermonde
V = Vandermonde_g(x,4)
# La traspuesta de V es V.T
A = np.dot(V.transpose(),V)       # multiplicamos V traspuesa por V (matriz del sistema)
b = np.dot(V.transpose(),y.transpose())       # multiplicamos V traspuesa por y traspuesto (vector de términos independientes)
print('Matriz del sistema =\n', A)    # mostramos la matriz A
print('Término independiente =', b)   # mostramos el vector b

# Resolvemos el sistema
p = np.linalg.solve(A, b)
print('Solución del sistema =',p) # p es el polinomio de aproximación

# Reordenamos los coeficientes del polinomio p
# para que parezcan de mayor a menor potencia de x
p = p[::-1]

# Para hacer la representación gráfica del polinomio
plt.close('all')

xx = np.linspace(-1,1)   # para hacer la gráfica del polinomio p  en [-1,1]
yy = np.polyval(p,xx) # evaluamos p en xx

plt.figure(1)
plt.plot(x,y,'ro')      # dibujamos los puntos (en rojo con o)
plt.plot(xx,yy, label = 'p')      # dibujamos el polinomio
plt.grid()
plt.legend()
plt.show()


import math
import numpy as np

def info_gain(examples, attribute, target_index=-1):
    if attribute.type_ == 'discrete':
        return info_gain_discrete(examples, attribute.index, target_index)
    if attribute.type_ == 'continuous':
        return info_gain_continuous(examples, attribute.index, target_index)
    raise ValueError('invalid attibute type')

def info_gain_discrete(examples, attribute_index, target_index=-1):
    subsets_dict = {}
    for example in examples:
        key = example[attribute_index]
        if key in subsets_dict:
            subsets_dict[key].append(example)
        else:
            subsets_dict[key] = [example]

    gain = entropy(examples, target_index)
    for subset in subsets_dict.values():
        proportion = 1.0 * len(subset) / len(examples)
        gain -= proportion * entropy(subset)
    return gain, None

def info_gain_continuous(examples, attribute_index, target_index=-1):
    values = sorted(set([example[attribute_index] for example in examples]))
    if len(values) == 1:
        return -1, 0

    thresholds = []
    for i in range(1, len(values)):
        thresholds.append(0.5 * (values[i-1] + values[i]))

    gains = []
    for threshold in thresholds:
        subsets = [[], []]
        for example in examples:
            if example[attribute_index] >= threshold:
                subsets[0].append(example)
            else:
                subsets[1].append(example)

        gain = entropy(examples, target_index)
        for subset in subsets:
            proportion = 1.0 * len(subset) / len(examples)
            gain -= proportion * entropy(subset)

        gains.append(gain)

    index = np.argmax(gains)
    return gains[index], thresholds[index]

def entropy(examples, target_index=-1):
    target_counts = {}
    for example in examples:
        target = example[target_index]
        if target in target_counts:
            target_counts[target] += 1
        else:
            target_counts[target] = 1

    result = 0
    for count in target_counts.values():
        proportion = 1.0 * count / len(examples)
        result -= proportion * math.log(proportion, 2)
    return result


#!/usr/bin/python3
from sympy import *

tx,ty,gamma = symbols('tx ty gamma')
x,y = symbols('x y')

R_z = Matrix([[cos(gamma),-sin(gamma)],[sin(gamma), cos(gamma)]])

v = Matrix([[x],[y]])
t = Matrix([[tx],[ty]])

v_prime = (R_z * v) + t

d = Matrix([tx,ty,gamma])
J = v_prime.jacobian(d)

init_printing()

pprint(v_prime)
pprint(J)

print(latex(v_prime[0]))
print("-------------------------")
print(latex(v_prime[1]))
print("-------------------------")
print(latex(J.col(0)))
#print("-------------------------")
#print(latex(J.jacobian(d)))

hessian = []
for i in range(3):
    print("-------------------------")
    print(latex(J.col(i).jacobian(d)))
    hessian.append(J.col(i).jacobian(d))
pprint(hessian)


import numpy as np
from scipy.linalg import eigh
from matplotlib import pyplot as plt

# enter number of stories
story = int(input("Enter the number of stories: "))

# enter the story stiffnesses
k_stories = []
for item in range(1,story+1):
  k_ = float(input(f"Enter the k{item}: "))
  k_stories.append(k_)

#enter the story masses
m_stories = []
for item in range(1,story+1):
  m_ = float(input(f"Enter the m{item}: "))
  m_stories.append(m_)

# creation of mass matrix of the system
I = np.identity(story)
M_system = I * m_stories

#creation of stiffness matrix of the system
K_system = np.zeros((story,story))
for i in range(story-1):
    for j in range(story):
        if i == j :
            K_system[i,j]= k_stories[i] + k_stories[i+1]
        elif i - j == 1 :
            K_system[i,j]= -1* k_stories[i]
        elif j-i == 1:
            K_system[i,j]= -1* k_stories[j]
        else:
            K_system[i,j] = 0

K_system[story-1,story-1] = k_stories[-1]
K_system[story-1,story-2] = -1*k_stories[-1]

# calculation of eigenvalues and eigenvectors;
evals, evecs = eigh(K_system,M_system)

# calculation of natural frequencies and periods
frequencies = np.sqrt(evals)
periods = 2*np.pi/frequencies
print(f"natural frequencies: {frequencies}")
print(50*"=")
print(f"periods: {periods}")
print(50*"=")

# determination of the modal matrix
mods = evecs.copy()
for j in range(story):
  mods[0::,j] = evecs[0::,j]/evecs[-1,j]
print(f"MODAL MATRIX: \n{mods}")
print(50*"=")

# Plotting of the mode shapes
  # arrangement for modal matrix to plot
mod_g = np.zeros((story+1,story))
mod_g[1::,0::] = mods
mods_list = {}
for item in range(1,story+1):
  mods_list[item] = mod_g[0::,item-1]

  #Plotting
for item in range(1,story+1):
  plt.figure(figsize=(4,9))
  plt.grid(color= "r", axis="y")
  plt.plot(mods_list[item],range(0,story+1,1),"k*-")

  ax = plt.gca()
  ax.axes.yaxis.set_ticklabels([])

  for a,b in zip(mods_list[item], range(0,story+1,1)):
    plt.text(a, b, str(np.around(a,4)), fontsize=20,bbox=dict(facecolor='w', alpha=0.5))

  plt.title(f'Mod {item}', fontsize=20, bbox=dict(facecolor='r', alpha=0.50))
  plt.show()


import numpy as np
import math
from sklearn.decomposition import PCA
''' Multiview 2D projection (M2DP) descriptor.

 Input:
       data        n*3     Point cloud. Each row is [x y z]
 Output:
       desM2DP     192*1   M2DP descriptor of the input cloud data
       A           64*128  Signature matrix

 Introduction:
 M2DP is a global descriptor of input point cloud. Details of M2DP can be
 found in the following paper:

 Li He, Xiaolong Wang and Hong Zhang, M2DP: A Novel 3D Point Cloud
 Descriptor and Its Application in Loop Closure Detection, IROS 2016.

 Li He, Dept. of Computing Science, University of Alberta
 lhe2@ualberta.ca
'''

def cart2sph(x,y,z):
    azimuth = np.arctan2(y,x)
    elevation = np.arctan2(z,np.sqrt(x**2 + y**2))
    r = np.sqrt(x**2 + y**2 + z**2)
    return azimuth, elevation, r

def sph2cart(azimuth,elevation,r):
    x = r * np.cos(elevation) * np.cos(azimuth)
    y = r * np.cos(elevation) * np.sin(azimuth)
    z = r * np.sin(elevation)
    return x, y, z
def cart2pol(x, y):
    rho = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)
    return(rho, phi)

def pol2cart(rho, phi):
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)
    return(x, y)
def GetSignatureMatrix(azimuthList,elevationList, cloud_pca,
                       numT, numR, maxRho):
    shape_A=(azimuthList.shape[0]*elevationList.shape[0],numT*numR)
    thetaList=np.linspace(-math.pi,math.pi,numT+1)
    #rho list
    rhoList = np.linspace(0,np.sqrt(maxRho),numR+1)
    rhoList = np.square(rhoList)
    rhoList[-1] = rhoList[-1]+.001 #make sure all points in bins
    n=0
    A=[]
    #loop on azimuth
    for azm in azimuthList:
        #loop on evevation
        for elv in elevationList:
            #normal vector vecN of the selected 2D plane
            x,y,z=sph2cart(azm,elv,1)
            vecN=np.array([x,y,z])
            #distance of vector [1,0,0] to the surface with normal vector vecN
            op= np.array([1,0,0])
            h = op@vecN.T
            #a new vector, c = h*vecN, so that vector [1,0,0]-c is the
            # projection of x-axis onto the plane with normal vector vecN
            c= h*vecN
            #x-axis - c, the projection
            px= op-c
            #given the normal vector vecN and the projected x-axis px, the
            # y- axis is cross(vecN,px)
            py= np.cross(vecN,px)
            #projection of data onto space span{px,py}
            pcx=cloud_pca@px.T
            pcy=cloud_pca@py.T
            #pdata = np.array([pcx,pcy])
            #represent data in polar coordinates
            rho,theta=cart2pol(pcx,pcy)

            #main function, count points in bins
            hist= np.histogram2d(theta.T,rho.T,[thetaList,rhoList])[0]
            hist=hist/cloud_pca.shape[0]
            A.append(hist.T.ravel())
    return np.array(A)

def M2DP(cloud):
    #key parameter
    # number of bins in theta, the 't' in paper
    numT = 16
    #number of bins in rho, the 'l' in paper
    numR = 8
    # number of azimuth angles, the 'p' in paper
    numP = 4
    # number of elevation angles, the 'q' in paper
    numQ = 16

    '''3D rotate input data so that
    x-axis and y-axis are the
    1st and 2nd PCs of data respectively
    rotation invariant'''
    pca=PCA()
    cloud_pca=pca.fit_transform(cloud)
    np.negative(cloud_pca[:,1])
    np.negative(cloud_pca[:,2])
    half_pi=math.pi/2
    azimuthList =np.linspace(-half_pi,half_pi,numP)
    elevationList = np.linspace(0,half_pi,numQ)
    #get the farthest point distance
    rho2=np.sum(np.square(cloud_pca),axis=1)
    maxRho=np.sqrt(rho2.max())
    #main function, get the signature matrix A
    A=GetSignatureMatrix(azimuthList, elevationList, cloud_pca, numT, numR, maxRho)
    #run SVD on A and use [u0,v0] as the final output
    u,s,v = np.linalg.svd(A);
    desM2DP = np.append(u[:,0],v.T[:,0])
    return desM2DP,A


import matplotlib.pyplot as plt
import numpy as np

Ar = .8
fr = 2.0
phi = np.pi/2
fs = 100
t = np.arange(-1, 1, 1.0/fs)
x = Ar * np.cos(2*np.pi*fr*t+phi)

plt.figure(1, figsize=(5, 3))
plt.plot(t, x, 'b', lw=1.5)
plt.axis([-1, 1,-.8,.8])
plt.xlabel('time (seconds)')
plt.ylabel('amplitude')

plt.tight_layout()
plt.savefig('sine-synthesis.png')
plt.show()


import LinearAlgebraPurePython as la
import MachineLearningPurePy as ml

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
import numpy as np

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

import random
import sys

# Section 1: Fake data prep and visualization
X = [[x for x in range(2,9)],
     [2**(x/1.0)  for x in range(0,7)]]

def y_of_x(xa):
    return 0.2*xa[0]**2 + 0.03*xa[0]*xa[1] + \
           0.7*xa[0] + 0.01*xa[1]**2 + \
           0.04*xa[1] + 2.0
           #+ random.uniform(-2.0,2.0)
Y = []
for xa in la.transpose(X):
    Y.append(y_of_x(xa))

fig = plt.figure()
ax = plt.axes(projection='3d')
ax.scatter3D(X[0], X[1], Y)

# Section 2: Get fake data in correct format
X = la.transpose(X)
Y = la.transpose([Y])

# Section 3: Pure Python Tools Fit
poly_pp = ml.Poly_Features_Pure_Py(order=2)
Xpp = poly_pp.fit_transform(X)
ls_pp = ml.Least_Squares(add_ones_column=False)
ls_pp.fit(Xpp, Y)

# Section 4: SciKit Learn Fit
poly_sk = PolynomialFeatures(degree = 2)
Xps = poly_sk.fit_transform(X)
ls_sk = LinearRegression()
ls_sk.fit(Xps, Y)

# Section 5: Coefficients Comparison
tmp_ls_pp_coefs = sorted(ls_pp.coefs)
rounded_ls_pp_coefs = [round(x,8)+0
for x in la.transpose(tmp_ls_pp_coefs)[0]] #
print('PurePy  LS coefficients:',
        rounded_ls_pp_coefs)

tmp_ls_sk_coefs = ls_sk.intercept_.tolist() + \
                  ls_sk.coef_[0][1:].tolist()
tmp_ls_sk_coefs = sorted(tmp_ls_sk_coefs)
rounded_ls_sk_coefs = [round(x,8)+0
                        for x in tmp_ls_sk_coefs] #
print('SKLearn LS coefficients:',
        rounded_ls_sk_coefs)

print('Coef Deltas:', [rounded_ls_pp_coefs[i] -
        rounded_ls_sk_coefs[i]
        for i in range(len(rounded_ls_pp_coefs))])

# Section 6: Create Fake Test Data
XLS = [[x/2.0 for x in range(4,18)],
       [2**(x/2.0)  for x in range(0,14)]]

# Section 6.1: Predict with Fake Test Data
#   Using Pure Python Tools
XLSpp = poly_pp.transform(la.transpose(XLS))
YLSpp = ls_pp.predict(XLSpp)
YLSpp = la.transpose(YLSpp)[0]

# Section 6.2: Predict with Fake Test Data
#   Using SciKit Learn Tools
XLSsk = poly_sk.transform(la.transpose(XLS))
YLSsk = ls_sk.predict(XLSsk)
YLSsk = YLSsk.tolist()
YLSsk = la.transpose(YLSsk)[0]

# Section 7: Calculate Prediction Differences
YLSpp.sort()
YLSsk.sort()
deltas = [ YLSpp[i] - YLSsk[i]
            for i in range(len(YLSpp)) ]
print( '\nPrediction Deltas:', deltas , '\n')

# Section 8: Plot Both Methods
ax.plot3D(XLS[0], XLS[1], YLSpp)
ax.plot3D(XLS[0], XLS[1], YLSsk)
ax.set_xlabel('X0 Values')
ax.set_ylabel('X1 Values')
ax.set_zlabel('Y Values')
Title = 'Pure Py Lst Sqrs Fit /w 2 x Deg 2 Inputs'
ax.set_title(Title)
plt.show()


#!/usr/bin/python

#############################################################
# Logistic regression #
# Sk. Mashfiqur Rahman #
# collect data from http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data #
#############################################################

import numpy as np
from scipy.optimize import minimize

def flower_to_float(s):
    d = {b'Iris-setosa': 0., b'Iris-versicolor': 1., b'Iris-virginica': 2.}
    return d[s]

def phi(_x, _i):
    phi1 = np.ones(5)
    phi1[1:5] = _x[_i,1:5]
    return phi1

def f(_w, training_x, training_y, prior):
    N = training_x.shape[0]
    K = 3
    log_likelihood= 0.
    for n in range(N):
        ll2 = 0.
        ll3 = 0.
        for k in range(K):
            ll2 += training_y[n, k] * _w[5*k:(5*k)+5].T.dot(phi(training_x,n))
        for l in range(K):
            ll3 += np.exp(_w[5*l:(5*l)+5].T.dot(phi(training_x,n)))
        log_likelihood += ll2
        log_likelihood -= np.log(ll3)
        #  forced normalize the overflow encountered in exp
        if log_likelihood == -np.inf:
            log_likelihood = 0.
    return prior - log_likelihood

irises = np.loadtxt('iris.txt', delimiter=',', converters={4:flower_to_float})
M = irises.shape[0]
K = 3
x = np.array(irises[:, :4])
x = np.concatenate((np.ones(shape=(M, 1)), x), axis=1)
y = np.zeros(shape=(M, K))
for i in range(M):
    y[i, int(irises[i,4])] = 1

r = np.arange(M)
np.random.shuffle(r)
x = np.array(x[r.reshape(-1)])
y = np.array(y[r.reshape(-1)])

training_x = x[:int(M/2)]
training_y = y[:int(M/2)]
test_x = x[int(M/2):]
test_y = y[int(M/2):]

alpha = 0.0031257
w_init = np.ones(15)
prior = (alpha/2.) * w_init.T.dot(w_init)

w_hat = minimize(f, w_init, args=(training_x, training_y, prior)).x

b = []
c = []
for i in range(test_x.shape[0]):
    z = np.zeros(K)
    for k in range(K):
        z[k] = np.exp(w_hat[5*k:(5*k)+5].T.dot(phi(test_x,i)))
        #  forced normalize the overflow encountered in exp
        if z[k] == np.inf:
            z[k] = 1000.
    s = z / np.sum(z)
    q = np.amax(s)
    b.append((s.tolist()).index(q))
for i in range(test_y.shape[0]):
    s = test_y[i,:]
    q = np.amax(s)
    c.append((s.tolist()).index(q))
k = np.equal(b,c)
print("overall classification accuracy = ", float(np.sum(k) / len(test_y))*100., '%')


import numpy as np
from jeeautodiff.autodiff import Node
from jeeautodiff.reverse_mode import Node_b

def sin(N):
    if isinstance(N, Node):
        val = np.sin(N.val)
        der = np.cos(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sin(N.val))
        z.parents = [(N, np.cos(N.val))]
        return z

    try:
        float(N)
        return np.sin(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def cos(N):
    if isinstance(N, Node):
        val = np.cos(N.val)
        der = -(np.sin(N.val) * N.der)
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.cos(N.val))
        z.parents = [(N, -np.sin(N.val))]
        return z

    try:
        float(N)
        return np.cos(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def tan(N):
    if isinstance(N, Node):
        val = np.tan(N.val)
        der = 1 / (np.cos(N.val) ** 2) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        z = Node_b(np.tan(N.val))
        z.parents = [(N, 1 / (np.cos(N.val) ** 2))]
        return z

    try:
        float(N)
        return np.tan(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def exp(N):
    if isinstance(N, Node):
        val = np.exp(N.val)
        der = np.exp(N.val) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        z = Node_b(np.exp(N.val))
        z.parents = [(N, np.exp(N.val))]
        return z

    try:
        float(N)
        return np.exp(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def log(N):
    if isinstance(N, Node):
        val = np.log(N.val)
        der = 1 / N.val * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.log(N.val))
        z.parents = [(N,1 / N.val)]
        return z

    try:
        float(N)
        return np.log(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arcsin(N):
    if isinstance(N, Node):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        val = np.arcsin(N.val)
        der = (1 / np.sqrt(1 - N.val**2)) * N.der
        return Node(val, der)
    if isinstance(N, Node_b):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        z = Node_b(np.arcsin(N.val))
        z.parents = [(N, (1 / np.sqrt(1 - N.val**2)))]
        return z

    try:
        float(N)
        return np.arcsin(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arccos(N):
    if isinstance(N, Node):
        if N.val > 1 or N.val < -1 :
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arccos use".format(N))
        val = np.arccos(N.val)
        der = (-1 / np.sqrt(1 - N.val**2)) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        if N.val > 1 or N.val < -1:
            raise ValueError("{} should have values and derivatives -1 <= x <= 1 for arcsin use".format(N))
        z = Node_b(np.arccos(N.val))
        z.parents = [(N, (-1 / np.sqrt(1 - N.val ** 2)))]
        return z

    try:
        float(N)
        return np.arccos(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def arctan(N):
    if isinstance(N, Node):
        val = np.arctan(N.val)
        der = 1 / (N.val**2 + 1) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.arctan(N.val))
        z.parents = [ (N,1 / (N.val**2 + 1)) ]
        return z
    try:
        float(N)
        return np.arctan(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def sinh(N):
    if isinstance(N, Node):
        val = np.sinh(N.val)
        der = np.cosh(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sinh(N.val))
        z.parents = [(N, np.cosh(N.val))]
        return z
    try:
        float(N)
        return np.sinh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def cosh(N):
    if isinstance(N, Node):
        val = np.cosh(N.val)
        der = np.sinh(N.val) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.cosh(N.val))
        z.parents = [(N, np.sinh(N.val))]
        return z
    try:
        float(N)
        return np.cosh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def tanh(N):
    if isinstance(N, Node):
        val = np.tanh(N.val)
        der = (1-np.tanh(N.val)**2) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.tanh(N.val))
        z.parents = [(N, (1-np.tanh(N.val)**2))]
        return z

    try:
        float(N)
        return np.tanh(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def sqrt(N):
    if isinstance(N, Node):
        val = np.sqrt(N.val)
        der = 0.5 * (N.val ** -0.5) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b(np.sqrt(N.val))
        z.parents = [(N, 0.5 * (N.val ** -0.5))]
        return z

    try:
        float(N)
        return np.sqrt(N)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def logistic(N):
    if isinstance(N, Node):
        val = np.exp(N.val) / (np.exp(N.val) + 1)
        der = ( np.exp(-N.val) / (1+np.exp(-N.val))**2 ) * N.der
        return Node(val, der)

    if isinstance(N, Node_b):
        z = Node_b( np.exp(N.val) / (np.exp(N.val) + 1))
        z.parents = [(N, ( np.exp(-N.val) / (1+np.exp(-N.val))**2 ))]
        return z

    try:
        float(N)
        return np.exp(N) / (np.exp(N) + 1)
    except:
        raise ValueError("{}should either be a Node instance or a number".format(N))

def logb(b, N):
    try:
        float(b)
        if isinstance(N,Node):
            new_val = np.log(N.val)/np.log(b)
            new_der = (1/(np.log(b)*N.val))*N.der
            return Node(val=new_val,der=new_der)

        if isinstance(N, Node_b):
            z = Node_b(np.log(N.val)/np.log(b))
            z.parents = [(N, (1/(np.log(b)*N.val)))]
            return z
        try:
            float(N)
            new_val = np.log(N)/np.log(b)
            return new_val
        except:
            raise ValueError("{}should either be a Node instance or a number".format(N))
    except:
        raise ValueError("the base of a logb should be an int or a float")

def power(b,N):
    try:
        float(b)
        if isinstance(N,Node):
            val=b**N.val
            der=val*log(b)*N.der
            return Node(val, der)

        if isinstance(N, Node_b):
            val = b ** N.val
            z = Node_b(val)
            z.parents = [(N, val*log(b))]
            return z
        try:
            float(N)
            return b**N
        except:
            raise ValueError("{}should either be a Node instance or a number".format(N))
    except:
        raise ValueError("the base of a power should be an int or a float")


import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
import scipy.optimize as opt

data = loadmat('data/ex3data1.mat')
theta = loadmat('data/ex4weights.mat')
theta1 = theta['Theta1']
theta2 = theta['Theta2']

x = data['X']
y_prime = data['y'].transpose()[0]
y_prime = np.array([elem if elem != 10 else 0 for elem in y_prime])
y = np.zeros((y_prime.shape[0], 10))
for i in range(y_prime.shape[0]):
	y[i][y_prime[i]] = 1

def g(z):
	#sigmoid function
	return np.float64(1.0) / (1.0 + math.pow(math.e, -z))

g = np.vectorize(g, otypes=[np.float64])
#now, when g is applied to a vector, it is applied to each cell

def unroll(thetas):
	unrolled_theta = np.concatenate(tuple([theta.flatten() for theta in thetas]), axis=0)
	return unrolled_theta

def roll(theta, layers):
	#returns a list of rolled theta matrices
	rolled_theta = []
	offset = 0
	for i in range(1, len(layers)):
		m, n = layers[i], layers[i - 1] + 1
		rolled_theta.append(theta[offset: offset + m * n].reshape((m, n)))
		offset += m * n
	return rolled_theta

def randomInit(layers):
	rolled_theta = []
	for i in range(1, len(layers)):
		m, n = layers[i], layers[i - 1] + 1
		rolled_theta.append((np.random.rand(m, n) - 0.5 * np.ones((m, n))))
	unrolled_theta = unroll(rolled_theta)
	return unrolled_theta

def cost(theta, h, y, layers, λ):
	#ith row of h or y is the hypothesis or expected output for the ith training set
	cost, m = 0, y.shape[0]
	for i in range(m):
		cost += y[i] @ np.log(h[i]) + (np.ones((y[i].shape)) - y[i]) @ np.log(np.ones(h[i].shape) - h[i])
	cost = -cost / m
	rolled_theta = roll(theta, layers)
	for theta_i in rolled_theta:
		cost += (λ / (2 * m)) * (np.sum(np.square(theta_i[:,1:])))
	return cost

def classifier(h):
	tmp = np.argmax(h, axis=1)
	output = np.zeros((h.shape), dtype='float64')
	for i in range(tmp.shape[0]):
		output[i][tmp[i]] = 1
	return output

def feedForward(theta, x, layers):
	#returns a list or np.ndarrays where list[i][j] is the
	#activator vector for the ith layer and jth dataset
	rolled_theta = roll(theta, layers)
	a = [x.transpose(),]
	for i in range(len(layers) - 1):
		a[-1] = np.insert(a[-1], [0], np.ones((a[-1].shape[1],), dtype=np.float64), axis=0)
		a.append(g(rolled_theta[i] @ a[-1]))
	for i in range(len(a)):
		a[i] = a[i].transpose()
	return a

def gradient(theta, x, y, layers, λ):
	print("running gradient descent")
	Δ = [0 for el in range(len(layers) - 1)]
	m = x.shape[0]
	a = feedForward(theta, x, layers)
	rolled_theta = roll(theta, layers)
	#list of np.ndarrays where list[i][j] contains δ for ith layer and jth dataset
	#δ fr ith layer is a matrix of m rows, each row for one training set
	δ = [0 for el in layers]
	δ[-1] = a[-1] - y
	for i in range(len(layers) - 2, -1, -1):
		g_prime = (a[i] * (np.ones(a[i].shape) - a[i]))
		tmp = np.array([rolled_theta[i].transpose() @ row for row in δ[i + 1]])
		#no error term for i = 0, but here we are calculating just for fun!
		#also no error term for bias units which is why we discard the first column of δ[i]
		δ[i] = (tmp * g_prime)[:,1:]
		Δ[i] = sum([np.outer(δ[i + 1][j], a[i][j]) for j in range(m)])
	for i in range(len(layers) - 1):
		tmp = np.insert(rolled_theta[i][:,1:], 0, np.zeros((rolled_theta[i].shape[0],)), axis=1)
		Δ[i] = Δ[i] / np.float64(m) + (λ / np.float64(m)) * tmp
	Δ_unrolled = unroll(Δ)
	return Δ_unrolled

def grad_check(theta, x, y, layers, λ):
	epsilon = 0.0001
	Δ_unrolled = np.zeros((theta.shape), dtype=np.float64)
	for i in range(len(theta) -1, 0, -1):
		tmp = np.zeros((theta.shape))
		tmp[i] = epsilon
		theta_up = theta + tmp
		theta_lo = theta - tmp
		cost_up = cost(theta_up, feedForward(theta_up, x, layers)[-1], y, layers, λ)
		cost_lo = cost(theta_lo, feedForward(theta_lo, x, layers)[-1], y, layers, λ)
		Δ_unrolled[i] = (cost_up - cost_lo) / (2 * epsilon)
		print(i, Δ_unrolled[i])
	return Δ_unrolled

def findAccuracy(theta, x, y, layers):
	y_pred = classifier(feedForward(theta, x, layers)[-1])
	correct = [1 if np.all(a == b) else 0 for (a, b) in zip(y_pred, y)]
	accuracy = (sum(map(int, correct)) / float(len(correct)))
	return accuracy

layers = [400, 25, 10]
#theta = unroll((theta1, theta2))
#randomly initialize a theta
theta = randomInit(layers)
λ = 1
#h = feedForward(theta, x, layers)[-1]
#print(cost(theta, h, y, layers, λ))
'''
grad = gradient(theta, x, y, layers, λ)
for i in range(grad.shape[0]):
	print(i, grad[i])
print("//")
print(grad_check(theta, x, y, layers, λ))
'''
#a = np.array([[[1, 2], [3, 4]], [[5, 6, 7], [8, 9, 10], [11, 12, 13]]])
#print(a.shape)
#out = classifier(h)
#print(out[4003])
#print(y[4003])

def backPropagate(theta, x, y, layers, λ):
	j = cost(theta, feedForward(theta, x, layers)[-1], y, layers, λ)
	grad = gradient(theta, x, y, layers, λ)
	return j, grad

fmin = opt.minimize(fun=backPropagate, x0=theta, args=(x, y, layers, λ), method='TNC', jac=True, options={'maxiter': 250})
theta = fmin.x
np.set_printoptions(threshold=np.nan)
print(fmin)
print(findAccuracy(theta, x, y, layers))


__all__ = [ 'sphericalToCartesian', 'cartesianToSpherical', 'normalTriad', 'elementaryRotationMatrix',
'phaseSpaceToAstrometry', 'astrometryToPhaseSpace']

from numpy import cos, sin, sqrt, arctan2, zeros_like, isscalar
from numpy import array, transpose, dot, any

# Astronomical Unit in meter, IAU constant and defining length
_auInMeter = 149597870700.0

# AU expressed in mas*pc or muas*kpc
_auMasParsec = 1000.0

# Number of seconds in Julian year
_julianYearSeconds = 365.25 * 86400.0

# AU expressed in km*yr/s
_auKmYearPerSec = _auInMeter/(_julianYearSeconds*1000.0)

def sphericalToCartesian(r, phi, theta):
  """
  Convert spherical to Cartesian coordinates. The input can be scalars or 1-dimensional numpy arrays.
  Note that the angle coordinates follow the astronomical convention of using elevation (declination,
  latitude) rather than its complement (pi/2-elevation), where the latter is commonly used in the
  mathematical treatment of spherical coordinates.

  Parameters
  ----------

  r     - length of input Cartesian vector.
  phi   - longitude-like angle (e.g., right ascension, ecliptic longitude) in radians
  theta - latitide-like angle (e.g., declination, ecliptic latitude) in radians

  Returns
  -------

  The Cartesian vector components x, y, z
  """
  ctheta=cos(theta)
  x=r*cos(phi)*ctheta
  y=r*sin(phi)*ctheta
  z=r*sin(theta)
  return x, y, z

def cartesianToSpherical(x, y, z):
  """
  Convert Cartesian to spherical coordinates. The input can be scalars or 1-dimensional numpy arrays.
  Note that the angle coordinates follow the astronomical convention of using elevation (declination,
  latitude) rather than its complement (pi/2-elevation), which is commonly used in the mathematical
  treatment of spherical coordinates.

  Parameters
  ----------

  x - Cartesian vector component along the X-axis
  y - Cartesian vector component along the Y-axis
  z - Cartesian vector component along the Z-axis

  Returns
  -------

  The spherical coordinates r=sqrt(x*x+y*y+z*z), longitude phi, latitude theta.

  NOTE THAT THE LONGITUDE ANGLE IS BETWEEN -PI AND +PI. FOR r=0 AN EXCEPTION IS RAISED.
  """
  rCylSq=x*x+y*y
  r=sqrt(rCylSq+z*z)
  if any(r==0.0):
    raise Exception("Error: one or more of the points is at distance zero.")
  return r, arctan2(y,x), arctan2(z,sqrt(rCylSq))

def normalTriad(phi, theta):
  """
  Calculate the so-called normal triad [p, q, r] which is associated with a spherical coordinate system .
  The three vectors are:

  p - The unit tangent vector in the direction of increasing longitudinal angle phi.
  q - The unit tangent vector in the direction of increasing latitudinal angle theta.
  r - The unit vector toward the point (phi, theta).

  Parameters
  ----------

  phi   - longitude-like angle (e.g., right ascension, ecliptic longitude) in radians
  theta - latitide-like angle (e.g., declination, ecliptic latitude) in radians

  Returns
  -------

  The normal triad as the vectors p, q, r
  """
  sphi=sin(phi)
  stheta=sin(theta)
  cphi=cos(phi)
  ctheta=cos(theta)
  p=array([-sphi, cphi, zeros_like(phi)])
  q=array([-stheta*cphi, -stheta*sphi, ctheta])
  r=array([ctheta*cphi, ctheta*sphi, stheta])
  return p, q, r

def elementaryRotationMatrix(axis, rotationAngle):
  """
  Construct an elementary rotation matrix describing a rotation around the x, y, or z-axis.

  Parameters
  ----------

  axis          - Axis around which to rotate ("x", "y", or "z")
  rotationAngle - the rotation angle in radians

  Returns
  -------

  The rotation matrix

  Example usage
  -------------

  rotmat = elementaryRotationMatrix("y", pi/6.0)
  """
  if (axis=="x" or axis=="X"):
    return array([[1.0, 0.0, 0.0], [0.0, cos(rotationAngle), sin(rotationAngle)], [0.0,
      -sin(rotationAngle), cos(rotationAngle)]])
  elif (axis=="y" or axis=="Y"):
    return array([[cos(rotationAngle), 0.0, -sin(rotationAngle)], [0.0, 1.0, 0.0], [sin(rotationAngle),
      0.0, cos(rotationAngle)]])
  elif (axis=="z" or axis=="Z"):
    return array([[cos(rotationAngle), sin(rotationAngle), 0.0], [-sin(rotationAngle),
      cos(rotationAngle), 0.0], [0.0, 0.0, 1.0]])
  else:
    raise Exception("Unknown rotation axis "+axis+"!")

def phaseSpaceToAstrometry(x, y, z, vx, vy, vz):
  """
  From the given phase space coordinates calculate the astrometric observables, including the radial
  velocity, which here is seen as the sixth astrometric parameter. The phase space coordinates are
  assumed to represent barycentric (i.e. centred on the Sun) positions and velocities.

  This function has no mechanism to deal with units. The velocity units are always assumed to be km/s,
  and the code is set up such that for positions in pc, the return units for the astrometry are radians,
  milliarcsec, milliarcsec/year and km/s. For positions in kpc the return units are: radians,
  microarcsec, microarcsec/year, and km/s.

  NOTE that the doppler factor k=1/(1-vrad/c) is NOT used in the calculations. This is not a problem for
  sources moving at typical velocities of Galactic stars.

  Parameters
  ----------

  x - The x component of the barycentric position vector (in pc or kpc).
  y - The y component of the barycentric position vector (in pc or kpc).
  z - The z component of the barycentric position vector (in pc or kpc).
  vx - The x component of the barycentric velocity vector (in km/s).
  vy - The y component of the barycentric velocity vector (in km/s).
  vz - The z component of the barycentric velocity vector (in km/s).

  Returns
  -------

  phi       - The longitude-like angle of the position of the source (radians).
  theta     - The latitude-like angle of the position of the source (radians).
  parallax  - The parallax of the source (in mas or muas, see above)
  muphistar - The proper motion in the longitude-like angle, multiplied by cos(theta) (mas/yr or muas/yr,
  see above)
  mutheta   - The proper motion in the latitude-like angle (mas/yr or muas/yr, see above)
  vrad      - The radial velocity (km/s)
  """
  u, phi, theta = cartesianToSpherical(x, y, z)
  parallax = _auMasParsec/u
  p, q, r = normalTriad(phi, theta)
  velocitiesArray=array([vx,vy,vz])
  if  isscalar(u):
    muphistar=dot(p,velocitiesArray)*parallax/_auKmYearPerSec
    mutheta=dot(q,velocitiesArray)*parallax/_auKmYearPerSec
    vrad=dot(r,velocitiesArray)
  else:
    muphistar=zeros_like(parallax)
    mutheta=zeros_like(parallax)
    vrad=zeros_like(parallax)
    for i in xrange(parallax.size):
      muphistar[i]=dot(p[:,i],velocitiesArray[:,i])*parallax[i]/_auKmYearPerSec
      mutheta[i]=dot(q[:,i],velocitiesArray[:,i])*parallax[i]/_auKmYearPerSec
      vrad[i]=dot(r[:,i],velocitiesArray[:,i])

  return phi, theta, parallax, muphistar, mutheta, vrad

def astrometryToPhaseSpace(phi, theta, parallax, muphistar, mutheta, vrad):
  """
  From the input astrometric parameters calculate the phase space coordinates. The output phase space
  coordinates represent barycentric (i.e. centred on the Sun) positions and velocities.

  This function has no mechanism to deal with units. The code is set up such that for input astrometry
  with parallaxes and proper motions in mas and mas/yr, and radial velocities in km/s, the phase space
  coordinates are in pc and km/s. For input astrometry with parallaxes and proper motions in muas and
  muas/yr, and radial velocities in km/s, the phase space coordinates are in pc and km/s. Only positive
  parallaxes are accepted, an exception is thrown if this condition is not met.

  NOTE that the doppler factor k=1/(1-vrad/c) is NOT used in the calculations. This is not a problem for
  sources moving at typical velocities of Galactic stars.

  THIS FUNCTION SHOULD NOT BE USED WHEN THE PARALLAXES HAVE RELATIVE ERRORS LARGER THAN ABOUT 10-15 PER
  CENT. For astrometric data with relatively large parallax errors you should consider doing your
  analysis in the data space and use forward modelling of some kind.

  Parameters
  ----------

  phi       - The longitude-like angle of the position of the source (radians).
  theta     - The latitude-like angle of the position of the source (radians).
  parallax  - The parallax of the source (in mas or muas, see above)
  muphistar - The proper motion in the longitude-like angle, multiplied by cos(theta) (mas/yr or muas/yr,
  see above)
  mutheta   - The proper motion in the latitude-like angle (mas/yr or muas/yr, see above)
  vrad      - The radial velocity (km/s)

  Returns
  -------

  x - The x component of the barycentric position vector (in pc or kpc).
  y - The y component of the barycentric position vector (in pc or kpc).
  z - The z component of the barycentric position vector (in pc or kpc).
  vx - The x component of the barycentric velocity vector (in km/s).
  vy - The y component of the barycentric velocity vector (in km/s).
  vz - The z component of the barycentric velocity vector (in km/s).
  """
  if any(parallax<=0.0):
    raise Exception("One or more of the input parallaxes is non-positive")
  x, y, z = sphericalToCartesian(_auMasParsec/parallax, phi, theta)
  p, q, r = normalTriad(phi, theta)
  transverseMotionArray = array([muphistar*_auKmYearPerSec/parallax, mutheta*_auKmYearPerSec/parallax,
    vrad])
  if isscalar(parallax):
    velocityArray=dot(transpose(array([p, q, r])),transverseMotionArray)
    vx = velocityArray[0]
    vy = velocityArray[1]
    vz = velocityArray[2]
  else:
    vx = zeros_like(parallax)
    vy = zeros_like(parallax)
    vz = zeros_like(parallax)
    for i in xrange(parallax.size):
      velocityArray = dot(transpose(array([p[:,i], q[:,i], r[:,i]])), transverseMotionArray[:,i])
      vx[i] = velocityArray[0]
      vy[i] = velocityArray[1]
      vz[i] = velocityArray[2]
  return x, y, z, vx, vy, vz


import numpy as np
from matplotlib import pyplot as plt
from matplotlib.axes import Axes

from pymatviz.utils import NumArray

def marchenko_pastur_pdf(x: float, gamma: float, sigma: float = 1) -> float:
    """The Marchenko-Pastur probability density function describes the
    distribution of singular values of large rectangular random matrices.

    See https://wikipedia.org/wiki/Marchenko-Pastur_distribution.

    By comparing the eigenvalue distribution of a correlation matrix to this
    PDF, one can gauge the significance of correlations.

    Args:
        x (float): Position at which to compute probability density.
        gamma (float): Also referred to as lambda. The distribution's main parameter
            that measures how well sampled the data is.
        sigma (float, optional): Standard deviation of random variables assumed
            to be independent identically distributed. Defaults to 1 as
            appropriate for correlation matrices.

    Returns:
        float: Marchenko-Pastur density for given gamma at x
    """
    lambda_m = (sigma * (1 - np.sqrt(1 / gamma))) ** 2  # Largest eigenvalue
    lambda_p = (sigma * (1 + np.sqrt(1 / gamma))) ** 2  # Smallest eigenvalue

    prefac = gamma / (2 * np.pi * sigma**2 * x)
    root = np.sqrt((lambda_p - x) * (x - lambda_m))
    unit_step = x > lambda_p or x < lambda_m

    return prefac * root * (0 if unit_step else 1)

def marchenko_pastur(
    matrix: NumArray,
    gamma: float,
    sigma: float = 1,
    filter_high_evals: bool = False,
    ax: Axes = None,
) -> Axes:
    """Plot the eigenvalue distribution of a symmetric matrix (usually a correlation
    matrix) against the Marchenko Pastur distribution.

    The probability of a random matrix having eigenvalues >= (1 + sqrt(gamma))^2 in the
    absence of any signal is vanishingly small. Thus, if eigenvalues larger than that
    appear, they correspond to statistically significant signals.

    Args:
        matrix (Array): 2d array
        gamma (float): The Marchenko-Pastur ratio of random variables to observation
            count. E.g. for N=1000 variables and p=500 observations of each,
            gamma = p/N = 1/2.
        sigma (float, optional): Standard deviation of random variables. Defaults to 1.
        filter_high_evals (bool, optional): Whether to filter out eigenvalues larger
            than theoretical random maximum. Useful for focusing the plot on the area
            of the MP PDF. Defaults to False.
        ax (Axes, optional): matplotlib Axes on which to plot. Defaults to None.

    Returns:
        ax: The plot's matplotlib Axes.
    """
    if ax is None:
        ax = plt.gca()

    # use eigvalsh for speed since correlation matrix is symmetric
    evals = np.linalg.eigvalsh(matrix)

    lambda_m = (sigma * (1 - np.sqrt(1 / gamma))) ** 2  # Largest eigenvalue
    lambda_p = (sigma * (1 + np.sqrt(1 / gamma))) ** 2  # Smallest eigenvalue

    if filter_high_evals:
        # Remove eigenvalues larger than those expected in a purely random matrix
        evals = evals[evals <= lambda_p + 1]

    ax.hist(evals, bins=50, edgecolor="black", density=True)

    # Plot the theoretical density
    mp_pdf = np.vectorize(lambda x: marchenko_pastur_pdf(x, gamma, sigma))
    x = np.linspace(max(1e-4, lambda_m), lambda_p, 200)
    ax.plot(x, mp_pdf(x), linewidth=5)

    # Compute and display matrix rank
    # A ratio less than one indicates an undersampled set of RVs
    rank = np.linalg.matrix_rank(matrix)
    n_rows = len(matrix)

    plt.text(
        *[0.95, 0.9],
        f"rank deficiency: {rank}/{n_rows} {'(None)' if n_rows == rank else ''}",
        transform=ax.transAxes,
        ha="right",
    )

    return ax


"""
This code computes the gradient descent for Gaussian mixture model and plots
result of fitting
"""

import scipy.optimize as opt
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-3, 3, 200)
y = np.exp( -(x ** 2)/2  ) - np.exp( -(x ** 2)  )*2 + np.exp( -(x ** 2)/0.25  ) *2.5- np.exp( -(x ** 2)/0.125  )*1.6

# deviation / mean / output weights
#W = np.array([ [0.2], [1.2], [1] ])# best model by enumeration (by hand :D )
W = np.array([ [1,0.3,0.3,1], [-2,-0.3,0.3,2], [1,1,1,1] ])# bad initialization
#W = np.array([ [1.7,1,0.5,0.35], [0,0,0,0], [1,-2,2.5,-1.6] ])# initialization that leads to global optimum
#W = np.array([ [1,0.3,0.3,1, 0.3, 0.3, 0.2], [-2,-0.3,0.3,2, -0.7, 0.7, 0], [1,1,1,1,1,1,1] ])# better results with more neurons

# Here I translate the parameters to more useful form
tW = np.abs(W[0,])
W[0,] = 1 / tW
W[1,] = W[1,] / tW

shapeOfW = W.shape
W0 = np.squeeze(np.asarray(W))

# this is required for optimization procedure
def Unflatten(Win):
    return np.reshape(Win, shapeOfW)

def ComputeGaussians(W, X):
    return np.dot( np.exp( -(np.outer(X, W[0,:]) - W[1,]) ** 2 ) , W[2,])

def Objective(Win):
    return np.linalg.norm( ComputeGaussians(Unflatten(Win), x) - y) ** 2 # Sq. L2 norm

sol = opt.minimize(Objective, W0)
W = Unflatten(sol.x)
print "solution objective:", Objective(W)

yp = ComputeGaussians(W, x);

fig,ax = plt.subplots(figsize=(10,4))
# plot data and predictions
ax.plot(x, y,'*')
ax.plot(x, yp,'-')
# plot Gaussian positions
xp = W[1,] / W[0,] # location of Gaussian

ax.plot(xp, xp*0,'o')

# plot separate gaussians, if necessary

"""
for i in range(4):
    ax.plot(x, ComputeGaussians(W[:,[i]], x), '-') """

ax.set_xlabel('x')
ax.set_ylabel('y')
fig.show()
plt.grid()
plt.show()


import numpy as np
import matplotlib.pyplot as plt

def box_muller(rand_gen, num_samples):
    """
    Implementation of Box-Muller
    :param rand_gen:
    :param num_samples:
    :return:
    """
    z1 = []
    z2 = []
    for _ in range(num_samples):
        z1.append(next(rand_gen))
        z2.append(next(rand_gen))

    z1 = np.asarray(z1)
    z2 = np.asarray(z2)

    x1 = np.cos(2 * np.pi * z2) * np.sqrt((-2) * np.log(z1))
    return x1

def map_to_guass(x, u, sigma):
    # First change variance

    x = x * sigma

    # then change the mean
    x = x + u

    return x

def one_b(rand_gen):
    """
    Box Muller Method
    :param rand_gen:
    :return:
    """
    sigma = 2.4
    u = 3
    gauss = box_muller(rand_gen, 1000)
    gauss = map_to_guass(gauss, u=u, sigma=sigma)

    def gaussian(x, u=0, variance=1.):
        """
        Equation for the Gaussian PDF
        :param x:
        :param u:
        :param variance:
        :return:
        """
        return 1 / (np.sqrt(2 * np.pi * variance)) * np.exp(-(x - u) ** 2 / (2 * variance))

    pdf_x = np.linspace(u - 5 * sigma, u + 5 * sigma, 10000)
    pdf_y = gaussian(pdf_x, u, sigma * sigma)

    plt.plot(pdf_x, pdf_y, c='r', label='Gaussian PDF')
    plt.hist(gauss, density=True, bins=12, label='Box-Muller')
    plt.axvline(x=u + sigma, c='r', ls='dashed', label='1$\sigma$')
    plt.axvline(x=u - sigma, c='r', ls='dashed')
    plt.axvline(x=u - 2 * sigma, c='purple', ls='dashed', label='2$\sigma$')
    plt.axvline(x=u - 3 * sigma, c='g', ls='dashed', label='3$\sigma$')
    plt.axvline(x=u - 4 * sigma, c='y', ls='dashed', label='4$\sigma$')
    plt.axvline(x=u + 2 * sigma, c='purple', ls='dashed')
    plt.axvline(x=u + 3 * sigma, c='g', ls='dashed')
    plt.axvline(x=u + 4 * sigma, c='y', ls='dashed')
    plt.xlim(u - 5 * sigma, u + 5 * sigma)
    plt.xlabel("Random Number")
    plt.ylabel("Normalized Count")
    plt.legend(loc='best')
    plt.savefig("./plots/box_gauss.png", dpi=300)
    plt.cla()
    plt.close()


import symengine as sm
import itertools
import math

def mv_series(function_expression,
              variable_list,
              evaluation_point,
              degree):
    """
    Multivariate version of sympy series() function.
    https://stackoverflow.com/questions/22857162/multivariate-taylor-approximation-in-sympy
    """

    n_var = len(variable_list)

    # dict of variables and their evaluation_point coordinates, ready for the subs() method
    point_coordinates = {i : j for i, j in zip(variable_list, evaluation_point) }

    # list with exponents of the partial derivatives
    deriv_orders = list(itertools.product(range(degree + 1), repeat=n_var))

    # Discarding some higher-order terms
    deriv_orders = [deriv_orders[i] for i in range(len(deriv_orders)) if sum(deriv_orders[i]) <= degree]
    n_terms = len(deriv_orders)

    # same as before, but now ready for the diff() method
    deriv_orders_as_input = [list(sum(list(zip(variable_list, deriv_orders[i])), ())) for i in range(n_terms)]

    polynomial = 0
    for i in range(n_terms):
        partial_derivatives_at_point = function_expression.diff(*deriv_orders_as_input[i]).subs(point_coordinates)
        # e.g. df/(dx*dy**2) evaluated at (x0,y0)

        denominator = math.prod([math.factorial(j) for j in deriv_orders[i]])
        # e.g. (1! * 2!)

        distances_powered = math.prod([(sm.Matrix(variable_list) - sm.Matrix(evaluation_point))[j] ** deriv_orders[i][j] for j in range(n_var)])
        # e.g. (x-x0)*(y-y0)**2

        polynomial += partial_derivatives_at_point / denominator * distances_powered
    return polynomial

def compare_degrees(degrees0, degrees1):
    def find_first_hit_pos(degrees):
        for i,degree in enumerate(degrees):
            if degree != 0:
                return i
        return -1

    def compare(a, b):
        if a == b:
            return 0
        if a > b:
            return 1
        else:
            return -1

    var_size = len(degrees0) // 2

    xs0 = degrees0[:var_size]
    xs1 = degrees1[:var_size]
    xdots0 = degrees0[var_size:]
    xdots1 = degrees1[var_size:]

    x_max_degree0 = max(xs0)
    x_max_degree1 = max(xs1)
    xdot_max_degree0 = max(xdots0)
    xdot_max_degree1 = max(xdots1)

    x_sum_degree0 = sum(xs0)
    x_sum_degree1 = sum(xs1)
    xdot_sum_degree0 = sum(xdots0)
    xdot_sum_degree1 = sum(xdots1)

    x_first_hit0 = find_first_hit_pos(xs0)
    x_first_hit1 = find_first_hit_pos(xs1)
    xdot_first_hit0 = find_first_hit_pos(xdots0)
    xdot_first_hit1 = find_first_hit_pos(xdots1)

    ret = compare(xdot_sum_degree0, xdot_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(x_sum_degree0, x_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(x_first_hit0, x_first_hit1)
    if ret != 0:
        return ret

    ret = compare(x_max_degree0, x_max_degree1)
    if ret != 0:
        return -ret

    ret = compare(xdot_sum_degree0, xdot_sum_degree1)
    if ret != 0:
        return ret

    ret = compare(xdot_first_hit0, xdot_first_hit1)
    if ret != 0:
        return ret

    ret = compare(xdot_max_degree0, xdot_max_degree1)
    if ret != 0:
        return -ret

    return 0

def get_term_name(degrees):
    if sum(degrees) == 0:
        return '1'

    ret = ''
    for i,degree in enumerate(degrees):
        if degree > 0:
            if i < len(degrees) // 2:
                ret += f'x{i}'
            else:
                ret += f'xd{i-len(degrees)//2}'
            if degree >= 2:
                ret += f'^{degree}'
    return ret

def get_poly_dict(p, var_list):
    """
    When making poly symengine expression, error occurs with var list arguemnt poly(expr, var_list),
    so we made workaround to reorder coeffs.
    """
    def reoder_key(key, indices):
        ret = [0] * len(var_list)
        for degree,index in zip(key, indices):
            ret[index] = degree
        return tuple(ret)

    poly_vars = p.args[1:]
    indices = []
    for poly_var in poly_vars:
        index = var_list.index(poly_var)
        indices.append(index)
    d = p.as_dict()
    new_d = {}
    for key,value in d.items():
        new_key = reoder_key(key, indices)
        new_d[new_key] = value
    return new_d

import numpy as np

def get_target_coeffs(target_coeff_dicts, degrees_list):
    target_coeffs = []

    for coeff_dict in target_coeff_dicts:
        coeffs = []
        for degrees in degrees_list:
            if degrees in coeff_dict:
                coeffs.append(coeff_dict[degrees])
            else:
                coeffs.append(0)
        target_coeffs.append(coeffs)

    target_coeffs = np.array(target_coeffs, dtype=np.float64)
    return target_coeffs

import joblib
import sympy as smp
from functools import cmp_to_key

def process_poly(expr,
                 x_xdot_list,
                 x_xdot_zero,
                 series_degree):
    r_ex = mv_series(expr,
                     x_xdot_list,
                     x_xdot_zero,
                     series_degree)
    # Using sympy instead of symengine for poly().
    p_c = smp.poly(r_ex)
    return get_poly_dict(p_c, x_xdot_list)

def calc_coeffs(expr,
                x_xdot_list,
                x_xdot_zero_list,
                series_degree,
                verbose=0):
    r_dim = len(expr)

    coeff_dicts = joblib.Parallel(
        n_jobs=-1,
        verbose=verbose)( [joblib.delayed(process_poly)(
            expr[r_index, 0],
            x_xdot_list,
            x_xdot_zero_list,
            series_degree) for r_index in range(r_dim)])

    degrees_sets = set()

    for coeff_dict in coeff_dicts:
        for degrees in coeff_dict.keys():
            degrees_sets.add(degrees)

    degrees_list = sorted(list(degrees_sets),
                          key=cmp_to_key(compare_degrees))

    all_coeffs = []
    for coeff_dict in coeff_dicts:
        coeffs = []
        for degrees in degrees_list:
            if degrees in coeff_dict:
                coeffs.append(coeff_dict[degrees])
            else:
                coeffs.append(0)
        all_coeffs.append(coeffs)

    all_coeffs = np.array(all_coeffs, dtype=np.float64)
    return all_coeffs, degrees_list


import numpy as np
import matplotlib.pyplot as plt

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

# our model for the forward pass
def forward(x):
    return x * w

# The zip fucntion allows you to iterate in parallel. It The takes iterables (can be zero or more), aggregates them in a tuple, and returns it.
print(list(zip(x_data, y_data)))

# Loss function
def loss(x, y):
    y_pred = forward(x)
    return (y_pred - y) * (y_pred - y)

# List of weights/Mean square Error (Mse) for each input
w_list = []
mse_list = []

for w in np.arange(0.0, 4.1, 0.1):
    # Print the weights and initialize the lost
    print("w=", w)
    l_sum = 0

    for x_val, y_val in zip(x_data, y_data):
        # For each input and output, calculate y_hat
        # Compute the total loss and add to the total error
        y_pred_val = forward(x_val)
        l = loss(x_val, y_val)
        l_sum += l
        print("\t", x_val, y_val, y_pred_val, l)
    # Now compute the Mean squared error (mse) of each
    # Aggregate the weight/mse from this run
    print("MSE=", l_sum / len(x_data))
    w_list.append(w)
    mse_list.append(l_sum / len(x_data))

# Plot it all
plt.plot(w_list, mse_list)
plt.ylabel('Loss')
plt.xlabel('w')
# plt.show()
plt.savefig("1_Basics.png")


# Take 100 samples
# chose a k = 10 for now
# divide sample
import numpy as np
import random
import matplotlib.pyplot as plt

def linearregression(A,b):
    ATA = A.T.dot(A)
    ATb = A.T.dot(b)
    parameters = np.linalg.lstsq(ATA, ATb)[0]
    # print(parameters)
    # print(  np.linalg.solve(ATA, ATb))
    return parameters

def kfold(k,sample_size,x,y):
    ksets = []
    i = 0
    while i < sample_size:
        ksets.append([i,i+k-1])
        i = i+k
    # print(ksets)
    ones = np.ones(sample_size-k)
    errors = np.array([])
    for i in range(sample_size//k):
        todeleteindex = ksets[i]
        index = range(todeleteindex[0],todeleteindex[1]+1)
        a = np.delete(x, index)
        A = np.column_stack((a,ones))
        b = np.delete(y,index)
        parameters = linearregression(A,b)
        error = (np.abs(b - (parameters[0] * (a) + parameters[1])))
        errors = np.append(errors,error)
    # errors = np.array(errors)
    return errors

mu = 0
sigma = 1
sample_size = 100
noise = np.random.normal(mu, sigma, sample_size)

x = np.linspace(-10, 10, num=100)
y = [  (np.sin(x[i]) + noise[i] ) for i in range(len(x))]
y = np.array(y)

k = range(2,100)
meansCorrespondingToK = np.array([])
varianceCorrespondingToK = np.array([])

for i in k:
    print('i in k',i)
    errors = kfold(i,sample_size,x,y)
    mean = np.mean(errors)
    var = np.var(errors)
    meansCorrespondingToK = np.append(meansCorrespondingToK,mean)
    varianceCorrespondingToK = np.append(varianceCorrespondingToK,var)

plt.subplot(2,1,1)
plt.plot(k,meansCorrespondingToK, label= "Mean of Error",linestyle='solid')
plt.plot(k,varianceCorrespondingToK, label= "Variance of Error",linestyle='solid')
plt.legend()
plt.title("Plot for 100 points")
plt.legend()

#### 100000

sample_size1 = 10000
noise1 = np.random.normal(mu, sigma, sample_size1)

x1 = np.linspace(-1000, 1000, num=10000)
print(len(x1))
y1 = [  (np.sin(x1[i]) + noise1[i] ) for i in range(len(x1))]
y1 = np.array(y1)

k1 = range(2,1000)
meansCorrespondingToK1 = np.array([])
varianceCorrespondingToK1 = np.array([])

for i in k1:
    print('k',i)
    errors1 = kfold(i,sample_size1,x1,y1)
    mean1 = np.mean(errors1)
    var1 = np.var(errors1)
    meansCorrespondingToK1 = np.append(meansCorrespondingToK1,mean1)
    varianceCorrespondingToK1 = np.append(varianceCorrespondingToK1,var1)

plt.subplot(2,1,2)
plt.plot(k1,meansCorrespondingToK1, label= "Mean of Error",linestyle='solid')
plt.plot(k1,varianceCorrespondingToK1, label= "Variance of Error",linestyle='solid')
plt.legend()
plt.title("Plot for 100,000 points")
plt.legend()
plt.show()


import numpy as np

def neural_network(X, y):
    W1 = np.random.rand(2, 4)
    W2 = np.random.rand(4, 1)
    output = np.zeros((4,1))

    for epochs in range(10000):
        layer = 1 / (1 + np.exp(-np.dot(X, W1)))
        output = 1 / (1 + np.exp(-np.dot(layer, W2)))
        error = (y - output)
        W2 += np.dot(layer.T, (2 * error * output * (1 - output)))
        W1 += np.dot(X.T, (np.dot(2 * error * output * (1 - output), W2.T)
            * layer * (1 - layer)))

    return np.round(output).flatten()

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

print("OR", neural_network(X, np.array([[0, 1, 1, 1]]).T))
print("AND", neural_network(X, np.array([[0, 0, 0, 1]]).T))
print("XOR", neural_network(X, np.array([[0, 1, 1, 0]]).T))
print("NAND", neural_network(X, np.array([[1, 1, 1, 0]]).T))
print("NOR", neural_network(X, np.array([[1, 0, 0, 0]]).T))

# OR [0., 1., 1., 1.]
# AND [0., 0., 0., 1.]
# XOR [0., 1., 1., 0.]
# NAND [1., 1., 1., 0.]
# NOR [1., 0., 0., 0.]


import numpy as np
from math import acos, sqrt
from numpy.linalg import eig, det

class Vector:
    def determinant(self, m: list[list]):
        """
        Determinant of the given matrix m

        Parameters
        ----------
        m : matrix array

        Returns
        -------
        det: int

        Examples
        --------
        >>> np.linalg.det([[1,0], [0,1]])
        1.0
        >>> np.linalg.det([[1,1,1],[4,2,1],[6,7,0]])
        15.0
        """
        return det(np.array(m))

    def dot_product(self, u: list, v: list) -> int:
        """
        Calculate the dot product of two vectors u, v
        """
        return np.dot(u, v)

    def cross_product(self, u: list, v: list) -> int:
        """
        Calculate the cross product of two vectors u, v
        """
        return np.cross(u, v)

    def magnitude(self, u: list) -> float:
        """
        Calculate the magnitude of vector u
        """
        return sqrt(sum(list(map(lambda x: x ** 2, np.array(u)))))

    def angle(self, u, v):
        """
        Angle between two vectors: u, v in radians
        """
        return acos(self.dot_product(u, v) / (self.magnitude(u) * self.magnitude(v)))

    def eigen(self, m: list[list]):
        """
        Calculate eigenvalues and eigenvectors of
        """
        eigenvalues, eigenvectors = eig(np.array(m))
        np.set_printoptions(precision=4)
        print("Eigenvalues:", eigenvalues)
        print("Eigenvectors:", eigenvectors)

if __name__ == "__main__":
    import doctest

    doctest.testmod()


import numpy as np
from scipy.integrate import quad

# Cuadratura dreptunghiului
def int_drept(a, b, f):
    # Pasul 1
    h = b-a

    # Pasul 2
    I = h * f((a+b)/2.)

    # Pasul 3
    return I

# Cuadratura trapezului
def int_trap(a, b, f):
    # Pasul 1
    h = b - a

    # Pasul 2
    I = h/2. * (f(a) + f(b))

    # Pasul 3
    return I

# Cuadratura Simpson
def int_simp(a, b, f):
    # Pasul 1
    h = b - a

    # Pasul 2
    I = h/6. * (f(a) + 4*f((a+b)/2.) + f(b))

    # Pasul 3
    return I

# Functie exemplu ex1
def func1(x):
    y = 3*x / (x**2 - 9.)
    return y

# Exercitiul 1
def ex1():
    # Punctul a
    a = 4
    b = 5
    I_exact, error = quad(func1, a, b)  # Valoarea exacta a integralei
    """
    Puteti calcula integrala exacta fie cu functia de mai sus, fie de mana :)
    """
    print(f'Integrala exacta este I = {I_exact:.6f}')

    # Punctul b
    I_int_drep = int_drept(a, b, func1)  # dreptunghi
    I_int_trap = int_trap(a, b, func1)  # trapez
    I_int_simp = int_simp(a, b, func1)  # simpson

    # Afisarea rezultatelor
    print(f'Integrala calculata cu cuadratura dreptunghiului este I = {I_int_drep:.6f}')
    print(f'Integrala calculata cu cuadratura trapezului este I = {I_int_trap:.6f}')
    print(f'Integrala calculata cu cuadratura simpson este I = {I_int_simp:.6f}')

    # Punctul c
    eroare_int_drep = np.abs(I_exact-I_int_drep)  # eroare dreptunghi
    eroare_int_trap = np.abs(I_exact-I_int_trap)  # eroare trapez
    eroare_int_simp = np.abs(I_exact-I_int_simp)  # eroare simpson

    # Afisarea erorilor
    print(f'\nEroarea cuadraturii dreptunghiului este e = {eroare_int_drep:.6f}')
    print(f'Eroarea cuadraturii trapezului este e = {eroare_int_trap:.6f}')
    print(f'Eroarea cuadraturii simpson este e = {eroare_int_simp:.6f}')

# Cuadratura sumata a dreptunghiului
def int_sum_drept(f, X):
    # Pasul 1
    m = np.int32((np.shape(X)[0] - 1)/2.)
    h = (X[2*m] - X[0]) / (2.*m)

    # Pasul 2
    suma = 0
    for i in range(m):
        suma += f(X[2*i])
    I = 2*h*suma

    # Pasul 3
    return I

# Cuadratura sumata a trapezului
def int_sum_trap(f, X):
    # Pasul 1
    m = np.shape(X)[0] - 1
    h = (X[m] - X[0]) / m

    # Pasul 2
    I = h/2. * (f(X[0]) + 2*np.sum(X[1:]) + f(X[-1]))

    # Pasul 3
    return I

# Cuadratura sumata Simpson
def int_sum_simp(f, X):
    # Pasul 1
    m = np.int32((np.shape(X)[0]-1)/2.)
    h = (X[-1] - X[0]) / (2.*m)

    # Pasul 2
    suma1 = suma2 = 0
    for i in range(m):
        suma1 += f(X[2*i])

    for i in range(m-1):
        suma2 += f(X[2*i+1])

    I = h/3. * (f(X[0]) + 4*suma1 + 2*suma2 + f(X[-1]))

    # Pasul 3
    return I

# Functie exemplu ex2
def func2(x):
    y = 1/(1+x)
    return y

# Exercitiul 2
def ex2():
    # Punctul a
    a = 0
    b = 1
    I_exact, error = quad(func2, a, b)  # Valoarea exacta a integralei
    print(f'Integrala exacta este I = {I_exact:.6f}')

    # Punctul b
    for m in [3, 4, 5]:
        x_drep_sum = np.linspace(a, b, 2*m+1)  # 2m+1 luat din algoritm X apartine R^(2m+1)
        x_trap = np.linspace(a, b, m+1)   # m+1 luat din algoritm X apartine R^(m+1)

        I_sum_drept = int_sum_drept(func2, x_drep_sum)  # sumata dreptunghi
        I_sum_trap = int_sum_trap(func2, x_trap)  # sumata trapez
        I_sum_simp = int_sum_simp(func2, x_drep_sum)  # sumata simpson

        # Afisarea rezultatelor
        print(f'\nIntegrala calculata cu cuadratura sumata a dreptunghiului pentru m = {m} este I = {I_sum_drept:.6f}')
        print(f'Integrala calculata cu cuadratura sumata a trapezului pentru m = {m} este I = {I_sum_trap:.6f}')
        print(f'Integrala calculata cu cuadratura sumata simpson pentru m = {m} este I = {I_sum_simp:.6f}')

    print('\n\n')
    # Punctul c
    for m in [3, 4, 5]:
        eroare_sum_drep = np.abs(I_exact-I_sum_drept)  # eroare sumata dreptunghi
        eroare_sum_trap = np.abs(I_exact-I_sum_trap)  # eroare sumata trapez
        eroare_sum_simp = np.abs(I_exact-I_sum_simp)  # eroare sumata simpson

        # Afisarea erorilor
        print(f'\nEroarea cuadraturii sumate a dreptunghiului pentru m = {m} este e = {eroare_sum_drep:.6f}')
        print(f'Eroarea cuadraturii sumate a trapezului pentru m = {m} este e = {eroare_sum_trap:.6f}')
        print(f'Eroarea cuadraturii sumate simpson pentru m = {m} este e = {eroare_sum_simp:.6f}')

if __name__ == '__main__':
    # ex1()
    ex2()


import numpy as np
import matplotlib.pyplot as plt

# Grid parameters
N = 50
x_start, x_end = -2.0, 2.0
y_start, y_end = -1.0, 1.0

x = np.linspace(x_start, x_end, N)
y = np.linspace(y_start, y_end, N)

X, Y = np.meshgrid(x, y)

class Source(object):

    def __init__(self, strength, x, y):
        self.strength = strength
        self.x = x
        self.y = y

    def velocity(self, X, Y):

        self.u = (self.strength / (2 * np.pi) * (X - self.x) /
                  ((X - self.x)**2 + (Y - self.y)**2))

        self.v = (self.strength / (2 * np.pi) * (Y - self.y) /
                  ((X - self.x)**2 + (Y - self.y)**2))

    def stream_function(self, X, Y):

        self.psi = (self.strength / (2 * np.pi) *
                    np.arctan2((Y - self.y), (X - self.x)))

strength_vortex = 1.0
x_vortex, y_vortex = 0.0, 0.5

vortex = Vortex(strength_vortex, x_vortex, y_vortex)
vortex.velocity(X, Y)
vortex.stream_function(X, Y)

vortex_image = Vortex(-strength_vortex, x_vortex, -y_vortex)
vortex_image.velocity(X, Y)
vortex_image.stream_function(X, Y)

# Superposition
u = vortex.u + vortex_image.u
v = vortex.v + vortex_image.v
psi = vortex.psi + vortex_image.psi

size = 10
plt.figure(figsize=(size, (y_end - y_start) / (x_end - x_start) * size))
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.xlim(x_start, x_end)
plt.ylim(y_start, y_end)
plt.streamplot(X, Y, u, v, density=2, linewidth=1,
               arrowsize=1, arrowstyle='->')
plt.scatter(vortex.x, vortex.y, color='#CD2305', s=80, marker='o')
plt.scatter(vortex_image.x, vortex_image.y, color='#CD2305', s=80, marker='D')
plt.axhline(0., color='k', linestyle='--', linewidth=4)

plt.show()

strength_vortex = 1.0
x_vortex1, y_vortex1 = -0.1, 0.5
x_vortex2, y_vortex2 = +0.1, 0.5

# creates two vortices at different locations
vortex1 = Vortex(+strength_vortex, x_vortex1, y_vortex1)
vortex2 = Vortex(-strength_vortex, x_vortex2, y_vortex2)

# computes the velocity and stream-function for each vortex
vortex1.velocity(X, Y)
vortex1.stream_function(X, Y)
vortex2.velocity(X, Y)
vortex2.stream_function(X, Y)

# creates an image for each vortex
vortex1_image = Vortex(-strength_vortex, x_vortex1, -y_vortex1)
vortex2_image = Vortex(+strength_vortex, x_vortex2, -y_vortex2)

# computes the velcoity and stream-function of each image
vortex1_image.velocity(X, Y)
vortex1_image.stream_function(X, Y)
vortex2_image.velocity(X, Y)
vortex2_image.stream_function(X, Y)

# superposition of the vortex pair and its image
u = vortex1.u + vortex2.u + vortex1_image.u + vortex2_image.u
v = vortex1.v + vortex2.v + vortex1_image.v + vortex2_image.v
psi = vortex1.psi + vortex2.psi + vortex1_image.psi + vortex2_image.psi

# plot the streamlines
size = 10
plt.figure(figsize=(size, (y_end - y_start) / (x_end - x_start) * size))
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.xlim(x_start, x_end)
plt.ylim(y_start, y_end)
plt.streamplot(X, Y, u, v, density=2, linewidth=1,
               arrowsize=1, arrowstyle='->')
plt.scatter(vortex1.x, vortex1.y, color='#CD2305', s=80, marker='o')
plt.scatter(vortex2.x, vortex2.y, color='g', s=80, marker='o')
plt.scatter(vortex1_image.x, vortex1_image.y,
            color='#CD2305', s=80, marker='D')
plt.scatter(vortex2_image.x, vortex2_image.y, color='g', s=80, marker='D')
plt.axhline(0., color='k', linestyle='--', linewidth=4)

plt.show()

u_inf = 1.0    # free-stream speed

# calculates the velocity and stream-function of the free-stream flow
u_freestream = u_inf * np.ones((N, N), dtype=float)
v_freestream = np.zeros((N, N), dtype=float)
psi_freestream = u_inf * Y

class Doublet:

    def __init__(self, strength, x, y):

        self.strength = strength
        self.x, self.y = x, y

    def velocity(self, X, Y):

        self.u = (-self.strength / (2 * np.pi) *
                  ((X - self.x)**2 - (Y - self.y)**2) /
                  ((X - self.x)**2 + (Y - self.y)**2)**2)

        self.v = (-self.strength / (2 * np.pi) *
                  2 * (X - self.x) * (Y - self.y) /
                  ((X - self.x)**2 + (Y - self.y)**2)**2)

    def stream_function(self, X, Y):

        self.psi = (-self.strength /
                    (2 * np.pi) * (Y - self.y) /
                    ((X - self.x)**2 + (Y - self.y)**2))

strength_doublet = 1.0                # strength of the doublet
x_doublet, y_doublet = 0.0, 0.3       # location of the doublet

# creates a doublet (object of class Doublet)
doublet = Doublet(strength_doublet, x_doublet, y_doublet)

# computes the velocity and stream-function of the doublet on the mesh
doublet.velocity(X, Y)
doublet.stream_function(X, Y)

# creates the image of the doublet
doublet_image = Doublet(strength_doublet, x_doublet, -y_doublet)

# computes the velocity and stream-function of the image on the mesh
doublet_image.velocity(X, Y)
doublet_image.stream_function(X, Y)

# superposition of the doublet and its image to the uniform flow
u = u_freestream + doublet.u + doublet_image.u
v = v_freestream + doublet.v + doublet_image.v
psi = psi_freestream + doublet.psi + doublet_image.psi

# plots the streamlines
size = 10
plt.figure(figsize=(size, (y_end - y_start) / (x_end - x_start) * size))
plt.xlabel('x', fontsize=16)
plt.ylabel('y', fontsize=16)
plt.xlim(x_start, x_end)
plt.ylim(y_start, y_end)
plt.streamplot(X, Y, u, v, density=2, linewidth=1,
               arrowsize=1, arrowstyle='->')
plt.scatter(doublet.x, doublet.y, color='r', s=80, marker='o')
plt.scatter(doublet_image.x, doublet_image.y, color='r', s=80, marker='D')
plt.axhline(0., color='k', linestyle='--', linewidth=4)

plt.show()


import numpy as np
import pandas as pd
from scipy.optimize import minimize

TOLERANCE = 1e-10

# load strategy data
daily_returns = pd.read_csv("../daily_returns.csv", index_col=0, parse_dates=True)

# code from https://quantdare.com/risk-parity-in-python/

def _allocation_risk(weights, covariances):
    # We calculate the risk of the weights distribution
    portfolio_risk = np.sqrt((weights * covariances * weights.T))[0, 0]

    # It returns the risk of the weights distribution
    return portfolio_risk

def _assets_risk_contribution_to_allocation_risk(weights, covariances):
    # We calculate the risk of the weights distribution
    portfolio_risk = _allocation_risk(weights, covariances)

    # We calculate the contribution of each asset to the risk of the weights
    # distribution
    assets_risk_contribution = np.multiply(weights.T, covariances * weights.T) \
                               / portfolio_risk

    # It returns the contribution of each asset to the risk of the weights
    # distribution
    return assets_risk_contribution

def _risk_budget_objective_error(weights, args):
    # The covariance matrix occupies the first position in the variable
    covariances = args[0]

    # The desired contribution of each asset to the portfolio risk occupies the
    # second position
    assets_risk_budget = args[1]

    # We convert the weights to a matrix
    weights = np.matrix(weights)

    # We calculate the risk of the weights distribution
    portfolio_risk = _allocation_risk(weights, covariances)

    # We calculate the contribution of each asset to the risk of the weights
    # distribution
    assets_risk_contribution = \
        _assets_risk_contribution_to_allocation_risk(weights, covariances)

    # We calculate the desired contribution of each asset to the risk of the
    # weights distribution
    assets_risk_target = \
        np.asmatrix(np.multiply(portfolio_risk, assets_risk_budget))

    # Error between the desired contribution and the calculated contribution of
    # each asset
    error = \
        sum(np.square(assets_risk_contribution - assets_risk_target.T))[0, 0]

    # It returns the calculated error
    return error

def _get_risk_parity_weights(covariances, assets_risk_budget, initial_weights):
    # Restrictions to consider in the optimisation: only long positions whose
    # sum equals 100%
    constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0},  # sum to zero
                   {'type': 'ineq', 'fun': lambda x: x - 0.05})  # positive

    # Optimisation process in scipy
    optimize_result = minimize(fun=_risk_budget_objective_error,
                               x0=initial_weights,
                               args=[covariances, assets_risk_budget],
                               method='SLSQP',
                               constraints=constraints,
                               tol=TOLERANCE,
                               options={'disp': False})

    # Recover the weights from the optimised object
    weights = optimize_result.x

    # It returns the optimised weights
    return weights

def get_weights(returns):
    # We calculate the covariance matrix
    covariances = returns.cov().values

    # The desired contribution of each asset to the portfolio risk: we want all
    # asset to contribute equally
    assets_risk_budget = [1 / returns.shape[1]] * returns.shape[1]

    # Initial weights: equally weighted
    init_weights = [1 / returns.shape[1]] * returns.shape[1]

    # Optimisation process of weights
    weights = \
        _get_risk_parity_weights(covariances, assets_risk_budget, init_weights)

    # Convert the weights to a pandas Series
    weights = pd.Series(weights, index=returns.columns, name='weight')

    # It returns the optimised weights
    # return weights
    return weights

window = 10

max_size = daily_returns.shape[0]

alloc = pd.DataFrame([get_weights(daily_returns.iloc[i:i + window]) for i in range(0, max_size - window)])

alloc.index = daily_returns.index[window:]

alloc.plot()

alloc.to_csv('../risk_parity.csv')


import math
import statistics
import pandas as pd
import random
from scipy.stats import kurtosis
from scipy.stats import skew
import csv

n = 100

# usage :
# 	data ( List of number )
# 	rfArray ( List of number )
def ASKSR(data,rfArray):

	lamda = 1 # absolute risk aversion, doesn't matter in this case
	rfArray = [x for x in rfArray if str(x) != 'nan']

	rf = sum(rfArray)

	cleanedList = [x for x in data if str(x) != 'nan']

	Dvar = statistics.stdev(cleanedList) ** 0.5
	Dmean = statistics.mean(cleanedList)
	Dkur = kurtosis(cleanedList,nan_policy = 'raise')
	Dskew = skew(cleanedList,nan_policy = 'raise')

	alpha = 3 * ( abs(3 * Dkur  - 4 * (Dskew**2) - 9)**0.5) / (Dvar**2 * ( 3 * Dkur  - 5 * (Dskew**2) - 9 ) )
	beta = 3 * Dskew / ( Dvar *( 3 * Dkur  - 5 * (Dskew**2) - 9 ) )
	nn = Dmean - 3 * Dvar * Dskew / (3 * Dkur  - 4 * (Dskew**2) - 9)
	delta = 3 * Dvar * ( abs(3 * Dkur  - 5 * (Dskew**2) - 9)**0.5) / (3 * Dkur  - 4 * (Dskew**2) - 9)

	astar = 1/lamda * (beta + alpha * (nn - rf)/(delta**2 + (nn-rf)**2)**0.5)

	ASKSR = (2 * ( lamda * astar * (nn - rf) - delta * ((alpha**2 - beta**2)**0.5  - (alpha**2 - (beta - lamda * astar)**2)) ) ) ** 0.5

	return abs(ASKSR)

if __name__ == '__main__':

	A = {}
	d = pd.read_csv('../data/output_weekly_with_rf.csv')
	rf = d["Rf"].tolist()
	# print(rf)
	for key in d:
		if key == "Date":
			pass
		else:
			A[key] = ASKSR(d[key].tolist(),rf)

	sorted_x = sorted(A.items(), key=lambda kv: kv[1])
	sorted_x.reverse()
	indexr = 1
	print("|Rank|Name|ASKSR-Value|")
	print("| ------------- | ------------- |------------- | ")
	for i in sorted_x:
		if i[0] != "Rf":
			print("| {}  | {}  | {}  |".format(indexr,i[0],i[1]))
			indexr += 1


#!python3

import numpy as np
import pandas as pd

M = np.matrix([[1, 1], [2, 4], [3, 9], [4, 6]])
# print("M is:\n{}".format(M))

#(a)
print("M*M.T is:\n{}".format(np.matmul(M.T, M)))
print("M.T*M is:\n{}".format(np.matmul(M, M.T)))
#(b)
w, v = np.linalg.eig(np.matmul(M.T, M))
print("Eigenvalues w1 of M.T*M is: {}".format(w[0]))
print("Eigenvectors v1 of M.T*M is: {}".format(v.T[0]))
print("Eigenvalues w2 of M.T*M is: {}".format(w[1]))
print("Eigenvectors v2 of M.T*M is: {}".format(v.T[1]))
#(c)
w2, v2 = np.linalg.eig(np.matmul(M, M.T))
print("Eigenvalues w1, w2 and w3 of M*M.T is: {:.3f}, {:.3f}, {:.3f}".format(w2[0], w2[1], w2[3]))
#(d)
print("Eigenvalues w1, w2, w3, w4 of M*M.T is:\n {}".format(v2))

#SVD
A = np.matrix([[1, 1], [1, 1], [0, 0]])
print(A)
w3, v3 = np.linalg.eig(np.matmul(A, A.T))
w4, v4 = np.linalg.eig(np.matmul(A.T, A))
w3 = sorted(w3, reverse=True)
print(w3)
print(w4)
print(v3)
print(v4)
S = np.matrix([[w3[0]**0.5, 0.], [0., w3[1]**0.5]])
print("U is:\n{}".format(v3))
print("S is:\n{}".format(S))
print("V is:\n{}".format(v4.T))
#verifying
print("SVD is:\n{}".format(np.linalg.svd(A)))


import numpy as np
import numpy.linalg as nl

def log_sum_exp(X):
    """
    Computes log sum_i exp(X_i).
    Useful if you want to solve log \int f(x)p(x) dx
    where you have samples from p(x) and can compute log f(x)
    """
    X0 = X.min()
    X_without_X0 = np.delete(X, X.argmin())

    return X0 + np.log(1. + np.sum(np.exp(X_without_X0 - X0)))

def log_mean_exp(X):
    """
    Computes log 1/n sum_i exp(X_i).
    Useful if you want to solve log \int f(x)p(x) dx
    where you have samples from p(x) and can compute log f(x)
    """

    return log_sum_exp(X) - np.log(len(X))

def avg_prob_of_log_probs(X):
    """
    Given a set of log-probabilities, this computes log-mean-exp of them.
    Careful checking is done to prevent buffer overflows
    Similar to calling (but overflow-safe): log_mean_exp(log_prob)
    """

    # extract inf inds (no need to delete X0 from X here)
    X0 = X.min()
    inf_inds = np.isinf(np.exp(X - X0))

    # remove these numbers
    X_without_inf = X[~inf_inds]

    # return exp-log-mean-exp on shortened array
    avg_prob_without_inf = np.exp(log_mean_exp(X_without_inf))

    # re-normalise by the full length, which is equivalent to adding a zero probability observation
    renormaliser = float(len(X_without_inf)) / len(X)
    avg_prob_without_inf = avg_prob_without_inf * renormaliser

    return avg_prob_without_inf

def qmult(b):
    """
    QMULT  Pre-multiply by random orthogonal matrix.
       QMULT(A) is Q*A where Q is a random real orthogonal matrix from
       the Haar distribution, of dimension the number of rows in A.
       Special case: if A is a scalar then QMULT(A) is the same as
                     QMULT(EYE(A)).

       Called by RANDSVD.

       Reference:
       G.W. Stewart, The efficient generation of random
       orthogonal matrices with an application to condition estimators,
       SIAM J. Numer. Anal., 17 (1980), 403-409.
    """
    try:
        n = b.shape[0]
        a = b.copy()
    except AttributeError:
        n = b
        a = np.eye(n)

    d = np.zeros(n)

    for k in range(n - 2, -1, -1):
        # Generate random Householder transformation.
        x = np.random.randn(n - k)
        s = nl.norm(x)
        # Modification to make sign(0) == 1
        sgn = np.sign(x[0]) + float(x[0] == 0)
        s = sgn * s
        d[k] = -sgn
        x[0] = x[0] + s
        beta = s * x[0]

        # Apply the transformation to a
        y = np.dot(x, a[k:n, :])
        a[k:n, :] = a[k:n, :] - np.outer(x, (y / beta))

    # Tidy up signs.
    for i in range(n - 1):
        a[i, :] = d[i] * a[i, :]

    # Now randomly change the sign (Gaussian dist)
    a[n - 1, :] = a[n - 1, :] * np.sign(np.random.randn())

    return a

def score(est_grad, true_grad):
    ndim = est_grad.ndim
    assert ndim == true_grad.ndim and ndim <= 2 and ndim > 0

    if ndim == 2:
        return np.mean(np.sum((est_grad - true_grad) ** 2, axis=1))
    else:
        return np.sum((est_grad - true_grad) ** 2)

def hypercube(D):
    if D <= 1:
        return [[0], [1]]
    else:
        so_far = hypercube(D - 1)
        result = [each + [0] for each in so_far]
        result += [each + [1] for each in so_far]
        return result


import numpy as np
import math

class RSquare():

    def __init__(self,X):

        self.__matrix = X
        self.__n = len(X)
        self.__m = len(X.T)
        self.__sumColumn = np.zeros((self.__m))
        self.__sumColumnSquare = np.zeros((self.__m))
        self.__poll = None
        self.__rMatrix = np.zeros((self.__m,self.__m))

        self.__sum()
        self.__sumSquare()
        self.__calcPoll()
        self.__calRSquare()

    def __sum(self):

        for i in range(self.__m):

            self.__sumColumn[i] = sum(self.__matrix[:,i])

    def __sumSquare(self):

        for i in range(self.__m):
            summ = 0
            for j in range(self.__n):

                summ = summ + math.pow(self.__matrix[j,i],2)

            self.__sumColumnSquare[i] = summ

    def __calcPoll(self):

        self.__poll = self.__matrix.T.dot(self.__matrix)

    def __calRSquare(self):

        for i in range(self.__m):
            for j in range(i,self.__m):

                result1 = (self.__n * self.__poll[i,j]) - (self.__sumColumn[i] * self.__sumColumn[j])
                result2 = (self.__n * self.__sumColumnSquare[i]) - math.pow(self.__sumColumn[i],2)
                result3 = (self.__n * self.__sumColumnSquare[j]) - math.pow(self.__sumColumn[j],2)

                r = result1 / math.sqrt(result2 * result3)

                self.__rMatrix[i,j] = math.pow(r,2)
                self.__rMatrix[j,i] = math.pow(r,2)

    def getRMatrix(self):

        return self.__rMatrix


import numpy as np

def DFT_1D(fx):
    fx = np.asarray(fx, dtype=complex)
    M = fx.shape[0]
    fu = fx.copy()

    for i in range(M):
        u = i
        sum = 0
        for j in range(M):
            x = j
            tmp = fx[x]*np.exp(-2j*np.pi*x*u*np.divide(1, M, dtype=complex))
            sum += tmp
        # print(sum)
        fu[u] = sum
    # print(fu)

    return fu

def inverseDFT_1D(fu):
    fu = np.asarray(fu, dtype=complex)
    M = fu.shape[0]
    fx = np.zeros(M, dtype=complex)

    for i in range(M):
        x = i
        sum = 0
        for j in range(M):
            u = j
            tmp = fu[u]*np.exp(2j*np.pi*x*u*np.divide(1, M, dtype=complex))
            sum += tmp
        fx[x] = np.divide(sum, M, dtype=complex)

    return fx

def FFT_1D(fx):
    """ use recursive method to speed up"""
    fx = np.asarray(fx, dtype=complex)
    M = fx.shape[0]
    minDivideSize = 4

    if M % 2 != 0:
        raise ValueError("the input size must be 2^n")

    if M <= minDivideSize:
        return DFT_1D(fx)
    else:
        fx_even = FFT_1D(fx[::2])  # compute the even part
        fx_odd = FFT_1D(fx[1::2])  # compute the odd part
        W_ux_2k = np.exp(-2j * np.pi * np.arange(M) / M)

        f_u = fx_even + fx_odd * W_ux_2k[:M//2]

        f_u_plus_k = fx_even + fx_odd * W_ux_2k[M//2:]

        fu = np.concatenate([f_u, f_u_plus_k])

    return fu

def inverseFFT_1D(fu):
    """ use recursive method to speed up"""
    fu = np.asarray(fu, dtype=complex)
    fu_conjugate = np.conjugate(fu)

    fx = FFT_1D(fu_conjugate)

    fx = np.conjugate(fx)
    fx = fx / fu.shape[0]

    return fx

def FFT_2D(fx):
    h, w = fx.shape[0], fx.shape[1]

    fu = np.zeros(fx.shape, dtype=complex)

    if len(fx.shape) == 2:
        for i in range(h):
            fu[i, :] = FFT_1D(fx[i, :])

        for i in range(w):
            fu[:, i] = FFT_1D(fu[:, i])
    elif len(fx.shape) == 3:
        for ch in range(3):
            fu[:, :, ch] = FFT_2D(fx[:, :, ch])
    return fu

def inverseDFT_2D(fu):
    h, w = fu.shape[0], fu.shape[1]

    fx = np.zeros(fu.shape, dtype=complex)

    if len(fu.shape) == 2:
        for i in range(h):
            fx[i, :] = inverseDFT_1D(fu[i, :])

        for i in range(w):
            fx[:, i] = inverseDFT_1D(fx[:, i])

    elif len(fu.shape) == 3:
        for ch in range(3):
            fx[:, :, ch] = inverseDFT_2D(fu[:, :, ch])

    fx = np.real(fx)
    return fx

def inverseFFT_2D(fu):
    h, w = fu.shape[0], fu.shape[1]

    fx = np.zeros(fu.shape, dtype=complex)

    if len(fu.shape) == 2:
        for i in range(h):
            fx[i, :] = inverseFFT_1D(fu[i, :])

        for i in range(w):
            fx[:, i] = inverseFFT_1D(fx[:, i])

    elif len(fu.shape) == 3:
        for ch in range(3):
            fx[:, :, ch] = inverseFFT_2D(fu[:, :, ch])

    fx = np.real(fx)
    return fx


from scipy import stats
from scipy import array
import numpy

class Sample:
	"""
	Container class for storaging sample data and computing characteristics of a sample
	"""

	__sample__: array
	__normal_distribution_size__ = 10**5
	__normal_distribution__: array
	__p__ = 0.05

	def __init__(self, sample):
		self.__sample__ = sample
		self.__normal_distribution__ = stats.norm.rvs(
			loc = self.mean(),
			scale = self.std(),
			size = self.__normal_distribution_size__
		)

	def mean(self):
		"""
		Compute mean

		:return: mean value of the sample
		"""
		return self.__sample__.mean()

	def std(self):
		"""
		Compute standard deviation

		:return: standard deviation of the sample
		"""
		return self.__sample__.std()

	def kolmogorov_norm_test(self):
		"""
		Define sample are normal distributed or not with level 0.05

		:return: True if the sample satisfies normal distribution and False if does not
		"""
		temp = stats.ks_2samp(self.__sample__, self.__normal_distribution__)
		response = {"statistic": temp[0]*self.__sample__.shape[0]**0.5, "p-value": temp[1]}
		return response["p-value"] > self.__p__

	def kolmogorov_norm_test_statistics(self):
		"""
		Define sample are normal distributed or not with level 0.05

		:return: statistics and p-value
		"""
		temp = stats.ks_2samp(self.__sample__, self.__normal_distribution__)
		response = u"статистика критерия: " + temp[0].__str__() + "\np-value: " + temp[1].__str__()
		return response

	def get_distribution_function(self):
		"""
		Return distribution function of the sample data

		:return: tuple of 2 numpy arrays (1 - highs, 2 - bins)
		"""
		return numpy.histogram(self.__sample__, bins = "auto")

	def get_cumulative_distribution_function(self):
		"""
		Return cumulative distribution function of the sample data

		:return: tuple of 2 numpy arrays (1 - highs, 2 - bins)
		"""
		response = [
			numpy.sort(self.__sample__),
			numpy.array(range(self.__sample__.shape[0]))/float(self.__sample__.shape[0])
		]
		return tuple(response)

	def get_normal_distribution_function(self):
		"""
		Return distribution function of random normal sample

		:return: tuple of 2 numpy arrays (1 - highs, 2 - bins)
		"""
		response = list(numpy.histogram(self.__normal_distribution__, bins = "auto"))
		response[0] = response[0]/max(response[0])*max(self.get_distribution_function()[0])
		return tuple(response)

	def get_normal_cumulative_distribution_function(self):
		"""
		Return cumulative distribution function of random normal sample

		:return: tuple of 2 numpy arrays (1 - highs, 2 - bins)
		"""
		response = [
				numpy.sort(self.__normal_distribution__),
				numpy.array(range(self.__normal_distribution_size__))/float(self.__normal_distribution_size__)
		]
		return tuple(response)

	def get_sample(self):
		"""
		Return sample data

		:return: numpy array
		"""
		return self.__sample__


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CS231n Convolutional Neural Networks for Visual Recognition
http://cs231n.github.io/

Python Numpy Tutorial
http://cs231n.github.io/python-numpy-tutorial/

Numpy Reference
https://docs.scipy.org/doc/numpy/reference/

SciPy
https://docs.scipy.org/doc/scipy/reference/index.html

￣
We will highlight some parts of SciPy that you might find useful for this class.

python_numpy_tutorial-scipy_distance_between_points.py
2019-07-03 (Wed)
"""
# Python Numpy Tutorial > SciPy > Distance between points
#   SciPy defines some useful functions for
#     computing distances between sets of points.
#   scipy.spatial.distance.pdist
#     computes the distance between all pairs of points in a given set.
#   A similar function scipy.spatial.distance.cdist
#     computes the distance between all pairs of points across two sets of points.
#
# Read more about:
#     scipy.spatial.distance.pdist
#     https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html
#
#     scipy.spatial.distance.cdist
#     https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
#
# Python Numpy Tutorial > SciPy > MATLAB files
#   The functions scipy.io.loadmat & scipy.io.savemat allow you to
#     read and write MATLAB files.
# Read more about:
#     Input and output (scipy.io)
#     https://docs.scipy.org/doc/scipy/reference/io.html

import numpy as np
from scipy.spatial.distance import pdist, squareform

x = np.array( [[0,1],[1,0],[2,0]] )
print(x)
#[[0 1]
# [1 0]
# [2 0]]

print( pdist(x, 'euclidean') )
# [1.41421356 2.23606798 1.        ]

d = squareform( pdist(x, 'euclidean') )
print( d )
#[[0.         1.41421356 2.23606798]
# [1.41421356 0.         1.        ]
# [2.23606798 1.         0.        ]]


import numpy as np
from matplotlib import pyplot as plt

np.set_printoptions(precision=6)

def featureNormalize(X):
    """Normalizes the features in X

    featureNormalize(X) returns a normalized version of X where
    the mean value of each feature is 0 and the standard deviation
    is 1. This is often a good preprocessing step to do when
    working with learning algorithms.
    :param X:
    :return:
    """
    mu = X.mean(axis=0)
    X_norm = X - mu

    sigma = X_norm.std(axis=0)
    X_norm = X_norm / sigma
    return X_norm, mu, sigma

def pca(X):
    """Run principal component analysis on the dataset X

    [U, S, X] = pca(X) computes eigenvectors of the covariance matrix of X
    :param X:
    :return: the eigenvectors U, the eigenvalues (on diagonal) in S
    """
    # Useful values
    m, n = X.shape

    sigma = 1 / m * np.matmul(X.transpose(), X)
    u, s, _ = np.linalg.svd(sigma)
    U = u
    S = np.diag(s)
    return U, S

def drawLine(p1, p2, *args, **kwargs):
    """Draws a line from point p1 to point p2

    drawLine(p1, p2) Draws a line from point p1 to point p2 and holds the
    current figure
    :param p1:
    :param p2:
    :param args:
    :return:
    """
    plt.plot([p1[0], p2[0]], [p1[1], p2[1]], *args, **kwargs)

def projectData(X, U, K):
    """Computes the reduced data representation when projecting only
    on to the top k eigenvectors

    Z = projectData(X, U, K) computes the projection of
    the normalized inputs X into the reduced dimensional space spanned by
    the first K columns of U. It returns the projected examples in Z.
    :param X:
    :param U:
    :param K:
    :return:
    """
    Z = np.matmul(X, U[:, :K])
    return Z

def recoverData(Z, U, K):
    """Recovers an approximation of the original data when using the
    projected data

    X_rec = recoverData(Z, U, K) recovers an approximation the
    original data that has been reduced to K dimensions. It returns the
    approximate reconstruction in X_rec.
    :param Z:
    :param U:
    :param K:
    :return:
    """
    X_rec = np.matmul(Z, U.transpose()[:K])
    return X_rec

def displayData(X, example_width = None):
    """Display 2D data in a nice grid

    [h, display_array] = displayData(X, example_width) displays 2D data
    stored in X in a nice grid.
    :param X:
    :param example_width:
    :return: the figure handle h and the displayed array if requested.
    """
    # Set example_width automatically if not passed in
    if example_width is None:
        example_width = int(round(np.sqrt(X.shape[1])))

    # Gray Image
    colormap = 'gray'

    # Compute rows, cols
    m, n = X.shape
    example_height = int(n / example_width)

    # Compute number of items to display
    display_rows = int(np.floor(np.sqrt(m)))
    display_cols = int(np.ceil(m / display_rows))

    # Between images padding
    pad = 1

    # Setup blank display
    display_array = -np.ones((pad + display_rows * (example_height + pad), pad + display_cols * (example_width + pad)))

    # Copy each example into a patch on the display array
    curr_ex = 0
    for j in range(display_rows):
        for i in range(display_cols):
            if curr_ex > m:
                break
            # Copy the patch

            # Get the max value of the patch
            max_val = max(abs(X[curr_ex, :]))
            sample_y = pad + j * (example_height + pad)
            sample_x = pad + i * (example_width + pad)
            sample_pixel = X[curr_ex, :].reshape((example_height, example_width), order='F') / max_val
            display_array[sample_y:sample_y + example_height, sample_x:sample_x + example_width] = sample_pixel
            curr_ex += 1
        if curr_ex > m:
            break

    # Display Image
    h = plt.imshow(display_array, cmap=colormap)
    # Do not show axis
    plt.axis('off')

    # plt.show()
    return h, display_array